Compare commits
29 Commits
fix/dev-bu
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
324987901b | ||
|
|
a07bbb9e3d | ||
|
|
a5bb862587 | ||
|
|
d508052bfc | ||
|
|
c8c590846c | ||
|
|
467003bd9a | ||
|
|
7b30c4efb8 | ||
|
|
430691afd8 | ||
|
|
b63f869cbc | ||
|
|
65dd21178f | ||
|
|
e8266b4a7b | ||
|
|
564909d371 | ||
|
|
652bd35dc0 | ||
|
|
62ac9c5a5a | ||
|
|
7ed9f310eb | ||
|
|
64d481ae2f | ||
|
|
0dba9b709d | ||
|
|
dc259f2fce | ||
|
|
a44e8b04e8 | ||
|
|
8e785e8bb4 | ||
|
|
a35e18a904 | ||
|
|
75ed19b20e | ||
|
|
65d6098613 | ||
|
|
1cbf4dceeb | ||
|
|
8127ccd0f7 | ||
|
|
fc81002938 | ||
|
|
361f782e7c | ||
|
|
a0920d619e | ||
|
|
293d390e74 |
4
.github/workflows/label-conflcts.yaml
vendored
4
.github/workflows/label-conflcts.yaml
vendored
@@ -11,5 +11,5 @@ jobs:
|
||||
with:
|
||||
CONFLICT_LABEL_NAME: 'has conflicts'
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MAX_RETRIES: 5
|
||||
WAIT_MS: 5000
|
||||
MAX_RETRIES: 10
|
||||
WAIT_MS: 60000
|
||||
|
||||
11
.github/workflows/lint.yml
vendored
11
.github/workflows/lint.yml
vendored
@@ -21,13 +21,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '18'
|
||||
cache: 'yarn'
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.19.4
|
||||
go-version: 1.19.5
|
||||
- run: yarn --frozen-lockfile
|
||||
|
||||
- name: Run linters
|
||||
uses: wearerequired/lint-action@v1
|
||||
with:
|
||||
@@ -42,6 +41,6 @@ jobs:
|
||||
- name: GolangCI-Lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
version: v1.52.2
|
||||
working-directory: api
|
||||
args: -c .golangci.yaml
|
||||
args: --timeout=10m -c .golangci.yaml
|
||||
|
||||
127
.github/workflows/nightly-security-scan.yml
vendored
127
.github/workflows/nightly-security-scan.yml
vendored
@@ -2,21 +2,22 @@ name: Nightly Code Security Scan
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
- cron: '0 20 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
client-dependencies:
|
||||
name: Client dependency check
|
||||
name: Client Dependency Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >- # only run for develop branch
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
js: ${{ steps.set-matrix.outputs.js_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
- name: scan vulnerabilities by Snyk
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
@@ -24,46 +25,48 @@ jobs:
|
||||
with:
|
||||
json: true
|
||||
|
||||
- name: Upload js security scan result as artifact
|
||||
- name: upload scan result as develop artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: js-security-scan-develop-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Export scan result to html file
|
||||
- name: develop scan report export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/js-result")
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=snyk --path="/data/snyk.json" --output-type=table --export --export-filename="/data/js-result")
|
||||
|
||||
- name: Upload js result html file
|
||||
- name: upload html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-js-result-${{github.run_id}}
|
||||
path: js-result.html
|
||||
|
||||
- name: Analyse the js result
|
||||
- name: analyse vulnerabilities
|
||||
id: set-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
|
||||
echo "::set-output name=js_result::${result}"
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=snyk --path="/data/snyk.json" --output-type=matrix)
|
||||
echo "js_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
server-dependencies:
|
||||
name: Server dependency check
|
||||
name: Server Dependency Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >- # only run for develop branch
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
go: ${{ steps.set-matrix.outputs.go_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
- name: install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19.4'
|
||||
go-version: '1.19.5'
|
||||
|
||||
- name: Download go modules
|
||||
- name: download Go modules
|
||||
run: cd ./api && go get -t -v -d ./...
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
- name: scan vulnerabilities by Snyk
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
@@ -71,100 +74,66 @@ jobs:
|
||||
yarn global add snyk
|
||||
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || :
|
||||
|
||||
- name: Upload go security scan result as artifact
|
||||
- name: upload scan result as develop artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: go-security-scan-develop-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Export scan result to html file
|
||||
- name: develop scan report export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=table -export -export-filename="/data/go-result")
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=snyk --path="/data/snyk.json" --output-type=table --export --export-filename="/data/go-result")
|
||||
|
||||
- name: Upload go result html file
|
||||
- name: upload html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-go-result-${{github.run_id}}
|
||||
path: go-result.html
|
||||
|
||||
- name: Analyse the go result
|
||||
- name: analyse vulnerabilities
|
||||
id: set-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=snyk -path="/data/snyk.json" -output-type=matrix)
|
||||
echo "::set-output name=go_result::${result}"
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=snyk --path="/data/snyk.json" --output-type=matrix)
|
||||
echo "go_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
image-vulnerability:
|
||||
name: Build docker image and Image vulnerability check
|
||||
name: Image Vulnerability Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
image: ${{ steps.set-matrix.outputs.image_result }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Use golang 1.19.4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19.4'
|
||||
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Install packages
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: build
|
||||
run: make build
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: build/linux/Dockerfile
|
||||
tags: trivy-portainer:${{ github.sha }}
|
||||
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Load docker image
|
||||
run: |
|
||||
docker load --input /tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
- name: scan vulnerabilities by Trivy
|
||||
uses: docker://docker.io/aquasec/trivy:latest
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
|
||||
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress portainerci/portainer:develop
|
||||
|
||||
- name: Upload image security scan result as artifact
|
||||
- name: upload image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-develop-result
|
||||
path: image-trivy.json
|
||||
|
||||
- name: Export scan result to html file
|
||||
- name: develop scan report export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=table -export -export-filename="/data/image-result")
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=table --export --export-filename="/data/image-result")
|
||||
|
||||
- name: Upload go result html file
|
||||
- name: upload html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-${{github.run_id}}
|
||||
path: image-result.html
|
||||
|
||||
- name: Analyse the trivy result
|
||||
- name: analyse vulnerabilities
|
||||
id: set-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 summary -report-type=trivy -path="/data/image-trivy.json" -output-type=matrix)
|
||||
echo "::set-output name=image_result::${result}"
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=matrix)
|
||||
echo "image_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
result-analysis:
|
||||
name: Analyse scan result
|
||||
name: Analyse Scan Results
|
||||
needs: [client-dependencies, server-dependencies, image-vulnerability]
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
@@ -175,21 +144,21 @@ jobs:
|
||||
go: ${{fromJson(needs.server-dependencies.outputs.go)}}
|
||||
image: ${{fromJson(needs.image-vulnerability.outputs.image)}}
|
||||
steps:
|
||||
- name: Display the results of js, go and image
|
||||
- name: display the results of js, Go, and image scan
|
||||
run: |
|
||||
echo ${{ matrix.js.status }}
|
||||
echo ${{ matrix.go.status }}
|
||||
echo ${{ matrix.image.status }}
|
||||
echo ${{ matrix.js.summary }}
|
||||
echo ${{ matrix.go.summary }}
|
||||
echo ${{ matrix.image.summary }}
|
||||
echo "${{ matrix.js.status }}"
|
||||
echo "${{ matrix.go.status }}"
|
||||
echo "${{ matrix.image.status }}"
|
||||
echo "${{ matrix.js.summary }}"
|
||||
echo "${{ matrix.go.summary }}"
|
||||
echo "${{ matrix.image.summary }}"
|
||||
|
||||
- name: Send Slack message
|
||||
if: >-
|
||||
- name: send message to Slack
|
||||
if: >-
|
||||
matrix.js.status == 'failure' ||
|
||||
matrix.go.status == 'failure' ||
|
||||
matrix.image.status == 'failure'
|
||||
uses: slackapi/slack-github-action@v1.18.0
|
||||
uses: slackapi/slack-github-action@v1.23.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
|
||||
112
.github/workflows/pr-security.yml
vendored
112
.github/workflows/pr-security.yml
vendored
@@ -12,10 +12,11 @@ on:
|
||||
- 'build/linux/Dockerfile'
|
||||
- 'build/linux/alpine.Dockerfile'
|
||||
- 'build/windows/Dockerfile'
|
||||
- '.github/workflows/pr-security.yml'
|
||||
|
||||
jobs:
|
||||
client-dependencies:
|
||||
name: Client dependency check
|
||||
name: Client Dependency Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
@@ -23,9 +24,10 @@ jobs:
|
||||
outputs:
|
||||
jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
- name: scan vulnerabilities by Snyk
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
@@ -33,13 +35,13 @@ jobs:
|
||||
with:
|
||||
json: true
|
||||
|
||||
- name: Upload js security scan result as artifact
|
||||
- name: upload scan result as pull-request artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: js-security-scan-feat-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Download artifacts from develop branch
|
||||
- name: download artifacts from develop branch built by nightly scan
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -51,24 +53,24 @@ jobs:
|
||||
echo "null" > ./js-snyk-develop.json
|
||||
fi
|
||||
|
||||
- name: Export scan result to html file
|
||||
- name: pr vs develop scan report comparison export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="/data/js-snyk-develop.json" -output-type=table -export -export-filename="/data/js-result")
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=snyk --path="/data/js-snyk-feature.json" --compare-to="/data/js-snyk-develop.json" --output-type=table --export --export-filename="/data/js-result")
|
||||
|
||||
- name: Upload js result html file
|
||||
- name: upload html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-js-result-compare-to-develop-${{github.run_id}}
|
||||
path: js-result.html
|
||||
|
||||
- name: Analyse the js diff result
|
||||
- name: analyse different vulnerabilities against develop branch
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/js-snyk-feature.json" -compare-to="./data/js-snyk-develop.json" -output-type=matrix)
|
||||
echo "::set-output name=js_diff_result::${result}"
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=snyk --path="/data/js-snyk-feature.json" --compare-to="/data/js-snyk-develop.json" --output-type=matrix)
|
||||
echo "js_diff_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
server-dependencies:
|
||||
name: Server dependency check
|
||||
name: Server Dependency Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
@@ -76,16 +78,18 @@ jobs:
|
||||
outputs:
|
||||
godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
- name: install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19.4'
|
||||
go-version: '1.19.5'
|
||||
|
||||
- name: Download go modules
|
||||
- name: download Go modules
|
||||
run: cd ./api && go get -t -v -d ./...
|
||||
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
- name: scan vulnerabilities by Snyk
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
@@ -93,13 +97,13 @@ jobs:
|
||||
yarn global add snyk
|
||||
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || :
|
||||
|
||||
- name: Upload go security scan result as artifact
|
||||
- name: upload scan result as pull-request artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: go-security-scan-feature-result
|
||||
path: snyk.json
|
||||
|
||||
- name: Download artifacts from develop branch
|
||||
- name: download artifacts from develop branch built by nightly scan
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -111,24 +115,24 @@ jobs:
|
||||
echo "null" > ./go-snyk-develop.json
|
||||
fi
|
||||
|
||||
- name: Export scan result to html file
|
||||
- name: pr vs develop scan report comparison export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=table -export -export-filename="/data/go-result")
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=snyk --path="/data/go-snyk-feature.json" --compare-to="/data/go-snyk-develop.json" --output-type=table --export --export-filename="/data/go-result")
|
||||
|
||||
- name: Upload go result html file
|
||||
- name: upload html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-go-result-compare-to-develop-${{github.run_id}}
|
||||
path: go-result.html
|
||||
|
||||
- name: Analyse the go diff result
|
||||
- name: analyse different vulnerabilities against develop branch
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=snyk -path="/data/go-snyk-feature.json" -compare-to="/data/go-snyk-develop.json" -output-type=matrix)
|
||||
echo "::set-output name=go_diff_result::${result}"
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=snyk --path="/data/go-snyk-feature.json" --compare-to="/data/go-snyk-develop.json" --output-type=matrix)
|
||||
echo "go_diff_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
image-vulnerability:
|
||||
name: Build docker image and Image vulnerability check
|
||||
name: Image Vulnerability Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
@@ -136,16 +140,16 @@ jobs:
|
||||
outputs:
|
||||
imagediff: ${{ steps.set-diff-matrix.outputs.image_diff_result }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Use golang 1.19.4
|
||||
- name: install Go 1.19.5
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19.4'
|
||||
go-version: '1.19.5'
|
||||
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
- name: install Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
@@ -153,36 +157,36 @@ jobs:
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: build
|
||||
run: make build
|
||||
run: make build-all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: set up docker buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
- name: build and compress image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: build/linux/Dockerfile
|
||||
tags: trivy-portainer:${{ github.sha }}
|
||||
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Load docker image
|
||||
- name: load docker image
|
||||
run: |
|
||||
docker load --input /tmp/trivy-portainer-image.tar
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
- name: scan vulnerabilities by Trivy
|
||||
uses: docker://docker.io/aquasec/trivy:latest
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }}
|
||||
|
||||
- name: Upload image security scan result as artifact
|
||||
- name: upload image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-feature-result
|
||||
path: image-trivy.json
|
||||
|
||||
- name: Download artifacts from develop branch
|
||||
- name: download artifacts from develop branch built by nightly scan
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -194,24 +198,24 @@ jobs:
|
||||
echo "null" > ./image-trivy-develop.json
|
||||
fi
|
||||
|
||||
- name: Export scan result to html file
|
||||
- name: pr vs develop scan report comparison export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="/data/image-trivy-develop.json" -output-type=table -export -export-filename="/data/image-result")
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=table --export --export-filename="/data/image-result")
|
||||
|
||||
- name: Upload image result html file
|
||||
- name: upload html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-compare-to-develop-${{github.run_id}}
|
||||
path: image-result.html
|
||||
|
||||
- name: Analyse the image diff result
|
||||
- name: analyse different vulnerabilities against develop branch
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data oscarzhou/scan-report:0.1.8 diff -report-type=trivy -path="/data/image-trivy-feature.json" -compare-to="./data/image-trivy-develop.json" -output-type=matrix)
|
||||
echo "::set-output name=image_diff_result::${result}"
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=matrix)
|
||||
echo "image_diff_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
result-analysis:
|
||||
name: Analyse scan result compared to develop
|
||||
name: Analyse Scan Result Against develop Branch
|
||||
needs: [client-dependencies, server-dependencies, image-vulnerability]
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
@@ -223,16 +227,16 @@ jobs:
|
||||
godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}}
|
||||
imagediff: ${{fromJson(needs.image-vulnerability.outputs.imagediff)}}
|
||||
steps:
|
||||
- name: Check job status of diff result
|
||||
- name: check job status of diff result
|
||||
if: >-
|
||||
matrix.jsdiff.status == 'failure' ||
|
||||
matrix.godiff.status == 'failure' ||
|
||||
matrix.imagediff.status == 'failure'
|
||||
run: |
|
||||
echo ${{ matrix.jsdiff.status }}
|
||||
echo ${{ matrix.godiff.status }}
|
||||
echo ${{ matrix.imagediff.status }}
|
||||
echo ${{ matrix.jsdiff.summary }}
|
||||
echo ${{ matrix.godiff.summary }}
|
||||
echo ${{ matrix.imagediff.summary }}
|
||||
echo "${{ matrix.jsdiff.status }}"
|
||||
echo "${{ matrix.godiff.status }}"
|
||||
echo "${{ matrix.imagediff.status }}"
|
||||
echo "${{ matrix.jsdiff.summary }}"
|
||||
echo "${{ matrix.godiff.summary }}"
|
||||
echo "${{ matrix.imagediff.summary }}"
|
||||
exit 1
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@ __debug_bin
|
||||
api/docs
|
||||
.idea
|
||||
.env
|
||||
go.work.sum
|
||||
|
||||
|
||||
@@ -91,7 +91,15 @@ Then build and run the project in a Docker container:
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
Portainer can now be accessed at <https://localhost:9443>.
|
||||
Portainer server can now be accessed at <https://localhost:9443>. and UI dev server runs on <http://localhost:8999>.
|
||||
|
||||
if you want to build the project you can run:
|
||||
|
||||
```sh
|
||||
make build-all
|
||||
```
|
||||
|
||||
For additional make commands, run `make help`.
|
||||
|
||||
Find more detailed steps at <https://docs.portainer.io/contribute/build>.
|
||||
|
||||
|
||||
86
Makefile
86
Makefile
@@ -4,77 +4,89 @@
|
||||
PLATFORM=$(shell go env GOOS)
|
||||
ARCH=$(shell go env GOARCH)
|
||||
|
||||
TAG=latest
|
||||
SWAG_VERSION=v1.8.11
|
||||
|
||||
# build target, can be one of "production", "testing", "development"
|
||||
ENV=development
|
||||
WEBPACK_CONFIG=webpack/webpack.$(ENV).js
|
||||
TAG=latest
|
||||
|
||||
SWAG=go run github.com/swaggo/swag/cmd/swag@v1.8.11
|
||||
GOTESTSUM=go run gotest.tools/gotestsum@latest
|
||||
|
||||
# Don't change anything below this line unless you know what you're doing
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: help build-storybook build-client devops download-binaries tidy clean client-deps
|
||||
|
||||
##@ Building
|
||||
|
||||
.PHONY: init-dist build-storybook build build-client build-server build-image devops
|
||||
init-dist:
|
||||
@mkdir -p dist
|
||||
|
||||
build-storybook:
|
||||
yarn storybook:build
|
||||
build-all: deps build-server build-client ## Build the client, server and download external dependancies (doesn't build an image)
|
||||
|
||||
build: build-server build-client ## Build the server and client
|
||||
|
||||
build-client: init-dist client-deps ## Build the client
|
||||
build-client: init-dist ## Build the client
|
||||
export NODE_ENV=$(ENV) && yarn build --config $(WEBPACK_CONFIG)
|
||||
|
||||
build-server: init-dist ## Build the server binary
|
||||
./build/build_binary.sh "$(PLATFORM)" "$(ARCH)"
|
||||
|
||||
build-image: build ## Build the Portainer image
|
||||
build-image: build-all ## Build the Portainer image locally
|
||||
docker buildx build --load -t portainerci/portainer:$(TAG) -f build/linux/Dockerfile .
|
||||
|
||||
devops: clean init-dist download-binaries build-client ## Build the server binary for CI
|
||||
build-storybook: ## Build and serve the storybook files
|
||||
yarn storybook:build
|
||||
|
||||
devops: clean deps build-client ## Build the everything target specifically for CI
|
||||
echo "Building the devops binary..."
|
||||
@./build/build_binary_azuredevops.sh "$(PLATFORM)" "$(ARCH)"
|
||||
|
||||
##@ Dependencies
|
||||
##@ Build dependencies
|
||||
.PHONY: deps server-deps client-deps tidy
|
||||
deps: server-deps client-deps ## Download all client and server build dependancies
|
||||
|
||||
download-binaries: ## Download dependant binaries
|
||||
server-deps: init-dist ## Download dependant server binaries
|
||||
@./build/download_binaries.sh $(PLATFORM) $(ARCH)
|
||||
|
||||
tidy: ## Tidy up the go.mod file
|
||||
cd api && go mod tidy
|
||||
|
||||
client-deps: ## Install client dependencies
|
||||
yarn
|
||||
|
||||
##@ Cleanup
|
||||
tidy: ## Tidy up the go.mod file
|
||||
cd api && go mod tidy
|
||||
|
||||
|
||||
##@ Cleanup
|
||||
.PHONY: clean
|
||||
clean: ## Remove all build and download artifacts
|
||||
@echo "Clearing the dist directory..."
|
||||
@rm -rf dist/*
|
||||
|
||||
|
||||
##@ Testing
|
||||
.PHONY: test test-client test-server
|
||||
test: test-server test-client ## Run all tests
|
||||
|
||||
test-client: ## Run client tests
|
||||
yarn test
|
||||
|
||||
test-server: ## Run server tests
|
||||
cd api && go test -v ./...
|
||||
|
||||
test: test-client test-server ## Run all tests
|
||||
cd api && $(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover ./...
|
||||
|
||||
##@ Dev
|
||||
.PHONY: dev dev-client dev-server
|
||||
dev: ## Run both the client and server in development mode
|
||||
make dev-server
|
||||
make dev-client
|
||||
|
||||
dev-client: ## Run the client in development mode
|
||||
yarn dev
|
||||
|
||||
dev-server: build-image ## Run the server in development mode
|
||||
dev-server: build-server ## Run the server in development mode
|
||||
@./dev/run_container.sh
|
||||
|
||||
|
||||
##@ Format
|
||||
.PHONY: format format-client format-server
|
||||
|
||||
format: format-client format-server ## Format all code
|
||||
|
||||
format-client: ## Format client code
|
||||
yarn format
|
||||
@@ -82,10 +94,8 @@ format-client: ## Format client code
|
||||
format-server: ## Format server code
|
||||
cd api && go fmt ./...
|
||||
|
||||
format: format-client format-server ## Format all code
|
||||
|
||||
##@ Lint
|
||||
|
||||
.PHONY: lint lint-client lint-server
|
||||
lint: lint-client lint-server ## Lint all code
|
||||
|
||||
lint-client: ## Lint client code
|
||||
@@ -94,29 +104,23 @@ lint-client: ## Lint client code
|
||||
lint-server: ## Lint server code
|
||||
cd api && go vet ./...
|
||||
|
||||
##@ Extension
|
||||
|
||||
##@ Extension
|
||||
.PHONY: dev-extension
|
||||
dev-extension: build-server build-client ## Run the extension in development mode
|
||||
make local -f build/docker-extension/Makefile
|
||||
|
||||
|
||||
##@ Docs
|
||||
|
||||
docs-deps: ## Install docs dependencies
|
||||
go install github.com/swaggo/swag/cmd/swag@$(SWAG_VERSION)
|
||||
|
||||
docs-build: docs-deps ## Build docs
|
||||
cd api && swag init -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 --markdownFiles ./
|
||||
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
|
||||
docs-build: init-dist ## Build docs
|
||||
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 --markdownFiles ./
|
||||
|
||||
docs-validate: docs-build ## Validate docs
|
||||
yarn swagger2openapi --warnOnly api/docs/swagger.yaml -o api/docs/openapi.yaml
|
||||
yarn swagger-cli validate api/docs/openapi.yaml
|
||||
|
||||
docs-clean: ## Clean docs
|
||||
rm -rf api/docs
|
||||
|
||||
docs-validate-clean: docs-validate docs-clean ## Validate and clean docs
|
||||
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
|
||||
yarn swagger-cli validate dist/docs/openapi.yaml
|
||||
|
||||
##@ Helpers
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/demo"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/exec"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/git"
|
||||
@@ -233,8 +234,8 @@ func initSSLService(addr, certPath, keyPath string, fileService portainer.FileSe
|
||||
return sslService, nil
|
||||
}
|
||||
|
||||
func initDockerClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService) *docker.ClientFactory {
|
||||
return docker.NewClientFactory(signatureService, reverseTunnelService)
|
||||
func initDockerClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService) *dockerclient.ClientFactory {
|
||||
return dockerclient.NewClientFactory(signatureService, reverseTunnelService)
|
||||
}
|
||||
|
||||
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
|
||||
@@ -244,7 +245,7 @@ func initKubernetesClientFactory(signatureService portainer.DigitalSignatureServ
|
||||
func initSnapshotService(
|
||||
snapshotIntervalFromFlag string,
|
||||
dataStore dataservices.DataStore,
|
||||
dockerClientFactory *docker.ClientFactory,
|
||||
dockerClientFactory *dockerclient.ClientFactory,
|
||||
kubernetesClientFactory *kubecli.ClientFactory,
|
||||
shutdownCtx context.Context,
|
||||
) (portainer.SnapshotService, error) {
|
||||
|
||||
@@ -20,6 +20,8 @@ func CreateTLSConfiguration() *tls.Config {
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,20 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type PostInitMigrator struct {
|
||||
kubeFactory *cli.ClientFactory
|
||||
dockerFactory *docker.ClientFactory
|
||||
dockerFactory *dockerclient.ClientFactory
|
||||
dataStore dataservices.DataStore
|
||||
}
|
||||
|
||||
func NewPostInitMigrator(
|
||||
kubeFactory *cli.ClientFactory,
|
||||
dockerFactory *docker.ClientFactory,
|
||||
dockerFactory *dockerclient.ClientFactory,
|
||||
dataStore dataservices.DataStore,
|
||||
) *PostInitMigrator {
|
||||
return &PostInitMigrator{
|
||||
|
||||
@@ -713,8 +713,6 @@
|
||||
"ID": ""
|
||||
},
|
||||
"Isolation": "",
|
||||
"KernelMemory": false,
|
||||
"KernelMemoryTCP": false,
|
||||
"KernelVersion": "",
|
||||
"Labels": null,
|
||||
"LiveRestoreEnabled": false,
|
||||
@@ -945,6 +943,6 @@
|
||||
}
|
||||
],
|
||||
"version": {
|
||||
"VERSION": "{\"SchemaVersion\":\"2.18.2\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
|
||||
"VERSION": "{\"SchemaVersion\":\"2.18.4\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
8
api/docker/consts/labels.go
Normal file
8
api/docker/consts/labels.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package consts
|
||||
|
||||
const (
|
||||
ComposeStackNameLabel = "com.docker.compose.project"
|
||||
SwarmStackNameLabel = "com.docker.stack.namespace"
|
||||
SwarmServiceIdLabel = "com.docker.swarm.service.id"
|
||||
SwarmNodeIdLabel = "com.docker.swarm.node.id"
|
||||
)
|
||||
232
api/docker/container.go
Normal file
232
api/docker/container.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/docker/images"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ContainerService struct {
|
||||
factory *dockerclient.ClientFactory
|
||||
dataStore dataservices.DataStore
|
||||
sr *serviceRestore
|
||||
}
|
||||
|
||||
func NewContainerService(factory *dockerclient.ClientFactory, dataStore dataservices.DataStore) *ContainerService {
|
||||
return &ContainerService{
|
||||
factory: factory,
|
||||
dataStore: dataStore,
|
||||
sr: &serviceRestore{},
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate a container
|
||||
func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.Endpoint, containerId string, forcePullImage bool, imageTag, nodeName string) (*types.ContainerJSON, error) {
|
||||
cli, err := c.factory.CreateClient(endpoint, nodeName, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create client error")
|
||||
}
|
||||
|
||||
defer func(cli *client.Client) {
|
||||
cli.Close()
|
||||
}(cli)
|
||||
|
||||
log.Debug().Str("container_id", containerId).Msg("starting to fetch container information")
|
||||
|
||||
container, _, err := cli.ContainerInspectWithRaw(ctx, containerId, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetch container information error")
|
||||
}
|
||||
|
||||
log.Debug().Str("image", container.Config.Image).Msg("starting to parse image")
|
||||
img, err := images.ParseImage(images.ParseImageOptions{
|
||||
Name: container.Config.Image,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse image error")
|
||||
}
|
||||
|
||||
if imageTag != "" {
|
||||
err = img.WithTag(imageTag)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "set image tag error %s", imageTag)
|
||||
}
|
||||
|
||||
log.Debug().Str("image", container.Config.Image).Msg("new image with tag")
|
||||
|
||||
container.Config.Image = img.FullName()
|
||||
}
|
||||
|
||||
// 1. pull image if you need force pull
|
||||
if forcePullImage {
|
||||
puller := images.NewPuller(cli, images.NewRegistryClient(c.dataStore), c.dataStore)
|
||||
err = puller.Pull(ctx, img)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "pull image error %s", img.FullName())
|
||||
}
|
||||
}
|
||||
|
||||
// 2. stop the current container
|
||||
log.Debug().Str("container_id", containerId).Msg("starting to stop the container")
|
||||
err = cli.ContainerStop(ctx, containerId, dockercontainer.StopOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "stop container error")
|
||||
}
|
||||
|
||||
// 3. rename the current container
|
||||
log.Debug().Str("container_id", containerId).Msg("starting to rename the container")
|
||||
err = cli.ContainerRename(ctx, containerId, container.Name+"-old")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "rename container error")
|
||||
}
|
||||
|
||||
networkWithCreation := network.NetworkingConfig{
|
||||
EndpointsConfig: make(map[string]*network.EndpointSettings),
|
||||
}
|
||||
|
||||
// 4. disconnect all networks from the current container
|
||||
for name, network := range container.NetworkSettings.Networks {
|
||||
// This allows new container to use the same IP address if specified
|
||||
err = cli.NetworkDisconnect(ctx, network.NetworkID, containerId, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "disconnect network from old container error")
|
||||
}
|
||||
|
||||
// 5. get the first network attached to the current container
|
||||
if len(networkWithCreation.EndpointsConfig) == 0 {
|
||||
// Retrieve the first network that is linked to the present container, which
|
||||
// will be utilized when creating the container.
|
||||
networkWithCreation.EndpointsConfig[name] = network
|
||||
}
|
||||
}
|
||||
c.sr.enable()
|
||||
defer c.sr.close()
|
||||
defer c.sr.restore()
|
||||
|
||||
c.sr.push(func() {
|
||||
log.Debug().Str("container_id", containerId).Str("container", container.Name).Msg("restoring the container")
|
||||
cli.ContainerRename(ctx, containerId, container.Name)
|
||||
for _, network := range container.NetworkSettings.Networks {
|
||||
cli.NetworkConnect(ctx, network.NetworkID, containerId, network)
|
||||
}
|
||||
cli.ContainerStart(ctx, containerId, types.ContainerStartOptions{})
|
||||
})
|
||||
|
||||
log.Debug().Str("container", strings.Split(container.Name, "/")[1]).Msg("starting to create a new container")
|
||||
|
||||
// 6. create a new container
|
||||
// when a container is created without a network, docker connected it by default to the
|
||||
// bridge network with a random IP, also it can only connect to one network on creation.
|
||||
// to retain the same network settings we have to connect on creation to one of the old
|
||||
// container's networks, and connect to the other networks after creation.
|
||||
// see: https://portainer.atlassian.net/browse/EE-5448
|
||||
create, err := cli.ContainerCreate(ctx, container.Config, container.HostConfig, &networkWithCreation, nil, container.Name)
|
||||
|
||||
c.sr.push(func() {
|
||||
log.Debug().Str("container_id", create.ID).Msg("removing the new container")
|
||||
cli.ContainerStop(ctx, create.ID, dockercontainer.StopOptions{})
|
||||
cli.ContainerRemove(ctx, create.ID, types.ContainerRemoveOptions{})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create container error")
|
||||
}
|
||||
|
||||
newContainerId := create.ID
|
||||
|
||||
// 7. connect to networks
|
||||
// docker can connect to only one network at creation, so we need to connect to networks after creation
|
||||
// see https://github.com/moby/moby/issues/17750
|
||||
log.Debug().Str("container_id", newContainerId).Msg("connecting networks to container")
|
||||
networks := container.NetworkSettings.Networks
|
||||
for key, network := range networks {
|
||||
_, ok := networkWithCreation.EndpointsConfig[key]
|
||||
if ok {
|
||||
// skip the network that is used during container creation
|
||||
continue
|
||||
}
|
||||
|
||||
err = cli.NetworkConnect(ctx, network.NetworkID, newContainerId, network)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "connect container network error")
|
||||
}
|
||||
}
|
||||
|
||||
// 8. start the new container
|
||||
log.Debug().Str("container_id", newContainerId).Msg("starting the new container")
|
||||
err = cli.ContainerStart(ctx, newContainerId, types.ContainerStartOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "start container error")
|
||||
}
|
||||
|
||||
// 9. delete the old container
|
||||
log.Debug().Str("container_id", containerId).Msg("starting to remove the old container")
|
||||
_ = cli.ContainerRemove(ctx, containerId, types.ContainerRemoveOptions{})
|
||||
|
||||
c.sr.disable()
|
||||
|
||||
newContainer, _, err := cli.ContainerInspectWithRaw(ctx, newContainerId, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetch container information error")
|
||||
}
|
||||
|
||||
return &newContainer, nil
|
||||
}
|
||||
|
||||
type serviceRestore struct {
|
||||
restoreC chan struct{}
|
||||
fs []func()
|
||||
}
|
||||
|
||||
func (sr *serviceRestore) enable() {
|
||||
sr.restoreC = make(chan struct{}, 1)
|
||||
sr.fs = make([]func(), 0)
|
||||
sr.restoreC <- struct{}{}
|
||||
}
|
||||
|
||||
func (sr *serviceRestore) disable() {
|
||||
select {
|
||||
case <-sr.restoreC:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (sr *serviceRestore) push(f func()) {
|
||||
sr.fs = append(sr.fs, f)
|
||||
}
|
||||
|
||||
func (sr *serviceRestore) restore() {
|
||||
select {
|
||||
case <-sr.restoreC:
|
||||
l := len(sr.fs)
|
||||
if l > 0 {
|
||||
for i := l - 1; i >= 0; i-- {
|
||||
sr.fs[i]()
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (sr *serviceRestore) close() {
|
||||
if sr == nil || sr.restoreC == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-sr.restoreC:
|
||||
default:
|
||||
}
|
||||
|
||||
close(sr.restoreC)
|
||||
}
|
||||
184
api/docker/images/digest.go
Normal file
184
api/docker/images/digest.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
|
||||
"github.com/containers/image/v5/docker"
|
||||
imagetypes "github.com/containers/image/v5/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Options holds docker registry object options
|
||||
type Options struct {
|
||||
Auth imagetypes.DockerAuthConfig
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type DigestClient struct {
|
||||
clientFactory *dockerclient.ClientFactory
|
||||
opts Options
|
||||
sysCtx *imagetypes.SystemContext
|
||||
registryClient *RegistryClient
|
||||
}
|
||||
|
||||
func NewClientWithRegistry(registryClient *RegistryClient, clientFactory *dockerclient.ClientFactory) *DigestClient {
|
||||
return &DigestClient{
|
||||
clientFactory: clientFactory,
|
||||
registryClient: registryClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DigestClient) RemoteDigest(image Image) (digest.Digest, error) {
|
||||
ctx, cancel := c.timeoutContext()
|
||||
defer cancel()
|
||||
// Docker references with both a tag and digest are currently not supported
|
||||
if image.Tag != "" && image.Digest != "" {
|
||||
err := image.trimDigest()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
rmRef, err := ParseReference(image.String())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Cannot parse the image reference")
|
||||
}
|
||||
|
||||
sysCtx := c.sysCtx
|
||||
if c.registryClient != nil {
|
||||
username, password, err := c.registryClient.RegistryAuth(image)
|
||||
if err != nil {
|
||||
log.Info().Str("image up to date indicator", image.String()).Msg("No environment registry credentials found, using anonymous access")
|
||||
} else {
|
||||
sysCtx = &imagetypes.SystemContext{
|
||||
DockerAuthConfig: &imagetypes.DockerAuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve remote digest through HEAD request
|
||||
rmDigest, err := docker.GetDigest(ctx, sysCtx, rmRef)
|
||||
if err != nil {
|
||||
// fallback to public registry for hub
|
||||
if image.HubLink != "" {
|
||||
rmDigest, err = docker.GetDigest(ctx, c.sysCtx, rmRef)
|
||||
if err == nil {
|
||||
return rmDigest, nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Err(err).Msg("get remote digest error")
|
||||
|
||||
return "", errors.Wrap(err, "Cannot get image digest from HEAD request")
|
||||
}
|
||||
|
||||
return rmDigest, nil
|
||||
}
|
||||
|
||||
func ParseLocalImage(inspect types.ImageInspect) (*Image, error) {
|
||||
if IsLocalImage(inspect) || IsDanglingImage(inspect) {
|
||||
return nil, errors.New("the image is not regular")
|
||||
}
|
||||
|
||||
fromRepoDigests, err := ParseImage(ParseImageOptions{
|
||||
// including image name but no tag
|
||||
Name: inspect.RepoDigests[0],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if IsNoTagImage(inspect) {
|
||||
return &fromRepoDigests, nil
|
||||
}
|
||||
|
||||
fromRepoTags, err := ParseImage(ParseImageOptions{
|
||||
Name: inspect.RepoTags[0],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fromRepoDigests.Tag = fromRepoTags.Tag
|
||||
|
||||
return &fromRepoDigests, nil
|
||||
}
|
||||
|
||||
func ParseRepoDigests(repoDigests []string) []digest.Digest {
|
||||
digests := make([]digest.Digest, 0)
|
||||
for _, repoDigest := range repoDigests {
|
||||
d := ParseRepoDigest(repoDigest)
|
||||
if d == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
digests = append(digests, d)
|
||||
}
|
||||
|
||||
return digests
|
||||
}
|
||||
|
||||
func ParseRepoTags(repoTags []string) []*Image {
|
||||
images := make([]*Image, 0)
|
||||
for _, repoTag := range repoTags {
|
||||
image := ParseRepoTag(repoTag)
|
||||
if image != nil {
|
||||
images = append(images, image)
|
||||
}
|
||||
}
|
||||
|
||||
return images
|
||||
}
|
||||
|
||||
func ParseRepoDigest(repoDigest string) digest.Digest {
|
||||
if !strings.ContainsAny(repoDigest, "@") {
|
||||
return ""
|
||||
}
|
||||
|
||||
d, err := digest.Parse(strings.Split(repoDigest, "@")[1])
|
||||
if err != nil {
|
||||
log.Warn().Msgf("Skip invalid repo digest item: %s [error: %v]", repoDigest, err)
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func ParseRepoTag(repoTag string) *Image {
|
||||
if repoTag == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: repoTag,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("repoTag", repoTag).Msg("RepoTag cannot be parsed.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return &image
|
||||
}
|
||||
|
||||
func (c *DigestClient) timeoutContext() (context.Context, context.CancelFunc) {
|
||||
ctx := context.Background()
|
||||
var cancel context.CancelFunc = func() {}
|
||||
|
||||
if c.opts.Timeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(ctx, c.opts.Timeout)
|
||||
}
|
||||
|
||||
return ctx, cancel
|
||||
}
|
||||
182
api/docker/images/image.go
Normal file
182
api/docker/images/image.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ImageID string
|
||||
|
||||
// Image holds information about an image.
|
||||
type Image struct {
|
||||
// Domain is the registry host of this image
|
||||
Domain string
|
||||
// Path may include username like portainer/portainer-ee, no Tag or Digest
|
||||
Path string
|
||||
Tag string
|
||||
Digest digest.Digest
|
||||
HubLink string
|
||||
named reference.Named
|
||||
opts ParseImageOptions
|
||||
}
|
||||
|
||||
// ParseImageOptions holds image options for parsing.
|
||||
type ParseImageOptions struct {
|
||||
Name string
|
||||
HubTpl string
|
||||
}
|
||||
|
||||
// Name returns the full name representation of an image but no Tag or Digest.
|
||||
func (i *Image) Name() string {
|
||||
return i.named.Name()
|
||||
}
|
||||
|
||||
// FullName return the real full name may include Tag or Digest of the image, Tag first.
|
||||
func (i *Image) FullName() string {
|
||||
if i.Tag == "" {
|
||||
return fmt.Sprintf("%s@%s", i.Name(), i.Digest)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", i.Name(), i.Tag)
|
||||
}
|
||||
|
||||
// String returns the string representation of an image, including Tag and Digest if existed.
|
||||
func (i *Image) String() string {
|
||||
return i.named.String()
|
||||
}
|
||||
|
||||
// Reference returns either the digest if it is non-empty or the tag for the image.
|
||||
func (i *Image) Reference() string {
|
||||
if len(i.Digest.String()) > 1 {
|
||||
return i.Digest.String()
|
||||
}
|
||||
|
||||
return i.Tag
|
||||
}
|
||||
|
||||
// WithDigest sets the digest for an image.
|
||||
func (i *Image) WithDigest(digest digest.Digest) (err error) {
|
||||
i.Digest = digest
|
||||
i.named, err = reference.WithDigest(i.named, digest)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *Image) WithTag(tag string) (err error) {
|
||||
i.Tag = tag
|
||||
i.named, err = reference.WithTag(i.named, tag)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *Image) trimDigest() error {
|
||||
i.Digest = ""
|
||||
named, err := ParseImage(ParseImageOptions{Name: i.FullName()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.named = &named
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseImage returns an Image struct with all the values filled in for a given image.
|
||||
func ParseImage(parseOpts ParseImageOptions) (Image, error) {
|
||||
// Parse the image name and tag.
|
||||
named, err := reference.ParseNormalizedNamed(parseOpts.Name)
|
||||
if err != nil {
|
||||
return Image{}, errors.Wrapf(err, "parsing image %s failed", parseOpts.Name)
|
||||
}
|
||||
// Add the latest lag if they did not provide one.
|
||||
named = reference.TagNameOnly(named)
|
||||
|
||||
i := Image{
|
||||
opts: parseOpts,
|
||||
named: named,
|
||||
Domain: reference.Domain(named),
|
||||
Path: reference.Path(named),
|
||||
}
|
||||
|
||||
// Hub link
|
||||
i.HubLink, err = i.hubLink()
|
||||
if err != nil {
|
||||
return Image{}, errors.Wrap(err, fmt.Sprintf("resolving hub link for image %s failed", parseOpts.Name))
|
||||
}
|
||||
|
||||
// Add the tag if there was one.
|
||||
if tagged, ok := named.(reference.Tagged); ok {
|
||||
i.Tag = tagged.Tag()
|
||||
}
|
||||
|
||||
// Add the digest if there was one.
|
||||
if canonical, ok := named.(reference.Canonical); ok {
|
||||
i.Digest = canonical.Digest()
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (i *Image) hubLink() (string, error) {
|
||||
if i.opts.HubTpl != "" {
|
||||
var out bytes.Buffer
|
||||
tmpl, err := template.New("tmpl").
|
||||
Option("missingkey=error").
|
||||
Parse(i.opts.HubTpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = tmpl.Execute(&out, i)
|
||||
return out.String(), err
|
||||
}
|
||||
|
||||
switch i.Domain {
|
||||
case "docker.io":
|
||||
prefix := "r"
|
||||
path := i.Path
|
||||
if strings.HasPrefix(i.Path, "library/") {
|
||||
prefix = "_"
|
||||
path = strings.Replace(i.Path, "library/", "", 1)
|
||||
}
|
||||
return fmt.Sprintf("https://hub.docker.com/%s/%s", prefix, path), nil
|
||||
case "docker.bintray.io", "jfrog-docker-reg2.bintray.io":
|
||||
return fmt.Sprintf("https://bintray.com/jfrog/reg2/%s", strings.ReplaceAll(i.Path, "/", "%3A")), nil
|
||||
case "docker.pkg.github.com":
|
||||
return fmt.Sprintf("https://github.com/%s/packages", filepath.ToSlash(filepath.Dir(i.Path))), nil
|
||||
case "gcr.io":
|
||||
return fmt.Sprintf("https://%s/%s", i.Domain, i.Path), nil
|
||||
case "ghcr.io":
|
||||
ref := strings.Split(i.Path, "/")
|
||||
ghUser, ghPackage := ref[0], ref[1]
|
||||
return fmt.Sprintf("https://github.com/users/%s/packages/container/package/%s", ghUser, ghPackage), nil
|
||||
case "quay.io":
|
||||
return fmt.Sprintf("https://quay.io/repository/%s", i.Path), nil
|
||||
case "registry.access.redhat.com":
|
||||
return fmt.Sprintf("https://access.redhat.com/containers/#/registry.access.redhat.com/%s", i.Path), nil
|
||||
case "registry.gitlab.com":
|
||||
return fmt.Sprintf("https://gitlab.com/%s/container_registry", i.Path), nil
|
||||
default:
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsLocalImage checks if the image has been built locally
|
||||
func IsLocalImage(image types.ImageInspect) bool {
|
||||
return len(image.RepoDigests) == 0
|
||||
}
|
||||
|
||||
// IsDanglingImage returns whether the given image is "dangling" which means
|
||||
// that there are no repository references to the given image and it has no
|
||||
// child images
|
||||
func IsDanglingImage(image types.ImageInspect) bool {
|
||||
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
|
||||
}
|
||||
|
||||
// IsNoTagImage returns whether the given image is damaged, has no tags
|
||||
func IsNoTagImage(image types.ImageInspect) bool {
|
||||
return len(image.RepoTags) == 0
|
||||
}
|
||||
119
api/docker/images/image_test.go
Normal file
119
api/docker/images/image_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestImageParser(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
// portainer/portainer-ee
|
||||
t.Run("", func(t *testing.T) {
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: "portainer/portainer-ee",
|
||||
})
|
||||
is.NoError(err, "")
|
||||
is.Equal("docker.io/portainer/portainer-ee:latest", image.FullName())
|
||||
is.Equal("portainer/portainer-ee", image.opts.Name)
|
||||
is.Equal("latest", image.Tag)
|
||||
is.Equal("portainer/portainer-ee", image.Path)
|
||||
is.Equal("docker.io", image.Domain)
|
||||
is.Equal("docker.io/portainer/portainer-ee", image.Name())
|
||||
is.Equal("latest", image.Reference())
|
||||
is.Equal("docker.io/portainer/portainer-ee:latest", image.String())
|
||||
|
||||
})
|
||||
// gcr.io/k8s-minikube/kicbase@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2
|
||||
t.Run("", func(t *testing.T) {
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: "gcr.io/k8s-minikube/kicbase@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2",
|
||||
})
|
||||
is.NoError(err, "")
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.FullName())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.opts.Name)
|
||||
is.Equal("", image.Tag)
|
||||
is.Equal("k8s-minikube/kicbase", image.Path)
|
||||
is.Equal("gcr.io", image.Domain)
|
||||
is.Equal("https://gcr.io/k8s-minikube/kicbase", image.HubLink)
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase", image.Name())
|
||||
is.Equal("sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.Reference())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.String())
|
||||
})
|
||||
|
||||
// gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2
|
||||
t.Run("", func(t *testing.T) {
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: "gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2",
|
||||
})
|
||||
is.NoError(err, "")
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30", image.FullName())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.opts.Name)
|
||||
is.Equal("v0.0.30", image.Tag)
|
||||
is.Equal("k8s-minikube/kicbase", image.Path)
|
||||
is.Equal("gcr.io", image.Domain)
|
||||
is.Equal("https://gcr.io/k8s-minikube/kicbase", image.HubLink)
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase", image.Name())
|
||||
is.Equal("sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.Reference())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateParsedImage(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
// gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2
|
||||
t.Run("", func(t *testing.T) {
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: "gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2",
|
||||
})
|
||||
is.NoError(err, "")
|
||||
_ = image.WithTag("v0.0.31")
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.31", image.FullName())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.opts.Name)
|
||||
is.Equal("v0.0.31", image.Tag)
|
||||
is.Equal("k8s-minikube/kicbase", image.Path)
|
||||
is.Equal("gcr.io", image.Domain)
|
||||
is.Equal("https://gcr.io/k8s-minikube/kicbase", image.HubLink)
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase", image.Name())
|
||||
is.Equal("sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.Reference())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.31@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.String())
|
||||
})
|
||||
|
||||
// gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2
|
||||
t.Run("", func(t *testing.T) {
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: "gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2",
|
||||
})
|
||||
is.NoError(err, "")
|
||||
_ = image.WithDigest("sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b3")
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30", image.FullName())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.opts.Name)
|
||||
is.Equal("v0.0.30", image.Tag)
|
||||
is.Equal("k8s-minikube/kicbase", image.Path)
|
||||
is.Equal("gcr.io", image.Domain)
|
||||
is.Equal("https://gcr.io/k8s-minikube/kicbase", image.HubLink)
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase", image.Name())
|
||||
is.Equal("sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b3", image.Reference())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b3", image.String())
|
||||
})
|
||||
|
||||
// gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2
|
||||
t.Run("", func(t *testing.T) {
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: "gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2",
|
||||
})
|
||||
is.NoError(err, "")
|
||||
_ = image.trimDigest()
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30", image.FullName())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30@sha256:02c921df998f95e849058af14de7045efc3954d90320967418a0d1f182bbc0b2", image.opts.Name)
|
||||
is.Equal("v0.0.30", image.Tag)
|
||||
is.Equal("k8s-minikube/kicbase", image.Path)
|
||||
is.Equal("gcr.io", image.Domain)
|
||||
is.Equal("https://gcr.io/k8s-minikube/kicbase", image.HubLink)
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase", image.Name())
|
||||
is.Equal("v0.0.30", image.Reference())
|
||||
is.Equal("gcr.io/k8s-minikube/kicbase:v0.0.30", image.String())
|
||||
})
|
||||
}
|
||||
50
api/docker/images/puller.go
Normal file
50
api/docker/images/puller.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Puller struct {
|
||||
client *client.Client
|
||||
registryClient *RegistryClient
|
||||
dataStore dataservices.DataStore
|
||||
}
|
||||
|
||||
func NewPuller(client *client.Client, registryClient *RegistryClient, dataStore dataservices.DataStore) *Puller {
|
||||
return &Puller{
|
||||
client: client,
|
||||
registryClient: registryClient,
|
||||
dataStore: dataStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (puller *Puller) Pull(ctx context.Context, image Image) error {
|
||||
log.Debug().Str("image", image.FullName()).Msg("starting to pull the image")
|
||||
|
||||
registryAuth, err := puller.registryClient.EncodedRegistryAuth(image)
|
||||
if err != nil {
|
||||
log.Debug().
|
||||
Str("image", image.FullName()).
|
||||
Err(err).
|
||||
Msg("failed to get an encoded registry auth via image, try to pull image without registry auth")
|
||||
}
|
||||
|
||||
out, err := puller.client.ImagePull(ctx, image.FullName(), types.ImagePullOptions{
|
||||
RegistryAuth: registryAuth,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.ReadAll(out)
|
||||
|
||||
return err
|
||||
}
|
||||
16
api/docker/images/ref.go
Normal file
16
api/docker/images/ref.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
func ParseReference(imageStr string) (types.ImageReference, error) {
|
||||
if !strings.HasPrefix(imageStr, "//") {
|
||||
imageStr = fmt.Sprintf("//%s", imageStr)
|
||||
}
|
||||
return docker.ParseReference(imageStr)
|
||||
}
|
||||
147
api/docker/images/registry.go
Normal file
147
api/docker/images/registry.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/registryutils"
|
||||
)
|
||||
|
||||
var (
|
||||
_registriesCache = cache.New(5*time.Minute, 5*time.Minute)
|
||||
)
|
||||
|
||||
type (
|
||||
RegistryClient struct {
|
||||
dataStore dataservices.DataStore
|
||||
}
|
||||
)
|
||||
|
||||
func NewRegistryClient(dataStore dataservices.DataStore) *RegistryClient {
|
||||
return &RegistryClient{dataStore: dataStore}
|
||||
}
|
||||
|
||||
func (c *RegistryClient) RegistryAuth(image Image) (string, string, error) {
|
||||
registries, err := c.dataStore.Registry().Registries()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
registry, err := findBestMatchRegistry(image.opts.Name, registries)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if !registry.Authentication {
|
||||
return "", "", errors.New("authentication is disabled")
|
||||
}
|
||||
|
||||
return c.CertainRegistryAuth(registry)
|
||||
}
|
||||
|
||||
func (c *RegistryClient) CertainRegistryAuth(registry *portainer.Registry) (string, string, error) {
|
||||
err := registryutils.EnsureRegTokenValid(c.dataStore, registry)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if !registry.Authentication {
|
||||
return "", "", errors.New("authentication is disabled")
|
||||
}
|
||||
|
||||
return registryutils.GetRegEffectiveCredential(registry)
|
||||
}
|
||||
|
||||
func (c *RegistryClient) EncodedRegistryAuth(image Image) (string, error) {
|
||||
registries, err := c.dataStore.Registry().Registries()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
registry, err := findBestMatchRegistry(image.opts.Name, registries)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !registry.Authentication {
|
||||
return "", errors.New("authentication is disabled")
|
||||
}
|
||||
|
||||
return c.EncodedCertainRegistryAuth(registry)
|
||||
}
|
||||
|
||||
func (c *RegistryClient) EncodedCertainRegistryAuth(registry *portainer.Registry) (string, error) {
|
||||
err := registryutils.EnsureRegTokenValid(c.dataStore, registry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return registryutils.GetRegistryAuthHeader(registry)
|
||||
}
|
||||
|
||||
// findBestMatchRegistry finds out the best match registry for repository @Meng
|
||||
// matching precedence:
|
||||
// 1. both domain name and username matched (for dockerhub only)
|
||||
// 2. only URL matched
|
||||
// 3. pick up the first dockerhub registry
|
||||
func findBestMatchRegistry(repository string, registries []portainer.Registry) (*portainer.Registry, error) {
|
||||
cachedRegistry, err := cachedRegistry(repository)
|
||||
if err == nil {
|
||||
return cachedRegistry, nil
|
||||
}
|
||||
|
||||
var match1, match2, match3 *portainer.Registry
|
||||
for i := 0; i < len(registries); i++ {
|
||||
registry := registries[i]
|
||||
if registry.Type == portainer.DockerHubRegistry {
|
||||
|
||||
// try to match repository examples:
|
||||
// <USERNAME>/nginx:latest
|
||||
// docker.io/<USERNAME>/nginx:latest
|
||||
if strings.HasPrefix(repository, registry.Username+"/") || strings.HasPrefix(repository, registry.URL+"/"+registry.Username+"/") {
|
||||
match1 = ®istry
|
||||
}
|
||||
|
||||
// try to match repository examples:
|
||||
// portainer/portainer-ee:latest
|
||||
// <NON-USERNAME>/portainer-ee:latest
|
||||
if match3 == nil {
|
||||
match3 = ®istry
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(repository, registry.URL) {
|
||||
match2 = ®istry
|
||||
}
|
||||
}
|
||||
|
||||
match := match1
|
||||
if match == nil {
|
||||
match = match2
|
||||
}
|
||||
if match == nil {
|
||||
match = match3
|
||||
}
|
||||
|
||||
if match == nil {
|
||||
return nil, errors.New("no registries matched")
|
||||
}
|
||||
_registriesCache.Set(repository, match, 0)
|
||||
return match, nil
|
||||
}
|
||||
|
||||
func cachedRegistry(cacheKey string) (*portainer.Registry, error) {
|
||||
r, ok := _registriesCache.Get(cacheKey)
|
||||
if ok {
|
||||
registry, ok := r.(portainer.Registry)
|
||||
if ok {
|
||||
return ®istry, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("no registry found in cache: %s", cacheKey)
|
||||
}
|
||||
69
api/docker/images/registry_test.go
Normal file
69
api/docker/images/registry_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFindBestMatchNeedAuthRegistry(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
image := "USERNAME/nginx:latest"
|
||||
registries := []portainer.Registry{createNewRegistry("docker.io", "USERNAME", false),
|
||||
createNewRegistry("hub-mirror.c.163.com", "", false)}
|
||||
r, err := findBestMatchRegistry(image, registries)
|
||||
is.NoError(err, "")
|
||||
is.NotNil(r, "")
|
||||
is.False(r.Authentication, "")
|
||||
is.Equal("docker.io", r.URL)
|
||||
})
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
image := "USERNAME/nginx:latest"
|
||||
registries := []portainer.Registry{createNewRegistry("docker.io", "", false),
|
||||
createNewRegistry("hub-mirror.c.163.com", "USERNAME", false)}
|
||||
r, err := findBestMatchRegistry(image, registries)
|
||||
is.NoError(err, "")
|
||||
is.NotNil(r, "")
|
||||
is.False(r.Authentication, "")
|
||||
is.Equal("docker.io", r.URL)
|
||||
})
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
image := "docker.io/<USERNAME>/nginx:latest"
|
||||
registries := []portainer.Registry{createNewRegistry("docker.io", "USERNAME", true),
|
||||
createNewRegistry("hub-mirror.c.163.com", "", false)}
|
||||
r, err := findBestMatchRegistry(image, registries)
|
||||
is.NoError(err, "")
|
||||
is.NotNil(r, "")
|
||||
is.True(r.Authentication, "")
|
||||
is.Equal("docker.io", r.URL)
|
||||
})
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
image := "portainer/portainer-ee:latest"
|
||||
registries := []portainer.Registry{createNewRegistry("docker.io", "", true)}
|
||||
r, err := findBestMatchRegistry(image, registries)
|
||||
is.NoError(err, "")
|
||||
is.NotNil(r, "")
|
||||
is.True(r.Authentication, "")
|
||||
is.Equal("docker.io", r.URL)
|
||||
})
|
||||
}
|
||||
|
||||
func createNewRegistry(domain, username string, auth bool) portainer.Registry {
|
||||
registry := portainer.Registry{
|
||||
URL: domain,
|
||||
Authentication: auth,
|
||||
Username: username,
|
||||
}
|
||||
|
||||
if domain == "docker.io" {
|
||||
registry.Type = portainer.DockerHubRegistry
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
302
api/docker/images/status.go
Normal file
302
api/docker/images/status.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/patrickmn/go-cache"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
consts "github.com/portainer/portainer/api/docker/consts"
|
||||
"github.com/portainer/portainer/api/internal/slices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Status constants
|
||||
const (
|
||||
Processing = Status("processing")
|
||||
Outdated = Status("outdated")
|
||||
Updated = Status("updated")
|
||||
Skipped = Status("skipped")
|
||||
Preparing = Status("preparing")
|
||||
Error = Status("error")
|
||||
)
|
||||
|
||||
var (
|
||||
statusCache = cache.New(24*time.Hour, 24*time.Hour)
|
||||
remoteDigestCache = cache.New(5*time.Second, 5*time.Second)
|
||||
swarmID2NameCache = cache.New(5*time.Second, 5*time.Second)
|
||||
)
|
||||
|
||||
// Status holds Docker image analysis
|
||||
type Status string
|
||||
|
||||
func (c *DigestClient) ContainersImageStatus(ctx context.Context, containers []types.Container, endpoint *portainer.Endpoint) Status {
|
||||
cli, err := c.clientFactory.CreateClient(endpoint, "", nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot create docker client")
|
||||
return Error
|
||||
}
|
||||
|
||||
statuses := make([]Status, len(containers))
|
||||
for i, ct := range containers {
|
||||
var nodeName string
|
||||
if swarmNodeId := ct.Labels[consts.SwarmNodeIdLabel]; swarmNodeId != "" {
|
||||
if swarmNodeName, ok := swarmID2NameCache.Get(swarmNodeId); ok {
|
||||
nodeName, _ = swarmNodeName.(string)
|
||||
} else {
|
||||
node, _, err := cli.NodeInspectWithRaw(ctx, ct.Labels[consts.SwarmNodeIdLabel])
|
||||
if err != nil {
|
||||
return Error
|
||||
}
|
||||
|
||||
nodeName = node.Description.Hostname
|
||||
swarmID2NameCache.Set(swarmNodeId, nodeName, 0)
|
||||
}
|
||||
}
|
||||
|
||||
s, err := c.ContainerImageStatus(ctx, ct.ID, endpoint, nodeName)
|
||||
if err != nil {
|
||||
statuses[i] = Error
|
||||
log.Warn().Str("containerId", ct.ID).Err(err).Msg("error when fetching image status for container")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
statuses[i] = s
|
||||
|
||||
if s == Outdated || s == Processing {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return FigureOut(statuses)
|
||||
}
|
||||
|
||||
func FigureOut(statuses []Status) Status {
|
||||
if allMatch(statuses, Skipped) {
|
||||
return Skipped
|
||||
}
|
||||
|
||||
if allMatch(statuses, Preparing) {
|
||||
return Preparing
|
||||
}
|
||||
|
||||
if contains(statuses, Outdated) {
|
||||
return Outdated
|
||||
} else if contains(statuses, Processing) {
|
||||
return Processing
|
||||
} else if contains(statuses, Error) {
|
||||
return Error
|
||||
}
|
||||
|
||||
return Updated
|
||||
}
|
||||
|
||||
func (c *DigestClient) ContainerImageStatus(ctx context.Context, containerID string, endpoint *portainer.Endpoint, nodeName string) (Status, error) {
|
||||
cli, err := c.clientFactory.CreateClient(endpoint, nodeName, nil)
|
||||
if err != nil {
|
||||
log.Warn().Str("swarmNodeId", nodeName).Msg("Cannot create new docker client.")
|
||||
}
|
||||
|
||||
container, err := cli.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("containerID", containerID).Msg("Inspect container error.")
|
||||
return Skipped, nil
|
||||
}
|
||||
|
||||
var imageID string
|
||||
if strings.Contains(container.Image, "sha256") {
|
||||
imageID = container.Image[strings.Index(container.Image, "sha256"):]
|
||||
}
|
||||
|
||||
if imageID == "" {
|
||||
return Skipped, nil
|
||||
}
|
||||
|
||||
digs := make([]digest.Digest, 0)
|
||||
images := make([]*Image, 0)
|
||||
if i, err := ParseImage(ParseImageOptions{Name: container.Config.Image}); err == nil {
|
||||
images = append(images, &i)
|
||||
}
|
||||
|
||||
imageInspect, _, err := cli.ImageInspectWithRaw(ctx, imageID)
|
||||
if err != nil {
|
||||
log.Debug().Str("imageID", imageID).Msg("inspect failed")
|
||||
return Error, err
|
||||
}
|
||||
|
||||
if len(imageInspect.RepoDigests) > 0 {
|
||||
digs = append(digs, ParseRepoDigests(imageInspect.RepoDigests)...)
|
||||
}
|
||||
|
||||
if len(imageInspect.RepoTags) > 0 {
|
||||
images = append(images, ParseRepoTags(imageInspect.RepoTags)...)
|
||||
}
|
||||
|
||||
s, err := c.checkStatus(images, digs)
|
||||
if err != nil {
|
||||
log.Debug().Str("image", container.Image).Err(err).Msg("fetching a certain image status")
|
||||
return Error, err
|
||||
}
|
||||
|
||||
statusCache.Set(imageID, s, 0)
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (c *DigestClient) ServiceImageStatus(ctx context.Context, serviceID string, endpoint *portainer.Endpoint) (Status, error) {
|
||||
cli, err := c.clientFactory.CreateClient(endpoint, "", nil)
|
||||
if err != nil {
|
||||
return Error, nil
|
||||
}
|
||||
|
||||
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(filters.Arg("label", consts.SwarmServiceIdLabel+"="+serviceID)),
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("serviceID", serviceID).Msg("cannot list container for the service")
|
||||
return Error, err
|
||||
}
|
||||
|
||||
nonExistedOrStoppedContainers := make([]types.Container, 0)
|
||||
for _, container := range containers {
|
||||
if container.State == "exited" || container.State == "stopped" {
|
||||
continue
|
||||
}
|
||||
|
||||
// When there is a container with the state "Created" under the service, it
|
||||
// indicates that the Docker Swarm is replacing the existing task with
|
||||
// a new task. At the moment, the state of the new task is "Created", and
|
||||
// the state of the old task is "Running".
|
||||
// Until the new task runs up, the image status should be set "Preparing"
|
||||
if container.State == "created" {
|
||||
return Preparing, nil
|
||||
}
|
||||
nonExistedOrStoppedContainers = append(nonExistedOrStoppedContainers, container)
|
||||
}
|
||||
|
||||
if len(nonExistedOrStoppedContainers) == 0 {
|
||||
return Preparing, nil
|
||||
}
|
||||
|
||||
return c.ContainersImageStatus(ctx, nonExistedOrStoppedContainers, endpoint), nil
|
||||
}
|
||||
|
||||
func (c *DigestClient) checkStatus(images []*Image, digests []digest.Digest) (Status, error) {
|
||||
if digests == nil {
|
||||
digests = make([]digest.Digest, 0)
|
||||
}
|
||||
|
||||
for _, img := range images {
|
||||
if img.Digest != "" && !slices.Contains(digests, img.Digest) {
|
||||
log.Info().Str("localDigest", img.Domain).Msg("incoming local digest is not nil")
|
||||
digests = append([]digest.Digest{img.Digest}, digests...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(digests) == 0 {
|
||||
return Skipped, nil
|
||||
}
|
||||
|
||||
var imageStatus Status
|
||||
|
||||
for _, img := range images {
|
||||
var remoteDigest digest.Digest
|
||||
var err error
|
||||
if rd, ok := remoteDigestCache.Get(img.FullName()); ok {
|
||||
remoteDigest, _ = rd.(digest.Digest)
|
||||
}
|
||||
if remoteDigest == "" {
|
||||
remoteDigest, err = c.RemoteDigest(*img)
|
||||
if err != nil {
|
||||
log.Error().Str("image", img.String()).Msg("error when fetch remote digest for image")
|
||||
return Error, err
|
||||
}
|
||||
}
|
||||
remoteDigestCache.Set(img.FullName(), remoteDigest, 0)
|
||||
|
||||
log.Debug().Str("image", img.FullName()).Stringer("remote_digest", remoteDigest).
|
||||
Int("local_digest_size", len(digests)).
|
||||
Msg("Digests")
|
||||
|
||||
// final locals vs remote one
|
||||
for _, dig := range digests {
|
||||
log.Debug().
|
||||
Str("image", img.FullName()).
|
||||
Stringer("remote_digest", remoteDigest).
|
||||
Stringer("local_digest", dig).
|
||||
Msg("Comparing")
|
||||
|
||||
if dig == remoteDigest {
|
||||
log.Debug().Str("image", img.FullName()).
|
||||
Stringer("remote_digest", remoteDigest).
|
||||
Stringer("local_digest", dig).
|
||||
Msg("Found a match")
|
||||
return Updated, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imageStatus = Outdated
|
||||
|
||||
return imageStatus, nil
|
||||
}
|
||||
|
||||
func CachedResourceImageStatus(resourceID string) (Status, error) {
|
||||
if s, ok := statusCache.Get(resourceID); ok {
|
||||
return s.(Status), nil
|
||||
}
|
||||
|
||||
return "", errors.Errorf("no image found in cache: %s", resourceID)
|
||||
}
|
||||
|
||||
func CacheResourceImageStatus(resourceID string, status Status) {
|
||||
statusCache.Set(resourceID, status, 0)
|
||||
}
|
||||
|
||||
func CachedImageDigest(resourceID string) (Status, error) {
|
||||
if s, ok := statusCache.Get(resourceID); ok {
|
||||
return s.(Status), nil
|
||||
}
|
||||
|
||||
return "", errors.Errorf("no image found in cache: %s", resourceID)
|
||||
}
|
||||
|
||||
func EvictImageStatus(resourceID string) {
|
||||
statusCache.Delete(resourceID)
|
||||
}
|
||||
|
||||
func contains(statuses []Status, status Status) bool {
|
||||
if len(statuses) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, s := range statuses {
|
||||
if s == status {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func allMatch(statuses []Status, status Status) bool {
|
||||
if len(statuses) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, s := range statuses {
|
||||
if s != status {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package docker
|
||||
|
||||
const (
|
||||
ComposeStackNameLabel = "com.docker.compose.project"
|
||||
SwarmStackNameLabel = "com.docker.stack.namespace"
|
||||
)
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/docker/consts"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
_container "github.com/docker/docker/api/types/container"
|
||||
@@ -16,11 +18,11 @@ import (
|
||||
|
||||
// Snapshotter represents a service used to create environment(endpoint) snapshots
|
||||
type Snapshotter struct {
|
||||
clientFactory *ClientFactory
|
||||
clientFactory *dockerclient.ClientFactory
|
||||
}
|
||||
|
||||
// NewSnapshotter returns a new Snapshotter instance
|
||||
func NewSnapshotter(clientFactory *ClientFactory) *Snapshotter {
|
||||
func NewSnapshotter(clientFactory *dockerclient.ClientFactory) *Snapshotter {
|
||||
return &Snapshotter{
|
||||
clientFactory: clientFactory,
|
||||
}
|
||||
@@ -201,7 +203,7 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
|
||||
}
|
||||
|
||||
for k, v := range container.Labels {
|
||||
if k == ComposeStackNameLabel {
|
||||
if k == consts.ComposeStackNameLabel {
|
||||
stacks[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
79
api/go.mod
79
api/go.mod
@@ -4,28 +4,29 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Microsoft/go-winio v0.5.1
|
||||
github.com/Microsoft/go-winio v0.6.0
|
||||
github.com/VictoriaMetrics/fastcache v1.12.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/aws/aws-sdk-go-v2 v1.11.1
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.6.2
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.10.1
|
||||
github.com/cbroglie/mustache v1.4.0
|
||||
github.com/containers/image/v5 v5.25.0
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
|
||||
github.com/docker/cli v20.10.9+incompatible
|
||||
github.com/docker/docker v20.10.16+incompatible
|
||||
github.com/docker/docker v23.0.3+incompatible
|
||||
github.com/fvbommel/sortorder v1.0.2
|
||||
github.com/fxamacker/cbor/v2 v2.3.0
|
||||
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
|
||||
github.com/go-git/go-git/v5 v5.3.0
|
||||
github.com/go-ldap/ldap/v3 v3.1.8
|
||||
github.com/go-playground/validator/v10 v10.10.1
|
||||
github.com/go-playground/validator/v10 v10.12.0
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
@@ -33,6 +34,7 @@ require (
|
||||
github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -44,12 +46,12 @@ require (
|
||||
github.com/portainer/portainer/third_party/digest v0.0.0-20221201002639-8fd0efa34f73
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rs/zerolog v1.29.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/viney-shih/go-lock v1.1.1
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/sync v0.1.0
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.26.1
|
||||
@@ -59,6 +61,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
|
||||
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
|
||||
@@ -67,10 +71,14 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1 // indirect
|
||||
github.com/aws/smithy-go v1.9.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
|
||||
github.com/containers/ocicrypt v1.1.7 // indirect
|
||||
github.com/containers/storage v1.46.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
@@ -82,16 +90,18 @@ require (
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -99,41 +109,50 @@ require (
|
||||
github.com/jpillora/requestlog v1.0.0 // indirect
|
||||
github.com/jpillora/sizestr v1.0.0 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/klauspost/compress v1.16.3 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6-0.20220930104621-17e8dac29df8 // indirect
|
||||
github.com/leodido/go-urn v1.2.2 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
golang.org/x/net v0.4.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
|
||||
|
||||
468
api/go.sum
468
api/go.sum
@@ -1,46 +1,15 @@
|
||||
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.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 v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
@@ -57,8 +26,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.11.1 h1:GzvOVAdTbWxhEMRK4FfiblkGverOkAT0UodDxC1jHQM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.11.1/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.6.2 h1:2faRNX8JgZVy7dDxERkaGBqb/xo5Rgmc8JMPL5j1o58=
|
||||
@@ -80,16 +49,27 @@ github.com/cbroglie/mustache v1.4.0/go.mod h1:SS1FTIghy0sjse4DUVGV1k/40B1qE1XkD9
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
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/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containers/image/v5 v5.25.0 h1:TJ0unmalbU+scd0i3Txap2wjGsAnv06MSCwgn6bsizk=
|
||||
github.com/containers/image/v5 v5.25.0/go.mod h1:EKvys0WVlRFkDw26R8y52TuhV9Tfn0yq2luLX6W52Ls=
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
|
||||
github.com/containers/ocicrypt v1.1.7 h1:thhNr4fu2ltyGz8aMx8u48Ae0Pnbip3ePP9/mzkZ/3U=
|
||||
github.com/containers/ocicrypt v1.1.7/go.mod h1:7CAhjcj2H8AYp5YvEie7oVSK2AhBY8NscCYRawuDNtw=
|
||||
github.com/containers/storage v1.46.0 h1:K3Tw/U+ZwmMT/tzX04mh5wnK2PuIdEGS2BGMP7ZYAqw=
|
||||
github.com/containers/storage v1.46.0/go.mod h1:AVNewDV1jODy8b4Ia4AgsJ6UFKQSIaOQvQ8S6N4VuH0=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -99,12 +79,17 @@ github.com/docker/cli v20.10.9+incompatible h1:OJ7YkwQA+k2Oi51lmCojpjiygKpi76P7b
|
||||
github.com/docker/cli v20.10.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v20.10.16+incompatible h1:2Db6ZR/+FUR3hqPMwnogOPHFn405crbpxvWzKovETOQ=
|
||||
github.com/docker/docker v20.10.16+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v23.0.2+incompatible h1:q81C2qQ/EhPm8COZMUGOQYh4qLv4Xu6CXELJ3WK/mlU=
|
||||
github.com/docker/docker v23.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho=
|
||||
github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
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-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
|
||||
@@ -120,6 +105,7 @@ github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
@@ -142,9 +128,6 @@ github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbK
|
||||
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||
github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc=
|
||||
github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ldap/ldap/v3 v3.1.8 h1:5vU/2jOh9HqprwXp8aF915s9p6Z8wmbSEVF7/gdTFhM=
|
||||
github.com/go-ldap/ldap/v3 v3.1.8/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -156,17 +139,17 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig=
|
||||
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
||||
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -174,45 +157,31 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/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/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -221,37 +190,29 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
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/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
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/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
|
||||
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
@@ -277,12 +238,14 @@ github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2yg
|
||||
github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
|
||||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/pgzip v1.2.6-0.20220930104621-17e8dac29df8 h1:BcxbplxjtczA1a6d3wYoa7a0WL3rq9DKBMGHeKyjEF0=
|
||||
github.com/klauspost/pgzip v1.2.6-0.20220930104621-17e8dac29df8/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c h1:N7A4JCA2G+j5fuFxCsJqjFU/sZe0mj8H0sSoSwbaikw=
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c/go.mod h1:Nn5wlyECw3iJrzi0AhIWg+AJUb4PlRQVW4/3XHH1LZA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -290,17 +253,16 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
|
||||
github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
@@ -308,14 +270,17 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/term v0.0.0-20221120202655-abb19827d345 h1:J9c53/kxIH+2nTKBEfZYFMlhghtHpIHSXpm5VRGHSnU=
|
||||
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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -323,6 +288,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
@@ -330,13 +296,19 @@ github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
|
||||
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
|
||||
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/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -357,22 +329,26 @@ github.com/portainer/portainer/third_party/digest v0.0.0-20221201002639-8fd0efa3
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
||||
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -382,309 +358,154 @@ 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.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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/viney-shih/go-lock v1.1.1 h1:SwzDPPAiHpcwGCr5k8xD15d2gQSo8d4roRYd7TDV2eI=
|
||||
github.com/viney-shih/go-lock v1.1.1/go.mod h1:Yijm78Ljteb3kRiJrbLAxVntkUukGu5uzSxq/xV7OO8=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
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.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/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-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
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/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
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/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
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-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-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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
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.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.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20181019160139-8e24a49d80f8/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-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-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-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-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/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/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-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-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-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-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
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/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
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-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-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
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-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-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
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-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -697,8 +518,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -707,9 +529,9 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -720,19 +542,12 @@ 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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
|
||||
k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
|
||||
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
|
||||
@@ -745,9 +560,6 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
|
||||
@@ -5,27 +5,35 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
dockerClientFactory *dockerclient.ClientFactory
|
||||
dataStore dataservices.DataStore
|
||||
containerService *docker.ContainerService
|
||||
bouncer *security.RequestBouncer
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to process non-proxied requests to docker APIs directly.
|
||||
func NewHandler(routePrefix string, bouncer *security.RequestBouncer, dockerClientFactory *docker.ClientFactory) *Handler {
|
||||
func NewHandler(routePrefix string, bouncer *security.RequestBouncer, dataStore dataservices.DataStore, dockerClientFactory *dockerclient.ClientFactory, containerService *docker.ContainerService) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
|
||||
Router: mux.NewRouter(),
|
||||
dataStore: dataStore,
|
||||
dockerClientFactory: dockerClientFactory,
|
||||
containerService: containerService,
|
||||
bouncer: bouncer,
|
||||
}
|
||||
|
||||
router := h.PathPrefix(routePrefix).Subrouter()
|
||||
router.Use(bouncer.AuthenticatedAccess)
|
||||
|
||||
router.Handle("/{containerId}/gpus", httperror.LoggerHandler(h.containerGpusInspect)).Methods(http.MethodGet)
|
||||
router.Handle("/{containerId}/recreate", httperror.LoggerHandler(h.recreate)).Methods(http.MethodPost)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
97
api/http/handler/docker/containers/recreate.go
Normal file
97
api/http/handler/docker/containers/recreate.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker/consts"
|
||||
"github.com/portainer/portainer/api/docker/images"
|
||||
"github.com/portainer/portainer/api/http/middlewares"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type RecreatePayload struct {
|
||||
// PullImage if true will pull the image
|
||||
PullImage bool `json:"PullImage"`
|
||||
}
|
||||
|
||||
func (r RecreatePayload) Validate(request *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) recreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
containerID, err := request.RetrieveRouteVariableValue(r, "containerId")
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Invalid containerId", err)
|
||||
}
|
||||
|
||||
var payload RecreatePayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Invalid request payload", err)
|
||||
}
|
||||
|
||||
endpoint, err := middlewares.FetchEndpoint(r)
|
||||
if err != nil {
|
||||
return httperror.NotFound("Unable to find an environment on request context", err)
|
||||
}
|
||||
|
||||
err = handler.bouncer.AuthorizedEndpointOperation(r, endpoint)
|
||||
if err != nil {
|
||||
return httperror.Forbidden("Permission denied to force update service", err)
|
||||
}
|
||||
|
||||
agentTargetHeader := r.Header.Get(portainer.PortainerAgentTargetHeader)
|
||||
|
||||
newContainer, err := handler.containerService.Recreate(r.Context(), endpoint, containerID, payload.PullImage, "", agentTargetHeader)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Error recreating container", err)
|
||||
}
|
||||
|
||||
handler.updateWebhook(containerID, newContainer.ID)
|
||||
handler.createResourceControl(containerID, newContainer.ID)
|
||||
|
||||
go func() {
|
||||
images.EvictImageStatus(containerID)
|
||||
images.EvictImageStatus(newContainer.Config.Labels[consts.ComposeStackNameLabel])
|
||||
images.EvictImageStatus(newContainer.Config.Labels[consts.SwarmServiceIdLabel])
|
||||
}()
|
||||
return response.JSON(w, newContainer)
|
||||
}
|
||||
|
||||
func (handler *Handler) createResourceControl(oldContainerId string, newContainerId string) {
|
||||
resourceControls, err := handler.dataStore.ResourceControl().ResourceControls()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Exporting Resource Controls")
|
||||
return
|
||||
}
|
||||
|
||||
resourceControl := authorization.GetResourceControlByResourceIDAndType(oldContainerId, portainer.ContainerResourceControl, resourceControls)
|
||||
if resourceControl == nil {
|
||||
return
|
||||
}
|
||||
resourceControl.ResourceID = newContainerId
|
||||
err = handler.dataStore.ResourceControl().Create(resourceControl)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("containerId", newContainerId).Msg("Failed to create new resource control for container")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *Handler) updateWebhook(oldContainerId string, newContainerId string) {
|
||||
webhook, err := handler.dataStore.Webhook().WebhookByResourceID(oldContainerId)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("containerId", oldContainerId).Msg("cannot find webhook by containerId")
|
||||
return
|
||||
}
|
||||
|
||||
webhook.ResourceID = newContainerId
|
||||
err = handler.dataStore.Webhook().UpdateWebhook(webhook.ID, webhook)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Int("webhookId", int(webhook.ID)).Msg("cannot update webhook")
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -21,18 +22,20 @@ type Handler struct {
|
||||
*mux.Router
|
||||
requestBouncer *security.RequestBouncer
|
||||
dataStore dataservices.DataStore
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
dockerClientFactory *dockerclient.ClientFactory
|
||||
authorizationService *authorization.Service
|
||||
containerService *docker.ContainerService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to process non-proxied requests to docker APIs directly.
|
||||
func NewHandler(bouncer *security.RequestBouncer, authorizationService *authorization.Service, dataStore dataservices.DataStore, dockerClientFactory *docker.ClientFactory) *Handler {
|
||||
func NewHandler(bouncer *security.RequestBouncer, authorizationService *authorization.Service, dataStore dataservices.DataStore, dockerClientFactory *dockerclient.ClientFactory, containerService *docker.ContainerService) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
requestBouncer: bouncer,
|
||||
authorizationService: authorizationService,
|
||||
dataStore: dataStore,
|
||||
dockerClientFactory: dockerClientFactory,
|
||||
containerService: containerService,
|
||||
}
|
||||
|
||||
// endpoints
|
||||
@@ -40,7 +43,7 @@ func NewHandler(bouncer *security.RequestBouncer, authorizationService *authoriz
|
||||
endpointRouter.Use(middlewares.WithEndpoint(dataStore.Endpoint(), "id"))
|
||||
endpointRouter.Use(dockerOnlyMiddleware)
|
||||
|
||||
containersHandler := containers.NewHandler("/{id}/containers", bouncer, dockerClientFactory)
|
||||
containersHandler := containers.NewHandler("/{id}/containers", bouncer, dataStore, dockerClientFactory, containerService)
|
||||
endpointRouter.PathPrefix("/containers").Handler(containersHandler)
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package endpoints
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/libhttp/request"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
@@ -103,7 +101,6 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
||||
if paginatedEndpoints[idx].EdgeCheckinInterval == 0 {
|
||||
paginatedEndpoints[idx].EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||
}
|
||||
paginatedEndpoints[idx].QueryDate = time.Now().Unix()
|
||||
endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings)
|
||||
if !query.excludeSnapshots {
|
||||
err = handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx])
|
||||
|
||||
@@ -82,7 +82,7 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
// @version 2.18.2
|
||||
// @version 2.18.4
|
||||
// @description.markdown api-description.md
|
||||
// @termsOfService
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ type Handler struct {
|
||||
*mux.Router
|
||||
OpenAMTService portainer.OpenAMTService
|
||||
DataStore dataservices.DataStore
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
DockerClientFactory *dockerclient.ClientFactory
|
||||
}
|
||||
|
||||
// NewHandler returns a new Handler
|
||||
|
||||
@@ -125,26 +125,23 @@ func (handler *Handler) registryConfigure(w http.ResponseWriter, r *http.Request
|
||||
return httperror.InternalServerError("Unable to find a registry with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
registry.ManagementConfiguration = &portainer.RegistryManagementConfiguration{
|
||||
Type: registry.Type,
|
||||
}
|
||||
|
||||
if payload.Authentication {
|
||||
registry.ManagementConfiguration.Authentication = true
|
||||
registry.ManagementConfiguration.Username = payload.Username
|
||||
if payload.Username == registry.Username && payload.Password == "" {
|
||||
registry.ManagementConfiguration.Password = registry.Password
|
||||
} else {
|
||||
registry.ManagementConfiguration.Password = payload.Password
|
||||
registry.Authentication = true
|
||||
|
||||
registry.Username = payload.Username
|
||||
|
||||
if payload.Password != "" {
|
||||
registry.Password = payload.Password
|
||||
}
|
||||
|
||||
if payload.Region != "" {
|
||||
registry.ManagementConfiguration.Ecr.Region = payload.Region
|
||||
registry.Ecr.Region = payload.Region
|
||||
}
|
||||
}
|
||||
|
||||
var tlsConfig portainer.TLSConfiguration
|
||||
if payload.TLS {
|
||||
registry.ManagementConfiguration.TLSConfig = portainer.TLSConfiguration{
|
||||
tlsConfig = portainer.TLSConfiguration{
|
||||
TLS: true,
|
||||
TLSSkipVerify: payload.TLSSkipVerify,
|
||||
}
|
||||
@@ -156,22 +153,25 @@ func (handler *Handler) registryConfigure(w http.ResponseWriter, r *http.Request
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to persist TLS certificate file on disk", err)
|
||||
}
|
||||
registry.ManagementConfiguration.TLSConfig.TLSCertPath = certPath
|
||||
tlsConfig.TLSCertPath = certPath
|
||||
|
||||
keyPath, err := handler.FileService.StoreRegistryManagementFileFromBytes(folder, "key.pem", payload.TLSKeyFile)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to persist TLS key file on disk", err)
|
||||
}
|
||||
registry.ManagementConfiguration.TLSConfig.TLSKeyPath = keyPath
|
||||
tlsConfig.TLSKeyPath = keyPath
|
||||
|
||||
cacertPath, err := handler.FileService.StoreRegistryManagementFileFromBytes(folder, "ca.pem", payload.TLSCACertFile)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to persist TLS CA certificate file on disk", err)
|
||||
}
|
||||
registry.ManagementConfiguration.TLSConfig.TLSCACertPath = cacertPath
|
||||
tlsConfig.TLSCACertPath = cacertPath
|
||||
}
|
||||
}
|
||||
|
||||
registry.ManagementConfiguration = syncConfig(registry)
|
||||
registry.ManagementConfiguration.TLSConfig = tlsConfig
|
||||
|
||||
err = handler.DataStore.Registry().UpdateRegistry(registry.ID, registry)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to persist registry changes inside the database", err)
|
||||
|
||||
@@ -119,6 +119,8 @@ func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *
|
||||
Ecr: payload.Ecr,
|
||||
}
|
||||
|
||||
registry.ManagementConfiguration = syncConfig(registry)
|
||||
|
||||
registries, err := handler.DataStore.Registry().Registries()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve registries from the database", err)
|
||||
|
||||
@@ -140,6 +140,8 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
}
|
||||
}
|
||||
|
||||
registry.ManagementConfiguration = syncConfig(registry)
|
||||
|
||||
if payload.URL != nil {
|
||||
shouldUpdateSecrets = shouldUpdateSecrets || (*payload.URL != registry.URL)
|
||||
|
||||
@@ -183,6 +185,21 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
return response.JSON(w, registry)
|
||||
}
|
||||
|
||||
func syncConfig(registry *portainer.Registry) *portainer.RegistryManagementConfiguration {
|
||||
config := registry.ManagementConfiguration
|
||||
if config == nil {
|
||||
config = &portainer.RegistryManagementConfiguration{}
|
||||
}
|
||||
|
||||
config.Authentication = registry.Authentication
|
||||
config.Username = registry.Username
|
||||
config.Password = registry.Password
|
||||
config.Ecr = registry.Ecr
|
||||
config.Type = registry.Type
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (handler *Handler) updateEndpointRegistryAccess(endpoint *portainer.Endpoint, registry *portainer.Registry, endpointAccess portainer.RegistryAccessPolicies) error {
|
||||
|
||||
cli, err := handler.K8sClientFactory.GetKubeClient(endpoint)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
@@ -30,7 +30,7 @@ type Handler struct {
|
||||
requestBouncer *security.RequestBouncer
|
||||
*mux.Router
|
||||
DataStore dataservices.DataStore
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
DockerClientFactory *dockerclient.ClientFactory
|
||||
FileService portainer.FileService
|
||||
GitService portainer.GitService
|
||||
SwarmStackManager portainer.SwarmStackManager
|
||||
|
||||
@@ -39,7 +39,7 @@ func (payload *teamCreatePayload) Validate(r *http.Request) error {
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 409 "Team already exists"
|
||||
// @failure 500 "Server error"
|
||||
// @router /team [post]
|
||||
// @router /teams [post]
|
||||
func (handler *Handler) teamCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload teamCreatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
|
||||
@@ -35,7 +35,7 @@ func (payload *teamUpdatePayload) Validate(r *http.Request) error {
|
||||
// @failure 403 "Permission denied"
|
||||
// @failure 404 "Team not found"
|
||||
// @failure 500 "Server error"
|
||||
// @router /team/{id} [put]
|
||||
// @router /teams/{id} [put]
|
||||
func (handler *Handler) teamUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
teamID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -17,7 +17,7 @@ type Handler struct {
|
||||
*mux.Router
|
||||
requestBouncer *security.RequestBouncer
|
||||
DataStore dataservices.DataStore
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
DockerClientFactory *dockerclient.ClientFactory
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage webhooks operations.
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dataerrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
@@ -35,7 +35,7 @@ type (
|
||||
dataStore dataservices.DataStore
|
||||
signatureService portainer.DigitalSignatureService
|
||||
reverseTunnelService portainer.ReverseTunnelService
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
dockerClientFactory *dockerclient.ClientFactory
|
||||
gitService portainer.GitService
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ type (
|
||||
DataStore dataservices.DataStore
|
||||
SignatureService portainer.DigitalSignatureService
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
DockerClientFactory *dockerclient.ClientFactory
|
||||
}
|
||||
|
||||
restrictedDockerOperationContext struct {
|
||||
|
||||
@@ -5,11 +5,10 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
)
|
||||
|
||||
const azureAPIBaseURL = "https://management.azure.com"
|
||||
@@ -20,7 +19,7 @@ type (
|
||||
dataStore dataservices.DataStore
|
||||
signatureService portainer.DigitalSignatureService
|
||||
reverseTunnelService portainer.ReverseTunnelService
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
dockerClientFactory *dockerclient.ClientFactory
|
||||
kubernetesClientFactory *cli.ClientFactory
|
||||
kubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
||||
gitService portainer.GitService
|
||||
@@ -28,7 +27,7 @@ type (
|
||||
)
|
||||
|
||||
// NewProxyFactory returns a pointer to a new instance of a ProxyFactory
|
||||
func NewProxyFactory(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *docker.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService) *ProxyFactory {
|
||||
func NewProxyFactory(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *dockerclient.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService) *ProxyFactory {
|
||||
return &ProxyFactory{
|
||||
dataStore: dataStore,
|
||||
signatureService: signatureService,
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ type (
|
||||
)
|
||||
|
||||
// NewManager initializes a new proxy Service
|
||||
func NewManager(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *docker.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService) *Manager {
|
||||
func NewManager(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *dockerclient.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService) *Manager {
|
||||
return &Manager{
|
||||
endpointProxies: cmap.New(),
|
||||
k8sClientFactory: kubernetesClientFactory,
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/demo"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/http/handler"
|
||||
"github.com/portainer/portainer/api/http/handler/auth"
|
||||
"github.com/portainer/portainer/api/http/handler/backup"
|
||||
@@ -95,7 +96,7 @@ type Server struct {
|
||||
KubeClusterAccessService k8s.KubeClusterAccessService
|
||||
Handler *handler.Handler
|
||||
SSLService *ssl.Service
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
DockerClientFactory *dockerclient.ClientFactory
|
||||
KubernetesClientFactory *cli.ClientFactory
|
||||
KubernetesDeployer portainer.KubernetesDeployer
|
||||
HelmPackageManager libhelm.HelmPackageManager
|
||||
@@ -190,7 +191,9 @@ func (server *Server) Start() error {
|
||||
|
||||
var kubernetesHandler = kubehandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.JWTService, server.KubeClusterAccessService, server.KubernetesClientFactory, nil)
|
||||
|
||||
var dockerHandler = dockerhandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.DockerClientFactory)
|
||||
containerService := docker.NewContainerService(server.DockerClientFactory, server.DataStore)
|
||||
|
||||
var dockerHandler = dockerhandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.DockerClientFactory, containerService)
|
||||
|
||||
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"), adminMonitor.WasInstanceDisabled)
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ func InitialStorageDetection(endpoint *portainer.Endpoint, endpointService datas
|
||||
|
||||
func UpdateEdgeEndpointHeartbeat(endpoint *portainer.Endpoint, settings *portainer.Settings) {
|
||||
if IsEdgeEndpoint(endpoint) {
|
||||
endpoint.QueryDate = time.Now().Unix()
|
||||
checkInInterval := getEndpointCheckinInterval(endpoint, settings)
|
||||
endpoint.Heartbeat = endpoint.QueryDate-endpoint.LastCheckInDate <= int64(checkInInterval*2+20)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
libstack "github.com/portainer/docker-compose-wrapper"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/platform"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -150,7 +150,7 @@ func (service *service) upgradeDocker(licenseKey, version, envType string) error
|
||||
}
|
||||
|
||||
func (service *service) checkImage(ctx context.Context, image string, skipPullImage bool) error {
|
||||
cli, err := docker.CreateClientFromEnv()
|
||||
cli, err := dockerclient.CreateClientFromEnv()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create docker client")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ func DetermineContainerPlatform() (ContainerPlatform, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
dockerCli, err := docker.CreateSimpleClient()
|
||||
dockerCli, err := dockerclient.CreateSimpleClient()
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "failed to create docker client")
|
||||
}
|
||||
|
||||
@@ -1514,7 +1514,7 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "2.18.2"
|
||||
APIVersion = "2.18.4"
|
||||
// Edition is what this edition of Portainer is called
|
||||
Edition = PortainerCE
|
||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||
|
||||
@@ -259,6 +259,9 @@
|
||||
|
||||
/* Dark Theme */
|
||||
[theme='dark'] {
|
||||
--BE-only: var(--ui-blue-8);
|
||||
--bg-BE-only: rgba(225, 223, 223, 0.08);
|
||||
|
||||
--text-log-viewer-color-json-grey: var(--text-log-viewer-color);
|
||||
--text-log-viewer-color-json-magenta: var(--text-log-viewer-color);
|
||||
--text-log-viewer-color-json-yellow: var(--text-log-viewer-color);
|
||||
|
||||
3
app/assets/ico/beta.svg
Normal file
3
app/assets/ico/beta.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="auto" height="auto" viewBox="0 0 8 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 14.3223V3.49792C1 2.11836 2.11832 1 3.49792 1C4.87753 1 5.99585 2.11836 5.99585 3.49792C5.99585 4.87749 4.87753 5.99585 3.49792 5.99585L3.91425 5.99609C5.52374 5.99609 6.82849 7.30084 6.82849 8.91034C6.82849 10.5198 5.52374 11.8246 3.91425 11.8246C2.30475 11.8246 1 10.5198 1 8.91034" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 493 B |
11
app/assets/ico/lock-2.svg
Normal file
11
app/assets/ico/lock-2.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="76" height="75" viewBox="0 0 76 75" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_2)">
|
||||
<path opacity="0.2" d="M75.5 37.5C75.5 16.7893 58.7107 0 38 0C17.2893 0 0.5 16.7893 0.5 37.5C0.5 58.2107 17.2893 75 38 75C58.7107 75 75.5 58.2107 75.5 37.5Z" fill="#E1DFDF" fill-opacity="0.08"/>
|
||||
<path d="M31.5353 36.1666V30.8333C31.5353 29.0652 32.2164 27.3695 33.4288 26.1192C34.6412 24.869 36.2855 24.1666 38 24.1666C39.7145 24.1666 41.3588 24.869 42.5712 26.1192C43.7835 27.3695 44.4646 29.0652 44.4646 30.8333V36.1666M28.9495 36.1666H47.0505C48.4786 36.1666 49.6364 37.3605 49.6364 38.8333V48.1666C49.6364 49.6394 48.4786 50.8333 47.0505 50.8333H28.9495C27.5214 50.8333 26.3636 49.6394 26.3636 48.1666V38.8333C26.3636 37.3605 27.5214 36.1666 28.9495 36.1666Z" stroke="#0086C9" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_2">
|
||||
<rect width="76" height="75" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 986 B |
@@ -117,6 +117,7 @@ angular.module('portainer.docker').factory('ContainerHelper', [
|
||||
}
|
||||
}
|
||||
config.HostConfig.Binds = binds;
|
||||
config.HostConfig.Mounts = null;
|
||||
config.Volumes = volumes;
|
||||
return config;
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import { HealthStatus } from '@/react/docker/containers/ItemView/HealthStatus';
|
||||
import { GpusList } from '@/react/docker/host/SetupView/GpusList';
|
||||
import { GpusInsights } from '@/react/docker/host/SetupView/GpusInsights';
|
||||
import { InsightsBox } from '@/react/components/InsightsBox';
|
||||
import { BetaAlert } from '@/react/portainer/environments/update-schedules/common/BetaAlert';
|
||||
|
||||
export const componentsModule = angular
|
||||
.module('portainer.docker.react.components', [])
|
||||
@@ -65,4 +66,5 @@ export const componentsModule = angular
|
||||
'className',
|
||||
])
|
||||
)
|
||||
.component('betaAlert', r2a(BetaAlert, ['className', 'message', 'isHtml']))
|
||||
.component('gpusInsights', r2a(GpusInsights, [])).name;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
resumeContainer,
|
||||
startContainer,
|
||||
stopContainer,
|
||||
recreateContainer,
|
||||
} from '@/react/docker/containers/containers.service';
|
||||
import { ContainerDetailsViewModel, ContainerStatsViewModel, ContainerViewModel } from '../models/container';
|
||||
import { formatLogs } from '../helpers/logHelper';
|
||||
@@ -24,6 +25,7 @@ function ContainerServiceFactory($q, Container, $timeout, EndpointProvider) {
|
||||
resumeContainer: withEndpointId(resumeContainer),
|
||||
startContainer: withEndpointId(startContainer),
|
||||
stopContainer: withEndpointId(stopContainer),
|
||||
recreateContainer: withEndpointId(recreateContainer),
|
||||
remove: withEndpointId(removeContainer),
|
||||
updateRestartPolicy,
|
||||
updateLimits,
|
||||
|
||||
@@ -7,10 +7,10 @@ angular.module('portainer.docker').factory('SwarmService', [
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.swarm = function () {
|
||||
service.swarm = function (endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Swarm.get()
|
||||
Swarm.get(endpointId ? { endpointId } : undefined)
|
||||
.$promise.then(function success(data) {
|
||||
var swarm = new SwarmViewModel(data);
|
||||
deferred.resolve(swarm);
|
||||
|
||||
@@ -304,84 +304,9 @@ angular.module('portainer.docker').controller('ContainerController', [
|
||||
|
||||
function recreateContainer(pullImage) {
|
||||
var container = $scope.container;
|
||||
var config = ContainerHelper.configFromContainer(container.Model);
|
||||
$scope.state.recreateContainerInProgress = true;
|
||||
var isRunning = container.State.Running;
|
||||
|
||||
return pullImageIfNeeded()
|
||||
.then(stopContainerIfNeeded)
|
||||
.then(renameContainer)
|
||||
.then(setMainNetworkAndCreateContainer)
|
||||
.then(connectContainerToOtherNetworks)
|
||||
.then(startContainerIfNeeded)
|
||||
.then(createResourceControl)
|
||||
.then(deleteOldContainer)
|
||||
.then(notifyAndChangeView)
|
||||
.catch(notifyOnError);
|
||||
|
||||
function stopContainerIfNeeded() {
|
||||
if (!isRunning) {
|
||||
return $q.when();
|
||||
}
|
||||
return ContainerService.stopContainer(container.Id);
|
||||
}
|
||||
|
||||
function renameContainer() {
|
||||
return ContainerService.renameContainer(container.Id, container.Name + '-old');
|
||||
}
|
||||
|
||||
function pullImageIfNeeded() {
|
||||
if (!pullImage) {
|
||||
return $q.when();
|
||||
}
|
||||
return RegistryService.retrievePorRegistryModelFromRepository(container.Config.Image, endpoint.Id).then((registryModel) => {
|
||||
return ImageService.pullImage(registryModel, false);
|
||||
});
|
||||
}
|
||||
|
||||
function setMainNetworkAndCreateContainer() {
|
||||
var networks = config.NetworkingConfig.EndpointsConfig;
|
||||
var networksNames = Object.keys(networks);
|
||||
if (networksNames.length > 1) {
|
||||
config.NetworkingConfig.EndpointsConfig = {};
|
||||
config.NetworkingConfig.EndpointsConfig[networksNames[0]] = networks[0];
|
||||
}
|
||||
return $q.all([ContainerService.createContainer(config), networks]);
|
||||
}
|
||||
|
||||
function connectContainerToOtherNetworks(createContainerData) {
|
||||
var newContainer = createContainerData[0];
|
||||
var networks = createContainerData[1];
|
||||
var networksNames = Object.keys(networks);
|
||||
var connectionPromises = networksNames.map(function connectToNetwork(name) {
|
||||
NetworkService.connectContainer(name, newContainer.Id);
|
||||
});
|
||||
return $q.all(connectionPromises).then(function onConnectToNetworkSuccess() {
|
||||
return newContainer;
|
||||
});
|
||||
}
|
||||
|
||||
function deleteOldContainer(newContainer) {
|
||||
return ContainerService.remove(container, true).then(function onRemoveSuccess() {
|
||||
return newContainer;
|
||||
});
|
||||
}
|
||||
|
||||
function startContainerIfNeeded(newContainer) {
|
||||
if (!isRunning) {
|
||||
return $q.when(newContainer);
|
||||
}
|
||||
return ContainerService.startContainer(newContainer.Id).then(function onStartSuccess() {
|
||||
return newContainer;
|
||||
});
|
||||
}
|
||||
|
||||
function createResourceControl(newContainer) {
|
||||
const userId = Authentication.getUserDetails().ID;
|
||||
const oldResourceControl = container.ResourceControl;
|
||||
const newResourceControl = newContainer.Portainer.ResourceControl;
|
||||
return ResourceControlService.duplicateResourceControl(userId, oldResourceControl, newResourceControl);
|
||||
}
|
||||
return ContainerService.recreateContainer(container.Id, pullImage).then(notifyAndChangeView).catch(notifyOnError);
|
||||
|
||||
function notifyAndChangeView() {
|
||||
Notifications.success('Success', 'Container successfully re-created');
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<code-editor
|
||||
identifier="image-build-editor"
|
||||
placeholder="Define or paste the content of your Dockerfile here"
|
||||
yml="false"
|
||||
docker-file="true"
|
||||
on-change="(editorUpdate)"
|
||||
></code-editor>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
|
||||
import { ScheduleCreateRequest, ScheduleUpdateRequest } from '@/portainer/models/schedule';
|
||||
|
||||
@@ -26,7 +27,9 @@ function EdgeJobService(EdgeJobs, EdgeJobResults, FileUploadService) {
|
||||
service.jobResults = jobResults;
|
||||
async function jobResults(edgeJobId) {
|
||||
try {
|
||||
return await EdgeJobResults.query({ id: edgeJobId }).$promise;
|
||||
const results = await EdgeJobResults.query({ id: edgeJobId }).$promise;
|
||||
|
||||
return _.sortBy(results, ['Id']);
|
||||
} catch (err) {
|
||||
throw { msg: 'Unable to retrieve results associated to the edgeJob', err: err };
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
<page-header title="'Helm'" breadcrumbs="['Charts']" reload="true"></page-header>
|
||||
|
||||
<information-panel title-text="Information" ng-if="!$ctrl.state.chart">
|
||||
<beta-alert
|
||||
is-html="true"
|
||||
message="'Beta feature - initial version of Helm charts functionality, for more information see this <a href=\'https://www.portainer.io/blog/portainer-now-with-helm-support\' target=\'_blank\' class=\'hyperlink\'>blog post</a>.'"
|
||||
></beta-alert>
|
||||
<span class="small text-muted">
|
||||
<p class="inline-flex flex-row items-center">
|
||||
<pr-icon icon="'info'" class="vertical-center mr-1" mode="'primary'"></pr-icon>
|
||||
This is a first version for Helm charts, for more information see this <a
|
||||
class="hyperlink"
|
||||
href="https://www.portainer.io/blog/portainer-now-with-helm-support"
|
||||
target="_blank"
|
||||
>blog post</a
|
||||
>.</p
|
||||
>
|
||||
<p ng-if="$ctrl.state.globalRepository === ''" class="inline-flex items-center">
|
||||
<pr-icon icon="'info'"></pr-icon>
|
||||
<span>The Global Helm Repository is not configured.</span>
|
||||
@@ -23,7 +18,7 @@
|
||||
<!-- helmchart-form -->
|
||||
<div class="col-sm-12" ng-if="$ctrl.state.chart">
|
||||
<rd-widget>
|
||||
<div class="toolBarTitle vertical-center text-muted px-5 pt-5">
|
||||
<div class="toolBarTitle vertical-center px-5 pt-5 text-[16px] font-medium">
|
||||
<fallback-image src="$ctrl.state.chart.icon" fallback-icon="'svg-helm'" class-name="'h-8 w-8'" size="'lg'"></fallback-image>
|
||||
{{ $ctrl.state.chart.name }}
|
||||
</div>
|
||||
|
||||
@@ -20,10 +20,11 @@
|
||||
Release
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolBarTitle text-muted small vertical-center !gap-0 px-5">
|
||||
<pr-icon icon="'info'" mode="'primary'" class-name="'!mr-1'" class="vertical-center"></pr-icon>
|
||||
This is a first version for Helm charts, for more information see this
|
||||
<a href="https://www.portainer.io/blog/portainer-now-with-helm-support" target="_blank" class="hyperlink">blog post</a>.
|
||||
<div class="toolBarTitle vertical-center !gap-0 px-5">
|
||||
<beta-alert
|
||||
is-html="true"
|
||||
message="'Beta feature - initial version of Helm charts functionality, for more information see this <a href=\'https://www.portainer.io/blog/portainer-now-with-helm-support\' target=\'_blank\' class=\'hyperlink\'>blog post</a>.'"
|
||||
></beta-alert>
|
||||
</div>
|
||||
<rd-widget-body>
|
||||
<table class="table">
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
ng-if="ctrl.state.BuildMethod === ctrl.BuildMethods.GIT"
|
||||
value="ctrl.formValues"
|
||||
on-change="(ctrl.onChangeFormValues)"
|
||||
environment-type="KUBERNETES"
|
||||
is-force-pull-visible="false"
|
||||
is-additional-files-field-visible="true"
|
||||
is-auth-explanation-visible="true"
|
||||
deploy-method="{{ ctrl.state.DeployType === ctrl.ManifestDeployTypes.COMPOSE ? 'compose' : 'manifest' }}"
|
||||
@@ -182,7 +184,7 @@
|
||||
<form class="form-horizontal mt-3">
|
||||
<div class="form-group" ng-if="ctrl.state.activeTab === 1">
|
||||
<div class="col-sm-12">
|
||||
<code-editor identifier="kubernetes-deploy-logs" read-only="true" yml="false" value="ctrl.errorLog"></code-editor>
|
||||
<code-editor identifier="kubernetes-deploy-logs" yml="true" read-only="true" value="ctrl.errorLog"></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<information-panel title-text="Information">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<pr-icon icon="'flask-conical'" mode="'warning'"></pr-icon>
|
||||
This is a beta feature.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
@@ -1,3 +0,0 @@
|
||||
angular.module('portainer.app').component('betaPanel', {
|
||||
templateUrl: './betaPanel.html',
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
id="$ctrl.identifier"
|
||||
placeholder="$ctrl.placeholder"
|
||||
yaml="$ctrl.yml"
|
||||
docker-file="$ctrl.dockerFile"
|
||||
readonly="$ctrl.readOnly"
|
||||
on-change="($ctrl.handleChange)"
|
||||
value="$ctrl.value"
|
||||
|
||||
@@ -7,6 +7,7 @@ angular.module('portainer.app').component('codeEditor', {
|
||||
identifier: '@',
|
||||
placeholder: '@',
|
||||
yml: '<',
|
||||
dockerFile: '<',
|
||||
readOnly: '<',
|
||||
onChange: '<',
|
||||
value: '<',
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
Switch to simple mode to define variables line by line, or load from .env file
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<code-editor identifier="environment-variables-editor" placeholder="e.g. key=value" value="$ctrl.editorText" yml="false" on-change="($ctrl.editorUpdate)"></code-editor>
|
||||
<code-editor identifier="environment-variables-editor" placeholder="e.g. key=value" value="$ctrl.editorText" on-change="($ctrl.editorUpdate)"></code-editor>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted" ng-if="$ctrl.showHelpMessage">
|
||||
<pr-icon icon="'alert-circle'" mode="'primary'"></pr-icon>
|
||||
|
||||
@@ -8,6 +8,7 @@ export const gitForm: IComponentOptions = {
|
||||
<react-git-form
|
||||
value="$ctrl.value"
|
||||
on-change="$ctrl.handleChange"
|
||||
environment-type="$ctrl.environmentType"
|
||||
is-docker-standalone="$ctrl.isDockerStandalone"
|
||||
deploy-method="$ctrl.deployMethod"
|
||||
is-additional-files-field-visible="$ctrl.isAdditionalFilesFieldVisible"
|
||||
@@ -22,6 +23,7 @@ export const gitForm: IComponentOptions = {
|
||||
bindings: {
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
environmentType: '@',
|
||||
isDockerStandalone: '<',
|
||||
deployMethod: '@',
|
||||
baseWebhookUrl: '@',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { parseAutoUpdateResponse } from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
|
||||
import { baseStackWebhookUrl, createWebhookId } from '@/portainer/helpers/webhookHelper';
|
||||
import { confirmEnableTLSVerify } from '@/react/portainer/gitops/utils';
|
||||
|
||||
class KubernetesRedeployAppGitFormController {
|
||||
/* @ngInject */
|
||||
@@ -71,6 +72,13 @@ class KubernetesRedeployAppGitFormController {
|
||||
|
||||
onChangeTLSSkipVerify(value) {
|
||||
return this.$async(async () => {
|
||||
if (this.stack.GitConfig.TLSSkipVerify && !value) {
|
||||
const confirmed = await confirmEnableTLSVerify();
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.onChange({ TLSSkipVerify: value });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
value="$ctrl.formValues.AutoUpdate"
|
||||
on-change="($ctrl.onChangeAutoUpdate)"
|
||||
environment-type="KUBERNETES"
|
||||
is-force-pull-visible="false"
|
||||
base-webhook-url="{{ $ctrl.state.baseWebhookUrl }}"
|
||||
webhook-id="{{ $ctrl.state.webhookId }}"
|
||||
webhooks-docs="https://docs.portainer.io/user/kubernetes/applications/webhooks"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { confirmStackUpdate } from '@/react/docker/stacks/common/confirm-stack-u
|
||||
|
||||
import { parseAutoUpdateResponse } from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
|
||||
import { baseStackWebhookUrl, createWebhookId } from '@/portainer/helpers/webhookHelper';
|
||||
import { confirmEnableTLSVerify } from '@/react/portainer/gitops/utils';
|
||||
|
||||
class StackRedeployGitFormController {
|
||||
/* @ngInject */
|
||||
@@ -95,8 +96,17 @@ class StackRedeployGitFormController {
|
||||
this.onChange({ Env: value });
|
||||
}
|
||||
|
||||
onChangeTLSSkipVerify(value) {
|
||||
this.onChange({ TLSSkipVerify: value });
|
||||
async onChangeTLSSkipVerify(value) {
|
||||
return this.$async(async () => {
|
||||
if (this.model.TLSSkipVerify && !value) {
|
||||
const confirmed = await confirmEnableTLSVerify();
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.onChange({ TLSSkipVerify: value });
|
||||
});
|
||||
}
|
||||
|
||||
onChangeOption(values) {
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<box-selector radio-name="'theme'" value="$ctrl.state.themeColor" options="$ctrl.state.availableThemes" on-change="($ctrl.setThemeColor)"></box-selector>
|
||||
|
||||
<p class="vertical-center mt-2">
|
||||
<pr-icon icon="'alert-circle'" class-name="'icon-primary'"></pr-icon>
|
||||
<span class="small">Dark and High-contrast theme are experimental. Some UI components might not display properly.</span>
|
||||
</p>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
|
||||
@@ -18,7 +18,8 @@ export function RegistryViewModel(data) {
|
||||
}
|
||||
|
||||
export function RegistryManagementConfigurationDefaultModel(registry) {
|
||||
this.Authentication = false;
|
||||
this.Authentication = registry.Authentication;
|
||||
this.Username = registry.Username;
|
||||
this.Password = '';
|
||||
this.TLS = false;
|
||||
this.TLSSkipVerify = false;
|
||||
|
||||
@@ -18,6 +18,7 @@ export const gitFormModule = angular
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(GitForm))), [
|
||||
'value',
|
||||
'onChange',
|
||||
'environmentType',
|
||||
'isDockerStandalone',
|
||||
'deployMethod',
|
||||
'isAdditionalFilesFieldVisible',
|
||||
|
||||
@@ -189,6 +189,7 @@ export const componentsModule = angular
|
||||
'id',
|
||||
'placeholder',
|
||||
'yaml',
|
||||
'dockerFile',
|
||||
'readonly',
|
||||
'onChange',
|
||||
'value',
|
||||
|
||||
@@ -50,7 +50,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||
service.migrateSwarmStack = function (stack, targetEndpointId, newName) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
SwarmService.swarm(targetEndpointId)
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
if (swarm.Id === stack.SwarmId) {
|
||||
@@ -324,7 +324,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||
service.createSwarmStackFromFileContent = function (name, stackFileContent, env, endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
SwarmService.swarm(endpointId)
|
||||
.then(function success(swarm) {
|
||||
var payload = {
|
||||
Name: name,
|
||||
|
||||
@@ -27,6 +27,8 @@ axios.interceptors.request.use(async (config) => {
|
||||
return newConfig;
|
||||
});
|
||||
|
||||
export const agentTargetHeader = 'X-PortainerAgent-Target';
|
||||
|
||||
export function agentInterceptor(config: AxiosRequestConfig) {
|
||||
if (!config.url || !config.url.includes('/docker/')) {
|
||||
return config;
|
||||
@@ -35,7 +37,7 @@ export function agentInterceptor(config: AxiosRequestConfig) {
|
||||
const newConfig = { headers: config.headers || {}, ...config };
|
||||
const target = portainerAgentTargetHeader();
|
||||
if (target) {
|
||||
newConfig.headers['X-PortainerAgent-Target'] = target;
|
||||
newConfig.headers[agentTargetHeader] = target;
|
||||
}
|
||||
|
||||
if (portainerAgentManagerOperation()) {
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
}
|
||||
|
||||
.be-indicator .be-indicator-icon {
|
||||
color: #000000;
|
||||
@apply text-black th-highcontrast:text-white th-dark:text-blue-8;
|
||||
}
|
||||
|
||||
.be-indicator:hover {
|
||||
text-decoration: none;
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
.be-indicator:hover .be-indicator-label {
|
||||
text-decoration: underline;
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
.be-indicator-container {
|
||||
|
||||
@@ -38,10 +38,11 @@
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
/* used for BE teaser. Dark theme specs defined by EE-5621 */
|
||||
.box-selector-item.limited.business label,
|
||||
.box-selector-item.limited.business input:checked + label {
|
||||
@apply border-warning-7 bg-warning-1 text-black;
|
||||
@apply th-dark:bg-warning-8 th-dark:bg-opacity-10 th-dark:text-white;
|
||||
@apply th-dark:border-blue-8 th-dark:bg-[color:var(--bg-BE-only)] th-dark:text-white;
|
||||
@apply th-highcontrast:bg-warning-8 th-highcontrast:bg-opacity-10 th-highcontrast:text-white;
|
||||
|
||||
filter: none;
|
||||
|
||||
@@ -11,9 +11,9 @@ interface Props {
|
||||
export function LimitedToBeIndicator({ tooltipId, url }: Props) {
|
||||
return (
|
||||
<div className="absolute left-0 top-0 w-full">
|
||||
<div className="mx-auto flex max-w-fit items-center gap-1 rounded-b-lg bg-warning-4 py-1 px-3 text-sm">
|
||||
<div className="mx-auto flex max-w-fit items-center gap-1 rounded-b-lg bg-warning-4 py-1 px-3 text-sm th-dark:bg-[color:var(--bg-BE-only)]">
|
||||
<a
|
||||
className="text-warning-9"
|
||||
className="text-warning-9 th-dark:text-blue-8"
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -26,7 +26,10 @@ export function LimitedToBeIndicator({ tooltipId, url }: Props) {
|
||||
heading="Business Edition feature."
|
||||
message="This feature is currently limited to Business Edition users only."
|
||||
>
|
||||
<HelpCircle className="ml-1 !text-warning-7" aria-hidden="true" />
|
||||
<HelpCircle
|
||||
className="ml-1 !text-warning-7 th-dark:!text-blue-8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</TooltipWithChildren>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
--text-cm-string-color: var(--red-3);
|
||||
--text-cm-number-color: var(--green-1);
|
||||
--text-cm-keyword-color: var(--ui-blue-dark-9);
|
||||
--text-cm-comment-color: var(--ui-orange-6);
|
||||
--text-cm-variable-name-color: var(--ui-green-8);
|
||||
--text-codemirror-color: var(--black-color);
|
||||
--bg-codemirror-color: var(--white-color);
|
||||
--bg-codemirror-gutters-color: var(--grey-17);
|
||||
@@ -60,9 +62,50 @@
|
||||
}
|
||||
|
||||
.root :global(.cm-button) {
|
||||
@apply bg-blue-8;
|
||||
color: var(--text-codemirror-color);
|
||||
background-image: none;
|
||||
border-radius: 4px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.root :global(.cm-button[name='next']),
|
||||
.root :global(.cm-button[name='replace']) {
|
||||
@apply border-blue-8 bg-blue-8 text-white;
|
||||
@apply hover:border-blue-9 hover:bg-blue-9 hover:text-white;
|
||||
@apply th-dark:hover:border-blue-7 th-dark:hover:bg-blue-7;
|
||||
}
|
||||
|
||||
.root :global(.cm-button[name='prev']),
|
||||
.root :global(.cm-button[name='replaceAll']) {
|
||||
@apply border border-solid;
|
||||
|
||||
@apply border-blue-8 bg-blue-2 text-blue-9;
|
||||
@apply hover:bg-blue-3;
|
||||
|
||||
@apply th-dark:border-blue-7 th-dark:bg-gray-10 th-dark:text-blue-3;
|
||||
@apply th-dark:hover:bg-blue-11;
|
||||
}
|
||||
|
||||
.root :global(.cm-button[name='select']) {
|
||||
@apply border-gray-5 bg-white text-gray-9;
|
||||
@apply hover:border-gray-5 hover:bg-gray-3 hover:text-gray-10;
|
||||
|
||||
/* dark mode */
|
||||
@apply th-dark:border-gray-warm-7 th-dark:bg-gray-iron-10 th-dark:text-gray-warm-4;
|
||||
@apply th-dark:hover:border-gray-6 th-dark:hover:bg-gray-iron-9 th-dark:hover:text-gray-warm-4;
|
||||
|
||||
@apply th-highcontrast:border-gray-2 th-highcontrast:bg-black th-highcontrast:text-white;
|
||||
@apply th-highcontrast:hover:border-gray-6 th-highcontrast:hover:bg-gray-9 th-highcontrast:hover:text-gray-warm-4;
|
||||
}
|
||||
|
||||
.root :global(.cm-search) label {
|
||||
font-weight: 400;
|
||||
@apply text-gray-7;
|
||||
@apply th-dark:text-gray-warm-3;
|
||||
@apply th-highcontrast:text-white;
|
||||
}
|
||||
|
||||
.root :global(.cm-search) input {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.root :global(.cm-textfield) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { StreamLanguage, LanguageSupport } from '@codemirror/language';
|
||||
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
|
||||
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
|
||||
import { useMemo } from 'react';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { tags as highlightTags } from '@lezer/highlight';
|
||||
@@ -12,6 +13,7 @@ interface Props {
|
||||
id: string;
|
||||
placeholder?: string;
|
||||
yaml?: boolean;
|
||||
dockerFile?: boolean;
|
||||
readonly?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
value: string;
|
||||
@@ -37,10 +39,18 @@ const theme = createTheme({
|
||||
},
|
||||
{ tag: highlightTags.number, color: 'var(--text-cm-number-color)' },
|
||||
{ tag: highlightTags.keyword, color: 'var(--text-cm-keyword-color)' },
|
||||
{ tag: highlightTags.comment, color: 'var(--text-cm-comment-color)' },
|
||||
{
|
||||
tag: highlightTags.variableName,
|
||||
color: 'var(--text-cm-variable-name-color)',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const yamlLanguage = new LanguageSupport(StreamLanguage.define(yaml));
|
||||
const dockerFileLanguage = new LanguageSupport(
|
||||
StreamLanguage.define(dockerFile)
|
||||
);
|
||||
|
||||
export function CodeEditor({
|
||||
id,
|
||||
@@ -50,8 +60,18 @@ export function CodeEditor({
|
||||
value,
|
||||
height = '500px',
|
||||
yaml: isYaml,
|
||||
dockerFile: isDockerFile,
|
||||
}: Props) {
|
||||
const extensions = useMemo(() => (isYaml ? [yamlLanguage] : []), [isYaml]);
|
||||
const extensions = useMemo(() => {
|
||||
const extensions = [];
|
||||
if (isYaml) {
|
||||
extensions.push(yamlLanguage);
|
||||
}
|
||||
if (isDockerFile) {
|
||||
extensions.push(dockerFileLanguage);
|
||||
}
|
||||
return extensions;
|
||||
}, [isYaml, isDockerFile]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -65,6 +85,10 @@ export function CodeEditor({
|
||||
id={id}
|
||||
extensions={extensions}
|
||||
height={height}
|
||||
basicSetup={{
|
||||
highlightSelectionMatches: false,
|
||||
autocompletion: false,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ import nomadicon from '@/assets/ico/vendor/nomad-icon.svg?c';
|
||||
import openldap from '@/assets/ico/vendor/openldap.svg?c';
|
||||
import proget from '@/assets/ico/vendor/proget.svg?c';
|
||||
import quay from '@/assets/ico/vendor/quay.svg?c';
|
||||
import beta from '@/assets/ico/beta.svg?c';
|
||||
|
||||
const placeholder = Placeholder;
|
||||
|
||||
@@ -76,6 +77,7 @@ export const SvgIcons = {
|
||||
proget,
|
||||
quay,
|
||||
kube,
|
||||
beta,
|
||||
};
|
||||
|
||||
interface SvgProps {
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface Props {
|
||||
icon?: React.ReactNode;
|
||||
color?: Color;
|
||||
className?: string;
|
||||
childrenWrapperClassName?: string;
|
||||
}
|
||||
|
||||
export function TextTip({
|
||||
@@ -17,11 +18,13 @@ export function TextTip({
|
||||
icon = AlertCircle,
|
||||
className,
|
||||
children,
|
||||
childrenWrapperClassName = 'text-muted',
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div className={clsx('small inline-flex items-center gap-1', className)}>
|
||||
<Icon icon={icon} mode={getMode(color)} className="shrink-0" />
|
||||
<span className="text-muted">{children}</span>
|
||||
<div className={clsx('small inline-flex gap-1', className)}>
|
||||
<Icon icon={icon} mode={getMode(color)} className="!mt-[2px]" />
|
||||
|
||||
<span className={childrenWrapperClassName}>{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
}
|
||||
|
||||
.tooltip-beteaser {
|
||||
color: var(--ui-warning-5);
|
||||
@apply text-warning-5 th-dark:text-blue-8;
|
||||
}
|
||||
|
||||
.tooltip-beteaser:hover {
|
||||
color: var(--ui-warning-5);
|
||||
@apply text-warning-5 th-dark:text-blue-8;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* used for BE teaser. Dark theme specs defined by EE-5621 */
|
||||
.btn-warninglight {
|
||||
@apply border-warning-5 bg-warning-2 text-black;
|
||||
@apply th-dark:bg-warning-5 th-dark:bg-opacity-10 th-dark:text-white;
|
||||
@apply th-dark:border-blue-8 th-dark:bg-blue-8 th-dark:bg-opacity-10 th-dark:text-white;
|
||||
@apply th-highcontrast:bg-warning-5 th-highcontrast:bg-opacity-10 th-highcontrast:text-white;
|
||||
}
|
||||
|
||||
@@ -64,9 +64,12 @@ function OptionItem({
|
||||
color="light"
|
||||
as="label"
|
||||
disabled={disabled || readOnly}
|
||||
className={clsx({
|
||||
active: selected,
|
||||
})}
|
||||
className={clsx(
|
||||
{
|
||||
active: selected,
|
||||
},
|
||||
'!static !z-auto'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<input
|
||||
|
||||
@@ -52,12 +52,9 @@ export function ContainersDatatable({
|
||||
|
||||
const [search, setSearch] = useSearchBarState(storageKey);
|
||||
|
||||
const containersQuery = useContainers(
|
||||
environment.Id,
|
||||
true,
|
||||
undefined,
|
||||
settings.autoRefreshRate * 1000
|
||||
);
|
||||
const containersQuery = useContainers(environment.Id, {
|
||||
autoRefreshRate: settings.autoRefreshRate * 1000,
|
||||
});
|
||||
|
||||
return (
|
||||
<RowProvider context={{ environment }}>
|
||||
|
||||
@@ -23,6 +23,16 @@ export async function stopContainer(
|
||||
await axios.post<void>(urlBuilder(endpointId, id, 'stop'), {});
|
||||
}
|
||||
|
||||
export async function recreateContainer(
|
||||
endpointId: EnvironmentId,
|
||||
id: ContainerId,
|
||||
pullImage: boolean
|
||||
) {
|
||||
await axios.post<void>(`/docker/${endpointId}/containers/${id}/recreate`, {
|
||||
PullImage: pullImage,
|
||||
});
|
||||
}
|
||||
|
||||
export async function restartContainer(
|
||||
endpointId: EnvironmentId,
|
||||
id: ContainerId
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import axios, {
|
||||
agentTargetHeader,
|
||||
parseAxiosError,
|
||||
} from '@/portainer/services/axios';
|
||||
import { withGlobalError } from '@/react-tools/react-query';
|
||||
|
||||
import { urlBuilder } from '../containers.service';
|
||||
import { DockerContainerResponse } from '../types/response';
|
||||
@@ -10,20 +14,27 @@ import { parseViewModel } from '../utils';
|
||||
import { Filters } from './types';
|
||||
import { queryKeys } from './query-keys';
|
||||
|
||||
interface UseContainers {
|
||||
all?: boolean;
|
||||
filters?: Filters;
|
||||
nodeName?: string;
|
||||
}
|
||||
|
||||
export function useContainers(
|
||||
environmentId: EnvironmentId,
|
||||
all = true,
|
||||
filters?: Filters,
|
||||
autoRefreshRate?: number
|
||||
{
|
||||
autoRefreshRate,
|
||||
|
||||
...params
|
||||
}: UseContainers & {
|
||||
autoRefreshRate?: number;
|
||||
} = {}
|
||||
) {
|
||||
return useQuery(
|
||||
queryKeys.filters(environmentId, all, filters),
|
||||
() => getContainers(environmentId, all, filters),
|
||||
queryKeys.filters(environmentId, params),
|
||||
() => getContainers(environmentId, params),
|
||||
{
|
||||
meta: {
|
||||
title: 'Failure',
|
||||
message: 'Unable to retrieve containers',
|
||||
},
|
||||
...withGlobalError('Unable to retrieve containers'),
|
||||
refetchInterval() {
|
||||
return autoRefreshRate ?? false;
|
||||
},
|
||||
@@ -33,14 +44,18 @@ export function useContainers(
|
||||
|
||||
async function getContainers(
|
||||
environmentId: EnvironmentId,
|
||||
all = true,
|
||||
filters?: Filters
|
||||
{ all = true, filters, nodeName }: UseContainers = {}
|
||||
) {
|
||||
try {
|
||||
const { data } = await axios.get<DockerContainerResponse[]>(
|
||||
urlBuilder(environmentId, undefined, 'json'),
|
||||
{
|
||||
params: { all, filters: filters && JSON.stringify(filters) },
|
||||
headers: nodeName
|
||||
? {
|
||||
[agentTargetHeader]: nodeName,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
);
|
||||
return data.map((c) => parseViewModel(c));
|
||||
|
||||
@@ -8,8 +8,10 @@ export const queryKeys = {
|
||||
list: (environmentId: EnvironmentId) =>
|
||||
[dockerQueryKeys.root(environmentId), 'containers'] as const,
|
||||
|
||||
filters: (environmentId: EnvironmentId, all?: boolean, filters?: Filters) =>
|
||||
[...queryKeys.list(environmentId), { all, filters }] as const,
|
||||
filters: (
|
||||
environmentId: EnvironmentId,
|
||||
params: { all?: boolean; filters?: Filters; nodeName?: string } = {}
|
||||
) => [...queryKeys.list(environmentId), params] as const,
|
||||
|
||||
container: (environmentId: EnvironmentId, id: string) =>
|
||||
[...queryKeys.list(environmentId), id] as const,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter, useCurrentStateAndParams } from '@uirouter/react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { AccessControlPanel } from '@/react/portainer/access-control/AccessControlPanel/AccessControlPanel';
|
||||
@@ -15,7 +13,7 @@ import { PageHeader } from '@@/PageHeader';
|
||||
|
||||
import { useNetwork, useDeleteNetwork } from '../queries';
|
||||
import { isSystemNetwork } from '../network.helper';
|
||||
import { DockerNetwork, NetworkContainer } from '../types';
|
||||
import { NetworkResponseContainers } from '../types';
|
||||
|
||||
import { NetworkDetailsTable } from './NetworkDetailsTable';
|
||||
import { NetworkOptionsTable } from './NetworkOptionsTable';
|
||||
@@ -25,28 +23,18 @@ export function ItemView() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [networkContainers, setNetworkContainers] = useState<
|
||||
NetworkContainer[]
|
||||
>([]);
|
||||
const {
|
||||
params: { id: networkId, nodeName },
|
||||
} = useCurrentStateAndParams();
|
||||
const environmentId = useEnvironmentId();
|
||||
|
||||
const networkQuery = useNetwork(environmentId, networkId);
|
||||
const networkQuery = useNetwork(environmentId, networkId, { nodeName });
|
||||
const deleteNetworkMutation = useDeleteNetwork();
|
||||
const filters = {
|
||||
network: [networkId],
|
||||
};
|
||||
const containersQuery = useContainers(environmentId, true, filters);
|
||||
|
||||
useEffect(() => {
|
||||
if (networkQuery.data && containersQuery.data) {
|
||||
setNetworkContainers(
|
||||
filterContainersInNetwork(networkQuery.data, containersQuery.data)
|
||||
);
|
||||
}
|
||||
}, [networkQuery.data, containersQuery.data]);
|
||||
const containersQuery = useContainers(environmentId, {
|
||||
filters: {
|
||||
network: [networkId],
|
||||
},
|
||||
nodeName,
|
||||
});
|
||||
|
||||
if (!networkQuery.data) {
|
||||
return null;
|
||||
@@ -54,6 +42,10 @@ export function ItemView() {
|
||||
|
||||
const network = networkQuery.data;
|
||||
|
||||
const networkContainers = filterContainersInNetwork(
|
||||
network.Containers,
|
||||
containersQuery.data
|
||||
);
|
||||
const resourceControl = network.Portainer?.ResourceControl
|
||||
? new ResourceControlViewModel(network.Portainer.ResourceControl)
|
||||
: undefined;
|
||||
@@ -116,24 +108,20 @@ export function ItemView() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function filterContainersInNetwork(
|
||||
network: DockerNetwork,
|
||||
containers: DockerContainer[]
|
||||
) {
|
||||
const containersInNetwork = _.compact(
|
||||
containers.map((container) => {
|
||||
const containerInNetworkResponse = network.Containers[container.Id];
|
||||
if (containerInNetworkResponse) {
|
||||
const containerInNetwork: NetworkContainer = {
|
||||
...containerInNetworkResponse,
|
||||
Id: container.Id,
|
||||
};
|
||||
return containerInNetwork;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
return containersInNetwork;
|
||||
}
|
||||
}
|
||||
|
||||
function filterContainersInNetwork(
|
||||
networkContainers?: NetworkResponseContainers,
|
||||
containers: DockerContainer[] = []
|
||||
) {
|
||||
if (!networkContainers) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return containers
|
||||
.filter((container) => networkContainers[container.Id])
|
||||
.map((container) => ({
|
||||
...networkContainers[container.Id],
|
||||
Id: container.Id,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Authorized } from '@/react/hooks/useUser';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { Icon } from '@/react/components/Icon';
|
||||
|
||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
|
||||
import { TableContainer, TableTitle } from '@@/datatables';
|
||||
import { DetailsTable } from '@@/DetailsTable';
|
||||
import { Button } from '@@/buttons';
|
||||
import { Link } from '@@/Link';
|
||||
@@ -42,53 +42,51 @@ export function NetworkContainersTable({
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableTitle label="Containers in network" icon={Server} />
|
||||
<Table className="nopadding">
|
||||
<DetailsTable
|
||||
headers={tableHeaders}
|
||||
dataCy="networkDetails-networkContainers"
|
||||
>
|
||||
{networkContainers.map((container) => (
|
||||
<tr key={container.Id}>
|
||||
<td>
|
||||
<Link
|
||||
to="docker.containers.container"
|
||||
params={{
|
||||
id: container.Id,
|
||||
nodeName,
|
||||
<DetailsTable
|
||||
headers={tableHeaders}
|
||||
dataCy="networkDetails-networkContainers"
|
||||
>
|
||||
{networkContainers.map((container) => (
|
||||
<tr key={container.Id}>
|
||||
<td>
|
||||
<Link
|
||||
to="docker.containers.container"
|
||||
params={{
|
||||
id: container.Id,
|
||||
nodeName,
|
||||
}}
|
||||
title={container.Name}
|
||||
>
|
||||
{container.Name}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{container.IPv4Address || '-'}</td>
|
||||
<td>{container.IPv6Address || '-'}</td>
|
||||
<td>{container.MacAddress || '-'}</td>
|
||||
<td>
|
||||
<Authorized authorizations="DockerNetworkDisconnect">
|
||||
<Button
|
||||
data-cy={`networkDetails-disconnect${container.Name}`}
|
||||
size="xsmall"
|
||||
color="dangerlight"
|
||||
onClick={() => {
|
||||
if (container.Id) {
|
||||
disconnectContainer.mutate({
|
||||
containerId: container.Id,
|
||||
environmentId,
|
||||
networkId,
|
||||
});
|
||||
}
|
||||
}}
|
||||
title={container.Name}
|
||||
>
|
||||
{container.Name}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{container.IPv4Address || '-'}</td>
|
||||
<td>{container.IPv6Address || '-'}</td>
|
||||
<td>{container.MacAddress || '-'}</td>
|
||||
<td>
|
||||
<Authorized authorizations="DockerNetworkDisconnect">
|
||||
<Button
|
||||
data-cy={`networkDetails-disconnect${container.Name}`}
|
||||
size="xsmall"
|
||||
color="dangerlight"
|
||||
onClick={() => {
|
||||
if (container.Id) {
|
||||
disconnectContainer.mutate({
|
||||
containerId: container.Id,
|
||||
environmentId,
|
||||
networkId,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon={Trash2} class-name="icon-secondary icon-md" />
|
||||
Leave Network
|
||||
</Button>
|
||||
</Authorized>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</DetailsTable>
|
||||
</Table>
|
||||
<Icon icon={Trash2} class-name="icon-secondary icon-md" />
|
||||
Leave Network
|
||||
</Button>
|
||||
</Authorized>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</DetailsTable>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Share2, Trash2 } from 'lucide-react';
|
||||
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
||||
import { Authorized } from '@/react/hooks/useUser';
|
||||
|
||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
|
||||
import { TableContainer, TableTitle } from '@@/datatables';
|
||||
import { DetailsTable } from '@@/DetailsTable';
|
||||
import { Button } from '@@/buttons';
|
||||
import { Icon } from '@@/Icon';
|
||||
@@ -32,76 +32,74 @@ export function NetworkDetailsTable({
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableTitle label="Network details" icon={Share2} />
|
||||
<Table className="nopadding">
|
||||
<DetailsTable dataCy="networkDetails-detailsTable">
|
||||
{/* networkRowContent */}
|
||||
<DetailsTable.Row label="Name">{network.Name}</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Id">
|
||||
{network.Id}
|
||||
{allowRemoveNetwork && (
|
||||
<Authorized authorizations="DockerNetworkDelete">
|
||||
<Button
|
||||
data-cy="networkDetails-deleteNetwork"
|
||||
size="xsmall"
|
||||
color="danger"
|
||||
onClick={() => onRemoveNetworkClicked()}
|
||||
>
|
||||
<Icon
|
||||
icon={Trash2}
|
||||
className="space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Delete this network
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Driver">{network.Driver}</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Scope">{network.Scope}</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Attachable">
|
||||
{String(network.Attachable)}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Internal">
|
||||
{String(network.Internal)}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable dataCy="networkDetails-detailsTable">
|
||||
{/* networkRowContent */}
|
||||
<DetailsTable.Row label="Name">{network.Name}</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Id">
|
||||
{network.Id}
|
||||
{allowRemoveNetwork && (
|
||||
<Authorized authorizations="DockerNetworkDelete">
|
||||
<Button
|
||||
data-cy="networkDetails-deleteNetwork"
|
||||
size="xsmall"
|
||||
color="danger"
|
||||
onClick={() => onRemoveNetworkClicked()}
|
||||
>
|
||||
<Icon
|
||||
icon={Trash2}
|
||||
className="space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Delete this network
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Driver">{network.Driver}</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Scope">{network.Scope}</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Attachable">
|
||||
{String(network.Attachable)}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Internal">
|
||||
{String(network.Internal)}
|
||||
</DetailsTable.Row>
|
||||
|
||||
{/* IPV4 ConfigRowContent */}
|
||||
{ipv4Configs.map((config) => (
|
||||
<Fragment key={config.Subnet}>
|
||||
<DetailsTable.Row
|
||||
label={`IPV4 Subnet${getConfigDetails(config.Subnet)}`}
|
||||
>
|
||||
{`IPV4 Gateway${getConfigDetails(config.Gateway)}`}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row
|
||||
label={`IPV4 IP Range${getConfigDetails(config.IPRange)}`}
|
||||
>
|
||||
{`IPV4 Excluded IPs${getAuxiliaryAddresses(
|
||||
config.AuxiliaryAddresses
|
||||
)}`}
|
||||
</DetailsTable.Row>
|
||||
</Fragment>
|
||||
))}
|
||||
{/* IPV4 ConfigRowContent */}
|
||||
{ipv4Configs.map((config) => (
|
||||
<Fragment key={config.Subnet}>
|
||||
<DetailsTable.Row
|
||||
label={`IPV4 Subnet${getConfigDetails(config.Subnet)}`}
|
||||
>
|
||||
{`IPV4 Gateway${getConfigDetails(config.Gateway)}`}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row
|
||||
label={`IPV4 IP Range${getConfigDetails(config.IPRange)}`}
|
||||
>
|
||||
{`IPV4 Excluded IPs${getAuxiliaryAddresses(
|
||||
config.AuxiliaryAddresses
|
||||
)}`}
|
||||
</DetailsTable.Row>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
{/* IPV6 ConfigRowContent */}
|
||||
{ipv6Configs.map((config) => (
|
||||
<Fragment key={config.Subnet}>
|
||||
<DetailsTable.Row
|
||||
label={`IPV6 Subnet${getConfigDetails(config.Subnet)}`}
|
||||
>
|
||||
{`IPV6 Gateway${getConfigDetails(config.Gateway)}`}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row
|
||||
label={`IPV6 IP Range${getConfigDetails(config.IPRange)}`}
|
||||
>
|
||||
{`IPV6 Excluded IPs${getAuxiliaryAddresses(
|
||||
config.AuxiliaryAddresses
|
||||
)}`}
|
||||
</DetailsTable.Row>
|
||||
</Fragment>
|
||||
))}
|
||||
</DetailsTable>
|
||||
</Table>
|
||||
{/* IPV6 ConfigRowContent */}
|
||||
{ipv6Configs.map((config) => (
|
||||
<Fragment key={config.Subnet}>
|
||||
<DetailsTable.Row
|
||||
label={`IPV6 Subnet${getConfigDetails(config.Subnet)}`}
|
||||
>
|
||||
{`IPV6 Gateway${getConfigDetails(config.Gateway)}`}
|
||||
</DetailsTable.Row>
|
||||
<DetailsTable.Row
|
||||
label={`IPV6 IP Range${getConfigDetails(config.IPRange)}`}
|
||||
>
|
||||
{`IPV6 Excluded IPs${getAuxiliaryAddresses(
|
||||
config.AuxiliaryAddresses
|
||||
)}`}
|
||||
</DetailsTable.Row>
|
||||
</Fragment>
|
||||
))}
|
||||
</DetailsTable>
|
||||
</TableContainer>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Share2 } from 'lucide-react';
|
||||
|
||||
import { Table, TableContainer, TableTitle } from '@@/datatables';
|
||||
import { TableContainer, TableTitle } from '@@/datatables';
|
||||
import { DetailsTable } from '@@/DetailsTable';
|
||||
|
||||
import { NetworkOptions } from '../types';
|
||||
@@ -19,15 +19,13 @@ export function NetworkOptionsTable({ options }: Props) {
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableTitle label="Network options" icon={Share2} />
|
||||
<Table className="nopadding">
|
||||
<DetailsTable dataCy="networkDetails-networkOptionsTable">
|
||||
{networkEntries.map(([key, value]) => (
|
||||
<DetailsTable.Row key={key} label={key}>
|
||||
{value}
|
||||
</DetailsTable.Row>
|
||||
))}
|
||||
</DetailsTable>
|
||||
</Table>
|
||||
<DetailsTable dataCy="networkDetails-networkOptionsTable">
|
||||
{networkEntries.map(([key, value]) => (
|
||||
<DetailsTable.Row key={key} label={key}>
|
||||
{value}
|
||||
</DetailsTable.Row>
|
||||
))}
|
||||
</DetailsTable>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ContainerId } from '@/react/docker/containers/types';
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import axios, {
|
||||
agentTargetHeader,
|
||||
parseAxiosError,
|
||||
} from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { NetworkId, DockerNetwork } from './types';
|
||||
@@ -8,11 +11,19 @@ type NetworkAction = 'connect' | 'disconnect' | 'create';
|
||||
|
||||
export async function getNetwork(
|
||||
environmentId: EnvironmentId,
|
||||
networkId: NetworkId
|
||||
networkId: NetworkId,
|
||||
{ nodeName }: { nodeName?: string } = {}
|
||||
) {
|
||||
try {
|
||||
const { data: network } = await axios.get<DockerNetwork>(
|
||||
buildUrl(environmentId, networkId)
|
||||
buildUrl(environmentId, networkId),
|
||||
nodeName
|
||||
? {
|
||||
headers: {
|
||||
[agentTargetHeader]: nodeName,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
return network;
|
||||
} catch (e) {
|
||||
|
||||
@@ -14,10 +14,21 @@ import {
|
||||
} from './network.service';
|
||||
import { NetworkId } from './types';
|
||||
|
||||
export function useNetwork(environmentId: EnvironmentId, networkId: NetworkId) {
|
||||
export function useNetwork(
|
||||
environmentId: EnvironmentId,
|
||||
networkId: NetworkId,
|
||||
{ nodeName }: { nodeName?: string } = {}
|
||||
) {
|
||||
return useQuery(
|
||||
['environments', environmentId, 'docker', 'networks', networkId],
|
||||
() => getNetwork(environmentId, networkId),
|
||||
[
|
||||
'environments',
|
||||
environmentId,
|
||||
'docker',
|
||||
'networks',
|
||||
networkId,
|
||||
{ nodeName },
|
||||
],
|
||||
() => getNetwork(environmentId, networkId, { nodeName }),
|
||||
{
|
||||
onError: (err) => {
|
||||
notifyError('Failure', err as Error, 'Unable to get network');
|
||||
|
||||
@@ -49,14 +49,12 @@ export function StackContainersDatatable({ environment, stackName }: Props) {
|
||||
columns.filter((col) => col.canHide).map((col) => col.id)
|
||||
);
|
||||
|
||||
const containersQuery = useContainers(
|
||||
environment.Id,
|
||||
true,
|
||||
{
|
||||
const containersQuery = useContainers(environment.Id, {
|
||||
filters: {
|
||||
label: [`com.docker.compose.project=${stackName}`],
|
||||
},
|
||||
settings.autoRefreshRate * 1000
|
||||
);
|
||||
autoRefreshRate: settings.autoRefreshRate * 1000,
|
||||
});
|
||||
|
||||
return (
|
||||
<RowProvider context={{ environment }}>
|
||||
|
||||
@@ -55,7 +55,10 @@ function CreateView() {
|
||||
breadcrumbs="Edge agent update and rollback"
|
||||
/>
|
||||
|
||||
<BetaAlert />
|
||||
<BetaAlert
|
||||
className="ml-[15px] mb-2"
|
||||
message="Beta feature - currently limited to standalone Linux and Nomad edge devices."
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
|
||||
@@ -77,7 +77,10 @@ function ItemView() {
|
||||
]}
|
||||
/>
|
||||
|
||||
<BetaAlert />
|
||||
<BetaAlert
|
||||
className="ml-[15px] mb-2"
|
||||
message="Beta feature - currently limited to standalone Linux and Nomad edge devices."
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
|
||||
@@ -42,7 +42,10 @@ export function ListView() {
|
||||
breadcrumbs="Update and rollback"
|
||||
/>
|
||||
|
||||
<BetaAlert />
|
||||
<BetaAlert
|
||||
className="ml-[15px] mb-2"
|
||||
message="Beta feature - currently limited to standalone Linux and Nomad edge devices."
|
||||
/>
|
||||
|
||||
<Datatable
|
||||
dataset={listQuery.data}
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
import { InformationPanel } from '@@/InformationPanel';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
export function BetaAlert() {
|
||||
interface Props {
|
||||
message: string;
|
||||
className?: string;
|
||||
isHtml?: boolean;
|
||||
}
|
||||
|
||||
export function BetaAlert({ message, className, isHtml }: Props) {
|
||||
return (
|
||||
<InformationPanel title="Limited Feature">
|
||||
<TextTip>
|
||||
This feature is currently in beta and is limited to standalone linux
|
||||
edge devices.
|
||||
</TextTip>
|
||||
</InformationPanel>
|
||||
<TextTip
|
||||
icon="svg-beta"
|
||||
className={className}
|
||||
childrenWrapperClassName="text-warning"
|
||||
>
|
||||
{!isHtml ? (
|
||||
message
|
||||
) : (
|
||||
// eslint-disable-next-line react/no-danger
|
||||
<span dangerouslySetInnerHTML={{ __html: message }} />
|
||||
)}
|
||||
</TextTip>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user