Compare commits
4 Commits
release/2.
...
test-versi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ababd63d97 | ||
|
|
b40d22dc74 | ||
|
|
a257696c25 | ||
|
|
f742937359 |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -93,6 +93,7 @@ body:
|
||||
description: We only provide support for the most recent version of Portainer and the previous 3 versions. If you are on an older version of Portainer we recommend [upgrading first](https://docs.portainer.io/start/upgrade) in case your bug has already been fixed.
|
||||
multiple: false
|
||||
options:
|
||||
- '2.22.0'
|
||||
- '2.21.2'
|
||||
- '2.21.1'
|
||||
- '2.21.0'
|
||||
|
||||
166
.github/workflows/ci.yaml
vendored
Normal file
166
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'release/*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'release/*'
|
||||
- 'feat/*'
|
||||
- 'fix/*'
|
||||
- 'refactor/*'
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
DOCKER_HUB_REPO: portainerci/portainer-ce
|
||||
EXTENSION_HUB_REPO: portainerci/portainer-docker-extension
|
||||
NODE_VERSION: 18.x
|
||||
|
||||
jobs:
|
||||
build_images:
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- { platform: linux, arch: amd64, version: "" }
|
||||
- { platform: linux, arch: arm64, version: "" }
|
||||
- { platform: linux, arch: arm, version: "" }
|
||||
- { platform: linux, arch: ppc64le, version: "" }
|
||||
- { platform: windows, arch: amd64, version: 1809 }
|
||||
- { platform: windows, arch: amd64, version: ltsc2022 }
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: '[preparation] checkout the current branch'
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
- name: '[preparation] set up golang'
|
||||
uses: actions/setup-go@v5.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: '[preparation] set up node.js'
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
- name: '[preparation] set up qemu'
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
- name: '[preparation] set up docker context for buildx'
|
||||
run: docker context create builders
|
||||
- name: '[preparation] set up docker buildx'
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
with:
|
||||
endpoint: builders
|
||||
- name: '[preparation] docker login'
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
- name: '[preparation] set the container image tag'
|
||||
run: |
|
||||
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
|
||||
# use the release branch name as the tag for release branches
|
||||
# for instance, release/2.19 becomes 2.19
|
||||
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -d "/" -f 2)
|
||||
elif [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
|
||||
# use pr${{ github.event.number }} as the tag for pull requests
|
||||
# for instance, pr123
|
||||
CONTAINER_IMAGE_TAG="pr${{ github.event.number }}"
|
||||
else
|
||||
# replace / with - in the branch name
|
||||
# for instance, feature/1.0.0 -> feature-1.0.0
|
||||
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | sed 's/\//-/g')
|
||||
fi
|
||||
|
||||
echo "CONTAINER_IMAGE_TAG=${CONTAINER_IMAGE_TAG}-${{ matrix.config.platform }}${{ matrix.config.version }}-${{ matrix.config.arch }}" >> $GITHUB_ENV
|
||||
- name: '[execution] build linux & windows portainer binaries'
|
||||
run: |
|
||||
export YARN_VERSION=$(yarn --version)
|
||||
export WEBPACK_VERSION=$(yarn list webpack --depth=0 | grep webpack | awk -F@ '{print $2}')
|
||||
export BUILDNUMBER=${GITHUB_RUN_NUMBER}
|
||||
GIT_COMMIT_HASH_LONG=${{ github.sha }}
|
||||
export GIT_COMMIT_HASH_SHORT={GIT_COMMIT_HASH_LONG:0:7}
|
||||
|
||||
NODE_ENV="testing"
|
||||
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
|
||||
NODE_ENV="production"
|
||||
fi
|
||||
|
||||
make build-all PLATFORM=${{ matrix.config.platform }} ARCH=${{ matrix.config.arch }} ENV=${NODE_ENV}
|
||||
env:
|
||||
CONTAINER_IMAGE_TAG: ${{ env.CONTAINER_IMAGE_TAG }}
|
||||
- name: '[execution] build and push docker images'
|
||||
run: |
|
||||
if [ "${{ matrix.config.platform }}" == "windows" ]; then
|
||||
mv dist/portainer dist/portainer.exe
|
||||
docker buildx build --output=type=registry --attest type=provenance,mode=max --attest type=sbom,disabled=false --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} --build-arg OSVERSION=${{ matrix.config.version }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
|
||||
else
|
||||
docker buildx build --output=type=registry --attest type=provenance,mode=max --attest type=sbom,disabled=false --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
|
||||
docker buildx build --output=type=registry --attest type=provenance,mode=max --attest type=sbom,disabled=false --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" -f build/${{ matrix.config.platform }}/alpine.Dockerfile .
|
||||
|
||||
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
|
||||
docker buildx build --output=type=registry --attest type=provenance,mode=max --attest type=sbom,disabled=false --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
|
||||
docker buildx build --output=type=registry --attest type=provenance,mode=max --attest type=sbom,disabled=false --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" -f build/${{ matrix.config.platform }}/alpine.Dockerfile .
|
||||
fi
|
||||
fi
|
||||
env:
|
||||
CONTAINER_IMAGE_TAG: ${{ env.CONTAINER_IMAGE_TAG }}
|
||||
build_manifests:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
needs: [build_images]
|
||||
steps:
|
||||
- name: '[preparation] docker login'
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
- name: '[preparation] set up docker context for buildx'
|
||||
run: docker version && docker context create builders
|
||||
- name: '[preparation] set up docker buildx'
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
with:
|
||||
endpoint: builders
|
||||
- name: '[execution] build and push manifests'
|
||||
run: |
|
||||
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
|
||||
# use the release branch name as the tag for release branches
|
||||
# for instance, release/2.19 becomes 2.19
|
||||
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -d "/" -f 2)
|
||||
elif [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
|
||||
# use pr${{ github.event.number }} as the tag for pull requests
|
||||
# for instance, pr123
|
||||
CONTAINER_IMAGE_TAG="pr${{ github.event.number }}"
|
||||
else
|
||||
# replace / with - in the branch name
|
||||
# for instance, feature/1.0.0 -> feature-1.0.0
|
||||
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | sed 's/\//-/g')
|
||||
fi
|
||||
|
||||
docker buildx imagetools create -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-ppc64le" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-windows1809-amd64" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-windowsltsc2022-amd64"
|
||||
|
||||
docker buildx imagetools create -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64-alpine" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64-alpine" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm-alpine" \
|
||||
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-ppc64le-alpine"
|
||||
|
||||
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
|
||||
docker buildx imagetools create -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}" \
|
||||
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64" \
|
||||
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64"
|
||||
fi
|
||||
15
.github/workflows/label-conflcts.yaml
vendored
Normal file
15
.github/workflows/label-conflcts.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- 'release/**'
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mschilde/auto-label-merge-conflicts@master
|
||||
with:
|
||||
CONFLICT_LABEL_NAME: 'has conflicts'
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MAX_RETRIES: 10
|
||||
WAIT_MS: 60000
|
||||
55
.github/workflows/lint.yml
vendored
Normal file
55
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- release/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- release/*
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.5
|
||||
NODE_VERSION: 18.x
|
||||
|
||||
jobs:
|
||||
run-linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- run: yarn --frozen-lockfile
|
||||
- name: Run linters
|
||||
uses: wearerequired/lint-action@v1
|
||||
with:
|
||||
eslint: true
|
||||
eslint_extensions: ts,tsx,js,jsx
|
||||
prettier: true
|
||||
prettier_dir: app/
|
||||
gofmt: true
|
||||
gofmt_dir: api/
|
||||
- name: Typecheck
|
||||
uses: icrawl/action-tsc@v1
|
||||
- name: GolangCI-Lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.59.1
|
||||
args: --timeout=10m -c .golangci.yaml
|
||||
254
.github/workflows/nightly-security-scan.yml
vendored
Normal file
254
.github/workflows/nightly-security-scan.yml
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
name: Nightly Code Security Scan
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 20 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.5
|
||||
DOCKER_HUB_REPO: portainerci/portainer-ce
|
||||
DOCKER_HUB_IMAGE_TAG: develop
|
||||
|
||||
jobs:
|
||||
client-dependencies:
|
||||
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:
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: scan vulnerabilities by Snyk
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
json: true
|
||||
|
||||
- name: upload scan result as develop artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: js-security-scan-develop-result
|
||||
path: snyk.json
|
||||
|
||||
- name: develop scan report export to html
|
||||
run: |
|
||||
$(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 html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-js-result-${{github.run_id}}
|
||||
path: js-result.html
|
||||
|
||||
- name: analyse vulnerabilities
|
||||
id: set-matrix
|
||||
run: |
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
if: >- # only run for develop branch
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
go: ${{ steps.set-matrix.outputs.go_result }}
|
||||
steps:
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: download Go modules
|
||||
run: cd ./api && go get -t -v -d ./...
|
||||
|
||||
- name: scan vulnerabilities by Snyk
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
run: |
|
||||
yarn global add snyk
|
||||
snyk test --file=./go.mod --json-file-output=snyk.json 2>/dev/null || :
|
||||
|
||||
- name: upload scan result as develop artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: go-security-scan-develop-result
|
||||
path: snyk.json
|
||||
|
||||
- name: develop scan report export to html
|
||||
run: |
|
||||
$(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 html file as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-go-result-${{github.run_id}}
|
||||
path: go-result.html
|
||||
|
||||
- name: analyse vulnerabilities
|
||||
id: set-matrix
|
||||
run: |
|
||||
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: Image Vulnerability Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.ref == 'refs/heads/develop'
|
||||
outputs:
|
||||
image-trivy: ${{ steps.set-trivy-matrix.outputs.image_trivy_result }}
|
||||
image-docker-scout: ${{ steps.set-docker-scout-matrix.outputs.image_docker_scout_result }}
|
||||
steps:
|
||||
- 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 ${{ env.DOCKER_HUB_REPO }}:${{ env.DOCKER_HUB_IMAGE_TAG }}
|
||||
|
||||
- name: upload Trivy image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-develop-result
|
||||
path: image-trivy.json
|
||||
|
||||
- name: develop Trivy scan report export to html
|
||||
run: |
|
||||
$(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-trivy-result")
|
||||
|
||||
- name: upload html file as Trivy artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-${{github.run_id}}
|
||||
path: image-trivy-result.html
|
||||
|
||||
- name: analyse vulnerabilities from Trivy
|
||||
id: set-trivy-matrix
|
||||
run: |
|
||||
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_trivy_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: scan vulnerabilities by Docker Scout
|
||||
uses: docker/scout-action@v1
|
||||
continue-on-error: true
|
||||
with:
|
||||
command: cves
|
||||
image: ${{ env.DOCKER_HUB_REPO }}:${{ env.DOCKER_HUB_IMAGE_TAG }}
|
||||
sarif-file: image-docker-scout.json
|
||||
dockerhub-user: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
dockerhub-password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: upload Docker Scout image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-develop-result
|
||||
path: image-docker-scout.json
|
||||
|
||||
- name: develop Docker Scout scan report export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=docker-scout --path="/data/image-docker-scout.json" --output-type=table --export --export-filename="/data/image-docker-scout-result")
|
||||
|
||||
- name: upload html file as Docker Scout artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-${{github.run_id}}
|
||||
path: image-docker-scout-result.html
|
||||
|
||||
- name: analyse vulnerabilities from Docker Scout
|
||||
id: set-docker-scout-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=docker-scout --path="/data/image-docker-scout.json" --output-type=matrix)
|
||||
echo "image_docker_scout_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
result-analysis:
|
||||
name: Analyse Scan Results
|
||||
needs: [client-dependencies, server-dependencies, image-vulnerability]
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.ref == 'refs/heads/develop'
|
||||
strategy:
|
||||
matrix:
|
||||
js: ${{fromJson(needs.client-dependencies.outputs.js)}}
|
||||
go: ${{fromJson(needs.server-dependencies.outputs.go)}}
|
||||
image-trivy: ${{fromJson(needs.image-vulnerability.outputs.image-trivy)}}
|
||||
image-docker-scout: ${{fromJson(needs.image-vulnerability.outputs.image-docker-scout)}}
|
||||
steps:
|
||||
- name: display the results of js, Go, and image scan
|
||||
run: |
|
||||
echo "${{ matrix.js.status }}"
|
||||
echo "${{ matrix.go.status }}"
|
||||
echo "${{ matrix.image-trivy.status }}"
|
||||
echo "${{ matrix.image-docker-scout.status }}"
|
||||
echo "${{ matrix.js.summary }}"
|
||||
echo "${{ matrix.go.summary }}"
|
||||
echo "${{ matrix.image-trivy.summary }}"
|
||||
echo "${{ matrix.image-docker-scout.summary }}"
|
||||
|
||||
- name: send message to Slack
|
||||
if: >-
|
||||
matrix.js.status == 'failure' ||
|
||||
matrix.go.status == 'failure' ||
|
||||
matrix.image-trivy.status == 'failure' ||
|
||||
matrix.image-docker-scout.status == 'failure'
|
||||
uses: slackapi/slack-github-action@v1.23.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "Code Scanning Result (*${{ github.repository }}*)\n*<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|GitHub Actions Workflow URL>*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"attachments": [
|
||||
{
|
||||
"color": "#FF0000",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*JS dependency check*: *${{ matrix.js.status }}*\n${{ matrix.js.summary }}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Go dependency check*: *${{ matrix.go.status }}*\n${{ matrix.go.summary }}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Image Trivy vulnerability check*: *${{ matrix.image-trivy.status }}*\n${{ matrix.image-trivy.summary }}\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Image Docker Scout vulnerability check*: *${{ matrix.image-docker-scout.status }}*\n${{ matrix.image-docker-scout.summary }}\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SECURITY_SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
298
.github/workflows/pr-security.yml
vendored
Normal file
298
.github/workflows/pr-security.yml
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
name: PR Code Security Scan
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types:
|
||||
- submitted
|
||||
- edited
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'go.mod'
|
||||
- 'build/linux/Dockerfile'
|
||||
- 'build/linux/alpine.Dockerfile'
|
||||
- 'build/windows/Dockerfile'
|
||||
- '.github/workflows/pr-security.yml'
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.5
|
||||
NODE_VERSION: 18.x
|
||||
|
||||
jobs:
|
||||
client-dependencies:
|
||||
name: Client Dependency Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan' &&
|
||||
github.event.pull_request.draft == false
|
||||
outputs:
|
||||
jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }}
|
||||
steps:
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: scan vulnerabilities by Snyk
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
json: true
|
||||
|
||||
- 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 built by nightly scan
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./snyk.json ./js-snyk-feature.json
|
||||
(gh run download -n js-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
|
||||
if [[ -e ./snyk.json ]]; then
|
||||
mv ./snyk.json ./js-snyk-develop.json
|
||||
else
|
||||
echo "null" > ./js-snyk-develop.json
|
||||
fi
|
||||
|
||||
- name: pr vs develop scan report comparison export to html
|
||||
run: |
|
||||
$(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 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 different vulnerabilities against develop branch
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan' &&
|
||||
github.event.pull_request.draft == false
|
||||
outputs:
|
||||
godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }}
|
||||
steps:
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: download Go modules
|
||||
run: cd ./api && go get -t -v -d ./...
|
||||
|
||||
- name: scan vulnerabilities by Snyk
|
||||
continue-on-error: true # To make sure that artifact upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
run: |
|
||||
yarn global add snyk
|
||||
snyk test --file=./go.mod --json-file-output=snyk.json 2>/dev/null || :
|
||||
|
||||
- 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 built by nightly scan
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./snyk.json ./go-snyk-feature.json
|
||||
(gh run download -n go-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
|
||||
if [[ -e ./snyk.json ]]; then
|
||||
mv ./snyk.json ./go-snyk-develop.json
|
||||
else
|
||||
echo "null" > ./go-snyk-develop.json
|
||||
fi
|
||||
|
||||
- name: pr vs develop scan report comparison export to html
|
||||
run: |
|
||||
$(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 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 different vulnerabilities against develop branch
|
||||
id: set-diff-matrix
|
||||
run: |
|
||||
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: Image Vulnerability Check
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan' &&
|
||||
github.event.pull_request.draft == false
|
||||
outputs:
|
||||
imagediff-trivy: ${{ steps.set-diff-trivy-matrix.outputs.image_diff_trivy_result }}
|
||||
imagediff-docker-scout: ${{ steps.set-diff-docker-scout-matrix.outputs.image_diff_docker_scout_result }}
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install packages
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: build
|
||||
run: make build-all
|
||||
|
||||
- name: set up docker buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: build and compress image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: build/linux/Dockerfile
|
||||
tags: local-portainer:${{ github.sha }}
|
||||
outputs: type=docker,dest=/tmp/local-portainer-image.tar
|
||||
|
||||
- name: load docker image
|
||||
run: |
|
||||
docker load --input /tmp/local-portainer-image.tar
|
||||
|
||||
- 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 local-portainer:${{ github.sha }}
|
||||
|
||||
- name: upload Trivy image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-feature-result
|
||||
path: image-trivy.json
|
||||
|
||||
- name: download Trivy artifacts from develop branch built by nightly scan
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./image-trivy.json ./image-trivy-feature.json
|
||||
(gh run download -n image-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
|
||||
if [[ -e ./image-trivy.json ]]; then
|
||||
mv ./image-trivy.json ./image-trivy-develop.json
|
||||
else
|
||||
echo "null" > ./image-trivy-develop.json
|
||||
fi
|
||||
|
||||
- name: pr vs develop Trivy scan report comparison export to html
|
||||
run: |
|
||||
$(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-trivy-result")
|
||||
|
||||
- name: upload html file as Trivy artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-compare-to-develop-${{github.run_id}}
|
||||
path: image-trivy-result.html
|
||||
|
||||
- name: analyse different vulnerabilities against develop branch by Trivy
|
||||
id: set-diff-trivy-matrix
|
||||
run: |
|
||||
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_trivy_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: scan vulnerabilities by Docker Scout
|
||||
uses: docker/scout-action@v1
|
||||
continue-on-error: true
|
||||
with:
|
||||
command: cves
|
||||
image: local-portainer:${{ github.sha }}
|
||||
sarif-file: image-docker-scout.json
|
||||
dockerhub-user: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
dockerhub-password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: upload Docker Scout image security scan result as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image-security-scan-feature-result
|
||||
path: image-docker-scout.json
|
||||
|
||||
- name: download Docker Scout artifacts from develop branch built by nightly scan
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./image-docker-scout.json ./image-docker-scout-feature.json
|
||||
(gh run download -n image-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
|
||||
if [[ -e ./image-docker-scout.json ]]; then
|
||||
mv ./image-docker-scout.json ./image-docker-scout-develop.json
|
||||
else
|
||||
echo "null" > ./image-docker-scout-develop.json
|
||||
fi
|
||||
|
||||
- name: pr vs develop Docker Scout scan report comparison export to html
|
||||
run: |
|
||||
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=docker-scout --path="/data/image-docker-scout-feature.json" --compare-to="/data/image-docker-scout-develop.json" --output-type=table --export --export-filename="/data/image-docker-scout-result")
|
||||
|
||||
- name: upload html file as Docker Scout artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: html-image-result-compare-to-develop-${{github.run_id}}
|
||||
path: image-docker-scout-result.html
|
||||
|
||||
- name: analyse different vulnerabilities against develop branch by Docker Scout
|
||||
id: set-diff-docker-scout-matrix
|
||||
run: |
|
||||
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=docker-scout --path="/data/image-docker-scout-feature.json" --compare-to="/data/image-docker-scout-develop.json" --output-type=matrix)
|
||||
echo "image_diff_docker_scout_result=${result}" >> $GITHUB_OUTPUT
|
||||
|
||||
result-analysis:
|
||||
name: Analyse Scan Result Against develop Branch
|
||||
needs: [client-dependencies, server-dependencies, image-vulnerability]
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request &&
|
||||
github.event.review.body == '/scan' &&
|
||||
github.event.pull_request.draft == false
|
||||
strategy:
|
||||
matrix:
|
||||
jsdiff: ${{fromJson(needs.client-dependencies.outputs.jsdiff)}}
|
||||
godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}}
|
||||
imagediff-trivy: ${{fromJson(needs.image-vulnerability.outputs.imagediff-trivy)}}
|
||||
imagediff-docker-scout: ${{fromJson(needs.image-vulnerability.outputs.imagediff-docker-scout)}}
|
||||
steps:
|
||||
- name: check job status of diff result
|
||||
if: >-
|
||||
matrix.jsdiff.status == 'failure' ||
|
||||
matrix.godiff.status == 'failure' ||
|
||||
matrix.imagediff-trivy.status == 'failure' ||
|
||||
matrix.imagediff-docker-scout.status == 'failure'
|
||||
run: |
|
||||
echo "${{ matrix.jsdiff.status }}"
|
||||
echo "${{ matrix.godiff.status }}"
|
||||
echo "${{ matrix.imagediff-trivy.status }}"
|
||||
echo "${{ matrix.imagediff-docker-scout.status }}"
|
||||
echo "${{ matrix.jsdiff.summary }}"
|
||||
echo "${{ matrix.godiff.summary }}"
|
||||
echo "${{ matrix.imagediff-trivy.summary }}"
|
||||
echo "${{ matrix.imagediff-docker-scout.summary }}"
|
||||
exit 1
|
||||
19
.github/workflows/rebase.yml
vendored
Normal file
19
.github/workflows/rebase.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Automatic Rebase
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
28
.github/workflows/stale.yml
vendored
Normal file
28
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Close Stale Issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *'
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Issue Config
|
||||
days-before-issue-stale: 60
|
||||
days-before-issue-close: 7
|
||||
stale-issue-label: 'status/stale'
|
||||
exempt-all-issue-milestones: true # Do not stale issues in a milestone
|
||||
exempt-issue-labels: kind/enhancement, kind/style, kind/workaround, kind/refactor, bug/need-confirmation, bug/confirmed, status/discuss
|
||||
stale-issue-message: 'This issue has been marked as stale as it has not had recent activity, it will be closed if no further activity occurs in the next 7 days. If you believe that it has been incorrectly labelled as stale, leave a comment and the label will be removed.'
|
||||
close-issue-message: 'Since no further activity has appeared on this issue it will be closed. If you believe that it has been incorrectly closed, leave a comment mentioning `portainer/support` and one of our staff will then review the issue. Note - If it is an old bug report, make sure that it is reproduceable in the latest version of Portainer as it may have already been fixed.'
|
||||
|
||||
# Pull Request Config
|
||||
days-before-pr-stale: -1 # Do not stale pull request
|
||||
days-before-pr-close: -1 # Do not close pull request
|
||||
76
.github/workflows/test.yaml
vendored
Normal file
76
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: Test
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.5
|
||||
NODE_VERSION: 18.x
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- release/*
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- release/*
|
||||
|
||||
jobs:
|
||||
test-client:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
steps:
|
||||
- name: 'checkout the current branch'
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
|
||||
- name: 'set up node.js'
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn --frozen-lockfile
|
||||
|
||||
- name: Run tests
|
||||
run: make test-client ARGS="--maxWorkers=2 --minWorkers=1"
|
||||
|
||||
test-server:
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- { platform: linux, arch: amd64 }
|
||||
- { platform: linux, arch: arm64 }
|
||||
- { platform: windows, arch: amd64, version: 1809 }
|
||||
- { platform: windows, arch: amd64, version: ltsc2022 }
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
steps:
|
||||
- name: 'checkout the current branch'
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
|
||||
- name: 'set up golang'
|
||||
uses: actions/setup-go@v5.0.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: 'install dependencies'
|
||||
run: make test-deps PLATFORM=linux ARCH=amd64
|
||||
|
||||
- name: 'update $PATH'
|
||||
run: echo "$(pwd)/dist" >> $GITHUB_PATH
|
||||
|
||||
- name: 'run tests'
|
||||
run: make test-server
|
||||
39
.github/workflows/validate-openapi-spec.yaml
vendored
Normal file
39
.github/workflows/validate-openapi-spec.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Validate OpenAPI specs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- 'release/*'
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22.5
|
||||
NODE_VERSION: 18.x
|
||||
|
||||
jobs:
|
||||
openapi-spec:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Download golang modules
|
||||
run: cd ./api && go get -t -v -d ./...
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
- run: yarn --frozen-lockfile
|
||||
|
||||
- name: Validate OpenAPI Spec
|
||||
run: make docs-validate
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
cd $(dirname -- "$0") && yarn lint-staged
|
||||
yarn lint-staged
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package migrator
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
"github.com/portainer/portainer/api/dataservices/dockerhub"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
"github.com/portainer/portainer/api/docker/images"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
|
||||
@@ -98,8 +98,8 @@ func (handler *Handler) updateEndpointGroup(tx dataservices.DataStoreTx, endpoin
|
||||
payloadTagSet := tag.Set(payload.TagIDs)
|
||||
endpointGroupTagSet := tag.Set((endpointGroup.TagIDs))
|
||||
union := tag.Union(payloadTagSet, endpointGroupTagSet)
|
||||
intersection := tag.Intersection(payloadTagSet, endpointGroupTagSet)
|
||||
tagsChanged = len(union) > len(intersection)
|
||||
intersection := tag.IntersectionCount(payloadTagSet, endpointGroupTagSet)
|
||||
tagsChanged = len(union) > intersection
|
||||
|
||||
if tagsChanged {
|
||||
removeTags := tag.Difference(endpointGroupTagSet, payloadTagSet)
|
||||
|
||||
@@ -193,7 +193,7 @@ func (handler *Handler) filterEndpointsByQuery(
|
||||
return nil, 0, errors.WithMessage(err, "Unable to retrieve tags from the database")
|
||||
}
|
||||
|
||||
tagsMap := make(map[portainer.TagID]string)
|
||||
tagsMap := make(map[portainer.TagID]string, len(tags))
|
||||
for _, tag := range tags {
|
||||
tagsMap[tag.ID] = tag.Name
|
||||
}
|
||||
@@ -304,8 +304,7 @@ func filterEndpointsBySearchCriteria(
|
||||
) []portainer.Endpoint {
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
endpointTags := convertTagIDsToTags(tagsMap, endpoint.TagIDs)
|
||||
if endpointMatchSearchCriteria(&endpoint, endpointTags, searchCriteria) {
|
||||
if endpointMatchSearchCriteria(&endpoint, tagsMap, searchCriteria) {
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
|
||||
@@ -319,7 +318,7 @@ func filterEndpointsBySearchCriteria(
|
||||
continue
|
||||
}
|
||||
|
||||
if edgeGroupMatchSearchCriteria(&endpoint, edgeGroups, searchCriteria, endpoints, endpointGroups) {
|
||||
if edgeGroupMatchSearchCriteria(&endpoint, edgeGroups, searchCriteria, endpointGroups) {
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
|
||||
@@ -365,7 +364,7 @@ func filterEndpointsByStatuses(endpoints []portainer.Endpoint, statuses []portai
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, searchCriteria string) bool {
|
||||
func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tagsMap map[portainer.TagID]string, searchCriteria string) bool {
|
||||
if strings.Contains(strings.ToLower(endpoint.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
@@ -380,8 +379,8 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, se
|
||||
return true
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
for _, tagID := range endpoint.TagIDs {
|
||||
if strings.Contains(strings.ToLower(tagsMap[tagID]), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -391,16 +390,17 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, se
|
||||
|
||||
func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) bool {
|
||||
for _, group := range endpointGroups {
|
||||
if group.ID == endpoint.GroupID {
|
||||
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
if group.ID != endpoint.GroupID {
|
||||
continue
|
||||
}
|
||||
|
||||
tags := convertTagIDsToTags(tagsMap, group.TagIDs)
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, tagID := range group.TagIDs {
|
||||
if strings.Contains(strings.ToLower(tagsMap[tagID]), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,11 +413,10 @@ func edgeGroupMatchSearchCriteria(
|
||||
endpoint *portainer.Endpoint,
|
||||
edgeGroups []portainer.EdgeGroup,
|
||||
searchCriteria string,
|
||||
endpoints []portainer.Endpoint,
|
||||
endpointGroups []portainer.EndpointGroup,
|
||||
) bool {
|
||||
for _, edgeGroup := range edgeGroups {
|
||||
relatedEndpointIDs := edge.EdgeGroupRelatedEndpoints(&edgeGroup, endpoints, endpointGroups)
|
||||
relatedEndpointIDs := edge.EdgeGroupRelatedEndpoints(&edgeGroup, []portainer.Endpoint{*endpoint}, endpointGroups)
|
||||
|
||||
for _, endpointID := range relatedEndpointIDs {
|
||||
if endpointID == endpoint.ID {
|
||||
@@ -448,16 +447,6 @@ func filterEndpointsByTypes(endpoints []portainer.Endpoint, endpointTypes []port
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func convertTagIDsToTags(tagsMap map[portainer.TagID]string, tagIDs []portainer.TagID) []string {
|
||||
tags := make([]string, 0, len(tagIDs))
|
||||
|
||||
for _, tagID := range tagIDs {
|
||||
tags = append(tags, tagsMap[tagID])
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func filteredEndpointsByTags(endpoints []portainer.Endpoint, tagIDs []portainer.TagID, endpointGroups []portainer.EndpointGroup, partialMatch bool) []portainer.Endpoint {
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package endpoints
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
@@ -148,6 +149,103 @@ func Test_Filter_excludeIDs(t *testing.T) {
|
||||
runTests(tests, t, handler, environments)
|
||||
}
|
||||
|
||||
func BenchmarkFilterEndpointsBySearchCriteria_PartialMatch(b *testing.B) {
|
||||
n := 10000
|
||||
|
||||
endpointIDs := []portainer.EndpointID{}
|
||||
|
||||
endpoints := []portainer.Endpoint{}
|
||||
for i := range n {
|
||||
endpoints = append(endpoints, portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "endpoint-" + strconv.Itoa(i+1),
|
||||
GroupID: 1,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
})
|
||||
|
||||
endpointIDs = append(endpointIDs, portainer.EndpointID(i+1))
|
||||
}
|
||||
|
||||
endpointGroups := []portainer.EndpointGroup{}
|
||||
|
||||
edgeGroups := []portainer.EdgeGroup{}
|
||||
for i := range 1000 {
|
||||
edgeGroups = append(edgeGroups, portainer.EdgeGroup{
|
||||
ID: portainer.EdgeGroupID(i + 1),
|
||||
Name: "edge-group-" + strconv.Itoa(i+1),
|
||||
Endpoints: append([]portainer.EndpointID{}, endpointIDs...),
|
||||
Dynamic: true,
|
||||
TagIDs: []portainer.TagID{1, 2, 3},
|
||||
PartialMatch: true,
|
||||
})
|
||||
}
|
||||
|
||||
tagsMap := map[portainer.TagID]string{}
|
||||
for i := range 10 {
|
||||
tagsMap[portainer.TagID(i+1)] = "tag-" + strconv.Itoa(i+1)
|
||||
}
|
||||
|
||||
searchString := "edge-group"
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for range b.N {
|
||||
e := filterEndpointsBySearchCriteria(endpoints, endpointGroups, edgeGroups, tagsMap, searchString)
|
||||
if len(e) != n {
|
||||
b.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterEndpointsBySearchCriteria_FullMatch(b *testing.B) {
|
||||
n := 10000
|
||||
|
||||
endpointIDs := []portainer.EndpointID{}
|
||||
|
||||
endpoints := []portainer.Endpoint{}
|
||||
for i := range n {
|
||||
endpoints = append(endpoints, portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "endpoint-" + strconv.Itoa(i+1),
|
||||
GroupID: 1,
|
||||
TagIDs: []portainer.TagID{1, 2, 3},
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
})
|
||||
|
||||
endpointIDs = append(endpointIDs, portainer.EndpointID(i+1))
|
||||
}
|
||||
|
||||
endpointGroups := []portainer.EndpointGroup{}
|
||||
|
||||
edgeGroups := []portainer.EdgeGroup{}
|
||||
for i := range 1000 {
|
||||
edgeGroups = append(edgeGroups, portainer.EdgeGroup{
|
||||
ID: portainer.EdgeGroupID(i + 1),
|
||||
Name: "edge-group-" + strconv.Itoa(i+1),
|
||||
Endpoints: append([]portainer.EndpointID{}, endpointIDs...),
|
||||
Dynamic: true,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
})
|
||||
}
|
||||
|
||||
tagsMap := map[portainer.TagID]string{}
|
||||
for i := range 10 {
|
||||
tagsMap[portainer.TagID(i+1)] = "tag-" + strconv.Itoa(i+1)
|
||||
}
|
||||
|
||||
searchString := "edge-group"
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for range b.N {
|
||||
e := filterEndpointsBySearchCriteria(endpoints, endpointGroups, edgeGroups, tagsMap, searchString)
|
||||
if len(e) != n {
|
||||
b.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runTests(tests []filterTest, t *testing.T, handler *Handler, endpoints []portainer.Endpoint) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.title, func(t *testing.T) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/segmentio/encoding/json"
|
||||
)
|
||||
@@ -119,7 +119,7 @@ func HasNewerVersion(currentVersion, latestVersion string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return currentVersionSemver.LessThan(*latestVersionSemver)
|
||||
return currentVersionSemver.LessThan(latestVersionSemver)
|
||||
}
|
||||
|
||||
// @id Version
|
||||
|
||||
@@ -18,6 +18,60 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHasNewerVersion(t *testing.T) {
|
||||
// Test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
currentVersion string
|
||||
latestVersion string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "current version is less than latest version",
|
||||
currentVersion: "2.22.0",
|
||||
latestVersion: "v2.22.1",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "current version is equal to latest version",
|
||||
currentVersion: "2.22.0",
|
||||
latestVersion: "v2.22.0",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "current version is greater than latest version",
|
||||
currentVersion: "v2.22.2",
|
||||
latestVersion: "v2.22.1",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid current version",
|
||||
currentVersion: "invalid",
|
||||
latestVersion: "v2.22.0",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid latest version",
|
||||
currentVersion: "2.22.0",
|
||||
latestVersion: "invalid",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "both versions are invalid",
|
||||
currentVersion: "invalid",
|
||||
latestVersion: "invalid",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := HasNewerVersion(tt.currentVersion, tt.latestVersion)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSystemVersion(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ func edgeGroupRelatedToEndpoint(edgeGroup *portainer.EdgeGroup, endpoint *portai
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -84,12 +85,10 @@ func edgeGroupRelatedToEndpoint(edgeGroup *portainer.EdgeGroup, endpoint *portai
|
||||
if endpointGroup.TagIDs != nil {
|
||||
endpointTags = tag.Union(endpointTags, tag.Set(endpointGroup.TagIDs))
|
||||
}
|
||||
edgeGroupTags := tag.Set(edgeGroup.TagIDs)
|
||||
|
||||
if edgeGroup.PartialMatch {
|
||||
intersection := tag.Intersection(endpointTags, edgeGroupTags)
|
||||
return len(intersection) != 0
|
||||
return tag.PartialMatch(edgeGroup.TagIDs, endpointTags)
|
||||
}
|
||||
|
||||
return tag.FullMatch(edgeGroupTags, endpointTags)
|
||||
return tag.FullMatch(edgeGroup.TagIDs, endpointTags)
|
||||
}
|
||||
|
||||
@@ -1,64 +1,63 @@
|
||||
package tag
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type tagSet map[portainer.TagID]bool
|
||||
type tagSet map[portainer.TagID]struct{}
|
||||
|
||||
// Set converts an array of ids to a set
|
||||
func Set(tagIDs []portainer.TagID) tagSet {
|
||||
set := map[portainer.TagID]bool{}
|
||||
set := map[portainer.TagID]struct{}{}
|
||||
for _, tagID := range tagIDs {
|
||||
set[tagID] = true
|
||||
set[tagID] = struct{}{}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// Intersection returns a set intersection of the provided sets
|
||||
func Intersection(sets ...tagSet) tagSet {
|
||||
intersection := tagSet{}
|
||||
if len(sets) == 0 {
|
||||
return intersection
|
||||
// IntersectionCount returns the element count of the intersection of the sets
|
||||
func IntersectionCount(setA, setB tagSet) int {
|
||||
if len(setA) > len(setB) {
|
||||
setA, setB = setB, setA
|
||||
}
|
||||
setA := sets[0]
|
||||
|
||||
count := 0
|
||||
|
||||
for tag := range setA {
|
||||
inAll := true
|
||||
for _, setB := range sets {
|
||||
if !setB[tag] {
|
||||
inAll = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if inAll {
|
||||
intersection[tag] = true
|
||||
if _, ok := setB[tag]; ok {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return intersection
|
||||
return count
|
||||
}
|
||||
|
||||
// Union returns a set union of provided sets
|
||||
func Union(sets ...tagSet) tagSet {
|
||||
union := tagSet{}
|
||||
|
||||
for _, set := range sets {
|
||||
for tag := range set {
|
||||
union[tag] = true
|
||||
union[tag] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return union
|
||||
}
|
||||
|
||||
// Contains return true if setA contains setB
|
||||
func Contains(setA tagSet, setB tagSet) bool {
|
||||
func Contains(setA tagSet, setB []portainer.TagID) bool {
|
||||
if len(setA) == 0 || len(setB) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for tag := range setB {
|
||||
if !setA[tag] {
|
||||
for _, tag := range setB {
|
||||
if _, ok := setA[tag]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -67,8 +66,8 @@ func Difference(setA tagSet, setB tagSet) tagSet {
|
||||
set := tagSet{}
|
||||
|
||||
for tag := range setA {
|
||||
if !setB[tag] {
|
||||
set[tag] = true
|
||||
if _, ok := setB[tag]; !ok {
|
||||
set[tag] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package tag
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
// FullMatch returns true if environment tags matches all edge group tags
|
||||
func FullMatch(edgeGroupTags tagSet, environmentTags tagSet) bool {
|
||||
func FullMatch(edgeGroupTags []portainer.TagID, environmentTags tagSet) bool {
|
||||
return Contains(environmentTags, edgeGroupTags)
|
||||
}
|
||||
|
||||
// PartialMatch returns true if environment tags matches at least one edge group tag
|
||||
func PartialMatch(edgeGroupTags tagSet, environmentTags tagSet) bool {
|
||||
return len(Intersection(edgeGroupTags, environmentTags)) != 0
|
||||
func PartialMatch(edgeGroupTags []portainer.TagID, environmentTags tagSet) bool {
|
||||
for _, tagID := range edgeGroupTags {
|
||||
if _, ok := environmentTags[tagID]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -9,49 +9,49 @@ import (
|
||||
func TestFullMatch(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
edgeGroupTags tagSet
|
||||
edgeGroupTags []portainer.TagID
|
||||
environmentTag tagSet
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "environment tag partially match edge group tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2, 3}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2, 3},
|
||||
environmentTag: Set([]portainer.TagID{1, 2}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "edge group tags equal to environment tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{1, 2}),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "environment tags fully match edge group tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{1, 2, 3}),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "environment tags do not match edge group tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{3, 4}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "edge group has no tags and environment has tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{}),
|
||||
edgeGroupTags: []portainer.TagID{},
|
||||
environmentTag: Set([]portainer.TagID{1, 2}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "edge group has tags and environment has no tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "both edge group and environment have no tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{}),
|
||||
edgeGroupTags: []portainer.TagID{},
|
||||
environmentTag: Set([]portainer.TagID{}),
|
||||
expected: false,
|
||||
},
|
||||
@@ -70,55 +70,55 @@ func TestFullMatch(t *testing.T) {
|
||||
func TestPartialMatch(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
edgeGroupTags tagSet
|
||||
edgeGroupTags []portainer.TagID
|
||||
environmentTag tagSet
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "environment tags partially match edge group tags 1",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2, 3}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2, 3},
|
||||
environmentTag: Set([]portainer.TagID{1, 2}),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "environment tags partially match edge group tags 2",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2, 3}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2, 3},
|
||||
environmentTag: Set([]portainer.TagID{1, 4, 5}),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "edge group tags equal to environment tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{1, 2}),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "environment tags fully match edge group tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{1, 2, 3}),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "environment tags do not match edge group tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{3, 4}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "edge group has no tags and environment has tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{}),
|
||||
edgeGroupTags: []portainer.TagID{},
|
||||
environmentTag: Set([]portainer.TagID{1, 2}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "edge group has tags and environment has no tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{1, 2}),
|
||||
edgeGroupTags: []portainer.TagID{1, 2},
|
||||
environmentTag: Set([]portainer.TagID{}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "both edge group and environment have no tags",
|
||||
edgeGroupTags: Set([]portainer.TagID{}),
|
||||
edgeGroupTags: []portainer.TagID{},
|
||||
environmentTag: Set([]portainer.TagID{}),
|
||||
expected: false,
|
||||
},
|
||||
|
||||
@@ -7,49 +7,49 @@ import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func TestIntersection(t *testing.T) {
|
||||
func TestIntersectionCount(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
setA tagSet
|
||||
setB tagSet
|
||||
expected tagSet
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "positive numbers set intersection",
|
||||
setA: Set([]portainer.TagID{1, 2, 3, 4, 5}),
|
||||
setB: Set([]portainer.TagID{4, 5, 6, 7}),
|
||||
expected: Set([]portainer.TagID{4, 5}),
|
||||
expected: 2,
|
||||
},
|
||||
{
|
||||
name: "empty setA intersection",
|
||||
setA: Set([]portainer.TagID{1, 2, 3}),
|
||||
setB: Set([]portainer.TagID{}),
|
||||
expected: Set([]portainer.TagID{}),
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "empty setB intersection",
|
||||
setA: Set([]portainer.TagID{}),
|
||||
setB: Set([]portainer.TagID{1, 2, 3}),
|
||||
expected: Set([]portainer.TagID{}),
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "no common elements sets intersection",
|
||||
setA: Set([]portainer.TagID{1, 2, 3}),
|
||||
setB: Set([]portainer.TagID{4, 5, 6}),
|
||||
expected: Set([]portainer.TagID{}),
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "equal sets intersection",
|
||||
setA: Set([]portainer.TagID{1, 2, 3}),
|
||||
setB: Set([]portainer.TagID{1, 2, 3}),
|
||||
expected: Set([]portainer.TagID{1, 2, 3}),
|
||||
expected: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := Intersection(tc.setA, tc.setB)
|
||||
if !reflect.DeepEqual(result, tc.expected) {
|
||||
result := IntersectionCount(tc.setA, tc.setB)
|
||||
if result != tc.expected {
|
||||
t.Errorf("Expected %v, got %v", tc.expected, result)
|
||||
}
|
||||
})
|
||||
@@ -109,49 +109,49 @@ func TestContains(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
setA tagSet
|
||||
setB tagSet
|
||||
setB []portainer.TagID
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "setA contains setB",
|
||||
setA: Set([]portainer.TagID{1, 2, 3}),
|
||||
setB: Set([]portainer.TagID{1, 2}),
|
||||
setB: []portainer.TagID{1, 2},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "setA equals to setB",
|
||||
setA: Set([]portainer.TagID{1, 2}),
|
||||
setB: Set([]portainer.TagID{1, 2}),
|
||||
setB: []portainer.TagID{1, 2},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "setA contains parts of setB",
|
||||
setA: Set([]portainer.TagID{1, 2}),
|
||||
setB: Set([]portainer.TagID{1, 2, 3}),
|
||||
setB: []portainer.TagID{1, 2, 3},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "setA does not contain setB",
|
||||
setA: Set([]portainer.TagID{1, 2}),
|
||||
setB: Set([]portainer.TagID{3, 4}),
|
||||
setB: []portainer.TagID{3, 4},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "setA is empty and setB is not empty",
|
||||
setA: Set([]portainer.TagID{}),
|
||||
setB: Set([]portainer.TagID{1, 2}),
|
||||
setB: []portainer.TagID{1, 2},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "setA is not empty and setB is empty",
|
||||
setA: Set([]portainer.TagID{1, 2}),
|
||||
setB: Set([]portainer.TagID{}),
|
||||
setB: []portainer.TagID{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "setA is empty and setB is empty",
|
||||
setA: Set([]portainer.TagID{}),
|
||||
setB: Set([]portainer.TagID{}),
|
||||
setB: []portainer.TagID{},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@@ -3,7 +3,7 @@ module github.com/portainer/portainer
|
||||
go 1.22.7
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/semver/v3 v3.3.0
|
||||
github.com/Microsoft/go-winio v0.6.1
|
||||
github.com/VictoriaMetrics/fastcache v1.12.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
@@ -12,7 +12,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.14.0
|
||||
github.com/cbroglie/mustache v1.4.0
|
||||
github.com/containers/image/v5 v5.30.1
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5
|
||||
github.com/docker/cli v26.0.1+incompatible
|
||||
github.com/docker/docker v26.1.5+incompatible
|
||||
|
||||
6
go.sum
6
go.sum
@@ -8,8 +8,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzS
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/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/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
@@ -77,8 +77,6 @@ github.com/containers/ocicrypt v1.1.9 h1:2Csfba4jse85Raxk5HIyEk8OwZNjRvfkhEGijOj
|
||||
github.com/containers/ocicrypt v1.1.9/go.mod h1:dTKx1918d8TDkxXvarscpNVY+lyPakPNFN4jwA9GBys=
|
||||
github.com/containers/storage v1.53.0 h1:VSES3C/u1pxjTJIXvLrSmyP7OBtDky04oGu07UvdTEA=
|
||||
github.com/containers/storage v1.53.0/go.mod h1:pujcoOSc+upx15Jirdkebhtd8uJiLwbSd/mYT6zDJK8=
|
||||
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.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "storybook build -o ./dist/storybook",
|
||||
"analyze-webpack": "webpack --config ./webpack/webpack.analyze.js",
|
||||
"prepare": "cd ../.. && husky install package/server-ce/.husky"
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
|
||||
Reference in New Issue
Block a user