Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96256ac63e | ||
|
|
6701aa6dc1 | ||
|
|
ff8926bba8 | ||
|
|
217ca72eb3 | ||
|
|
648072436f | ||
|
|
6fe2468a5a | ||
|
|
948d927eb9 | ||
|
|
b2eaa3f7f8 | ||
|
|
93417dddb1 | ||
|
|
044c46fc9a | ||
|
|
c9d972d544 | ||
|
|
0aab4b1ac2 | ||
|
|
4f4b4fd2e6 | ||
|
|
66c9be357c | ||
|
|
1d36811dfe | ||
|
|
6e74123c65 | ||
|
|
b82f5f9a32 | ||
|
|
1937d0366d | ||
|
|
963a906c30 | ||
|
|
3f6eb7e41a | ||
|
|
a822c7c340 | ||
|
|
b8fb86f5a4 | ||
|
|
ee0c7f09b3 | ||
|
|
d3ddf8cf39 | ||
|
|
bf554e8ed2 | ||
|
|
d6e189df5e | ||
|
|
68749616ad | ||
|
|
7549eb13a0 | ||
|
|
902f9f5944 | ||
|
|
2cf24994d9 | ||
|
|
3a7c4822f8 | ||
|
|
b26db99ee7 | ||
|
|
de4d2f4724 | ||
|
|
640e30de8a | ||
|
|
e75118e0f0 | ||
|
|
a4c8b8714b | ||
|
|
ddb683efb6 | ||
|
|
5be6e20b03 | ||
|
|
1ba3631cc1 | ||
|
|
94d41bc1f5 | ||
|
|
a4c68eed16 | ||
|
|
22c53c365a | ||
|
|
59c30c2842 | ||
|
|
3842f054a5 | ||
|
|
8fa4c50379 | ||
|
|
510d46289b | ||
|
|
6e5b14c466 | ||
|
|
b3922864f2 | ||
|
|
31d4c1d34f | ||
|
|
0cdfdc725c | ||
|
|
bb132243ed | ||
|
|
d96115325a | ||
|
|
a450abc080 | ||
|
|
0a45ea44a4 | ||
|
|
e05c601325 | ||
|
|
5c78cb9ab3 | ||
|
|
901ab1157d | ||
|
|
c0523df696 | ||
|
|
f7f5623c71 | ||
|
|
184013b652 | ||
|
|
f8282c56cb | ||
|
|
c8d297a056 | ||
|
|
c718da20e3 | ||
|
|
9c53436470 | ||
|
|
17daa2071f | ||
|
|
3495fb1c76 | ||
|
|
0c93e5ed02 | ||
|
|
737e1fcef6 | ||
|
|
16118874cf | ||
|
|
b161ccafaf | ||
|
|
59a111de39 | ||
|
|
c0a641247b | ||
|
|
057cdbd927 | ||
|
|
0ecc166dcd | ||
|
|
3a26a5d39d | ||
|
|
9ae75a399b | ||
|
|
01b766dacc | ||
|
|
904e0a574d | ||
|
|
acd6dc62d0 | ||
|
|
8f26c31df6 | ||
|
|
84c05efaa5 | ||
|
|
d72d96f9d0 | ||
|
|
981f04fbd8 | ||
|
|
463dd6f4ec | ||
|
|
02034211c1 | ||
|
|
fafda283b1 | ||
|
|
199be20717 | ||
|
|
1c825d2a74 | ||
|
|
e936f22419 | ||
|
|
c253a4bac0 | ||
|
|
ecb15a224a | ||
|
|
73f1399e47 | ||
|
|
8e29ffed50 | ||
|
|
2d4dd05ec9 | ||
|
|
fb3460f5f6 | ||
|
|
cff7667155 | ||
|
|
7871c0928f | ||
|
|
04d714f19c | ||
|
|
a18f60bd05 | ||
|
|
deea5babbc | ||
|
|
e41b3dc09a | ||
|
|
6366794838 | ||
|
|
c01a39d881 | ||
|
|
3f2ce59766 | ||
|
|
bcf0c72e47 | ||
|
|
bd5892b70c | ||
|
|
70ea61f22f | ||
|
|
1b0c2e1764 | ||
|
|
3d532f6e0b | ||
|
|
81fd1a3758 | ||
|
|
b52c3fb61e | ||
|
|
bc3d92f9e6 | ||
|
|
ee08829a28 | ||
|
|
83f0d2c953 | ||
|
|
605be3ebad | ||
|
|
ca6dc2c964 | ||
|
|
dc41126e25 | ||
|
|
da4c542724 | ||
|
|
e7d93a5248 | ||
|
|
51aa4c73cd | ||
|
|
4a7510d36f | ||
|
|
ba0a1a651e | ||
|
|
068f41393d | ||
|
|
169e8b8f8b | ||
|
|
c6e2889075 | ||
|
|
f62f714863 | ||
|
|
da9ace5d9d | ||
|
|
97345123ce | ||
|
|
8aa9105ed3 | ||
|
|
e8088f85d0 | ||
|
|
0c8476c79b | ||
|
|
a7a66e8bc0 | ||
|
|
65a861a9b6 | ||
|
|
de51fd1c51 | ||
|
|
40c70673cd | ||
|
|
73b221d022 | ||
|
|
287b286940 | ||
|
|
1d0e85cf55 | ||
|
|
30ec6dc78d | ||
|
|
121a4d1146 | ||
|
|
b9be373671 | ||
|
|
aedd0e406c |
56
.dockerignore
Normal file
56
.dockerignore
Normal 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
|
||||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github: [DaneEveritt]
|
||||||
|
custom: ["https://paypal.me/PterodactylSoftware"]
|
||||||
66
.github/workflows/build-test.yml
vendored
66
.github/workflows/build-test.yml
vendored
@@ -1,54 +1,78 @@
|
|||||||
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 ]
|
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 }}
|
||||||
|
|
||||||
|
- name: Print Environment
|
||||||
|
id: env
|
||||||
|
run: |
|
||||||
|
printf "Go Executable Path: $(which go)\n"
|
||||||
|
printf "Go Version: $(go version)\n"
|
||||||
|
printf "\n\nGo Environment:\n\n"
|
||||||
|
go env
|
||||||
|
printf "\n\nSystem Environment:\n\n"
|
||||||
|
env
|
||||||
|
|
||||||
|
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
|
||||||
|
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||||
|
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||||
|
|
||||||
|
- name: Build Cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.env.outputs.go_cache }}
|
||||||
|
key: ${{ runner.os }}-${{ matrix.go }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.go }}-go
|
||||||
|
|
||||||
|
- name: Get Dependencies
|
||||||
|
run: |
|
||||||
|
go get -v -t -d ./...
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
SRC_PATH: github.com/pterodactyl/wings
|
||||||
run: |
|
run: |
|
||||||
go build -v -ldflags="-s -w -X github.com/pterodactyl/wings/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 }}
|
||||||
|
|||||||
35
.github/workflows/codeql-analysis.yml
vendored
35
.github/workflows/codeql-analysis.yml
vendored
@@ -1,29 +1,32 @@
|
|||||||
name: CodeQL Scanning
|
name: CodeQL
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
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.
|
|
||||||
fetch-depth: 2
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v1
|
||||||
# Override language selection by uncommenting this and choosing your languages
|
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v1
|
||||||
|
|||||||
57
.github/workflows/docker.yml
vendored
57
.github/workflows/docker.yml
vendored
@@ -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 }}
|
||||||
|
|||||||
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -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
|
||||||
47
.travis.yml
47
.travis.yml
@@ -1,47 +0,0 @@
|
|||||||
os: linux
|
|
||||||
dist: xenial
|
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.13.x
|
|
||||||
|
|
||||||
go_import_path: "github.com/pterodactyl/wings"
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
install:
|
|
||||||
- mkdir -p $GOPATH/bin
|
|
||||||
|
|
||||||
# Install used tools
|
|
||||||
- go get github.com/mitchellh/gox
|
|
||||||
- go get github.com/haya14busa/goverage
|
|
||||||
- go get github.com/schrej/godacov
|
|
||||||
|
|
||||||
- go mod download
|
|
||||||
|
|
||||||
script:
|
|
||||||
- make cross-build
|
|
||||||
- goverage -v -coverprofile=coverage.out ./...
|
|
||||||
- godacov -t $CODACY_TOKEN -r ./coverage.out -c $TRAVIS_COMMIT
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key:
|
|
||||||
secure: HQ8AvnSsOW2aDUKv25sU83SswK9rReGeFi68SotLGPdWyFBWJbp/JEHhw9swSqvhLPykx5QqLnRPG4nomOp2i5dVTXgM/7C3wQ2ULymkJDZqDJEAxjm1IuNsjXgcFqp0zcNXL3g0moaorHS2XZpzbgaewlCyYoEb+3SZUGzOCPIjSFvoIBaAYx6kRn+pyWo1I0mQChno2i7SGvAoZwh/hZIO6L5FZe5PcpBs/SxkZ+/shsGMk7CIyNMhG6CQTE1tlr+ZenluXjtliZfc4XwkHG/9MICNl8ihUrnN6YfdvJZXLQvolZQ0QJ5Eyb04jQd1yzKR1hcLx2S42IAWxaWTy5QxSN8QyG5wBRNg567ib5FEqY4M1nyQnWQbUbaiYloYBp14aR1L9DQw8+xmXnlgnTUPq1w+cOpQLeY/RENCalgHe7NoI3lClC2b7/c1j+O7RA68yYUFUod0y7ZXcCwsJkbRk7xgyDEAGs+rq8wLknj6f8y8cfNm179lRARwblnmo9uA43Tlee8DBSziSvJy/mYMzdIQeb+PHuznXjr4fze7x+zvronkiD/JH8MjJl3SWaE7DGtc5jz4+aRxU3rMbHwToEOY6u5pIsvz5PRFYWBvKX2+VoxmdR+m1qhAxsg0wtbA0CTnqgHNGMIFDWVTDQSy8LvJt+usUn1RtrYyyiI=
|
|
||||||
file_glob: true
|
|
||||||
file: build/*
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
branch: master
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
webhooks:
|
|
||||||
urls:
|
|
||||||
- https://misc.schrej.net/travistodiscord/pterodev.php
|
|
||||||
on_success: change
|
|
||||||
on_failure: always
|
|
||||||
on_error: always
|
|
||||||
on_cancel: always
|
|
||||||
on_start: never
|
|
||||||
60
CHANGELOG.md
60
CHANGELOG.md
@@ -1,5 +1,65 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.2.3
|
||||||
|
### Fixed
|
||||||
|
* **[Security]** Fixes a remaining security vulnerability in the code handling remote file downloads for servers relating to redirect validation.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Adds a configuration key at `api.disable_remote_download` that can be set to `true` to completely download the remote download system.
|
||||||
|
|
||||||
|
## v1.2.2
|
||||||
|
### Fixed
|
||||||
|
* Reverts changes to logic handling blocking until a server process is done running when polling stats. This change exposed a bug in the underlying Docker system causing servers to enter a state in which Wings was unable to terminate the process and Docker commands would hang if executed against the container.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Adds logic to handle a console stream unexpectedly returning an EOF when reading console logs. New code should automatically re-attach the stream avoiding issues where the console would stop live updating for servers.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
### Fixed
|
||||||
|
* Fixes `archive/tar: write too long` error when creating a server backup.
|
||||||
|
* Fixes server installation docker images not using authentication properly during the pull.
|
||||||
|
* Fixes temporary transfer files not being removed after the transfer is completed.
|
||||||
|
* Fixes TLS certificate checking to be all lowercase to avoid any lookup issues when an all-caps domain is provided.
|
||||||
|
* Fixes multiple interfaces with the same port not being publishable for a server.
|
||||||
|
* Fixes errors encountered during websocket processes being incorrectly passed back to the Panel as a JWT error rather than a generic Wings error for admin users.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Added logic to notify the Panel when archive generation fails.
|
||||||
|
* Added endpoint to run `chmod` commands against server files and updated API response to include the mode bits when requesting files.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Updated internals to call `Server.Environment.State()` rather than deprecated `Server.GetState()` functions.
|
||||||
|
* Improved error handling logic and massively simplified error passing around the codebase.
|
||||||
|
|
||||||
## v1.1.2
|
## v1.1.2
|
||||||
### Fixed
|
### Fixed
|
||||||
* Fixes binaries built as part of the release process not being usable in MUSL based environments (such as our Docker images).
|
* Fixes binaries built as part of the release process not being usable in MUSL based environments (such as our Docker images).
|
||||||
|
|||||||
42
Dockerfile
42
Dockerfile
@@ -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" ]
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -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_*
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
|||||||
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
|
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
|
||||||
| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. |
|
| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. |
|
||||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
||||||
| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
|
| [**XCORE**](https://xcore-server.de/) | XCORE offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
|
||||||
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. |
|
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. |
|
||||||
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims to cheap services on quality servers. Premium i9-9900K processors will run your game like a dream. |
|
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims to cheap services on quality servers. Premium i9-9900K processors will run your game like a dream. |
|
||||||
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||||
|
|||||||
17
api/api.go
17
api/api.go
@@ -2,17 +2,18 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/system"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/pterodactyl/wings/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initializes the requester instance.
|
// Initializes the requester instance.
|
||||||
@@ -69,7 +70,7 @@ func (r *Request) Endpoint(endpoint string) string {
|
|||||||
func (r *Request) Make(method, url string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) {
|
func (r *Request) Make(method, url string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) {
|
||||||
req, err := http.NewRequest(method, url, body)
|
req, err := http.NewRequest(method, url, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("User-Agent", fmt.Sprintf("Pterodactyl Wings/v%s (id:%s)", system.Version, config.Get().AuthenticationTokenId))
|
req.Header.Set("User-Agent", fmt.Sprintf("Pterodactyl Wings/v%s (id:%s)", system.Version, config.Get().AuthenticationTokenId))
|
||||||
@@ -127,7 +128,7 @@ func (r *Request) Get(url string, data Q) (*Response, error) {
|
|||||||
func (r *Request) Post(url string, data interface{}) (*Response, error) {
|
func (r *Request) Post(url string, data interface{}) (*Response, error) {
|
||||||
b, err := json.Marshal(data)
|
b, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Make(http.MethodPost, r.Endpoint(url), bytes.NewBuffer(b))
|
return r.Make(http.MethodPost, r.Endpoint(url), bytes.NewBuffer(b))
|
||||||
@@ -167,10 +168,10 @@ func (r *Response) Read() ([]byte, error) {
|
|||||||
func (r *Response) Bind(v interface{}) error {
|
func (r *Response) Bind(v interface{}) error {
|
||||||
b, err := r.Read()
|
b, err := r.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(json.Unmarshal(b, &v))
|
return json.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the error message from the API call as a string. The error message will be formatted
|
// Returns the error message from the API call as a string. The error message will be formatted
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackupRemoteUploadResponse struct {
|
type BackupRemoteUploadResponse struct {
|
||||||
CompleteMultipartUpload string `json:"complete_multipart_upload"`
|
|
||||||
AbortMultipartUpload string `json:"abort_multipart_upload"`
|
|
||||||
Parts []string `json:"parts"`
|
Parts []string `json:"parts"`
|
||||||
PartSize int64 `json:"part_size"`
|
PartSize int64 `json:"part_size"`
|
||||||
}
|
}
|
||||||
@@ -16,7 +13,7 @@ type BackupRemoteUploadResponse struct {
|
|||||||
func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupRemoteUploadResponse, error) {
|
func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupRemoteUploadResponse, error) {
|
||||||
resp, err := r.Get(fmt.Sprintf("/backups/%s", backup), Q{"size": strconv.FormatInt(size, 10)})
|
resp, err := r.Get(fmt.Sprintf("/backups/%s", backup), Q{"size": strconv.FormatInt(size, 10)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -26,7 +23,7 @@ func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupR
|
|||||||
|
|
||||||
var res BackupRemoteUploadResponse
|
var res BackupRemoteUploadResponse
|
||||||
if err := resp.Bind(&res); err != nil {
|
if err := resp.Bind(&res); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &res, nil
|
return &res, nil
|
||||||
@@ -44,7 +41,7 @@ type BackupRequest struct {
|
|||||||
func (r *Request) SendBackupStatus(backup string, data BackupRequest) error {
|
func (r *Request) SendBackupStatus(backup string, data BackupRequest) error {
|
||||||
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 errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/pterodactyl/wings/parser"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/pterodactyl/wings/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OutputLineMatcher struct {
|
type OutputLineMatcher struct {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -57,7 +57,7 @@ type RawServerData struct {
|
|||||||
func (r *Request) GetServers() ([]RawServerData, error) {
|
func (r *Request) GetServers() ([]RawServerData, error) {
|
||||||
resp, err := r.Get("/servers", Q{"per_page": strconv.Itoa(int(config.Get().RemoteQuery.BootServersPerPage))})
|
resp, err := r.Get("/servers", Q{"per_page": strconv.Itoa(int(config.Get().RemoteQuery.BootServersPerPage))})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ func (r *Request) GetServers() ([]RawServerData, error) {
|
|||||||
|
|
||||||
var res allServerResponse
|
var res allServerResponse
|
||||||
if err := resp.Bind(&res); err != nil {
|
if err := resp.Bind(&res); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
@@ -117,7 +117,7 @@ func (r *Request) GetServers() ([]RawServerData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := g.Wait(); err != nil {
|
if err := g.Wait(); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationRespon
|
|||||||
|
|
||||||
resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid), nil)
|
resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, errors.WithStackIf(err)
|
return cfg, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationRespon
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := resp.Bind(&cfg); err != nil {
|
if err := resp.Bind(&cfg); err != nil {
|
||||||
return cfg, errors.WithStackIf(err)
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
@@ -150,7 +150,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
|
|||||||
var is InstallationScript
|
var is InstallationScript
|
||||||
resp, err := r.Get(fmt.Sprintf("/servers/%s/install", uuid), nil)
|
resp, err := r.Get(fmt.Sprintf("/servers/%s/install", uuid), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return is, errors.WithStackIf(err)
|
return is, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := resp.Bind(&is); err != nil {
|
if err := resp.Bind(&is); err != nil {
|
||||||
return is, errors.WithStackIf(err)
|
return is, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return is, nil
|
return is, nil
|
||||||
@@ -169,7 +169,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
|
|||||||
func (r *Request) SendInstallationStatus(uuid string, successful bool) error {
|
func (r *Request) SendInstallationStatus(uuid string, successful bool) error {
|
||||||
resp, err := r.Post(fmt.Sprintf("/servers/%s/install", uuid), D{"successful": successful})
|
resp, err := r.Post(fmt.Sprintf("/servers/%s/install", uuid), D{"successful": successful})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -183,29 +183,22 @@ func (r *Request) SendInstallationStatus(uuid string, successful bool) error {
|
|||||||
func (r *Request) SendArchiveStatus(uuid string, successful bool) error {
|
func (r *Request) SendArchiveStatus(uuid string, successful bool) error {
|
||||||
resp, err := r.Post(fmt.Sprintf("/servers/%s/archive", uuid), D{"successful": successful})
|
resp, err := r.Post(fmt.Sprintf("/servers/%s/archive", uuid), D{"successful": successful})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
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 errors.WithStackIf(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 errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return resp.Error()
|
return resp.Error()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"regexp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SftpAuthRequest struct {
|
type SftpAuthRequest struct {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// We've gone through a couple of iterations of where the configuration is stored. This
|
// We've gone through a couple of iterations of where the configuration is stored. This
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"github.com/AlecAivazis/survey/v2/terminal"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -15,6 +11,11 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pterodactyl/wings/environment"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -16,11 +15,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/environment"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/AlecAivazis/survey/v2/terminal"
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
"github.com/docker/cli/components/engine/pkg/parsers/operatingsystem"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
|
"github.com/docker/docker/pkg/parsers/operatingsystem"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|||||||
140
cmd/root.go
140
cmd/root.go
@@ -3,70 +3,84 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/NYTimes/logrotate"
|
log2 "log"
|
||||||
"github.com/apex/log/handlers/multi"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/gammazero/workerpool"
|
|
||||||
"golang.org/x/crypto/acme"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/mitchellh/colorstring"
|
|
||||||
"github.com/pterodactyl/wings/loggers/cli"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
|
"github.com/NYTimes/logrotate"
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/apex/log/handlers/multi"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/gammazero/workerpool"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
profiler = ""
|
|
||||||
configPath = config.DefaultLocation
|
configPath = config.DefaultLocation
|
||||||
debug = false
|
debug = false
|
||||||
useAutomaticTls = false
|
|
||||||
tlsHostname = ""
|
|
||||||
showVersion = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var root = &cobra.Command{
|
var rootCommand = &cobra.Command{
|
||||||
Use: "wings",
|
Use: "wings",
|
||||||
Short: "The wings of the pterodactyl game management panel",
|
Short: "Runs the API server allowing programatic control of game servers for Pterodactyl Panel.",
|
||||||
Long: ``,
|
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
if useAutomaticTls && len(tlsHostname) == 0 {
|
if tls, _ := cmd.Flags().GetBool("auto-tls"); tls {
|
||||||
|
if host, _ := cmd.Flags().GetString("tls-hostname"); host == "" {
|
||||||
fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n ./wings --auto-tls --tls-hostname my.example.com")
|
fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n ./wings --auto-tls --tls-hostname my.example.com")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Run: rootCmdRun,
|
Run: rootCmdRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
var versionCommand = &cobra.Command{
|
||||||
root.PersistentFlags().BoolVar(&showVersion, "version", false, "show the version and exit")
|
Use: "version",
|
||||||
root.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file")
|
Short: "Prints the current executable version and exits.",
|
||||||
root.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
root.PersistentFlags().StringVar(&profiler, "profiler", "", "the profiler to run for this instance")
|
fmt.Printf("wings v%s\nCopyright © 2018 - 2021 Dane Everitt & Contributors\n", system.Version)
|
||||||
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.AddCommand(configureCmd)
|
func Execute() {
|
||||||
root.AddCommand(diagnosticsCmd)
|
if err := rootCommand.Execute(); err != nil {
|
||||||
|
log2.Fatalf("failed to execute command: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCommand.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file")
|
||||||
|
rootCommand.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
|
||||||
|
|
||||||
|
// Flags specifically used when running the API.
|
||||||
|
rootCommand.Flags().String("profiler", "", "the profiler to run for this instance")
|
||||||
|
rootCommand.Flags().Bool("auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt")
|
||||||
|
rootCommand.Flags().String("tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate")
|
||||||
|
rootCommand.Flags().Bool("ignore-certificate-errors", false, "ignore certificate verification errors when executing API calls")
|
||||||
|
|
||||||
|
rootCommand.AddCommand(versionCommand)
|
||||||
|
rootCommand.AddCommand(configureCmd)
|
||||||
|
rootCommand.AddCommand(diagnosticsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@@ -77,7 +91,7 @@ func readConfiguration() (*config.Configuration, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s, err := os.Stat(p); err != nil {
|
if s, err := os.Stat(p); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else if s.IsDir() {
|
} else if s.IsDir() {
|
||||||
return nil, errors.New("cannot use directory as configuration file path")
|
return nil, errors.New("cannot use directory as configuration file path")
|
||||||
}
|
}
|
||||||
@@ -85,13 +99,8 @@ func readConfiguration() (*config.Configuration, error) {
|
|||||||
return config.ReadConfiguration(p)
|
return config.ReadConfiguration(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rootCmdRun(*cobra.Command, []string) {
|
func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||||
if showVersion {
|
switch cmd.Flag("profiler").Value.String() {
|
||||||
fmt.Println(system.Version)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch profiler {
|
|
||||||
case "cpu":
|
case "cpu":
|
||||||
defer profile.Start(profile.CPUProfile).Stop()
|
defer profile.Start(profile.CPUProfile).Stop()
|
||||||
case "mem":
|
case "mem":
|
||||||
@@ -117,7 +126,6 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
exitWithConfigurationNotice()
|
exitWithConfigurationNotice()
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,8 +147,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 ok, _ := cmd.Flags().GetBool("ignore-certificate-errors"); ok {
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
@@ -199,7 +209,7 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
|
|
||||||
states, err := server.CachedServerStates()
|
states, err := server.CachedServerStates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithField("error", errors.WithStackIf(err)).Error("failed to retrieve locally cached server states from disk, assuming all servers in offline state")
|
log.WithField("error", err).Error("failed to retrieve locally cached server states from disk, assuming all servers in offline state")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new workerpool that limits us to 4 servers being bootstrapped at a time
|
// Create a new workerpool that limits us to 4 servers being bootstrapped at a time
|
||||||
@@ -235,7 +245,7 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
// as a result will result in a slow boot.
|
// as a result will result in a slow boot.
|
||||||
if !r && (st == environment.ProcessRunningState || st == environment.ProcessStartingState) {
|
if !r && (st == environment.ProcessRunningState || st == environment.ProcessStartingState) {
|
||||||
if err := s.HandlePowerAction(server.PowerActionStart); err != nil {
|
if err := s.HandlePowerAction(server.PowerActionStart); err != nil {
|
||||||
s.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to return server to running state")
|
s.Log().WithField("error", err).Warn("failed to return server to running state")
|
||||||
}
|
}
|
||||||
} else if r || (!r && s.IsRunning()) {
|
} else if r || (!r && s.IsRunning()) {
|
||||||
// If the server is currently running on Docker, mark the process as being in that state.
|
// If the server is currently running on Docker, mark the process as being in that state.
|
||||||
@@ -248,7 +258,7 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
|
|
||||||
s.Environment.SetState(environment.ProcessRunningState)
|
s.Environment.SetState(environment.ProcessRunningState)
|
||||||
if err := s.Environment.Attach(); err != nil {
|
if err := s.Environment.Attach(); err != nil {
|
||||||
s.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to attach to running server environment")
|
s.Log().WithField("error", err).Warn("failed to attach to running server environment")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// At this point we've determined that the server should indeed be in an offline state, so we'll
|
// At this point we've determined that the server should indeed be in an offline state, so we'll
|
||||||
@@ -278,9 +288,15 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
log.WithField("error", err).Error("failed to create backup directory")
|
log.WithField("error", err).Error("failed to create backup directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autotls, _ := cmd.Flags().GetBool("auto-tls")
|
||||||
|
tlshostname, _ := cmd.Flags().GetString("tls-hostname")
|
||||||
|
if autotls && tlshostname == "" {
|
||||||
|
autotls = false
|
||||||
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"use_ssl": c.Api.Ssl.Enabled,
|
"use_ssl": c.Api.Ssl.Enabled,
|
||||||
"use_auto_tls": useAutomaticTls && len(tlsHostname) > 0,
|
"use_auto_tls": autotls,
|
||||||
"host_address": c.Api.Host,
|
"host_address": c.Api.Host,
|
||||||
"host_port": c.Api.Port,
|
"host_port": c.Api.Port,
|
||||||
}).Info("configuring internal webserver")
|
}).Info("configuring internal webserver")
|
||||||
@@ -291,7 +307,6 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port),
|
Addr: fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port),
|
||||||
Handler: r,
|
Handler: r,
|
||||||
|
|
||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
NextProtos: []string{"h2", "http/1.1"},
|
NextProtos: []string{"h2", "http/1.1"},
|
||||||
// @see https://blog.cloudflare.com/exposing-go-on-the-internet
|
// @see https://blog.cloudflare.com/exposing-go-on-the-internet
|
||||||
@@ -311,14 +326,14 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the server should run with TLS but using autocert.
|
// Check if the server should run with TLS but using autocert.
|
||||||
if useAutomaticTls && len(tlsHostname) > 0 {
|
if autotls {
|
||||||
m := autocert.Manager{
|
m := autocert.Manager{
|
||||||
Prompt: autocert.AcceptTOS,
|
Prompt: autocert.AcceptTOS,
|
||||||
Cache: autocert.DirCache(path.Join(c.System.RootDirectory, "/.tls-cache")),
|
Cache: autocert.DirCache(path.Join(c.System.RootDirectory, "/.tls-cache")),
|
||||||
HostPolicy: autocert.HostWhitelist(tlsHostname),
|
HostPolicy: autocert.HostWhitelist(tlshostname),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithField("hostname", tlsHostname).
|
log.WithField("hostname", tlshostname).
|
||||||
Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt")
|
Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt")
|
||||||
|
|
||||||
// Hook autocert into the main http server.
|
// Hook autocert into the main http server.
|
||||||
@@ -334,9 +349,8 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
|
|
||||||
// Start the main http server with TLS using autocert.
|
// Start the main http server with TLS using autocert.
|
||||||
if err := s.ListenAndServeTLS("", ""); err != nil {
|
if err := s.ListenAndServeTLS("", ""); err != nil {
|
||||||
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlsHostname, "error": err}).
|
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlshostname, "error": err}).
|
||||||
Fatal("failed to configure HTTP server using auto-tls")
|
Fatal("failed to configure HTTP server using auto-tls")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -344,9 +358,8 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
|
|
||||||
// Check if main http server should run with TLS.
|
// Check if main http server should run with TLS.
|
||||||
if c.Api.Ssl.Enabled {
|
if c.Api.Ssl.Enabled {
|
||||||
if err := s.ListenAndServeTLS(c.Api.Ssl.CertificateFile, c.Api.Ssl.KeyFile); err != nil {
|
if err := s.ListenAndServeTLS(strings.ToLower(c.Api.Ssl.CertificateFile), strings.ToLower(c.Api.Ssl.KeyFile)); err != nil {
|
||||||
log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
|
log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -355,39 +368,34 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
s.TLSConfig = nil
|
s.TLSConfig = nil
|
||||||
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")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Execute calls cobra to handle cli commands
|
// Cancel the context on all of the running servers at this point, even though the
|
||||||
func Execute() error {
|
// program is just shutting down.
|
||||||
return root.Execute()
|
for _, s := range server.GetServers().All() {
|
||||||
|
s.CtxCancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configures the global logger for Zap so that we can call it from any location
|
// Configures the global logger for Zap so that we can call it from any location
|
||||||
// in the code without having to pass around a logger instance.
|
// in the code without having to pass around a logger instance.
|
||||||
func configureLogging(logDir string, debug bool) error {
|
func configureLogging(logDir string, debug bool) error {
|
||||||
if err := os.MkdirAll(path.Join(logDir, "/install"), 0700); err != nil {
|
if err := os.MkdirAll(path.Join(logDir, "/install"), 0700); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p := filepath.Join(logDir, "/wings.log")
|
p := filepath.Join(logDir, "/wings.log")
|
||||||
w, err := logrotate.NewFile(p)
|
w, err := logrotate.NewFile(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(errors.WrapIf(err, "failed to open process log file"))
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
if debug {
|
if debug {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
} else {
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.SetHandler(multi.New(
|
log.SetHandler(multi.New(cli.Default, cli.New(w.File, false)))
|
||||||
cli.Default,
|
|
||||||
cli.New(w.File, false),
|
|
||||||
))
|
|
||||||
|
|
||||||
log.WithField("path", p).Info("writing log files to disk")
|
log.WithField("path", p).Info("writing log files to disk")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -401,9 +409,9 @@ __ [blue][bold]Pterodactyl[reset] _____/___/_______ _______ ______
|
|||||||
\_____\ \/\/ / / / __ / ___/
|
\_____\ \/\/ / / / __ / ___/
|
||||||
\___\ / / / / /_/ /___ /
|
\___\ / / / / /_/ /___ /
|
||||||
\___/\___/___/___/___/___ /______/
|
\___/\___/___/___/___/___ /______/
|
||||||
/_______/ [bold]v%s[reset]
|
/_______/ [bold]%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
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cobaugh/osrelease"
|
|
||||||
"github.com/creasty/defaults"
|
|
||||||
"github.com/gbrlsnchs/jwt/v3"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -14,6 +9,12 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/cobaugh/osrelease"
|
||||||
|
"github.com/creasty/defaults"
|
||||||
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultLocation = "/etc/pterodactyl/config.yml"
|
const DefaultLocation = "/etc/pterodactyl/config.yml"
|
||||||
@@ -87,11 +88,16 @@ type ApiConfiguration struct {
|
|||||||
|
|
||||||
// SSL configuration for the daemon.
|
// SSL configuration for the daemon.
|
||||||
Ssl struct {
|
Ssl struct {
|
||||||
Enabled bool `default:"false"`
|
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||||
CertificateFile string `json:"cert" yaml:"cert"`
|
CertificateFile string `json:"cert" yaml:"cert"`
|
||||||
KeyFile string `json:"key" yaml:"key"`
|
KeyFile string `json:"key" yaml:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines if functionality for allowing remote download of files into server directories
|
||||||
|
// is enabled on this instance. If set to "true" remote downloads will not be possible for
|
||||||
|
// servers.
|
||||||
|
DisableRemoteDownload bool `json:"disable_remote_download" yaml:"disable_remote_download"`
|
||||||
|
|
||||||
// The maximum size for files uploaded through the Panel in bytes.
|
// The maximum size for files uploaded through the Panel in bytes.
|
||||||
UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
|
UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
|
||||||
}
|
}
|
||||||
@@ -192,7 +198,7 @@ func GetJwtAlgorithm() *jwt.HMACSHA {
|
|||||||
func NewFromPath(path string) (*Configuration, error) {
|
func NewFromPath(path string) (*Configuration, error) {
|
||||||
c := new(Configuration)
|
c := new(Configuration)
|
||||||
if err := defaults.Set(c); err != nil {
|
if err := defaults.Set(c); err != nil {
|
||||||
return c, errors.WithStackIf(err)
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.unsafeSetPath(path)
|
c.unsafeSetPath(path)
|
||||||
@@ -223,6 +229,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
|
||||||
@@ -230,35 +266,30 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return u, c.setSystemUser(u)
|
return u, c.setSystemUser(u)
|
||||||
} else if _, ok := err.(user.UnknownUserError); !ok {
|
} else if _, ok := err.(user.UnknownUserError); !ok {
|
||||||
return nil, errors.WithStackIf(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, errors.WithStackIf(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.
|
||||||
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
|
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
split := strings.Split(command, " ")
|
split := strings.Split(command, " ")
|
||||||
if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
|
if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := user.Lookup(c.System.Username); err != nil {
|
if u, err := user.Lookup(c.System.Username); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return u, c.setSystemUser(u)
|
return u, c.setSystemUser(u)
|
||||||
}
|
}
|
||||||
@@ -267,8 +298,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
|
||||||
@@ -300,11 +338,11 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
|
|
||||||
b, err := yaml.Marshal(&ccopy)
|
b, err := yaml.Marshal(&ccopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
|
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -314,7 +352,7 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
func getSystemName() (string, error) {
|
func getSystemName() (string, error) {
|
||||||
// use osrelease to get release version and ID
|
// use osrelease to get release version and ID
|
||||||
if release, err := osrelease.Read(); err != nil {
|
if release, err := osrelease.Read(); err != nil {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
} else {
|
} else {
|
||||||
return release["ID"], nil
|
return release["ID"], nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ func (c RegistryConfiguration) Base64() (string, error) {
|
|||||||
|
|
||||||
b, err := json.Marshal(authConfig)
|
b, err := json.Marshal(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64.URLEncoding.EncodeToString(b), nil
|
return base64.URLEncoding.EncodeToString(b), nil
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,6 +11,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/apex/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines basic system configuration settings.
|
// Defines basic system configuration settings.
|
||||||
@@ -59,11 +60,6 @@ type SystemConfiguration struct {
|
|||||||
// disk usage is not a concern.
|
// disk usage is not a concern.
|
||||||
DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
|
DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
|
||||||
|
|
||||||
// Determines if Wings should detect a server that stops with a normal exit code of
|
|
||||||
// "0" as being crashed if the process stopped without any Wings interaction. E.g.
|
|
||||||
// the user did not press the stop button, but the process stopped cleanly.
|
|
||||||
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
|
|
||||||
|
|
||||||
// If set to true, file permissions for a server will be checked when the process is
|
// If set to true, file permissions for a server will be checked when the process is
|
||||||
// booted. This can cause boot delays if the server has a large amount of files. In most
|
// booted. This can cause boot delays if the server has a large amount of files. In most
|
||||||
// cases disabling this should not have any major impact unless external processes are
|
// cases disabling this should not have any major impact unless external processes are
|
||||||
@@ -78,6 +74,46 @@ type SystemConfiguration struct {
|
|||||||
WebsocketLogCount int `default:"150" yaml:"websocket_log_count"`
|
WebsocketLogCount int `default:"150" yaml:"websocket_log_count"`
|
||||||
|
|
||||||
Sftp SftpConfiguration `yaml:"sftp"`
|
Sftp SftpConfiguration `yaml:"sftp"`
|
||||||
|
|
||||||
|
CrashDetection CrashDetection `yaml:"crash_detection"`
|
||||||
|
|
||||||
|
Backups Backups `yaml:"backups"`
|
||||||
|
|
||||||
|
Transfers Transfers `yaml:"transfers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CrashDetection struct {
|
||||||
|
// Determines if Wings should detect a server that stops with a normal exit code of
|
||||||
|
// "0" as being crashed if the process stopped without any Wings interaction. E.g.
|
||||||
|
// the user did not press the stop button, but the process stopped cleanly.
|
||||||
|
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
|
||||||
|
|
||||||
|
// Timeout specifies the timeout between crashes that will not cause the server
|
||||||
|
// to be automatically restarted, this value is used to prevent servers from
|
||||||
|
// becoming stuck in a boot-loop after multiple consecutive crashes.
|
||||||
|
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
|
||||||
@@ -98,7 +134,7 @@ func (sc *SystemConfiguration) ConfigureDirectories() error {
|
|||||||
// that.
|
// that.
|
||||||
if d, err := filepath.EvalSymlinks(sc.Data); err != nil {
|
if d, err := filepath.EvalSymlinks(sc.Data); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else if d != sc.Data {
|
} else if d != sc.Data {
|
||||||
sc.Data = d
|
sc.Data = d
|
||||||
@@ -134,13 +170,13 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if st, err := os.Stat("/etc/logrotate.d"); err != nil && !os.IsNotExist(err) {
|
if st, err := os.Stat("/etc/logrotate.d"); err != nil && !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
|
} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat("/etc/logrotate.d/wings"); err != nil && !os.IsNotExist(err) {
|
if _, err := os.Stat("/etc/logrotate.d/wings"); err != nil && !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -151,7 +187,7 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
|||||||
// it so files can be rotated easily.
|
// it so files can be rotated easily.
|
||||||
f, err := os.Create("/etc/logrotate.d/wings")
|
f, err := os.Create("/etc/logrotate.d/wings")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -171,10 +207,10 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
|||||||
}`)
|
}`)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WrapIf(t.Execute(f, sc), "failed to write logrotate file to disk")
|
return errors.WithMessage(t.Execute(f, sc), "failed to write logrotate file to disk")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the location of the JSON file that tracks server states.
|
// Returns the location of the JSON file that tracks server states.
|
||||||
@@ -194,7 +230,7 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
|
|||||||
if sc.Timezone == "" {
|
if sc.Timezone == "" {
|
||||||
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil {
|
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return errors.WrapIf(err, "failed to open /etc/timezone for automatic server timezone calibration")
|
return errors.WithMessage(err, "failed to open /etc/timezone for automatic server timezone calibration")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
@@ -228,5 +264,5 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
|
|||||||
|
|
||||||
_, err := time.LoadLocation(sc.Timezone)
|
_, err := time.LoadLocation(sc.Timezone)
|
||||||
|
|
||||||
return errors.WrapIf(err, fmt.Sprintf("the supplied timezone %s is invalid", sc.Timezone))
|
return errors.WithMessage(err, fmt.Sprintf("the supplied timezone %s is invalid", sc.Timezone))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -10,8 +11,10 @@ services:
|
|||||||
- "2022:2022"
|
- "2022:2022"
|
||||||
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
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package environment
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines the allocations available for a given server. When using the Docker environment
|
// Defines the allocations available for a given server. When using the Docker environment
|
||||||
@@ -38,15 +39,16 @@ func (a *Allocations) Bindings() nat.PortMap {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
binding := []nat.PortBinding{
|
binding := nat.PortBinding{
|
||||||
{
|
|
||||||
HostIP: ip,
|
HostIP: ip,
|
||||||
HostPort: strconv.Itoa(port),
|
HostPort: strconv.Itoa(port),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out[nat.Port(fmt.Sprintf("%d/tcp", port))] = binding
|
tcp := nat.Port(fmt.Sprintf("%d/tcp", port))
|
||||||
out[nat.Port(fmt.Sprintf("%d/udp", port))] = binding
|
udp := nat.Port(fmt.Sprintf("%d/udp", port))
|
||||||
|
|
||||||
|
out[tcp] = append(out[tcp], binding)
|
||||||
|
out[udp] = append(out[udp], binding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package environment
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/apex/log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||||
"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"
|
||||||
@@ -26,17 +26,33 @@ type imagePullStatus struct {
|
|||||||
Progress string `json:"progress"`
|
Progress string `json:"progress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A custom console writer that allows us to keep a function blocked until the
|
||||||
|
// given stream is properly closed. This does nothing special, only exists to
|
||||||
|
// make a noop io.Writer.
|
||||||
|
type noopWriter struct{}
|
||||||
|
|
||||||
|
var _ io.Writer = noopWriter{}
|
||||||
|
|
||||||
|
// Implement the required Write function to satisfy the io.Writer interface.
|
||||||
|
func (nw noopWriter) Write(b []byte) (int, error) {
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Attaches to the docker container itself and ensures that we can pipe data in and out
|
// Attaches to the docker container itself and ensures that we can pipe data in and out
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.followOutput(); err != nil {
|
if err := e.followOutput(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := types.ContainerAttachOptions{
|
opts := types.ContainerAttachOptions{
|
||||||
@@ -48,15 +64,13 @@ func (e *Environment) Attach() error {
|
|||||||
|
|
||||||
// Set the stream again with the container.
|
// Set the stream again with the container.
|
||||||
if st, err := e.client.ContainerAttach(context.Background(), e.Id, opts); err != nil {
|
if st, err := e.client.ContainerAttach(context.Background(), e.Id, opts); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
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 e.stream.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -64,27 +78,32 @@ func (e *Environment) Attach() error {
|
|||||||
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", errors.WithStackIf(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 stopped, we need to have this logic present
|
||||||
log.WithField("environment_id", e.Id).WithField("error", errors.WithStackIf(err)).Error("error while copying environment output to console")
|
// in order to cancel the context and therefore stop the routine that is spawned.
|
||||||
|
//
|
||||||
|
// For now, DO NOT use client#ContainerWait from the Docker package. There is a nasty
|
||||||
|
// bug causing containers to hang on deletion and cause servers to lock up on the system.
|
||||||
|
//
|
||||||
|
// This weird code isn't intuitive, but it keeps the function from ending until the container
|
||||||
|
// is stopped and therefore the stream reader ends up closed.
|
||||||
|
// @see https://github.com/moby/moby/issues/41827
|
||||||
|
c := new(noopWriter)
|
||||||
|
if _, err := io.Copy(c, e.stream.Reader); err != nil {
|
||||||
|
e.log().WithField("error", err).Error("could not copy from environment stream to noop writer")
|
||||||
}
|
}
|
||||||
}(c)
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -120,7 +139,7 @@ func (e *Environment) InSituUpdate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u := container.UpdateConfig{
|
u := container.UpdateConfig{
|
||||||
@@ -130,7 +149,7 @@ func (e *Environment) InSituUpdate() error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if _, err := e.client.ContainerUpdate(ctx, e.Id, u); err != nil {
|
if _, err := e.client.ContainerUpdate(ctx, e.Id, u); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -145,12 +164,12 @@ func (e *Environment) Create() error {
|
|||||||
if _, err := e.client.ContainerInspect(context.Background(), e.Id); err == nil {
|
if _, err := e.client.ContainerInspect(context.Background(), e.Id); err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else if !client.IsErrNotFound(err) {
|
} else if !client.IsErrNotFound(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to pull the requested image before creating the container.
|
// Try to pull the requested image before creating the container.
|
||||||
if err := e.ensureImageExists(e.meta.Image); err != nil {
|
if err := e.ensureImageExists(e.meta.Image); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a := e.Configuration.Allocations()
|
a := e.Configuration.Allocations()
|
||||||
@@ -212,6 +231,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,8 +244,8 @@ 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 errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -258,6 +278,8 @@ func (e *Environment) Destroy() error {
|
|||||||
Force: true,
|
Force: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
e.SetState(environment.ProcessOfflineState)
|
||||||
|
|
||||||
// Don't trigger a destroy failure if we try to delete a container that does not
|
// Don't trigger a destroy failure if we try to delete a container that does not
|
||||||
// exist on the system. We're just a step ahead of ourselves in that case.
|
// exist on the system. We're just a step ahead of ourselves in that case.
|
||||||
//
|
//
|
||||||
@@ -266,8 +288,6 @@ func (e *Environment) Destroy() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
e.SetState(environment.ProcessOfflineState)
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,9 +297,8 @@ func (e *Environment) Destroy() error {
|
|||||||
func (e *Environment) followOutput() error {
|
func (e *Environment) followOutput() error {
|
||||||
if exists, err := e.Exists(); !exists {
|
if exists, err := e.Exists(); !exists {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(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,70 +310,40 @@ 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 e.scanOutput(reader)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Environment) scanOutput(reader io.ReadCloser) {
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
r := bufio.NewReader(reader)
|
events := e.Events()
|
||||||
|
|
||||||
// Micro-optimization to create these replacements one time when this routine
|
err := system.ScanReader(reader, func(line string) {
|
||||||
// fires up, rather than on every line that is executed.
|
events.Publish(environment.ConsoleOutputEvent, line)
|
||||||
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")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}(reader)
|
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
// Return here if the server is offline or currently stopping.
|
||||||
|
if e.State() == environment.ProcessStoppingState || e.State() == environment.ProcessOfflineState {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the current reader before starting a new one, the defer will still run
|
||||||
|
// but it will do nothing if we already closed the stream.
|
||||||
|
_ = reader.Close()
|
||||||
|
|
||||||
|
// Start following the output of the server again.
|
||||||
|
go e.followOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -440,9 +429,11 @@ func (e *Environment) ensureImageExists(image string) error {
|
|||||||
// I'm not sure what the best approach here is, but this will block execution until the image
|
// I'm not sure what the best approach here is, but this will block execution until the image
|
||||||
// is done being pulled, which is what we need.
|
// is done being pulled, which is what we need.
|
||||||
scanner := bufio.NewScanner(out)
|
scanner := bufio.NewScanner(out)
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
s := imagePullStatus{}
|
s := imagePullStatus{}
|
||||||
fmt.Println(scanner.Text())
|
fmt.Println(scanner.Text())
|
||||||
|
|
||||||
if err := json.Unmarshal(scanner.Bytes(), &s); err == nil {
|
if err := json.Unmarshal(scanner.Bytes(), &s); err == nil {
|
||||||
e.Events().Publish(environment.DockerImagePullStatus, s.Status+" "+s.Progress)
|
e.Events().Publish(environment.DockerImagePullStatus, s.Status+" "+s.Progress)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
"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"
|
||||||
@@ -24,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.
|
||||||
@@ -71,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"
|
||||||
}
|
}
|
||||||
@@ -78,8 +82,9 @@ func (e *Environment) Type() string {
|
|||||||
// Set if this process is currently attached to the process.
|
// Set if this process is currently attached to the process.
|
||||||
func (e *Environment) SetStream(s *types.HijackedResponse) {
|
func (e *Environment) SetStream(s *types.HijackedResponse) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
e.stream = s
|
e.stream = s
|
||||||
e.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the this process is currently attached to the container.
|
// Determine if the this process is currently attached to the container.
|
||||||
@@ -91,12 +96,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
|
||||||
}
|
}
|
||||||
@@ -156,7 +158,7 @@ func (e *Environment) ExitState() (uint32, bool, error) {
|
|||||||
return 1, false, nil
|
return 1, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, false, errors.WithStackIf(err)
|
return 0, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint32(c.State.ExitCode), c.State.OOMKilled, nil
|
return uint32(c.State.ExitCode), c.State.OOMKilled, nil
|
||||||
@@ -174,12 +176,14 @@ func (e *Environment) Config() *environment.Configuration {
|
|||||||
// Sets the stop configuration for the environment.
|
// Sets the stop configuration for the environment.
|
||||||
func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) {
|
func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
e.meta.Stop = c
|
e.meta.Stop = c
|
||||||
e.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Environment) SetImage(i string) {
|
func (e *Environment) SetImage(i string) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
e.meta.Image = i
|
e.meta.Image = i
|
||||||
e.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,12 @@ import (
|
|||||||
//
|
//
|
||||||
// This process will also confirm that the server environment exists and is in a bootable
|
// This process will also confirm that the server environment exists and is in a bootable
|
||||||
// state. This ensures that unexpected container deletion while Wings is running does
|
// state. This ensures that unexpected container deletion while Wings is running does
|
||||||
// not result in the server becoming unbootable.
|
// not result in the server becoming un-bootable.
|
||||||
func (e *Environment) OnBeforeStart() error {
|
func (e *Environment) OnBeforeStart() error {
|
||||||
// Always destroy and re-create the server container to ensure that synced data from
|
// Always destroy and re-create the server container to ensure that synced data from the Panel is used.
|
||||||
// the Panel is usee.
|
|
||||||
if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
|
if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
|
||||||
if !client.IsErrNotFound(err) {
|
if !client.IsErrNotFound(err) {
|
||||||
return errors.WrapIf(err, "failed to remove server docker container during pre-boot")
|
return errors.WithMessage(err, "failed to remove server docker container during pre-boot")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +48,7 @@ func (e *Environment) OnBeforeStart() error {
|
|||||||
// call to OnBeforeStart().
|
// call to OnBeforeStart().
|
||||||
func (e *Environment) Start() error {
|
func (e *Environment) Start() error {
|
||||||
sawError := false
|
sawError := false
|
||||||
|
|
||||||
// If sawError is set to true there was an error somewhere in the pipeline that
|
// If sawError is set to true there was an error somewhere in the pipeline that
|
||||||
// got passed up, but we also want to ensure we set the server to be offline at
|
// got passed up, but we also want to ensure we set the server to be offline at
|
||||||
// that point.
|
// that point.
|
||||||
@@ -69,7 +69,7 @@ func (e *Environment) Start() error {
|
|||||||
//
|
//
|
||||||
// @see https://github.com/pterodactyl/panel/issues/2000
|
// @see https://github.com/pterodactyl/panel/issues/2000
|
||||||
if !client.IsErrNotFound(err) {
|
if !client.IsErrNotFound(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the server is running update our internal state and continue on with the attach.
|
// If the server is running update our internal state and continue on with the attach.
|
||||||
@@ -84,7 +84,7 @@ func (e *Environment) Start() error {
|
|||||||
// to truncate them.
|
// to truncate them.
|
||||||
if _, err := os.Stat(c.LogPath); err == nil {
|
if _, err := os.Stat(c.LogPath); err == nil {
|
||||||
if err := os.Truncate(c.LogPath, 0); err != nil {
|
if err := os.Truncate(c.LogPath, 0); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,14 +99,14 @@ func (e *Environment) Start() error {
|
|||||||
// exists on the system, and rebuild the container if that is required for server booting to
|
// exists on the system, and rebuild the container if that is required for server booting to
|
||||||
// occur.
|
// occur.
|
||||||
if err := e.OnBeforeStart(); err != nil {
|
if err := e.OnBeforeStart(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := e.client.ContainerStart(ctx, e.Id, types.ContainerStartOptions{}); err != nil {
|
if err := e.client.ContainerStart(ctx, e.Id, types.ContainerStartOptions{}); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// No errors, good to continue through.
|
// No errors, good to continue through.
|
||||||
@@ -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) {
|
||||||
@@ -169,7 +169,7 @@ func (e *Environment) Stop() error {
|
|||||||
// will be terminated forcefully depending on the value of the second argument.
|
// will be terminated forcefully depending on the value of the second argument.
|
||||||
func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
||||||
if err := e.Stop(); err != nil {
|
if err := e.Stop(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
|
||||||
@@ -185,10 +185,10 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
|||||||
if terminate {
|
if terminate {
|
||||||
log.WithField("container_id", e.Id).Info("server did not stop in time, executing process termination")
|
log.WithField("container_id", e.Id).Info("server did not stop in time, executing process termination")
|
||||||
|
|
||||||
return errors.WithStackIf(e.Terminate(os.Kill))
|
return e.Terminate(os.Kill)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(ctxErr)
|
return ctxErr
|
||||||
}
|
}
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -197,13 +197,13 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
|||||||
if errors.Is(err, context.DeadlineExceeded) {
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
l.Warn("deadline exceeded for container stop; terminating process")
|
l.Warn("deadline exceeded for container stop; terminating process")
|
||||||
} else {
|
} else {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Warn("error while waiting for container stop; terminating process")
|
l.WithField("error", err).Warn("error while waiting for container stop; terminating process")
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(e.Terminate(os.Kill))
|
return e.Terminate(os.Kill)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
case <-ok:
|
case <-ok:
|
||||||
}
|
}
|
||||||
@@ -215,7 +215,7 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
|||||||
func (e *Environment) Terminate(signal os.Signal) error {
|
func (e *Environment) Terminate(signal os.Signal) error {
|
||||||
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.State.Running {
|
if !c.State.Running {
|
||||||
@@ -235,7 +235,7 @@ func (e *Environment) Terminate(signal os.Signal) error {
|
|||||||
|
|
||||||
sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed")
|
sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed")
|
||||||
|
|
||||||
if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil {
|
if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil && !client.IsErrNotFound(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
"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/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 errors.WithStackIf(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 errors.WithStackIf(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", errors.WithStackIf(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", errors.WithStackIf(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)
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ type dockerLogLine struct {
|
|||||||
Log string `json:"log"`
|
Log string `json:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrNotAttached = errors.Sentinel("not attached to instance")
|
var ErrNotAttached = errors.New("not attached to instance")
|
||||||
|
|
||||||
func (e *Environment) setStream(s *types.HijackedResponse) {
|
func (e *Environment) setStream(s *types.HijackedResponse) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
e.stream = s
|
e.stream = s
|
||||||
e.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends the specified command to the stdin of the running container instance. There is no
|
// Sends the specified command to the stdin of the running container instance. There is no
|
||||||
@@ -42,7 +43,7 @@ func (e *Environment) SendCommand(c string) error {
|
|||||||
|
|
||||||
_, err := e.stream.Conn.Write([]byte(c + "\n"))
|
_, err := e.stream.Conn.Write([]byte(c + "\n"))
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the log file for the server. This does not care if the server is running or not, it will
|
// Reads the log file for the server. This does not care if the server is running or not, it will
|
||||||
@@ -54,7 +55,7 @@ func (e *Environment) Readlog(lines int) ([]string, error) {
|
|||||||
Tail: strconv.Itoa(lines),
|
Tail: strconv.Itoa(lines),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ func (e *Environment) Readlog(lines int) ([]string, error) {
|
|||||||
// Docker stores the logs for server output in a JSON format. This function will iterate over the JSON
|
// Docker stores the logs for server output in a JSON format. This function will iterate over the JSON
|
||||||
// that was read from the log file and parse it into a more human readable format.
|
// that was read from the log file and parse it into a more human readable format.
|
||||||
func (e *Environment) parseLogToStrings(b []byte) ([]string, error) {
|
func (e *Environment) parseLogToStrings(b []byte) ([]string, error) {
|
||||||
var hasError = false
|
hasError := false
|
||||||
var out []string
|
var out []string
|
||||||
|
|
||||||
scanner := bufio.NewScanner(bytes.NewReader(b))
|
scanner := bufio.NewScanner(bytes.NewReader(b))
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package environment
|
package environment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pterodactyl/wings/events"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package environment
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mount struct {
|
type Mount struct {
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkStats struct {
|
||||||
RxBytes uint64 `json:"rx_bytes"`
|
RxBytes uint64 `json:"rx_bytes"`
|
||||||
TxBytes uint64 `json:"tx_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
|
|
||||||
// onto any values incorrectly.
|
|
||||||
func (s *Stats) Empty() {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
s.Memory = 0
|
|
||||||
s.CpuAbsolute = 0
|
|
||||||
s.Network.TxBytes = 0
|
|
||||||
s.Network.RxBytes = 0
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gammazero/workerpool"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gammazero/workerpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@@ -69,7 +69,7 @@ func (e *EventBus) Publish(topic string, data string) {
|
|||||||
func (e *EventBus) PublishJson(topic string, data interface{}) error {
|
func (e *EventBus) PublishJson(topic string, data interface{}) error {
|
||||||
b, err := json.Marshal(data)
|
b, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Publish(topic, string(b))
|
e.Publish(topic, string(b))
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gammazero/workerpool"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gammazero/workerpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CallbackPool struct {
|
type CallbackPool struct {
|
||||||
|
|||||||
102
go.mod
102
go.mod
@@ -4,79 +4,79 @@ go 1.13
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
emperror.dev/errors v0.8.0
|
emperror.dev/errors v0.8.0
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.0
|
github.com/AlecAivazis/survey/v2 v2.2.7
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Jeffail/gabs/v2 v2.6.0
|
||||||
github.com/Jeffail/gabs/v2 v2.5.1
|
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||||
github.com/Microsoft/go-winio v0.4.14 // 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/cli v17.12.1-ce-rc2+incompatible
|
|
||||||
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/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/spf13/pflag v1.0.5
|
||||||
github.com/ulikunitz/xz v0.5.7 // indirect
|
github.com/ugorji/go v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
|
||||||
golang.org/x/text v0.3.3 // indirect
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // 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
|
|
||||||
)
|
)
|
||||||
|
|||||||
320
go.sum
320
go.sum
@@ -1,18 +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=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
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 h1:4lycVEx0sdJkwDUfQ9pdu6SR0x7rgympt5f4+ok8jDk=
|
||||||
emperror.dev/errors v0.8.0/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
|
emperror.dev/errors v0.8.0/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.0 h1:AT4+23hOFopXYZaNGugbk7MWItkz0SfTmH/Hk92KeeE=
|
github.com/AlecAivazis/survey/v2 v2.2.7 h1:5NbxkF4RSKmpywYdcRgUmos1o+roJY8duCLZXbVjoig=
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.0/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
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=
|
||||||
@@ -27,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=
|
||||||
@@ -58,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=
|
||||||
@@ -67,40 +82,51 @@ 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=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
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/cli v17.12.1-ce-rc2+incompatible h1:ESUycEAqvFuLglAHkUW66rCc2djYtd3i1x231svLq9o=
|
|
||||||
github.com/docker/cli v17.12.1-ce-rc2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
|
||||||
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=
|
||||||
@@ -118,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=
|
||||||
@@ -165,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=
|
||||||
@@ -176,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=
|
||||||
@@ -196,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=
|
||||||
@@ -211,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=
|
||||||
@@ -223,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=
|
||||||
@@ -232,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=
|
||||||
@@ -257,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=
|
||||||
@@ -277,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=
|
||||||
@@ -290,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=
|
||||||
@@ -309,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=
|
||||||
@@ -318,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=
|
||||||
@@ -325,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=
|
||||||
@@ -365,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=
|
||||||
@@ -384,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=
|
||||||
@@ -397,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=
|
||||||
@@ -419,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=
|
||||||
@@ -430,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=
|
||||||
@@ -444,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=
|
||||||
@@ -460,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=
|
||||||
@@ -471,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=
|
||||||
@@ -492,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=
|
||||||
@@ -506,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=
|
||||||
@@ -525,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=
|
||||||
@@ -538,26 +587,33 @@ 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=
|
||||||
@@ -580,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=
|
||||||
@@ -609,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=
|
||||||
@@ -627,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=
|
||||||
@@ -639,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=
|
||||||
@@ -677,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=
|
||||||
@@ -694,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=
|
||||||
@@ -743,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=
|
||||||
@@ -758,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=
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package installer
|
package installer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
@@ -43,21 +44,21 @@ func New(data []byte) (*Installer, error) {
|
|||||||
|
|
||||||
// Unmarshal the environment variables from the request into the server struct.
|
// Unmarshal the environment variables from the request into the server struct.
|
||||||
if b, _, _, err := jsonparser.Get(data, "environment"); err != nil {
|
if b, _, _, err := jsonparser.Get(data, "environment"); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
cfg.EnvVars = make(environment.Variables)
|
cfg.EnvVars = make(environment.Variables)
|
||||||
if err := json.Unmarshal(b, &cfg.EnvVars); err != nil {
|
if err := json.Unmarshal(b, &cfg.EnvVars); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal the allocation mappings from the request into the server struct.
|
// Unmarshal the allocation mappings from the request into the server struct.
|
||||||
if b, _, _, err := jsonparser.Get(data, "allocations", "mappings"); err != nil {
|
if b, _, _, err := jsonparser.Get(data, "allocations", "mappings"); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
cfg.Allocations.Mappings = make(map[string][]int)
|
cfg.Allocations.Mappings = make(map[string][]int)
|
||||||
if err := json.Unmarshal(b, &cfg.Allocations.Mappings); err != nil {
|
if err := json.Unmarshal(b, &cfg.Allocations.Mappings); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ func New(data []byte) (*Installer, error) {
|
|||||||
c, err := api.New().GetServerConfiguration(cfg.Uuid)
|
c, err := api.New().GetServerConfiguration(cfg.Uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New(err.Error())
|
return nil, errors.New(err.Error())
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
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"
|
||||||
"emperror.dev/errors"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Default = New(os.Stderr, true)
|
var Default = New(os.Stderr, true)
|
||||||
|
|
||||||
var bold = color2.New(color2.Bold)
|
var bold = color2.New(color2.Bold)
|
||||||
|
var boldred = color2.New(color2.Bold, color2.FgRed)
|
||||||
|
|
||||||
var Strings = [...]string{
|
var Strings = [...]string{
|
||||||
log.DebugLevel: "DEBUG",
|
log.DebugLevel: "DEBUG",
|
||||||
@@ -41,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]
|
||||||
@@ -60,7 +57,6 @@ func (h *Handler) HandleLog(e *log.Entry) error {
|
|||||||
if name == "source" {
|
if name == "source" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(h.Writer, " %s=%v", color.Sprint(name), e.Fields.Get(name))
|
fmt.Fprintf(h.Writer, " %s=%v", color.Sprint(name), e.Fields.Get(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,44 +67,63 @@ func (h *Handler) HandleLog(e *log.Entry) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var br = color2.New(color2.Bold, color2.FgRed)
|
|
||||||
if err, ok := e.Fields.Get("error").(error); ok {
|
if err, ok := e.Fields.Get("error").(error); ok {
|
||||||
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Stacktrace:"), getErrorStack(err, false))
|
// Attach the stacktrace if it is missing at this point, but don't point
|
||||||
} else {
|
// it specifically to this line since that is irrelevant.
|
||||||
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Invalid Error:"), err)
|
err = errors.WithStackDepthIf(err, 4)
|
||||||
|
formatted := fmt.Sprintf("\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
|
||||||
}
|
}
|
||||||
|
|
||||||
func getErrorStack(err error, i bool) errors.StackTrace {
|
|
||||||
e, ok := err.(tracer)
|
|
||||||
if !ok {
|
|
||||||
if i {
|
|
||||||
// Just abort out of this and return a stacktrace leading up to this point. It isn't perfect
|
|
||||||
// but it'll at least include what function lead to this being called which we can then handle.
|
|
||||||
return errors.WrapIf(err, "failed to generate stacktrace for caught error").(tracer).StackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
return getErrorStack(errors.WrapIf(err, err.Error()), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
st := e.StackTrace()
|
|
||||||
|
|
||||||
l := len(st)
|
|
||||||
// If this was an internal stack generation we're going to skip over the top four items in the stack
|
|
||||||
// trace since they'll point to the error that was generated by this function.
|
|
||||||
f := 0
|
|
||||||
if i {
|
|
||||||
f = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
if i && l > 9 {
|
|
||||||
l = 9
|
|
||||||
} else if !i && l > 5 {
|
|
||||||
l = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
return st[f:l]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ package parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/Jeffail/gabs/v2"
|
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/buger/jsonparser"
|
|
||||||
"github.com/iancoleman/strcase"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/Jeffail/gabs/v2"
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/buger/jsonparser"
|
||||||
|
"github.com/iancoleman/strcase"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Regex to match anything that has a value matching the format of {{ config.$1 }} which
|
// Regex to match anything that has a value matching the format of {{ config.$1 }} which
|
||||||
@@ -76,13 +77,13 @@ func (cfr *ConfigurationFileReplacement) getKeyValue(value []byte) interface{} {
|
|||||||
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
||||||
parsed, err := gabs.ParseJSON(data)
|
parsed, err := gabs.ParseJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range f.Replace {
|
for _, v := range f.Replace {
|
||||||
value, err := f.LookupConfigurationValue(v)
|
value, err := f.LookupConfigurationValue(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a wildcard character, and if found split the key on that value to
|
// Check for a wildcard character, and if found split the key on that value to
|
||||||
@@ -101,7 +102,7 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.WrapIf(err, "failed to set config value of array child")
|
return nil, errors.WithMessage(err, "failed to set config value of array child")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -110,7 +111,7 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.WrapIf(err, "unable to set config value at pathway: "+v.Match)
|
return nil, errors.WithMessage(err, "unable to set config value at pathway: "+v.Match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
|
|||||||
_, err = c.SetP(value, path)
|
_, err = c.SetP(value, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i, _ := strconv.Atoi(matches[2])
|
i, _ := strconv.Atoi(matches[2])
|
||||||
@@ -147,10 +148,10 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
|
|||||||
ct, err := c.ArrayElementP(i, matches[1])
|
ct, err := c.ArrayElementP(i, matches[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if i != 0 || (!errors.Is(err, gabs.ErrNotArray) && !errors.Is(err, gabs.ErrNotFound)) {
|
if i != 0 || (!errors.Is(err, gabs.ErrNotArray) && !errors.Is(err, gabs.ErrNotFound)) {
|
||||||
return errors.WrapIf(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.
|
||||||
@@ -162,7 +163,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
|
|||||||
// an empty object if we have additional things to set on the array, or just an empty array type
|
// an empty object if we have additional things to set on the array, or just an empty array type
|
||||||
// if there is not an object structure detected (no matches[3] available).
|
// if there is not an object structure detected (no matches[3] available).
|
||||||
if _, err = c.SetP(t, matches[1]); err != nil {
|
if _, err = c.SetP(t, matches[1]); err != nil {
|
||||||
return errors.WrapIf(err, "failed to create empty array for missing element")
|
return errors.WithMessage(err, "failed to create empty array for missing element")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set our cursor to be the array element we expect, which in this case is just the first element
|
// Set our cursor to be the array element we expect, which in this case is just the first element
|
||||||
@@ -170,7 +171,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
|
|||||||
// to match additional elements. In those cases the server will just have to be rebooted or something.
|
// to match additional elements. In those cases the server will just have to be rebooted or something.
|
||||||
ct, err = c.ArrayElementP(0, matches[1])
|
ct, err = c.ArrayElementP(0, matches[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WrapIf(err, "failed to find array element at path")
|
return errors.WithMessage(err, "failed to find array element at path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WrapIf(err, "failed to set value at config path: "+path)
|
return errors.WithMessage(err, "failed to set value at config path: "+path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -253,7 +254,7 @@ func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplac
|
|||||||
match, _, _, err := jsonparser.Get(f.configuration, path...)
|
match, _, _, err := jsonparser.Get(f.configuration, path...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return string(match), errors.WithStackIf(err)
|
return string(match), err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{"path": path, "filename": f.FileName}).Debug("attempted to load a configuration value that does not exist")
|
log.WithFields(log.Fields{"path": path, "filename": f.FileName}).Debug("attempted to load a configuration value that does not exist")
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ package parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
@@ -12,11 +18,6 @@ import (
|
|||||||
"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"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The file parsing options that are available for a server configuration file.
|
// The file parsing options that are available for a server configuration file.
|
||||||
@@ -166,17 +167,17 @@ func (f *ConfigurationFile) Parse(path string, internal bool) error {
|
|||||||
|
|
||||||
b := strings.TrimSuffix(path, filepath.Base(path))
|
b := strings.TrimSuffix(path, filepath.Base(path))
|
||||||
if err := os.MkdirAll(b, 0755); err != nil {
|
if err := os.MkdirAll(b, 0755); err != nil {
|
||||||
return errors.WrapIf(err, "failed to create base directory for missing configuration file")
|
return errors.WithMessage(err, "failed to create base directory for missing configuration file")
|
||||||
} else {
|
} else {
|
||||||
if _, err := os.Create(path); err != nil {
|
if _, err := os.Create(path); err != nil {
|
||||||
return errors.WrapIf(err, "failed to create missing configuration file")
|
return errors.WithMessage(err, "failed to create missing configuration file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Parse(path, true)
|
return f.Parse(path, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses an xml file.
|
// Parses an xml file.
|
||||||
@@ -219,7 +220,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
|
||||||
@@ -348,12 +349,12 @@ func (f *ConfigurationFile) parseJsonFile(path string) error {
|
|||||||
func (f *ConfigurationFile) parseYamlFile(path string) error {
|
func (f *ConfigurationFile) parseYamlFile(path string) error {
|
||||||
b, err := readFileBytes(path)
|
b, err := readFileBytes(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i := make(map[string]interface{})
|
i := make(map[string]interface{})
|
||||||
if err := yaml.Unmarshal(b, &i); err != nil {
|
if err := yaml.Unmarshal(b, &i); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal the yaml data into a JSON interface such that we can work with
|
// Unmarshal the yaml data into a JSON interface such that we can work with
|
||||||
@@ -361,20 +362,20 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
|
|||||||
// makes working with unknown JSON significantly easier.
|
// makes working with unknown JSON significantly easier.
|
||||||
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
|
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the data is converted, treat it just like JSON and pass it to the
|
// Now that the data is converted, treat it just like JSON and pass it to the
|
||||||
// iterator function to update values as necessary.
|
// iterator function to update values as necessary.
|
||||||
data, err := f.IterateOverJson(jsonBytes)
|
data, err := f.IterateOverJson(jsonBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remarshal the JSON into YAML format before saving it back to the disk.
|
// Remarshal the JSON into YAML format before saving it back to the disk.
|
||||||
marshaled, err := yaml.Marshal(data.Data())
|
marshaled, err := yaml.Marshal(data.Data())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(path, marshaled, 0644)
|
return ioutil.WriteFile(path, marshaled, 0644)
|
||||||
@@ -386,7 +387,7 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
|
|||||||
func (f *ConfigurationFile) parseTextFile(path string) error {
|
func (f *ConfigurationFile) parseTextFile(path string) error {
|
||||||
input, err := ioutil.ReadFile(path)
|
input, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(string(input), "\n")
|
lines := strings.Split(string(input), "\n")
|
||||||
@@ -403,7 +404,7 @@ func (f *ConfigurationFile) parseTextFile(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644); err != nil {
|
if err := ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -415,7 +416,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
// Open the file.
|
// Open the file.
|
||||||
f2, err := os.Open(path)
|
f2, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
@@ -437,20 +438,20 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
|
|
||||||
// Handle any scanner errors.
|
// Handle any scanner errors.
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the properties file.
|
// Decode the properties file.
|
||||||
p, err := properties.LoadFile(path, properties.UTF8)
|
p, err := properties.LoadFile(path, properties.UTF8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace any values that need to be replaced.
|
// Replace any values that need to be replaced.
|
||||||
for _, replace := range f.Replace {
|
for _, replace := range f.Replace {
|
||||||
data, err := f.LookupConfigurationValue(replace)
|
data, err := f.LookupConfigurationValue(replace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, ok := p.Get(replace.Match)
|
v, ok := p.Get(replace.Match)
|
||||||
@@ -462,7 +463,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, _, err := p.Set(replace.Match, data); err != nil {
|
if _, _, err := p.Set(replace.Match, data); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +483,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
// Open the file for writing.
|
// Open the file for writing.
|
||||||
w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
|
||||||
|
|||||||
319
router/downloader/downloader.go
Normal file
319
router/downloader/downloader.go
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
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,
|
||||||
|
// Disallow any redirect on a HTTP call. This is a security requirement: do not modify
|
||||||
|
// this logic without first ensuring that the new target location IS NOT within the current
|
||||||
|
// instance's local network.
|
||||||
|
//
|
||||||
|
// This specific error response just causes the client to not follow the redirect and
|
||||||
|
// returns the actual redirect response to the caller. Not perfect, but simple and most
|
||||||
|
// people won't be using URLs that redirect anyways hopefully?
|
||||||
|
//
|
||||||
|
// We'll re-evaluate this down the road if needed.
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
147
router/error.go
147
router/error.go
@@ -1,58 +1,65 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"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/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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 +67,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(errors.WithStackIf(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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,101 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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"
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Middleware struct{}
|
||||||
|
|
||||||
|
// A custom handler function allowing for errors bubbled up by c.Error() to be returned in a
|
||||||
|
// standardized format with tracking UUIDs on them for easier log searching.
|
||||||
|
func (m *Middleware) ErrorHandler() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Next()
|
||||||
|
err := c.Errors.Last()
|
||||||
|
if err == nil || err.Err == nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the access request control headers on all of the requests.
|
// Set the access request control headers on all of the requests.
|
||||||
func SetAccessControlHeaders(c *gin.Context) {
|
func (m *Middleware) SetAccessControlHeaders() gin.HandlerFunc {
|
||||||
c.Header("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
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")
|
o := c.GetHeader("Origin")
|
||||||
if o != config.Get().PanelLocation {
|
if o != location {
|
||||||
for _, origin := range config.Get().AllowedOrigins {
|
for _, origin := range origins {
|
||||||
if origin != "*" && o != origin {
|
if origin != "*" && o != origin {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("Access-Control-Allow-Origin", origin)
|
c.Header("Access-Control-Allow-Origin", origin)
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.Header("Access-Control-Allow-Origin", location)
|
||||||
c.Header("Access-Control-Allow-Origin", config.Get().PanelLocation)
|
|
||||||
c.Next()
|
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 {
|
||||||
|
token := config.Get().AuthenticationToken
|
||||||
|
return func(c *gin.Context) {
|
||||||
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
||||||
|
|
||||||
if len(auth) != 2 || auth[0] != "Bearer" {
|
if len(auth) != 2 || auth[0] != "Bearer" {
|
||||||
c.Header("WWW-Authenticate", "Bearer")
|
c.Header("WWW-Authenticate", "Bearer")
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
"error": "The required authorization heads were not present in the request.",
|
"error": "The required authorization heads were not present in the request.",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match the request against the global token for the Daemon, regardless
|
// All requests to Wings must be authorized with the authentication token present in
|
||||||
// of the permission type. If nothing is matched we will fall through to the Panel
|
// the Wings configuration file. Remeber, all requests to Wings come from the Panel
|
||||||
// API to try and validate permissions for a server.
|
// backend, or using a signed JWT for temporary authentication.
|
||||||
if auth[1] == config.Get().AuthenticationToken {
|
if auth[1] == token {
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||||
"error": "You are not authorized to access this endpoint.",
|
"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 +104,43 @@ 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 {
|
||||||
|
return func(c *gin.Context) {
|
||||||
u, err := uuid.Parse(c.Param("server"))
|
u, err := uuid.Parse(c.Param("server"))
|
||||||
if err != nil || GetServer(u.String()) == nil {
|
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.",
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if remote file downloading is enabled on this instance before allowing access
|
||||||
|
// to the given endpoint.
|
||||||
|
func (m *Middleware) CheckRemoteDownloadEnabled() gin.HandlerFunc {
|
||||||
|
disabled := config.Get().Api.DisableRemoteDownload
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if disabled {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "This functionality is not currently enabled on this instance.",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Next()
|
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"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -87,6 +86,11 @@ func Configure() *gin.Engine {
|
|||||||
files.POST("/delete", postServerDeleteFiles)
|
files.POST("/delete", postServerDeleteFiles)
|
||||||
files.POST("/compress", postServerCompressFiles)
|
files.POST("/compress", postServerCompressFiles)
|
||||||
files.POST("/decompress", postServerDecompressFiles)
|
files.POST("/decompress", postServerDecompressFiles)
|
||||||
|
files.POST("/chmod", postServerChmodFile)
|
||||||
|
|
||||||
|
files.GET("/pull", m.CheckRemoteDownloadEnabled(), getServerPullingFiles)
|
||||||
|
files.POST("/pull", m.CheckRemoteDownloadEnabled(), postServerPullRemoteFile)
|
||||||
|
files.DELETE("/pull/:download", m.CheckRemoteDownloadEnabled(), deleteServerPullRemoteFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
backup := server.Group("/backup")
|
backup := server.Group("/backup")
|
||||||
|
|||||||
@@ -3,19 +3,20 @@ package router
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handle a download request for a server backup.
|
// Handle a download request for a server backup.
|
||||||
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 +37,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 +59,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 +76,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 +87,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ package router
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
|
||||||
"github.com/pterodactyl/wings/server"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverProcData struct {
|
type serverProcData struct {
|
||||||
@@ -23,7 +25,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 +43,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 +112,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 +146,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 +190,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 +209,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 +230,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": errors.WithStackIf(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
|
||||||
})
|
})
|
||||||
@@ -247,7 +249,9 @@ func deleteServer(c *gin.Context) {
|
|||||||
// preventing any JWT generated before the current time from being used to connect to
|
// preventing any JWT generated before the current time from being used to connect to
|
||||||
// the socket or send along commands.
|
// the socket or send along commands.
|
||||||
func postServerDenyWSTokens(c *gin.Context) {
|
func postServerDenyWSTokens(c *gin.Context) {
|
||||||
var data struct{ JTIs []string `json:"jtis"` }
|
var data struct {
|
||||||
|
JTIs []string `json:"jtis"`
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.BindJSON(&data); err != nil {
|
if err := c.BindJSON(&data); err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backs up a server.
|
// Backs up a server.
|
||||||
@@ -34,13 +35,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 +69,7 @@ func deleteServerBackup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
NewServerError(err, s).Abort(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +78,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"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"
|
||||||
@@ -16,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,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
|
|
||||||
}
|
|
||||||
|
|
||||||
stats, err := s.Filesystem().ListDirectory(d)
|
|
||||||
if err != nil {
|
|
||||||
TrackedServerError(err, s).AbortFilesystemError(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, stats)
|
c.JSON(http.StatusOK, stats)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type renameFile struct {
|
type renameFile struct {
|
||||||
@@ -139,7 +129,7 @@ func putServerRenameFiles(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortFilesystemError(c)
|
NewServerError(err, s).AbortFilesystemError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,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"))
|
||||||
@@ -258,7 +319,7 @@ func postServerCreateDirectory(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
NewServerError(err, s).Abort(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +418,78 @@ func postServerDecompressFiles(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortFilesystemError(c)
|
NewServerError(err, s).AbortFilesystemError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
type chmodFile struct {
|
||||||
|
File string `json:"file"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidFileMode = errors.New("invalid file mode")
|
||||||
|
|
||||||
|
func postServerChmodFile(c *gin.Context) {
|
||||||
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Root string `json:"root"`
|
||||||
|
Files []chmodFile `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
log.Debug(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Files) == 0 {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
||||||
|
"error": "No files to chmod were provided.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g, ctx := errgroup.WithContext(context.Background())
|
||||||
|
|
||||||
|
// Loop over the array of files passed in and perform the move or rename action against each.
|
||||||
|
for _, p := range data.Files {
|
||||||
|
g.Go(func() error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
mode, err := strconv.ParseUint(p.Mode, 8, 32)
|
||||||
|
if err != nil {
|
||||||
|
return errInvalidFileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Filesystem().Chmod(path.Join(data.Root, p.File), os.FileMode(mode)); err != nil {
|
||||||
|
// Return nil if the error is an is not exists.
|
||||||
|
// NOTE: os.IsNotExist() does not work if the error is wrapped.
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
if errors.Is(err, errInvalidFileMode) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "Invalid file mode.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
NewServerError(err, s).AbortFilesystemError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +499,7 @@ func postServerDecompressFiles(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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,12 +553,12 @@ func postServerUploadFiles(c *gin.Context) {
|
|||||||
func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader) error {
|
func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader) error {
|
||||||
file, err := header.Open()
|
file, err := header.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
if err := s.Filesystem().Writefile(p, file); err != nil {
|
if err := s.Filesystem().Writefile(p, file); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package router
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
ws "github.com/gorilla/websocket"
|
ws "github.com/gorilla/websocket"
|
||||||
"github.com/pterodactyl/wings/router/websocket"
|
"github.com/pterodactyl/wings/router/websocket"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Upgrades a connection to a websocket and passes events along between.
|
// Upgrades a connection to a websocket and passes events along between.
|
||||||
@@ -14,7 +15,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 +25,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:
|
||||||
|
|||||||
@@ -2,21 +2,22 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/installer"
|
"github.com/pterodactyl/wings/installer"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns information about the system that wings is running on.
|
// Returns information about the system that wings is running on.
|
||||||
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 +46,7 @@ func postCreateServer(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackedError(err).AbortWithServerError(c)
|
NewTrackedError(err).Abort(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +90,8 @@ func postUpdateConfiguration(c *gin.Context) {
|
|||||||
//
|
//
|
||||||
// If you pass through manual locations in the API call this logic will be skipped.
|
// If you pass through manual locations in the API call this logic will be skipped.
|
||||||
if strings.HasPrefix(cfg.Api.Ssl.KeyFile, "/etc/letsencrypt/live/") {
|
if strings.HasPrefix(cfg.Api.Ssl.KeyFile, "/etc/letsencrypt/live/") {
|
||||||
cfg.Api.Ssl.KeyFile = ccopy.Api.Ssl.KeyFile
|
cfg.Api.Ssl.KeyFile = strings.ToLower(ccopy.Api.Ssl.KeyFile)
|
||||||
cfg.Api.Ssl.CertificateFile = ccopy.Api.Ssl.CertificateFile
|
cfg.Api.Ssl.CertificateFile = strings.ToLower(ccopy.Api.Ssl.CertificateFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Set(&cfg)
|
config.Set(&cfg)
|
||||||
@@ -99,7 +100,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,28 +2,54 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/apex/log"
|
"encoding/json"
|
||||||
"github.com/buger/jsonparser"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/mholt/archiver/v3"
|
|
||||||
"github.com/pterodactyl/wings/api"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/installer"
|
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
|
||||||
"github.com/pterodactyl/wings/server"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/juju/ratelimit"
|
||||||
|
"github.com/mholt/archiver/v3"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
|
"github.com/pterodactyl/wings/api"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/pterodactyl/wings/installer"
|
||||||
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
|
"github.com/pterodactyl/wings/server"
|
||||||
|
"github.com/pterodactyl/wings/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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 +63,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,47 +108,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) {
|
||||||
if err := s.Archiver.Archive(); err != nil {
|
|
||||||
s.Log().WithField("error", err).Error("failed to get archive for server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Log().Debug("successfully created server archive, notifying panel")
|
|
||||||
|
|
||||||
r := api.New()
|
r := api.New()
|
||||||
err := r.SendArchiveStatus(s.Id(), true)
|
l := log.WithField("server", s.Id())
|
||||||
if err != nil {
|
|
||||||
if !api.IsRequestError(err) {
|
// This function automatically adds the Source Node prefix and Timestamp to the log
|
||||||
s.Log().WithField("error", err).Error("failed to notify panel of archive status")
|
// output before sending it over the websocket.
|
||||||
return
|
sendTransferLog := func(data string) {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Log().WithField("error", err.Error()).Error("panel returned an error when sending the archive status")
|
s.Events().Publish(server.TransferStatusEvent, "starting")
|
||||||
|
sendTransferLog("Attempting to archive server...")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Log().Debug("successfully notified panel of 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() {
|
||||||
@@ -139,152 +130,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.
|
||||||
err := api.New().SendTransferFailure(serverID)
|
s.SetTransferring(false)
|
||||||
if err != nil {
|
|
||||||
|
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", errors.WithStackIf(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 {
|
if err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to send archive http request")
|
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 {
|
||||||
|
_ = 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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
size := res.ContentLength
|
||||||
|
if size == 0 {
|
||||||
|
data.log().WithField("error", err).Error("received an archive response with Content-Length of 0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendTransferLog("Got server archive response from remote node. (Content-Length: " + strconv.Itoa(int(size)) + ")")
|
||||||
|
sendTransferLog("Creating local archive file...")
|
||||||
|
file, err := data.createArchiveFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).WithField("status", res.StatusCode).Error("failed read transfer response body")
|
data.log().WithField("error", err).Error("failed to create archive file on local filesystem")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).WithField("status", res.StatusCode).Error("failed to request server archive")
|
sendTransferLog("Writing archive to disk...")
|
||||||
|
data.log().Info("writing transfer archive to disk...")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the path to the archive.
|
|
||||||
archivePath := filepath.Join(config.Get().System.ArchiveDirectory, serverID+".tar.gz")
|
|
||||||
|
|
||||||
// Check if the archive already exists and delete it if it does.
|
|
||||||
_, err = os.Stat(archivePath)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to stat archive file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := os.Remove(archivePath); err != nil {
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Warn("failed to remove old archive file")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the file.
|
|
||||||
file, err := os.Create(archivePath)
|
|
||||||
if err != nil {
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to open archive on disk")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the file.
|
// Copy the file.
|
||||||
|
progress := &downloadProgress{size: size}
|
||||||
|
ticker := time.NewTicker(3 * time.Second)
|
||||||
|
go func(progress *downloadProgress, t *time.Ticker) {
|
||||||
|
for range ticker.C {
|
||||||
|
// p = 100 (Downloaded)
|
||||||
|
// size = 1000 (Content-Length)
|
||||||
|
// p / size = 0.1
|
||||||
|
// * 100 = 10% (Multiply by 100 to get a percentage of the download)
|
||||||
|
// 10% / tickPercentage = (10% / (100 / 25)) (Divide by tick percentage to get the number of ticks)
|
||||||
|
// 2.5 (Number of ticks as a float64)
|
||||||
|
// 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.
|
||||||
|
width := ((float64(p) / float64(size)) * 100) / tickPercentage
|
||||||
|
bar := strings.Repeat("=", int(width)) + strings.Repeat(" ", ticks-int(width))
|
||||||
|
sendTransferLog("Downloading [" + bar + "] " + system.FormatBytes(p) + " / " + system.FormatBytes(progress.size))
|
||||||
|
}
|
||||||
|
}(progress, ticker)
|
||||||
|
|
||||||
|
var reader io.Reader
|
||||||
|
downloadLimit := float64(config.Get().System.Transfers.DownloadLimit) * 1024 * 1024
|
||||||
|
if downloadLimit > 0 {
|
||||||
|
// Wrap the body with a reader that is limited to the defined download limit speed.
|
||||||
|
reader = ratelimit.Reader(res.Body, ratelimit.NewBucketWithRate(downloadLimit, int64(downloadLimit)))
|
||||||
|
} else {
|
||||||
|
reader = res.Body
|
||||||
|
}
|
||||||
|
|
||||||
buf := make([]byte, 1024*4)
|
buf := make([]byte, 1024*4)
|
||||||
_, err = io.CopyBuffer(file, res.Body, buf)
|
if _, err := io.CopyBuffer(file, io.TeeReader(reader, progress), buf); err != nil {
|
||||||
if err != nil {
|
ticker.Stop()
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to copy archive file to disk")
|
_ = file.Close()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
// Show 100% completion.
|
||||||
|
humanSize := system.FormatBytes(progress.size)
|
||||||
|
sendTransferLog("Downloading [" + strings.Repeat("=", ticks) + "] " + humanSize + " / " + humanSize)
|
||||||
|
|
||||||
// Close the file so it can be opened to verify the checksum.
|
|
||||||
if err := file.Close(); err != nil {
|
if err := file.Close(); err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to close archive file")
|
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.WithField("server", serverID).Debug("server archive downloaded, computing checksum...")
|
// Create the server's environment.
|
||||||
|
sendTransferLog("Creating server environment, this could take a while..")
|
||||||
// Open the archive file for computing a checksum.
|
data.log().Info("creating server environment")
|
||||||
file, err = os.Open(archivePath)
|
|
||||||
if err != nil {
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to open archive on disk")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the sha256 checksum of the file.
|
|
||||||
hash := sha256.New()
|
|
||||||
buf = make([]byte, 1024*4)
|
|
||||||
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to copy archive file for checksum verification")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the two checksums.
|
|
||||||
if hex.EncodeToString(hash.Sum(nil)) != res.Header.Get("X-Checksum") {
|
|
||||||
l.Error("checksum verification failed for archive")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the file.
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to close archive file after calculating checksum")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Info("server archive transfer was successful")
|
|
||||||
|
|
||||||
// Get the server data from the request.
|
|
||||||
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", errors.WithStackIf(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", errors.WithStackIf(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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,21 +476,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", errors.WithStackIf(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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -28,23 +28,23 @@ 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
|
sync.RWMutex `json:"-"`
|
||||||
|
Connection *websocket.Conn `json:"-"`
|
||||||
Connection *websocket.Conn
|
jwt *tokens.WebsocketPayload
|
||||||
jwt *tokens.WebsocketPayload `json:"-"`
|
|
||||||
server *server.Server
|
server *server.Server
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrJwtNotPresent = errors.Sentinel("jwt: no jwt present")
|
ErrJwtNotPresent = errors.New("jwt: no jwt present")
|
||||||
ErrJwtNoConnectPerm = errors.Sentinel("jwt: missing connect permission")
|
ErrJwtNoConnectPerm = errors.New("jwt: missing connect permission")
|
||||||
ErrJwtUuidMismatch = errors.Sentinel("jwt: server uuid mismatch")
|
ErrJwtUuidMismatch = errors.New("jwt: server uuid mismatch")
|
||||||
ErrJwtOnDenylist = errors.Sentinel("jwt: created too far in past (denylist)")
|
ErrJwtOnDenylist = errors.New("jwt: created too far in past (denylist)")
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsJwtError(err error) bool {
|
func IsJwtError(err error) bool {
|
||||||
@@ -84,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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -108,7 +100,7 @@ func GetHandler(s *server.Server, w http.ResponseWriter, r *http.Request) (*Hand
|
|||||||
|
|
||||||
u, err := uuid.NewRandom()
|
u, err := uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
@@ -130,7 +122,6 @@ func (h *Handler) SendJson(v *Message) error {
|
|||||||
Event: JwtErrorEvent,
|
Event: JwtErrorEvent,
|
||||||
Args: []string{err.Error()},
|
Args: []string{err.Error()},
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,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 {
|
||||||
@@ -219,8 +217,11 @@ func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error
|
|||||||
Event: ErrorEvent,
|
Event: ErrorEvent,
|
||||||
Args: []string{"an unexpected error was encountered while handling this request"},
|
Args: []string{"an unexpected error was encountered while handling this request"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if isJWTError || (j != nil && j.HasPermission(PermissionReceiveErrors)) {
|
if isJWTError || (j != nil && j.HasPermission(PermissionReceiveErrors)) {
|
||||||
|
if isJWTError {
|
||||||
wsm.Event = JwtErrorEvent
|
wsm.Event = JwtErrorEvent
|
||||||
|
}
|
||||||
wsm.Args = []string{err.Error()}
|
wsm.Args = []string{err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +230,7 @@ func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error
|
|||||||
|
|
||||||
if !isJWTError && (len(shouldLog) == 0 || (len(shouldLog) == 1 && shouldLog[0] == true)) {
|
if !isJWTError && (len(shouldLog) == 0 || (len(shouldLog) == 1 && shouldLog[0] == true)) {
|
||||||
h.server.Log().WithFields(log.Fields{"event": msg.Event, "error_identifier": u.String(), "error": err}).
|
h.server.Log().WithFields(log.Fields{"event": msg.Event, "error_identifier": u.String(), "error": err}).
|
||||||
Error("failed to handle websocket process; an error was encountered processing an event")
|
Errorf("error processing websocket event \"%s\"", msg.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.unsafeSendJson(wsm)
|
return h.unsafeSendJson(wsm)
|
||||||
@@ -267,7 +268,6 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
Event: JwtErrorEvent,
|
Event: JwtErrorEvent,
|
||||||
Args: []string{err.Error()},
|
Args: []string{err.Error()},
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,7 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
|
|
||||||
// On every authentication event, send the current server status back
|
// On every authentication event, send the current server status back
|
||||||
// to the client. :)
|
// to the client. :)
|
||||||
state := h.server.GetState()
|
state := h.server.Environment.State()
|
||||||
h.SendJson(&Message{
|
h.SendJson(&Message{
|
||||||
Event: server.StatusEvent,
|
Event: server.StatusEvent,
|
||||||
Args: []string{state},
|
Args: []string{state},
|
||||||
@@ -320,6 +320,7 @@ 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 {
|
||||||
|
if !h.server.IsInstalling() && !h.server.IsTransferring() {
|
||||||
_ = h.server.Filesystem().HasSpaceAvailable(false)
|
_ = h.server.Filesystem().HasSpaceAvailable(false)
|
||||||
|
|
||||||
b, _ := json.Marshal(h.server.Proc())
|
b, _ := json.Marshal(h.server.Proc())
|
||||||
@@ -328,6 +329,7 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
Args: []string{string(b)},
|
Args: []string{string(b)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -398,7 +400,7 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.server.GetState() == environment.ProcessOfflineState {
|
if h.server.Environment.State() == environment.ProcessOfflineState {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +408,7 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
// so that we can better handle this and only set the environment to booted once we're attached.
|
// so that we can better handle this and only set the environment to booted once we're attached.
|
||||||
//
|
//
|
||||||
// Or maybe just an IsBooted function?
|
// Or maybe just an IsBooted function?
|
||||||
if h.server.GetState() == environment.ProcessStartingState {
|
if h.server.Environment.State() == environment.ProcessStartingState {
|
||||||
if e, ok := h.server.Environment.(*docker.Environment); ok {
|
if e, ok := h.server.Environment.(*docker.Environment); ok {
|
||||||
if !e.IsAttached() {
|
if !e.IsAttached() {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/mholt/archiver/v3"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/mholt/archiver/v3"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Archiver represents a Server Archiver.
|
// Archiver represents a Server Archiver.
|
||||||
@@ -41,7 +42,7 @@ func (a *Archiver) Exists() bool {
|
|||||||
func (a *Archiver) Stat() (*filesystem.Stat, error) {
|
func (a *Archiver) Stat() (*filesystem.Stat, error) {
|
||||||
s, err := os.Stat(a.Path())
|
s, err := os.Stat(a.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &filesystem.Stat{
|
return &filesystem.Stat{
|
||||||
@@ -58,7 +59,7 @@ func (a *Archiver) Archive() error {
|
|||||||
var files []string
|
var files []string
|
||||||
fileInfo, err := ioutil.ReadDir(path)
|
fileInfo, err := ioutil.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range fileInfo {
|
for _, file := range fileInfo {
|
||||||
@@ -94,17 +95,17 @@ func (a *Archiver) DeleteIfExists() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WrapIf(os.Remove(a.Path()), "archiver: failed to delete archive from system")
|
return errors.WithMessage(os.Remove(a.Path()), "archiver: failed to delete archive from system")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checksum computes a SHA256 checksum of the server's archive.
|
// Checksum computes a SHA256 checksum of the server's archive.
|
||||||
func (a *Archiver) Checksum() (string, error) {
|
func (a *Archiver) Checksum() (string, error) {
|
||||||
file, err := os.Open(a.Path())
|
file, err := os.Open(a.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStack(err)
|
return "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ func (a *Archiver) Checksum() (string, error) {
|
|||||||
|
|
||||||
buf := make([]byte, 1024*4)
|
buf := make([]byte, 1024*4)
|
||||||
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
||||||
return "", errors.WithStack(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
"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
|
||||||
// while performing this action.
|
// while performing this action.
|
||||||
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
|
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
|
||||||
err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful))
|
if err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful)); err != nil {
|
||||||
if err != nil {
|
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
s.Log().WithFields(log.Fields{
|
s.Log().WithFields(log.Fields{
|
||||||
"backup": uuid,
|
"backup": uuid,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("failed to notify panel of backup status due to wings error")
|
}).Error("failed to notify panel of backup status due to wings error")
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
return errors.New(err.Error())
|
||||||
@@ -31,67 +30,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, errors.WithStack(err)
|
return "", nil
|
||||||
}
|
}
|
||||||
} else {
|
return "", err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
b, err := ioutil.ReadAll(f)
|
||||||
if err := scanner.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
return string(b), nil
|
||||||
|
|
||||||
return ignored, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the included files based on the root path and the ignored files provided.
|
|
||||||
return s.Filesystem().GetIncludedFiles(s.Filesystem().Path(), ignored)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 errors.WithStackIf(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{
|
s.Log().WithFields(log.Fields{
|
||||||
"backup": b.Identifier(),
|
"backup": b.Identifier(),
|
||||||
"error": notifyError,
|
"error": err,
|
||||||
}).Warn("failed to notify 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": "",
|
||||||
@@ -105,14 +88,17 @@ func (s *Server) Backup(b backup.BackupInterface) error {
|
|||||||
// 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()
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
s.Log().WithField("error", notifyError).Info("failed to notify panel of successful backup state")
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
s.Log().WithField("backup", b.Identifier()).Info("notified panel of successful backup state")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
package backup
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
|
||||||
gzip "github.com/klauspost/pgzip"
|
|
||||||
"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 errors.WithStackIf(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 errors.WithStackIf(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 errors.WithStackIf(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 errors.WithStackIf(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 errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
header := &tar.Header{
|
|
||||||
// Trim the long server path from the name of the file so that the resulting
|
|
||||||
// archive is exactly how the user would see it in the panel file manager.
|
|
||||||
Name: strings.TrimPrefix(p, a.TrimPrefix),
|
|
||||||
Size: s.Size(),
|
|
||||||
Mode: int64(s.Mode()),
|
|
||||||
ModTime: s.ModTime(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 4*1024)
|
|
||||||
if _, err := io.CopyBuffer(w, f, buf); err != nil {
|
|
||||||
return errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package backup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
@@ -13,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 {
|
||||||
@@ -41,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
|
||||||
@@ -49,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.
|
||||||
@@ -87,7 +94,7 @@ func (b *Backup) Path() string {
|
|||||||
func (b *Backup) Size() (int64, error) {
|
func (b *Backup) Size() (int64, error) {
|
||||||
st, err := os.Stat(b.Path())
|
st, err := os.Stat(b.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.WithStackIf(err)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return st.Size(), nil
|
return st.Size(), nil
|
||||||
@@ -99,7 +106,7 @@ func (b *Backup) Checksum() ([]byte, error) {
|
|||||||
|
|
||||||
f, err := os.Open(b.Path())
|
f, err := os.Open(b.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -117,20 +124,25 @@ 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{
|
||||||
"backup": b.Identifier(),
|
"backup": b.Identifier(),
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("failed to calculate checksum for backup")
|
}).Error("failed to calculate checksum for backup")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"errors"
|
||||||
"emperror.dev/errors"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,13 +18,13 @@ func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
|
|||||||
b := &LocalBackup{
|
b := &LocalBackup{
|
||||||
Backup{
|
Backup{
|
||||||
Uuid: uuid,
|
Uuid: uuid,
|
||||||
IgnoredFiles: nil,
|
Ignore: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := os.Stat(b.Path())
|
st, err := os.Stat(b.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.WithStackIf(err)
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.IsDir() {
|
if st.IsDir() {
|
||||||
@@ -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...")
|
||||||
return nil, errors.WithStackIf(err)
|
if err := a.Create(b.Path()); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
b.log().Info("created backup successfully")
|
||||||
|
|
||||||
return b.Details(), nil
|
return b.Details(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
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.
|
||||||
@@ -20,7 +20,8 @@ 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
|
||||||
}
|
}
|
||||||
@@ -34,7 +35,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"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"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type S3Backup struct {
|
type S3Backup struct {
|
||||||
@@ -20,36 +16,43 @@ 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...")
|
||||||
return nil, errors.WithStackIf(err)
|
if err := a.Create(s.Path()); err != nil {
|
||||||
|
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 {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
if err := s.generateRemoteRequest(rc); err != nil {
|
if err := s.generateRemoteRequest(rc); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Details(), err
|
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
|
||||||
@@ -66,20 +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")
|
||||||
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",
|
|
||||||
}).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)
|
||||||
@@ -92,7 +95,7 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
|
|||||||
r.Header.Add("Content-Type", "application/x-gzip")
|
r.Header.Add("Content-Type", "application/x-gzip")
|
||||||
|
|
||||||
// Limit the reader to the size of the part.
|
// Limit the reader to the size of the part.
|
||||||
r.Body = Reader{io.LimitReader(rc, size)}
|
r.Body = Reader{Reader: io.LimitReader(rc, size)}
|
||||||
|
|
||||||
// This http request can block forever due to it not having a timeout,
|
// This http request can block forever due to it not having a timeout,
|
||||||
// but we are uploading up to 5GB of data, so there is not really
|
// but we are uploading up to 5GB of data, so there is not really
|
||||||
@@ -112,15 +115,10 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
|
|||||||
return res.Header.Get("ETag"), nil
|
return res.Header.Get("ETag"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start assembling the body that will be sent as apart of the CompleteMultipartUpload request.
|
|
||||||
var completeUploadBody bytes.Buffer
|
|
||||||
completeUploadBody.WriteString("<CompleteMultipartUpload>\n")
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -129,67 +127,15 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to upload the part.
|
// Attempt to upload the part.
|
||||||
etag, err := handlePart(part, partSize)
|
if _, err := handlePart(part, partSize); err != nil {
|
||||||
if err != nil {
|
s.log().WithField("part_id", i+1).WithError(err).Warn("failed to upload part")
|
||||||
log.WithError(err).Warn("failed to upload part")
|
|
||||||
|
|
||||||
// Send an AbortMultipartUpload request.
|
|
||||||
if err := s.finishUpload(urls.AbortMultipartUpload, nil); err != nil {
|
|
||||||
log.WithError(err).Warn("failed to abort multipart backup upload")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the part to the CompleteMultipartUpload body.
|
s.log().WithField("part_id", i+1).Info("successfully uploaded backup part")
|
||||||
completeUploadBody.WriteString("\t<Part>\n")
|
|
||||||
completeUploadBody.WriteString("\t\t<ETag>\"" + etag + "\"</ETag>\n")
|
|
||||||
completeUploadBody.WriteString("\t\t<PartNumber>" + strconv.Itoa(i+1) + "</PartNumber>\n")
|
|
||||||
completeUploadBody.WriteString("\t</Part>\n")
|
|
||||||
}
|
|
||||||
completeUploadBody.WriteString("</CompleteMultipartUpload>")
|
|
||||||
|
|
||||||
// Send a CompleteMultipartUpload request.
|
|
||||||
if err := s.finishUpload(urls.CompleteMultipartUpload, &completeUploadBody); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
s.log().WithField("parts", len(urls.Parts)).Info("backup has been successfully uploaded")
|
||||||
"backup_id": s.Uuid,
|
|
||||||
"adapter": "s3",
|
|
||||||
}).Info("backup has been successfully uploaded")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// finishUpload sends a requests to the specified url to either complete or abort the upload.
|
|
||||||
func (s *S3Backup) finishUpload(url string, body io.Reader) error {
|
|
||||||
r, err := http.NewRequest(http.MethodPost, url, body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new http client with a 10 second timeout.
|
|
||||||
c := &http.Client{
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := c.Do(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
// Handle non-200 status codes.
|
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
// If no body was sent, we were aborting the upload.
|
|
||||||
if body == nil {
|
|
||||||
return fmt.Errorf("failed to abort S3 multipart upload, %d:%s", res.StatusCode, res.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a body was sent we were completing the upload.
|
|
||||||
// TODO: Attempt to send abort request?
|
|
||||||
return fmt.Errorf("failed to complete S3 multipart upload, %d:%s", res.StatusCode, res.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gammazero/workerpool"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/gammazero/workerpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parent function that will update all of the defined configuration files for a server
|
// Parent function that will update all of the defined configuration files for a server
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pterodactyl/wings/environment"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/colorstring"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/system"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/pterodactyl/wings/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrTooMuchConsoleData = errors.Sentinel("console is outputting too much data")
|
var ErrTooMuchConsoleData = errors.New("console is outputting too much data")
|
||||||
|
|
||||||
type ConsoleThrottler struct {
|
type ConsoleThrottler struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -26,7 +27,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 +37,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 +58,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) {
|
||||||
reset := time.NewTicker(time.Duration(int64(ct.LineResetInterval)) * time.Millisecond)
|
ct.isThrottled.Store(false)
|
||||||
decay := time.NewTicker(time.Duration(int64(ct.DecayInterval)) * time.Millisecond)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
reset.Stop()
|
|
||||||
return
|
|
||||||
case <-reset.C:
|
|
||||||
ct.isThrottled.Set(false)
|
|
||||||
atomic.StoreUint64(&ct.count, 0)
|
atomic.StoreUint64(&ct.count, 0)
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
system.Every(ctx, time.Duration(int64(ct.DecayInterval))*time.Millisecond, func(_ time.Time) {
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
decay.Stop()
|
|
||||||
return
|
|
||||||
case <-decay.C:
|
|
||||||
ct.markActivation(false)
|
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 +100,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 +113,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pterodactyl/wings/config"
|
"strconv"
|
||||||
"github.com/pterodactyl/wings/environment"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/pterodactyl/wings/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CrashHandler struct {
|
type CrashHandler struct {
|
||||||
@@ -45,11 +46,10 @@ func (s *Server) handleServerCrash() error {
|
|||||||
// No point in doing anything here if the server isn't currently offline, there
|
// No point in doing anything here if the server isn't currently offline, there
|
||||||
// is no reason to do a crash detection event. If the server crash detection is
|
// is no reason to do a crash detection event. If the server crash detection is
|
||||||
// disabled we want to skip anything after this as well.
|
// disabled we want to skip anything after this as well.
|
||||||
if s.GetState() != environment.ProcessOfflineState || !s.Config().CrashDetectionEnabled {
|
if s.Environment.State() != environment.ProcessOfflineState || !s.Config().CrashDetectionEnabled {
|
||||||
if !s.Config().CrashDetectionEnabled {
|
if !s.Config().CrashDetectionEnabled {
|
||||||
s.Log().Debug("server triggered crash detection but handler is disabled for server process")
|
s.Log().Debug("server triggered crash detection but handler is disabled for server process")
|
||||||
|
s.PublishConsoleOutputFromDaemon("Aborting automatic restart, crash detection is disabled for this instance.")
|
||||||
s.PublishConsoleOutputFromDaemon("Server detected as crashed; crash detection is disabled for this instance.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -57,14 +57,13 @@ func (s *Server) handleServerCrash() error {
|
|||||||
|
|
||||||
exitCode, oomKilled, err := s.Environment.ExitState()
|
exitCode, oomKilled, err := s.Environment.ExitState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the system is not configured to detect a clean exit code as a crash, and the
|
// If the system is not configured to detect a clean exit code as a crash, and the
|
||||||
// crash is not the result of the program running out of memory, do nothing.
|
// crash is not the result of the program running out of memory, do nothing.
|
||||||
if exitCode == 0 && !oomKilled && !config.Get().System.DetectCleanExitAsCrash {
|
if exitCode == 0 && !oomKilled && !config.Get().System.CrashDetection.DetectCleanExitAsCrash {
|
||||||
s.Log().Debug("server exited with successful exit code; system is configured to not detect this as a crash")
|
s.Log().Debug("server exited with successful exit code; system is configured to not detect this as a crash")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +72,14 @@ func (s *Server) handleServerCrash() error {
|
|||||||
s.PublishConsoleOutputFromDaemon(fmt.Sprintf("Out of memory: %t", oomKilled))
|
s.PublishConsoleOutputFromDaemon(fmt.Sprintf("Out of memory: %t", oomKilled))
|
||||||
|
|
||||||
c := s.crasher.LastCrashTime()
|
c := s.crasher.LastCrashTime()
|
||||||
// If the last crash time was within the last 60 seconds we do not want to perform
|
timeout := config.Get().System.CrashDetection.Timeout
|
||||||
// an automatic reboot of the process. Return an error that can be handled.
|
|
||||||
if !c.IsZero() && c.Add(time.Second*60).After(time.Now()) {
|
|
||||||
s.PublishConsoleOutputFromDaemon("Aborting automatic reboot: last crash occurred less than 60 seconds ago.")
|
|
||||||
|
|
||||||
|
// If the last crash time was within the last `timeout` seconds we do not want to perform
|
||||||
|
// an automatic reboot of the process. Return an error that can be handled.
|
||||||
|
//
|
||||||
|
// If timeout is set to 0, always reboot the server (this is probably a terrible idea, but some people want it)
|
||||||
|
if timeout != 0 && !c.IsZero() && c.Add(time.Second*time.Duration(config.Get().System.CrashDetection.Timeout)).After(time.Now()) {
|
||||||
|
s.PublishConsoleOutputFromDaemon("Aborting automatic restart, last crash occurred less than " + strconv.Itoa(timeout) + " seconds ago.")
|
||||||
return &crashTooFrequent{}
|
return &crashTooFrequent{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import "emperror.dev/errors"
|
import (
|
||||||
|
"emperror.dev/errors"
|
||||||
|
)
|
||||||
|
|
||||||
var ErrIsRunning = errors.Sentinel("server is running")
|
var (
|
||||||
var ErrSuspended = errors.Sentinel("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 {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) Filesystem() *filesystem.Filesystem {
|
func (s *Server) Filesystem() *filesystem.Filesystem {
|
||||||
@@ -13,12 +13,12 @@ func (s *Server) Filesystem() *filesystem.Filesystem {
|
|||||||
// Ensures that the data directory for the server instance exists.
|
// Ensures that the data directory for the server instance exists.
|
||||||
func (s *Server) EnsureDataDirectoryExists() error {
|
func (s *Server) EnsureDataDirectoryExists() error {
|
||||||
if _, err := os.Stat(s.fs.Path()); err != nil && !os.IsNotExist(err) {
|
if _, err := os.Stat(s.fs.Path()); err != nil && !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
// Create the server data directory because it does not currently exist
|
// Create the server data directory because it does not currently exist
|
||||||
// on the system.
|
// on the system.
|
||||||
if err := os.MkdirAll(s.fs.Path(), 0700); err != nil {
|
if err := os.MkdirAll(s.fs.Path(), 0700); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.fs.Chown("/"); err != nil {
|
if err := s.fs.Chown("/"); err != nil {
|
||||||
|
|||||||
224
server/filesystem/archive.go
Normal file
224
server/filesystem/archive.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"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"
|
||||||
@@ -14,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, errors.WithStack(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, errors.WithStackIf(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
|
||||||
@@ -91,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
|
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, errors.WithStackIf(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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,13 +44,13 @@ 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, errors.WithStackIf(err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, errors.WithStackIf(err)
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decompress a file in a given directory by using the archiver tool to infer the file
|
// Decompress a file in a given directory by using the archiver tool to infer the file
|
||||||
@@ -60,12 +60,12 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
|
|||||||
func (fs *Filesystem) DecompressFile(dir string, file string) error {
|
func (fs *Filesystem) DecompressFile(dir string, file string) error {
|
||||||
source, err := fs.SafePath(filepath.Join(dir, file))
|
source, err := fs.SafePath(filepath.Join(dir, file))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the file exists basically.
|
// Make sure the file exists basically.
|
||||||
if _, err := os.Stat(source); err != nil {
|
if _, err := os.Stat(source); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk over all of the files spinning up an additional go-routine for each file we've encountered
|
// Walk over all of the files spinning up an additional go-routine for each file we've encountered
|
||||||
@@ -93,17 +93,17 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
|
|||||||
|
|
||||||
p, err := fs.SafePath(filepath.Join(dir, name))
|
p, err := fs.SafePath(filepath.Join(dir, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WrapIf(err, "failed to generate a safe path to server file")
|
return errors.WithMessage(err, "failed to generate a safe path to server file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WrapIf(fs.Writefile(p, f), "could not extract file from archive")
|
return errors.WithMessage(fs.Writefile(p, f), "could not extract file from archive")
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "format ") {
|
if strings.HasPrefix(err.Error(), "format ") {
|
||||||
return errors.WithStackIf(ErrUnknownArchiveFormat)
|
return &Error{code: ErrCodeUnknownArchive}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/karrick/godirwalk"
|
"github.com/karrick/godirwalk"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -36,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
|
||||||
@@ -79,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
|
||||||
@@ -107,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) {
|
||||||
@@ -133,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
|
||||||
@@ -158,7 +158,7 @@ func (fs *Filesystem) updateCachedDiskUsage() (int64, error) {
|
|||||||
func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
|
func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
|
||||||
d, err := fs.SafePath(dir)
|
d, err := fs.SafePath(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.WithStackIf(err)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var size int64
|
var size int64
|
||||||
@@ -172,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,24 +189,24 @@ func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return size, errors.WithStackIf(err)
|
return size, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := fs.DiskUsage(true)
|
s, err := fs.DiskUsage(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s + size) > fs.MaxDisk() {
|
if (s + size) > fs.MaxDisk() {
|
||||||
return ErrNotEnoughDiskSpace
|
return &Error{code: ErrCodeDiskSpace}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -8,41 +8,68 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrIsDirectory = errors.Sentinel("filesystem: is a directory")
|
type ErrorCode string
|
||||||
var ErrNotEnoughDiskSpace = errors.Sentinel("filesystem: not enough disk space")
|
|
||||||
var ErrUnknownArchiveFormat = errors.Sentinel("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 {
|
||||||
|
case ErrCodeIsDirectory:
|
||||||
|
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 == "" {
|
if r == "" {
|
||||||
r = "<empty>"
|
r = "<empty>"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("filesystem: server path [%s] resolves to a location outside the server root: %s", b.path, r)
|
return fmt.Sprintf("filesystem: server path [%s] resolves to a location outside the server root: %s", e.path, r)
|
||||||
|
}
|
||||||
|
return "filesystem: unhandled error type"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
@@ -56,8 +83,8 @@ 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 errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f != nil && f.IsDir() {
|
if f != nil && f.IsDir() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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,76 +51,85 @@ 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 errors.WithStack(err)
|
return err
|
||||||
} else if st.IsDir() {
|
|
||||||
return errors.WithStack(ErrIsDirectory)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(cleaned)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, err = bufio.NewReader(f).WriteTo(w)
|
|
||||||
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes a file to the system. If the file does not already exist one will be created.
|
// Writes a file to the system. If the file does not already exist one will be created.
|
||||||
func (fs *Filesystem) Writefile(p string, r io.Reader) error {
|
func (fs *Filesystem) Writefile(p string, r io.Reader) error {
|
||||||
cleaned, err := fs.SafePath(p)
|
cleaned, err := fs.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
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 errors.WithStackIf(err)
|
return err
|
||||||
}
|
} else if err == nil {
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
|
|
||||||
return errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
|
|
||||||
return errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if stat.IsDir() {
|
if stat.IsDir() {
|
||||||
return errors.WithStack(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.
|
||||||
file, err := o.open(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
file, err := o.open(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
@@ -138,7 +148,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
|
|||||||
func (fs *Filesystem) CreateDirectory(name string, p string) error {
|
func (fs *Filesystem) CreateDirectory(name string, p string) error {
|
||||||
cleaned, err := fs.SafePath(path.Join(p, name))
|
cleaned, err := fs.SafePath(path.Join(p, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.MkdirAll(cleaned, 0755)
|
return os.MkdirAll(cleaned, 0755)
|
||||||
@@ -148,12 +158,12 @@ func (fs *Filesystem) CreateDirectory(name string, p string) error {
|
|||||||
func (fs *Filesystem) Rename(from string, to string) error {
|
func (fs *Filesystem) Rename(from string, to string) error {
|
||||||
cleanedFrom, err := fs.SafePath(from)
|
cleanedFrom, err := fs.SafePath(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanedTo, err := fs.SafePath(to)
|
cleanedTo, err := fs.SafePath(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the target file or directory already exists the rename function will fail, so just
|
// If the target file or directory already exists the rename function will fail, so just
|
||||||
@@ -171,7 +181,7 @@ func (fs *Filesystem) Rename(from string, to string) error {
|
|||||||
// we're not at the root directory level.
|
// we're not at the root directory level.
|
||||||
if d != fs.Path() {
|
if d != fs.Path() {
|
||||||
if mkerr := os.MkdirAll(d, 0755); mkerr != nil {
|
if mkerr := os.MkdirAll(d, 0755); mkerr != nil {
|
||||||
return errors.WrapIf(mkerr, "failed to create directory structure for file rename")
|
return errors.WithMessage(mkerr, "failed to create directory structure for file rename")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +195,7 @@ func (fs *Filesystem) Rename(from string, to string) error {
|
|||||||
func (fs *Filesystem) Chown(path string) error {
|
func (fs *Filesystem) Chown(path string) error {
|
||||||
cleaned, err := fs.SafePath(path)
|
cleaned, err := fs.SafePath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.isTest {
|
if fs.isTest {
|
||||||
@@ -197,7 +207,7 @@ func (fs *Filesystem) Chown(path string) error {
|
|||||||
|
|
||||||
// Start by just chowning the initial path that we received.
|
// Start by just chowning the initial path that we received.
|
||||||
if err := os.Chown(cleaned, uid, gid); err != nil {
|
if err := os.Chown(cleaned, uid, gid); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is not a directory we can now return from the function, there is nothing
|
// If this is not a directory we can now return from the function, there is nothing
|
||||||
@@ -227,6 +237,23 @@ func (fs *Filesystem) Chown(path string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) Chmod(path string, mode os.FileMode) error {
|
||||||
|
cleaned, err := fs.SafePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs.isTest {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chmod(cleaned, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Begin looping up to 50 times to try and create a unique copy file name. This will take
|
// Begin looping up to 50 times to try and create a unique copy file name. This will take
|
||||||
// an input of "file.txt" and generate "file copy.txt". If that name is already taken, it will
|
// an input of "file.txt" and generate "file copy.txt". If that name is already taken, it will
|
||||||
// then try to write "file copy 2.txt" and so on, until reaching 50 loops. At that point we
|
// then try to write "file copy 2.txt" and so on, until reaching 50 loops. At that point we
|
||||||
@@ -237,7 +264,7 @@ func (fs *Filesystem) Chown(path string) 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 {
|
||||||
@@ -249,7 +276,7 @@ func (fs *Filesystem) findCopySuffix(dir string, name string, extension string)
|
|||||||
// does exist, we'll just continue to the next loop and try again.
|
// does exist, we'll just continue to the next loop and try again.
|
||||||
if _, err := fs.Stat(path.Join(dir, n)); err != nil {
|
if _, err := fs.Stat(path.Join(dir, n)); err != nil {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -268,12 +295,12 @@ func (fs *Filesystem) findCopySuffix(dir string, name string, extension string)
|
|||||||
func (fs *Filesystem) Copy(p string) error {
|
func (fs *Filesystem) Copy(p string) error {
|
||||||
cleaned, err := fs.SafePath(p)
|
cleaned, err := fs.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := os.Stat(cleaned)
|
s, err := os.Stat(cleaned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else if s.IsDir() || !s.Mode().IsRegular() {
|
} else if s.IsDir() || !s.Mode().IsRegular() {
|
||||||
// If this is a directory or not a regular file, just throw a not-exist error
|
// If this is a directory or not a regular file, just throw a not-exist error
|
||||||
// since anything calling this function should understand what that means.
|
// since anything calling this function should understand what that means.
|
||||||
@@ -281,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +327,7 @@ func (fs *Filesystem) Copy(p string) error {
|
|||||||
|
|
||||||
source, err := os.Open(cleaned)
|
source, err := os.Open(cleaned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package filesystem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -26,7 +25,7 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
|
|||||||
// is truly pointing to.
|
// is truly pointing to.
|
||||||
ep, err := filepath.EvalSymlinks(r)
|
ep, err := filepath.EvalSymlinks(r)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
} else if os.IsNotExist(err) {
|
} else if os.IsNotExist(err) {
|
||||||
// The requested directory doesn't exist, so at this point we need to iterate up the
|
// The requested directory doesn't exist, so at this point we need to iterate up the
|
||||||
// path chain until we hit a directory that _does_ exist and can be validated.
|
// path chain until we hit a directory that _does_ exist and can be validated.
|
||||||
@@ -138,5 +137,5 @@ func (fs *Filesystem) ParallelSafePath(paths []string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Block until all of the routines finish and have returned a value.
|
// Block until all of the routines finish and have returned a value.
|
||||||
return cleaned, errors.WithStackIf(g.Wait())
|
return cleaned, g.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ func (s *Stat) MarshalJSON() ([]byte, error) {
|
|||||||
Created string `json:"created"`
|
Created string `json:"created"`
|
||||||
Modified string `json:"modified"`
|
Modified string `json:"modified"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
|
ModeBits string `json:"mode_bits"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
Directory bool `json:"directory"`
|
Directory bool `json:"directory"`
|
||||||
File bool `json:"file"`
|
File bool `json:"file"`
|
||||||
@@ -29,6 +30,8 @@ func (s *Stat) MarshalJSON() ([]byte, error) {
|
|||||||
Created: s.CTime().Format(time.RFC3339),
|
Created: s.CTime().Format(time.RFC3339),
|
||||||
Modified: s.Info.ModTime().Format(time.RFC3339),
|
Modified: s.Info.ModTime().Format(time.RFC3339),
|
||||||
Mode: s.Info.Mode().String(),
|
Mode: s.Info.Mode().String(),
|
||||||
|
// Using `&os.ModePerm` on the file's mode will cause the mode to only have the permission values, and nothing else.
|
||||||
|
ModeBits: strconv.FormatUint(uint64(s.Info.Mode()&os.ModePerm), 8),
|
||||||
Size: s.Info.Size(),
|
Size: s.Info.Size(),
|
||||||
Directory: s.Info.IsDir(),
|
Directory: s.Info.IsDir(),
|
||||||
File: !s.Info.IsDir(),
|
File: !s.Info.IsDir(),
|
||||||
@@ -51,14 +54,14 @@ func (fs *Filesystem) Stat(p string) (*Stat, error) {
|
|||||||
func (fs *Filesystem) unsafeStat(p string) (*Stat, error) {
|
func (fs *Filesystem) unsafeStat(p string) (*Stat, error) {
|
||||||
s, err := os.Stat(p)
|
s, err := os.Stat(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var m *mimetype.MIME
|
var m *mimetype.MIME
|
||||||
if !s.IsDir() {
|
if !s.IsDir() {
|
||||||
m, err = mimetype.DetectFile(p)
|
m, err = mimetype.DetectFile(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@@ -13,13 +20,7 @@ import (
|
|||||||
"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"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"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
|
||||||
@@ -75,7 +76,7 @@ func (s *Server) Install(sync bool) error {
|
|||||||
// Reinstalls a server's software by utilizing the install script for the server egg. This
|
// Reinstalls a server's software by utilizing the install script for the server egg. This
|
||||||
// does not touch any existing files for the server, other than what the script modifies.
|
// does not touch any existing files for the server, other than what the script modifies.
|
||||||
func (s *Server) Reinstall() error {
|
func (s *Server) Reinstall() error {
|
||||||
if s.GetState() != environment.ProcessOfflineState {
|
if s.Environment.State() != environment.ProcessOfflineState {
|
||||||
s.Log().Debug("waiting for server instance to enter a stopped state")
|
s.Log().Debug("waiting for server instance to enter a stopped state")
|
||||||
if err := s.Environment.WaitForStop(10, true); err != nil {
|
if err := s.Environment.WaitForStop(10, true); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -90,7 +91,7 @@ func (s *Server) internalInstall() error {
|
|||||||
script, err := api.New().GetInstallationScript(s.Id())
|
script, err := api.New().GetInstallationScript(s.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
return errors.New(err.Error())
|
||||||
@@ -98,7 +99,7 @@ func (s *Server) internalInstall() error {
|
|||||||
|
|
||||||
p, err := NewInstallationProcess(s, &script)
|
p, err := NewInstallationProcess(s, &script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Log().Info("beginning installation process for server")
|
s.Log().Info("beginning installation process for server")
|
||||||
@@ -126,75 +127,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, errors.WithStackIf(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", errors.WithStackIf(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
|
||||||
@@ -204,8 +170,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
|
||||||
@@ -213,19 +179,17 @@ 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 {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cid, err := ip.Execute()
|
cid, err := ip.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ip.RemoveContainer()
|
ip.RemoveContainer()
|
||||||
|
return err
|
||||||
return errors.WithStackIf(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this step fails, log a warning but don't exit out of the process. This is completely
|
// If this step fails, log a warning but don't exit out of the process. This is completely
|
||||||
@@ -248,12 +212,12 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
|
|||||||
// Make sure the temp directory root exists before trying to make a directory within it. The
|
// Make sure the temp directory root exists before trying to make a directory within it. The
|
||||||
// ioutil.TempDir call expects this base to exist, it won't create it for you.
|
// ioutil.TempDir call expects this base to exist, it won't create it for you.
|
||||||
if err := os.MkdirAll(ip.tempDir(), 0700); err != nil {
|
if err := os.MkdirAll(ip.tempDir(), 0700); err != nil {
|
||||||
return errors.WrapIf(err, "could not create temporary directory for install process")
|
return errors.WithMessage(err, "could not create temporary directory for install process")
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(filepath.Join(ip.tempDir(), "install.sh"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
f, err := os.OpenFile(filepath.Join(ip.tempDir(), "install.sh"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WrapIf(err, "failed to write server installation script to disk before mount")
|
return errors.WithMessage(err, "failed to write server installation script to disk before mount")
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -265,7 +229,7 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
@@ -275,11 +239,62 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
|
|||||||
|
|
||||||
// Pulls the docker image to be used for the installation container.
|
// Pulls the docker image to be used for the installation container.
|
||||||
func (ip *InstallationProcess) pullInstallationImage() error {
|
func (ip *InstallationProcess) pullInstallationImage() error {
|
||||||
r, err := ip.client.ImagePull(ip.context, ip.Script.ContainerImage, types.ImagePullOptions{})
|
// Get a registry auth configuration from the config.
|
||||||
if err != nil {
|
var registryAuth *config.RegistryConfiguration
|
||||||
return errors.WithStackIf(err)
|
for registry, c := range config.Get().Docker.Registries {
|
||||||
|
if !strings.HasPrefix(ip.Script.ContainerImage, registry) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.WithField("registry", registry).Debug("using authentication for registry")
|
||||||
|
registryAuth = &c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the ImagePullOptions.
|
||||||
|
imagePullOptions := types.ImagePullOptions{All: false}
|
||||||
|
if registryAuth != nil {
|
||||||
|
b64, err := registryAuth.Base64()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("failed to get registry auth credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
// b64 is a string so if there is an error it will just be empty, not nil.
|
||||||
|
imagePullOptions.RegistryAuth = b64
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := ip.client.ImagePull(context.Background(), ip.Script.ContainerImage, imagePullOptions)
|
||||||
|
if err != nil {
|
||||||
|
images, ierr := ip.client.ImageList(context.Background(), types.ImageListOptions{})
|
||||||
|
if ierr != nil {
|
||||||
|
// Well damn, something has gone really wrong here, just go ahead and abort there
|
||||||
|
// isn't much anything we can do to try and self-recover from this.
|
||||||
|
return ierr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, img := range images {
|
||||||
|
for _, t := range img.RepoTags {
|
||||||
|
if t != ip.Script.ContainerImage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"image": ip.Script.ContainerImage,
|
||||||
|
"err": err.Error(),
|
||||||
|
}).Warn("unable to pull requested image from remote source, however the image exists locally")
|
||||||
|
|
||||||
|
// Okay, we found a matching container image, in that case just go ahead and return
|
||||||
|
// from this function, since there is nothing else we need to do here.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
log.WithField("image", ip.Script.ContainerImage).Debug("pulling docker image... this could take a bit of time")
|
||||||
|
|
||||||
// Block continuation until the image has been pulled successfully.
|
// Block continuation until the image has been pulled successfully.
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
@@ -287,7 +302,7 @@ func (ip *InstallationProcess) pullInstallationImage() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -298,24 +313,14 @@ func (ip *InstallationProcess) pullInstallationImage() error {
|
|||||||
// manner, if either one fails the error is returned.
|
// manner, if either one fails the error is returned.
|
||||||
func (ip *InstallationProcess) BeforeExecute() error {
|
func (ip *InstallationProcess) BeforeExecute() error {
|
||||||
if err := ip.writeScriptToDisk(); err != nil {
|
if err := ip.writeScriptToDisk(); err != nil {
|
||||||
return errors.WrapIf(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.WrapIf(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.WrapIf(err, "failed to remove existing install container for server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,12 +343,12 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !client.IsErrNotFound(err) {
|
if err != nil && !client.IsErrNotFound(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(ip.GetLogPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
f, err := os.OpenFile(ip.GetLogPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -372,15 +377,15 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
|||||||
| ------------------------------
|
| ------------------------------
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tmpl.Execute(f, ip); err != nil {
|
if err := tmpl.Execute(f, ip); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.Copy(f, reader); err != nil {
|
if _, err := io.Copy(f, reader); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -388,6 +393,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,
|
||||||
@@ -446,29 +457,36 @@ 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 "", errors.WithStackIf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container")
|
||||||
|
if err := ip.client.ContainerStart(ctx, r.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
|
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.
|
||||||
return "", errors.WithStackIf(err)
|
if err == nil {
|
||||||
|
ip.Server.Events().Publish(DaemonMessageEvent, "Installation process completed.")
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
case <-sChan:
|
case <-sChan:
|
||||||
}
|
}
|
||||||
@@ -479,31 +497,25 @@ 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,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(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": errors.WithStackIf(err),
|
|
||||||
}).Warn("error processing scanner line in installation output for server")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,7 +527,7 @@ func (s *Server) SyncInstallState(successful bool) error {
|
|||||||
err := api.New().SendInstallationStatus(s.Id(), successful)
|
err := api.New().SendInstallationStatus(s.Id(), successful)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
return errors.New(err.Error())
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"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/events"
|
"github.com/pterodactyl/wings/events"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var dockerEvents = []string{
|
var dockerEvents = []string{
|
||||||
@@ -65,9 +65,11 @@ func (s *Server) StartEventListeners() {
|
|||||||
// to terminate again.
|
// to terminate again.
|
||||||
if s.Environment.State() != environment.ProcessStoppingState {
|
if s.Environment.State() != environment.ProcessStoppingState {
|
||||||
s.Environment.SetState(environment.ProcessStoppingState)
|
s.Environment.SetState(environment.ProcessStoppingState)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
s.Log().Warn("stopping server instance, violating throttle limits")
|
s.Log().Warn("stopping server instance, violating throttle limits")
|
||||||
s.PublishConsoleOutputFromDaemon("Your server is being stopped for outputting too much data in a short period of time.")
|
s.PublishConsoleOutputFromDaemon("Your server is being stopped for outputting too much data in a short period of time.")
|
||||||
|
|
||||||
// Completely skip over server power actions and terminate the running instance. This gives the
|
// Completely skip over server power actions and terminate the running instance. This gives the
|
||||||
// server 15 seconds to finish stopping gracefully before it is forcefully terminated.
|
// server 15 seconds to finish stopping gracefully before it is forcefully terminated.
|
||||||
if err := s.Environment.WaitForStop(config.Get().Throttles.StopGracePeriod, true); err != nil {
|
if err := s.Environment.WaitForStop(config.Get().Throttles.StopGracePeriod, true); err != nil {
|
||||||
@@ -77,7 +79,7 @@ func (s *Server) StartEventListeners() {
|
|||||||
s.Environment.SetState(environment.ProcessRunningState)
|
s.Environment.SetState(environment.ProcessRunningState)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Log().WithField("error", errors.WithStackIf(err)).Error("failed to terminate environment after triggering throttle")
|
s.Log().WithField("error", err).Error("failed to terminate environment after triggering throttle")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -106,7 +108,7 @@ func (s *Server) StartEventListeners() {
|
|||||||
stats := func(e events.Event) {
|
stats := func(e events.Event) {
|
||||||
st := new(environment.Stats)
|
st := new(environment.Stats)
|
||||||
if err := json.Unmarshal([]byte(e.Data), st); err != nil {
|
if err := json.Unmarshal([]byte(e.Data), st); err != nil {
|
||||||
s.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to unmarshal server environment stats")
|
s.Log().WithField("error", err).Warn("failed to unmarshal server environment stats")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +154,7 @@ func (s *Server) onConsoleOutput(data string) {
|
|||||||
processConfiguration := s.ProcessConfiguration()
|
processConfiguration := s.ProcessConfiguration()
|
||||||
|
|
||||||
// Check if the server is currently starting.
|
// Check if the server is currently starting.
|
||||||
if s.GetState() == environment.ProcessStartingState {
|
if s.Environment.State() == environment.ProcessStartingState {
|
||||||
// Check if we should strip ansi color codes.
|
// Check if we should strip ansi color codes.
|
||||||
if processConfiguration.Startup.StripAnsi {
|
if processConfiguration.Startup.StripAnsi {
|
||||||
// Strip ansi color codes from the data string.
|
// Strip ansi color codes from the data string.
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/creasty/defaults"
|
|
||||||
"github.com/gammazero/workerpool"
|
"github.com/gammazero/workerpool"
|
||||||
"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/server/filesystem"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var servers = NewCollection(nil)
|
var servers = NewCollection(nil)
|
||||||
@@ -35,7 +35,7 @@ func LoadDirectory() error {
|
|||||||
configs, err := api.New().GetServers()
|
configs, err := api.New().GetServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
return errors.New(err.Error())
|
||||||
@@ -87,25 +87,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.WrapIf(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.WrapIf(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 +117,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.
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// To avoid confusion when working with mounts, assume that a server.Mount has not been properly
|
// To avoid confusion when working with mounts, assume that a server.Mount has not been properly
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"emperror.dev/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"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PowerAction string
|
type PowerAction string
|
||||||
@@ -62,6 +62,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)
|
||||||
}
|
}
|
||||||
@@ -80,13 +88,13 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
|
|||||||
// time than that passes an error will be propagated back up the chain and this
|
// time than that passes an error will be propagated back up the chain and this
|
||||||
// request will be aborted.
|
// request will be aborted.
|
||||||
if err := s.powerLock.Acquire(ctx, 1); err != nil {
|
if err := s.powerLock.Acquire(ctx, 1); err != nil {
|
||||||
return errors.WrapIf(err, "could not acquire lock on power state")
|
return errors.WithMessage(err, "could not acquire lock on power state")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no wait duration was provided we will attempt to immediately acquire the lock
|
// If no wait duration was provided we will attempt to immediately acquire the lock
|
||||||
// and bail out with a context deadline error if it is not acquired immediately.
|
// and bail out with a context deadline error if it is not acquired immediately.
|
||||||
if ok := s.powerLock.TryAcquire(1); !ok {
|
if ok := s.powerLock.TryAcquire(1); !ok {
|
||||||
return errors.WrapIf(context.DeadlineExceeded, "could not acquire lock on power state")
|
return errors.WithMessage(context.DeadlineExceeded, "could not acquire lock on power state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +112,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case PowerActionStart:
|
case PowerActionStart:
|
||||||
if s.GetState() != environment.ProcessOfflineState {
|
if s.Environment.State() != environment.ProcessOfflineState {
|
||||||
return ErrIsRunning
|
return ErrIsRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +157,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
|
|||||||
func (s *Server) onBeforeStart() error {
|
func (s *Server) onBeforeStart() error {
|
||||||
s.Log().Info("syncing server configuration with panel")
|
s.Log().Info("syncing server configuration with panel")
|
||||||
if err := s.Sync(); err != nil {
|
if err := s.Sync(); err != nil {
|
||||||
return errors.WrapIf(err, "unable to sync server data from Panel instance")
|
return errors.WithMessage(err, "unable to sync server data from Panel instance")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disallow start & restart if the server is suspended. Do this check after performing a sync
|
// Disallow start & restart if the server is suspended. Do this check after performing a sync
|
||||||
@@ -168,8 +176,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +193,7 @@ func (s *Server) onBeforeStart() error {
|
|||||||
s.PublishConsoleOutputFromDaemon("Ensuring file permissions are set correctly, this could take a few seconds...")
|
s.PublishConsoleOutputFromDaemon("Ensuring file permissions are set correctly, this could take a few seconds...")
|
||||||
// Ensure all of the server file permissions are set correctly before booting the process.
|
// Ensure all of the server file permissions are set correctly before booting the process.
|
||||||
if err := s.Filesystem().Chown("/"); err != nil {
|
if err := s.Filesystem().Chown("/"); err != nil {
|
||||||
return errors.WrapIf(err, "failed to chown root server directory during pre-boot process")
|
return errors.WithMessage(err, "failed to chown root server directory during pre-boot process")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"github.com/pterodactyl/wings/environment"
|
|
||||||
"github.com/pterodactyl/wings/system"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/environment"
|
||||||
|
"github.com/pterodactyl/wings/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
@@ -18,7 +18,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 +26,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() {
|
||||||
|
|||||||
@@ -2,18 +2,21 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"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"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// High level definition for a server instance being controlled by Wings.
|
// High level definition for a server instance being controlled by Wings.
|
||||||
@@ -21,9 +24,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 +56,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 +67,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 +92,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()),
|
||||||
@@ -115,7 +148,7 @@ func (s *Server) Sync() error {
|
|||||||
cfg, err := api.New().GetServerConfiguration(s.Id())
|
cfg, err := api.New().GetServerConfiguration(s.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err.(*api.RequestError).Status == "404" {
|
if err.(*api.RequestError).Status == "404" {
|
||||||
@@ -131,7 +164,7 @@ func (s *Server) Sync() error {
|
|||||||
func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error {
|
func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error {
|
||||||
// Update the data structure and persist it to the disk.
|
// Update the data structure and persist it to the disk.
|
||||||
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
|
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Lock()
|
s.Lock()
|
||||||
@@ -171,7 +204,7 @@ func (s *Server) IsBootable() bool {
|
|||||||
func (s *Server) CreateEnvironment() error {
|
func (s *Server) CreateEnvironment() error {
|
||||||
// Ensure the data directory exists before getting too far through this process.
|
// Ensure the data directory exists before getting too far through this process.
|
||||||
if err := s.EnsureDataDirectoryExists(); err != nil {
|
if err := s.EnsureDataDirectoryExists(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Environment.Create()
|
return s.Environment.Create()
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/environment"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/pterodactyl/wings/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stateMutex sync.Mutex
|
var stateMutex sync.Mutex
|
||||||
@@ -22,14 +22,14 @@ func CachedServerStates() (map[string]string, error) {
|
|||||||
// Open the states file.
|
// Open the states file.
|
||||||
f, err := os.OpenFile(config.Get().System.GetStatesPath(), os.O_RDONLY|os.O_CREATE, 0644)
|
f, err := os.OpenFile(config.Get().System.GetStatesPath(), os.O_RDONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Convert the json object to a map.
|
// Convert the json object to a map.
|
||||||
states := map[string]string{}
|
states := map[string]string{}
|
||||||
if err := json.NewDecoder(f).Decode(&states); err != nil && err != io.EOF {
|
if err := json.NewDecoder(f).Decode(&states); err != nil && err != io.EOF {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return states, nil
|
return states, nil
|
||||||
@@ -40,13 +40,13 @@ func saveServerStates() error {
|
|||||||
// Get the states of all servers on the daemon.
|
// Get the states of all servers on the daemon.
|
||||||
states := map[string]string{}
|
states := map[string]string{}
|
||||||
for _, s := range GetServers().All() {
|
for _, s := range GetServers().All() {
|
||||||
states[s.Id()] = s.GetState()
|
states[s.Id()] = s.Environment.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the map to a json object.
|
// Convert the map to a json object.
|
||||||
data, err := json.Marshal(states)
|
data, err := json.Marshal(states)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMutex.Lock()
|
stateMutex.Lock()
|
||||||
@@ -54,7 +54,7 @@ func saveServerStates() error {
|
|||||||
|
|
||||||
// Write the data to the file
|
// Write the data to the file
|
||||||
if err := ioutil.WriteFile(config.Get().System.GetStatesPath(), data, 0644); err != nil {
|
if err := ioutil.WriteFile(config.Get().System.GetStatesPath(), data, 0644); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -63,11 +63,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() {
|
||||||
@@ -92,10 +92,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +104,7 @@ func (s *Server) OnStateChange() {
|
|||||||
// automatically attempt to start the process back up for the user. This is done in a
|
// automatically attempt to start the process back up for the user. This is done in a
|
||||||
// separate thread as to not block any actions currently taking place in the flow
|
// separate thread as to not block any actions currently taking place in the flow
|
||||||
// that called this function.
|
// that called this function.
|
||||||
if (prevState == environment.ProcessStartingState || prevState == environment.ProcessRunningState) && s.GetState() == environment.ProcessOfflineState {
|
if (prevState == environment.ProcessStartingState || prevState == environment.ProcessRunningState) && s.Environment.State() == environment.ProcessOfflineState {
|
||||||
s.Log().Info("detected server as entering a crashed state; running crash handler")
|
s.Log().Info("detected server as entering a crashed state; running crash handler")
|
||||||
|
|
||||||
go func(server *Server) {
|
go func(server *Server) {
|
||||||
@@ -115,6 +112,7 @@ func (s *Server) OnStateChange() {
|
|||||||
if IsTooFrequentCrashError(err) {
|
if IsTooFrequentCrashError(err) {
|
||||||
server.Log().Info("did not restart server after crash; occurred too soon after the last")
|
server.Log().Info("did not restart server after crash; occurred too soon after the last")
|
||||||
} else {
|
} else {
|
||||||
|
s.PublishConsoleOutputFromDaemon("Server crash was detected but an error occurred while handling it.")
|
||||||
server.Log().WithField("error", err).Error("failed to handle server crash")
|
server.Log().WithField("error", err).Error("failed to handle server crash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +131,7 @@ func (s *Server) GetState() string {
|
|||||||
// environment state, it is simply the tracked state from this daemon instance, and
|
// environment state, it is simply the tracked state from this daemon instance, and
|
||||||
// not the response from Docker.
|
// not the response from Docker.
|
||||||
func (s *Server) IsRunning() bool {
|
func (s *Server) IsRunning() bool {
|
||||||
st := s.GetState()
|
st := s.Environment.State()
|
||||||
|
|
||||||
return st == environment.ProcessRunningState || st == environment.ProcessStartingState
|
return st == environment.ProcessRunningState || st == environment.ProcessStartingState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
func (s *Server) UpdateDataStructure(data []byte) error {
|
func (s *Server) UpdateDataStructure(data []byte) error {
|
||||||
src := new(Configuration)
|
src := new(Configuration)
|
||||||
if err := json.Unmarshal(data, src); err != nil {
|
if err := json.Unmarshal(data, src); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow obviously corrupted data to pass through into this function. If the UUID
|
// Don't allow obviously corrupted data to pass through into this function. If the UUID
|
||||||
@@ -47,7 +48,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
|
|||||||
// Merge the new data object that we have received with the existing server data object
|
// Merge the new data object that we have received with the existing server data object
|
||||||
// and then save it to the disk so it is persistent.
|
// and then save it to the disk so it is persistent.
|
||||||
if err := mergo.Merge(&c, src, mergo.WithOverride); err != nil {
|
if err := mergo.Merge(&c, src, mergo.WithOverride); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't explode if we're setting CPU limits to 0. Mergo sees that as an empty value
|
// Don't explode if we're setting CPU limits to 0. Mergo sees that as an empty value
|
||||||
@@ -65,7 +66,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
|
|||||||
// request is going to be boolean. Allegedly.
|
// request is going to be boolean. Allegedly.
|
||||||
if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
|
if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Build.OOMDisabled = v
|
c.Build.OOMDisabled = v
|
||||||
@@ -74,7 +75,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
|
|||||||
// Mergo also cannot handle this boolean value.
|
// Mergo also cannot handle this boolean value.
|
||||||
if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
|
if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Suspended = v
|
c.Suspended = v
|
||||||
@@ -82,7 +83,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
|
|||||||
|
|
||||||
if v, err := jsonparser.GetBoolean(data, "skip_egg_scripts"); err != nil {
|
if v, err := jsonparser.GetBoolean(data, "skip_egg_scripts"); err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.SkipEggScripts = v
|
c.SkipEggScripts = v
|
||||||
@@ -143,7 +144,7 @@ func (s *Server) SyncWithEnvironment() {
|
|||||||
} else {
|
} else {
|
||||||
// Checks if the server is now in a suspended state. If so and a server process is currently running it
|
// Checks if the server is now in a suspended state. If so and a server process is currently running it
|
||||||
// will be gracefully stopped (and terminated if it refuses to stop).
|
// will be gracefully stopped (and terminated if it refuses to stop).
|
||||||
if s.GetState() != environment.ProcessOfflineState {
|
if s.Environment.State() != environment.ProcessOfflineState {
|
||||||
s.Log().Info("server suspended with running process state, terminating now")
|
s.Log().Info("server suspended with running process state, terminating now")
|
||||||
|
|
||||||
go func(s *Server) {
|
go func(s *Server) {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/google/uuid"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebsocketBag struct {
|
type WebsocketBag struct {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package sftp
|
package sftp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
"github.com/pkg/sftp"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/pkg/sftp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileSystem struct {
|
type FileSystem struct {
|
||||||
@@ -58,14 +58,14 @@ func (fs FileSystem) Fileread(request *sftp.Request) (io.ReaderAt, error) {
|
|||||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||||
return nil, sftp.ErrSshFxNoSuchFile
|
return nil, sftp.ErrSshFxNoSuchFile
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
fs.logger.WithField("error", errors.WithStackIf(err)).Error("error while processing file stat")
|
fs.logger.WithField("error", err).Error("error while processing file stat")
|
||||||
|
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(p)
|
file, err := os.Open(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.logger.WithField("source", p).WithField("error", errors.WithStackIf(err)).Error("could not open file for reading")
|
fs.logger.WithField("source", p).WithField("error", err).Error("could not open file for reading")
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,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.
|
||||||
@@ -108,7 +108,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
||||||
l.WithFields(log.Fields{
|
l.WithFields(log.Fields{
|
||||||
"path": filepath.Dir(p),
|
"path": filepath.Dir(p),
|
||||||
"error": errors.WithStackIf(err),
|
"error": err,
|
||||||
}).Error("error making path for file")
|
}).Error("error making path for file")
|
||||||
|
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
@@ -116,7 +116,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
|
|
||||||
file, err := os.Create(p)
|
file, err := os.Create(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to create file")
|
l.WithField("error", err).Error("failed to create file")
|
||||||
|
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
// Not failing here is intentional. We still made the file, it is just owned incorrectly
|
// Not failing here is intentional. We still made the file, it is just owned incorrectly
|
||||||
// and will likely cause some issues.
|
// and will likely cause some issues.
|
||||||
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
|
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Warn("failed to set permissions on file")
|
l.WithField("error", err).Warn("failed to set permissions on file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return file, nil
|
return file, nil
|
||||||
@@ -133,7 +133,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
// If the stat error isn't about the file not existing, there is some other issue
|
// If the stat error isn't about the file not existing, there is some other issue
|
||||||
// at play and we need to go ahead and bail out of the process.
|
// at play and we need to go ahead and bail out of the process.
|
||||||
if statErr != nil {
|
if statErr != nil {
|
||||||
l.WithField("error", errors.WithStackIf(statErr)).Error("encountered error performing file stat")
|
l.WithField("error", statErr).Error("encountered error performing file stat")
|
||||||
|
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -159,14 +159,14 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
return nil, sftp.ErrSSHFxNoSuchFile
|
return nil, sftp.ErrSSHFxNoSuchFile
|
||||||
}
|
}
|
||||||
|
|
||||||
l.WithField("flags", request.Flags).WithField("error", errors.WithStackIf(err)).Error("failed to open existing file on system")
|
l.WithField("flags", request.Flags).WithField("error", err).Error("failed to open existing file on system")
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not failing here is intentional. We still made the file, it is just owned incorrectly
|
// Not failing here is intentional. We still made the file, it is just owned incorrectly
|
||||||
// and will likely cause some issues.
|
// and will likely cause some issues.
|
||||||
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
|
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Warn("error chowning file")
|
l.WithField("error", err).Warn("error chowning file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return file, nil
|
return file, nil
|
||||||
@@ -184,7 +184,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
|
||||||
@@ -203,7 +203,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 {
|
||||||
@@ -220,7 +220,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
return sftp.ErrSSHFxNoSuchFile
|
return sftp.ErrSSHFxNoSuchFile
|
||||||
}
|
}
|
||||||
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to perform setstat on item")
|
l.WithField("error", err).Error("failed to perform setstat on item")
|
||||||
return sftp.ErrSSHFxFailure
|
return sftp.ErrSSHFxFailure
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -234,7 +234,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
return sftp.ErrSSHFxNoSuchFile
|
return sftp.ErrSSHFxNoSuchFile
|
||||||
}
|
}
|
||||||
|
|
||||||
l.WithField("target", target).WithField("error", errors.WithStackIf(err)).Error("failed to rename file")
|
l.WithField("target", target).WithField("error", err).Error("failed to rename file")
|
||||||
|
|
||||||
return sftp.ErrSshFxFailure
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.RemoveAll(p); err != nil {
|
if err := os.RemoveAll(p); err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to remove directory")
|
l.WithField("error", err).Error("failed to remove directory")
|
||||||
|
|
||||||
return sftp.ErrSshFxFailure
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -258,7 +258,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(p, 0755); err != nil {
|
if err := os.MkdirAll(p, 0755); err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to create directory")
|
l.WithField("error", err).Error("failed to create directory")
|
||||||
|
|
||||||
return sftp.ErrSshFxFailure
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -270,7 +270,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Symlink(p, target); err != nil {
|
if err := os.Symlink(p, target); err != nil {
|
||||||
l.WithField("target", target).WithField("error", errors.WithStackIf(err)).Error("failed to create symlink")
|
l.WithField("target", target).WithField("error", err).Error("failed to create symlink")
|
||||||
|
|
||||||
return sftp.ErrSshFxFailure
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
return sftp.ErrSSHFxNoSuchFile
|
return sftp.ErrSSHFxNoSuchFile
|
||||||
}
|
}
|
||||||
|
|
||||||
l.WithField("error", errors.WithStackIf(err)).Error("failed to remove a file")
|
l.WithField("error", err).Error("failed to remove a file")
|
||||||
|
|
||||||
return sftp.ErrSshFxFailure
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -305,7 +305,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
// and will likely cause some issues. There is no logical check for if the file was removed
|
// and will likely cause some issues. There is no logical check for if the file was removed
|
||||||
// because both of those cases (Rmdir, Remove) have an explicit return rather than break.
|
// because both of those cases (Rmdir, Remove) have an explicit return rather than break.
|
||||||
if err := os.Chown(fileLocation, fs.User.Uid, fs.User.Gid); err != nil {
|
if err := os.Chown(fileLocation, fs.User.Uid, fs.User.Gid); err != nil {
|
||||||
l.WithField("error", errors.WithStackIf(err)).Warn("error chowning file")
|
l.WithField("error", err).Warn("error chowning file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sftp.ErrSshFxOk
|
return sftp.ErrSshFxOk
|
||||||
@@ -327,7 +327,7 @@ func (fs FileSystem) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
|
|||||||
|
|
||||||
files, err := ioutil.ReadDir(p)
|
files, err := ioutil.ReadDir(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.logger.WithField("error", errors.WithStackIf(err)).Error("error while listing directory")
|
fs.logger.WithField("error", err).Error("error while listing directory")
|
||||||
|
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,7 @@ func (fs FileSystem) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
|
|||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, sftp.ErrSshFxNoSuchFile
|
return nil, sftp.ErrSshFxNoSuchFile
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
fs.logger.WithField("source", p).WithField("error", errors.WithStackIf(err)).Error("error performing stat on file")
|
fs.logger.WithField("source", p).WithField("error", err).Error("error performing stat on file")
|
||||||
|
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,6 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
"github.com/pkg/sftp"
|
|
||||||
"github.com/pterodactyl/wings/api"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@@ -18,6 +13,12 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
"github.com/pterodactyl/wings/api"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ func Initialize(config config.SystemConfiguration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := New(s); err != nil {
|
if err := New(s); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the SFTP server in a background thread since this is
|
// Initialize the SFTP server in a background thread since this is
|
||||||
// a long running operation.
|
// a long running operation.
|
||||||
go func(s *Server) {
|
go func(s *Server) {
|
||||||
if err := s.Initialize(); err != nil {
|
if err := s.Initialize(); err != nil {
|
||||||
log.WithField("subsystem", "sftp").WithField("error", errors.WithStackIf(err)).Error("failed to initialize SFTP subsystem")
|
log.WithField("subsystem", "sftp").WithField("error", err).Error("failed to initialize SFTP subsystem")
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Information struct {
|
type Information struct {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user