mirror of https://github.com/goharbor/harbor.git
Merge branch 'main' into e2e-teardown
This commit is contained in:
commit
32e780bfab
|
@ -8,6 +8,9 @@
|
|||
* Add date here... Add signature here...
|
||||
- Add your reason here...
|
||||
|
||||
* Aug 12 2025 <yan-yw.wang@broadcom.com>
|
||||
- Refresh base image
|
||||
|
||||
* Oct 24 2024 <yan-yw.wang@broadcom.com>
|
||||
- Refresh base image
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ env:
|
|||
UI_BUILDER_VERSION: 1.6.0
|
||||
|
||||
on:
|
||||
# the paths-ignore is the same as the paths in pass-CI.yml, they should be synced together
|
||||
# see https://web.archive.org/web/20230506145443/https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
@ -23,7 +25,11 @@ on:
|
|||
- '!tests/**.sh'
|
||||
- '!tests/apitests/**'
|
||||
- '!tests/ci/**'
|
||||
- '!tests/resources/**'
|
||||
- '!tests/robot-cases/**'
|
||||
- '!tests/robot-cases/Group1-Nightly/**'
|
||||
push:
|
||||
# the paths-ignore is the same as the paths in pass-CI.yml, they should be synced together
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '**.md'
|
||||
|
@ -31,6 +37,9 @@ on:
|
|||
- '!tests/**.sh'
|
||||
- '!tests/apitests/**'
|
||||
- '!tests/ci/**'
|
||||
- '!tests/resources/**'
|
||||
- '!tests/robot-cases/**'
|
||||
- '!tests/robot-cases/Group1-Nightly/**'
|
||||
|
||||
jobs:
|
||||
UTTEST:
|
||||
|
@ -46,7 +55,7 @@ jobs:
|
|||
with:
|
||||
go-version: 1.23.2
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: setup env
|
||||
|
@ -162,7 +171,7 @@ jobs:
|
|||
with:
|
||||
go-version: 1.23.2
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: setup env
|
||||
|
@ -203,7 +212,12 @@ jobs:
|
|||
df -h
|
||||
bash ./tests/showtime.sh ./tests/ci/api_run.sh DB $IP
|
||||
df -h
|
||||
|
||||
- name: upload_logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: db-api-harbor-logs.tar.gz
|
||||
path: /home/runner/work/harbor/harbor/src/github.com/goharbor/harbor/integration_logs.tar.gz
|
||||
retention-days: 5
|
||||
APITEST_DB_PROXY_CACHE:
|
||||
env:
|
||||
APITEST_DB: true
|
||||
|
@ -217,7 +231,7 @@ jobs:
|
|||
with:
|
||||
go-version: 1.23.2
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: setup env
|
||||
|
@ -258,7 +272,12 @@ jobs:
|
|||
df -h
|
||||
bash ./tests/showtime.sh ./tests/ci/api_run.sh PROXY_CACHE $IP
|
||||
df -h
|
||||
|
||||
- name: upload_logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: proxy-api-harbor-logs.tar.gz
|
||||
path: /home/runner/work/harbor/harbor/src/github.com/goharbor/harbor/integration_logs.tar.gz
|
||||
retention-days: 5
|
||||
APITEST_LDAP:
|
||||
env:
|
||||
APITEST_LDAP: true
|
||||
|
@ -272,7 +291,7 @@ jobs:
|
|||
with:
|
||||
go-version: 1.23.2
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: setup env
|
||||
|
@ -311,7 +330,12 @@ jobs:
|
|||
cd src/github.com/goharbor/harbor
|
||||
bash ./tests/showtime.sh ./tests/ci/api_run.sh LDAP $IP
|
||||
df -h
|
||||
|
||||
- name: upload_logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ldap-api-harbor-logs.tar.gz
|
||||
path: /home/runner/work/harbor/harbor/src/github.com/goharbor/harbor/integration_logs.tar.gz
|
||||
retention-days: 5
|
||||
OFFLINE:
|
||||
env:
|
||||
OFFLINE: true
|
||||
|
@ -325,7 +349,7 @@ jobs:
|
|||
with:
|
||||
go-version: 1.23.2
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: setup env
|
||||
|
@ -375,7 +399,7 @@ jobs:
|
|||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: script
|
||||
|
@ -389,4 +413,4 @@ jobs:
|
|||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ./src/github.com/goharbor/harbor/src/portal/coverage/lcov.info
|
||||
flags: unittests
|
||||
flags: unittests
|
|
@ -31,13 +31,13 @@ jobs:
|
|||
with:
|
||||
docker_version: 20.10
|
||||
docker_channel: stable
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
- uses: jitterbit/get-changed-files@v1
|
||||
id: changed-files
|
||||
with:
|
||||
format: space-delimited
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: Build Base Image
|
||||
|
@ -85,7 +85,7 @@ jobs:
|
|||
if [ -z "$BUILD_BASE" ] || [ "$BUILD_BASE" != "true" ]; then
|
||||
echo "Do not need to build base images!"
|
||||
else
|
||||
build_base_params=" BUILD_BASE=true PUSHBASEIMAGE=true REGISTRYUSER=\"${{ secrets.DOCKER_HUB_USERNAME }}\" REGISTRYPASSWORD=\"${{ secrets.DOCKER_HUB_PASSWORD }}\""
|
||||
build_base_params=" BUILD_BASE=true PULL_BASE_FROM_DOCKERHUB=true PUSHBASEIMAGE=true REGISTRYUSER=\"${{ secrets.DOCKER_HUB_USERNAME }}\" REGISTRYPASSWORD=\"${{ secrets.DOCKER_HUB_PASSWORD }}\""
|
||||
fi
|
||||
sudo make package_offline GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=${Harbor_Build_Base_Tag} VERSIONTAG=${Harbor_Assets_Version} PKGVERSIONTAG=${Harbor_Package_Version} TRIVYFLAG=true EXPORTERFLAG=true HTTPPROXY= ${build_base_params}
|
||||
sudo make package_online GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=${Harbor_Build_Base_Tag} VERSIONTAG=${Harbor_Assets_Version} PKGVERSIONTAG=${Harbor_Package_Version} TRIVYFLAG=true EXPORTERFLAG=true HTTPPROXY= ${build_base_params}
|
||||
|
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
with:
|
||||
go-version: 1.23.2
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
path: src/github.com/goharbor/harbor
|
||||
- name: before_install
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
|
|
|
@ -2,6 +2,7 @@ name: CI
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
# the paths is the same as the paths-ignore in CI.yml, they should be synced together
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '**.md'
|
||||
|
@ -11,7 +12,9 @@ on:
|
|||
- '!tests/ci/**'
|
||||
- '!tests/resources/**'
|
||||
- '!tests/robot-cases/**'
|
||||
- '!tests/robot-cases/Group1-Nightly/**'
|
||||
push:
|
||||
# the paths is the same as the paths-ignore in CI.yml, they should be synced together
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '**.md'
|
||||
|
@ -21,6 +24,7 @@ on:
|
|||
- '!tests/ci/**'
|
||||
- '!tests/resources/**'
|
||||
- '!tests/robot-cases/**'
|
||||
- '!tests/robot-cases/Group1-Nightly/**'
|
||||
|
||||
jobs:
|
||||
UTTEST:
|
||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "CUR_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
|
|
|
@ -168,7 +168,7 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo
|
|||
| 2.11 | 1.22.3 |
|
||||
| 2.12 | 1.23.2 |
|
||||
| 2.13 | 1.23.8 |
|
||||
| 2.14 | 1.24.3 |
|
||||
| 2.14 | 1.24.6 |
|
||||
|
||||
|
||||
Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions.
|
||||
|
|
11
Makefile
11
Makefile
|
@ -111,8 +111,8 @@ PREPARE_VERSION_NAME=versions
|
|||
|
||||
#versions
|
||||
REGISTRYVERSION=v2.8.3-patch-redis
|
||||
TRIVYVERSION=v0.61.0
|
||||
TRIVYADAPTERVERSION=v0.33.0-rc.2
|
||||
TRIVYVERSION=v0.65.0
|
||||
TRIVYADAPTERVERSION=v0.34.0-rc.1
|
||||
NODEBUILDIMAGE=node:16.18.0
|
||||
|
||||
# version of registry for pulling the source code
|
||||
|
@ -151,7 +151,7 @@ GOINSTALL=$(GOCMD) install
|
|||
GOTEST=$(GOCMD) test
|
||||
GODEP=$(GOTEST) -i
|
||||
GOFMT=gofmt -w
|
||||
GOBUILDIMAGE=golang:1.24.3
|
||||
GOBUILDIMAGE=golang:1.24.6
|
||||
GOBUILDPATHINCONTAINER=/harbor
|
||||
|
||||
# go build
|
||||
|
@ -395,11 +395,6 @@ build:
|
|||
echo Do not push base images since no base images built. ; \
|
||||
exit 1; \
|
||||
fi
|
||||
# PULL_BASE_FROM_DOCKERHUB should be true if BUILD_BASE is not true
|
||||
@if [ "$(BUILD_BASE)" != "true" ] && [ "$(PULL_BASE_FROM_DOCKERHUB)" = "false" ] ; then \
|
||||
echo Should pull base images from registry in docker configuration since no base images built. ; \
|
||||
exit 1; \
|
||||
fi
|
||||
make -f $(MAKEFILEPATH_PHOTON)/Makefile $(BUILDTARGET) -e DEVFLAG=$(DEVFLAG) -e GOBUILDIMAGE=$(GOBUILDIMAGE) -e NODEBUILDIMAGE=$(NODEBUILDIMAGE) \
|
||||
-e REGISTRYVERSION=$(REGISTRYVERSION) -e REGISTRY_SRC_TAG=$(REGISTRY_SRC_TAG) -e DISTRIBUTION_SRC=$(DISTRIBUTION_SRC)\
|
||||
-e TRIVYVERSION=$(TRIVYVERSION) -e TRIVYADAPTERVERSION=$(TRIVYADAPTERVERSION) \
|
||||
|
|
|
@ -3031,6 +3031,8 @@ paths:
|
|||
type: string
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'409':
|
||||
$ref: '#/responses/409'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/usergroups/{group_id}':
|
||||
|
@ -7460,6 +7462,12 @@ definitions:
|
|||
type: boolean
|
||||
description: Whether to enable copy by chunk.
|
||||
x-isnullable: true
|
||||
single_active_replication:
|
||||
type: boolean
|
||||
description: |-
|
||||
Whether to skip execution until the previous active execution finishes,
|
||||
avoiding the execution of the same replication rules multiple times in parallel.
|
||||
x-isnullable: true # make this field optional to keep backward compatibility
|
||||
ReplicationTrigger:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -176,7 +176,7 @@ log:
|
|||
# port: 5140
|
||||
|
||||
#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY!
|
||||
_version: 2.13.0
|
||||
_version: 2.14.0
|
||||
|
||||
# Uncomment external_database if using external database.
|
||||
# external_database:
|
||||
|
|
|
@ -6,4 +6,6 @@ ALTER SEQUENCE permission_policy_id_seq AS BIGINT;
|
|||
|
||||
ALTER TABLE role_permission ALTER COLUMN permission_policy_id TYPE BIGINT;
|
||||
|
||||
ALTER TABLE vulnerability_record ADD COLUMN IF NOT EXISTS status text;
|
||||
ALTER TABLE vulnerability_record ADD COLUMN IF NOT EXISTS status text;
|
||||
|
||||
ALTER TABLE replication_policy ADD COLUMN IF NOT EXISTS single_active_replication boolean;
|
||||
|
|
|
@ -10,7 +10,7 @@ from migrations import accept_versions
|
|||
@click.command()
|
||||
@click.option('-i', '--input', 'input_', required=True, help="The path of original config file")
|
||||
@click.option('-o', '--output', default='', help="the path of output config file")
|
||||
@click.option('-t', '--target', default='2.13.0', help="target version of input path")
|
||||
@click.option('-t', '--target', default='2.14.0', help="target version of input path")
|
||||
def migrate(input_, output, target):
|
||||
"""
|
||||
migrate command will migrate config file style to specific version
|
||||
|
|
|
@ -2,4 +2,4 @@ import os
|
|||
|
||||
MIGRATION_BASE_DIR = os.path.dirname(__file__)
|
||||
|
||||
accept_versions = {'1.9.0', '1.10.0', '2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0', '2.7.0', '2.8.0', '2.9.0','2.10.0', '2.11.0', '2.12.0', '2.13.0'}
|
||||
accept_versions = {'1.9.0', '1.10.0', '2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0', '2.7.0', '2.8.0', '2.9.0','2.10.0', '2.11.0', '2.12.0', '2.13.0', '2.14.0'}
|
|
@ -0,0 +1,21 @@
|
|||
import os
|
||||
from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape
|
||||
from utils.migration import read_conf
|
||||
|
||||
revision = '2.14.0'
|
||||
down_revisions = ['2.13.0']
|
||||
|
||||
def migrate(input_cfg, output_cfg):
|
||||
current_dir = os.path.dirname(__file__)
|
||||
tpl = Environment(
|
||||
loader=FileSystemLoader(current_dir),
|
||||
undefined=StrictUndefined,
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
autoescape = select_autoescape()
|
||||
).get_template('harbor.yml.jinja')
|
||||
|
||||
config_dict = read_conf(input_cfg)
|
||||
|
||||
with open(output_cfg, 'w') as f:
|
||||
f.write(tpl.render(**config_dict))
|
|
@ -0,0 +1,775 @@
|
|||
# Configuration file of Harbor
|
||||
|
||||
# The IP address or hostname to access admin UI and registry service.
|
||||
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
|
||||
hostname: {{ hostname }}
|
||||
|
||||
# http related config
|
||||
{% if http is defined %}
|
||||
http:
|
||||
# port for http, default is 80. If https enabled, this port will redirect to https port
|
||||
port: {{ http.port }}
|
||||
{% else %}
|
||||
# http:
|
||||
# # port for http, default is 80. If https enabled, this port will redirect to https port
|
||||
# port: 80
|
||||
{% endif %}
|
||||
|
||||
{% if https is defined %}
|
||||
# https related config
|
||||
https:
|
||||
# https port for harbor, default is 443
|
||||
port: {{ https.port }}
|
||||
# The path of cert and key files for nginx
|
||||
certificate: {{ https.certificate }}
|
||||
private_key: {{ https.private_key }}
|
||||
# enable strong ssl ciphers (default: false)
|
||||
{% if strong_ssl_ciphers is defined %}
|
||||
strong_ssl_ciphers: {{ strong_ssl_ciphers | lower }}
|
||||
{% else %}
|
||||
strong_ssl_ciphers: false
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# https related config
|
||||
# https:
|
||||
# # https port for harbor, default is 443
|
||||
# port: 443
|
||||
# # The path of cert and key files for nginx
|
||||
# certificate: /your/certificate/path
|
||||
# private_key: /your/private/key/path
|
||||
# enable strong ssl ciphers (default: false)
|
||||
# strong_ssl_ciphers: false
|
||||
{% endif %}
|
||||
|
||||
# # Harbor will set ipv4 enabled only by default if this block is not configured
|
||||
# # Otherwise, please uncomment this block to configure your own ip_family stacks
|
||||
{% if ip_family is defined %}
|
||||
ip_family:
|
||||
# ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component
|
||||
{% if ip_family.ipv6 is defined %}
|
||||
ipv6:
|
||||
enabled: {{ ip_family.ipv6.enabled | lower }}
|
||||
{% else %}
|
||||
ipv6:
|
||||
enabled: false
|
||||
{% endif %}
|
||||
# ipv4Enabled set to true by default, currently it affected the nginx related component
|
||||
{% if ip_family.ipv4 is defined %}
|
||||
ipv4:
|
||||
enabled: {{ ip_family.ipv4.enabled | lower }}
|
||||
{% else %}
|
||||
ipv4:
|
||||
enabled: true
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# ip_family:
|
||||
# # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component
|
||||
# ipv6:
|
||||
# enabled: false
|
||||
# # ipv4Enabled set to true by default, currently it affected the nginx related component
|
||||
# ipv4:
|
||||
# enabled: true
|
||||
{% endif %}
|
||||
|
||||
{% if internal_tls is defined %}
|
||||
# Uncomment following will enable tls communication between all harbor components
|
||||
internal_tls:
|
||||
# set enabled to true means internal tls is enabled
|
||||
enabled: {{ internal_tls.enabled | lower }}
|
||||
{% if internal_tls.dir is defined %}
|
||||
# put your cert and key files on dir
|
||||
dir: {{ internal_tls.dir }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# internal_tls:
|
||||
# # set enabled to true means internal tls is enabled
|
||||
# enabled: true
|
||||
# # put your cert and key files on dir
|
||||
# dir: /etc/harbor/tls/internal
|
||||
{% endif %}
|
||||
|
||||
# Uncomment external_url if you want to enable external proxy
|
||||
# And when it enabled the hostname will no longer used
|
||||
{% if external_url is defined %}
|
||||
external_url: {{ external_url }}
|
||||
{% else %}
|
||||
# external_url: https://reg.mydomain.com:8433
|
||||
{% endif %}
|
||||
|
||||
# The initial password of Harbor admin
|
||||
# It only works in first time to install harbor
|
||||
# Remember Change the admin password from UI after launching Harbor.
|
||||
{% if harbor_admin_password is defined %}
|
||||
harbor_admin_password: {{ harbor_admin_password }}
|
||||
{% else %}
|
||||
harbor_admin_password: Harbor12345
|
||||
{% endif %}
|
||||
|
||||
# Harbor DB configuration
|
||||
database:
|
||||
{% if database is defined %}
|
||||
# The password for the root user of Harbor DB. Change this before any production use.
|
||||
password: {{ database.password}}
|
||||
# The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
|
||||
max_idle_conns: {{ database.max_idle_conns }}
|
||||
# The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections.
|
||||
# Note: the default number of connections is 1024 for postgres of harbor.
|
||||
max_open_conns: {{ database.max_open_conns }}
|
||||
# The maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's age.
|
||||
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
{% if database.conn_max_lifetime is defined %}
|
||||
conn_max_lifetime: {{ database.conn_max_lifetime }}
|
||||
{% else %}
|
||||
conn_max_lifetime: 5m
|
||||
{% endif %}
|
||||
# The maximum amount of time a connection may be idle. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's idle time.
|
||||
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
{% if database.conn_max_idle_time is defined %}
|
||||
conn_max_idle_time: {{ database.conn_max_idle_time }}
|
||||
{% else %}
|
||||
conn_max_idle_time: 0
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# The password for the root user of Harbor DB. Change this before any production use.
|
||||
password: root123
|
||||
# The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
|
||||
max_idle_conns: 100
|
||||
# The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections.
|
||||
# Note: the default number of connections is 1024 for postgres of harbor.
|
||||
max_open_conns: 900
|
||||
# The maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's age.
|
||||
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
conn_max_lifetime: 5m
|
||||
# The maximum amount of time a connection may be idle. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's idle time.
|
||||
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
conn_max_idle_time: 0
|
||||
{% endif %}
|
||||
|
||||
{% if data_volume is defined %}
|
||||
# The default data volume
|
||||
data_volume: {{ data_volume }}
|
||||
{% else %}
|
||||
# The default data volume
|
||||
data_volume: /data
|
||||
{% endif %}
|
||||
|
||||
# Harbor Storage settings by default is using /data dir on local filesystem
|
||||
# Uncomment storage_service setting If you want to using external storage
|
||||
{% if storage_service is defined %}
|
||||
storage_service:
|
||||
{% for key, value in storage_service.items() %}
|
||||
{% if key == 'ca_bundle' %}
|
||||
# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore
|
||||
# # of registry's and chart repository's containers. This is usually needed when the user hosts a internal storage with self signed certificate.
|
||||
ca_bundle: {{ value if value is not none else '' }}
|
||||
{% elif key == 'redirect' %}
|
||||
# # set disable to true when you want to disable registry redirect
|
||||
redirect:
|
||||
{% if storage_service.redirect.disabled is defined %}
|
||||
disable: {{ storage_service.redirect.disabled | lower}}
|
||||
{% else %}
|
||||
disable: {{ storage_service.redirect.disable | lower}}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss
|
||||
# # for more info about this configuration please refer https://distribution.github.io/distribution/about/configuration/
|
||||
# # and https://distribution.github.io/distribution/storage-drivers/
|
||||
{{ key }}:
|
||||
{% for k, v in value.items() %}
|
||||
{{ k }}: {{ v if v is not none else '' }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
# storage_service:
|
||||
# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore
|
||||
# # of registry's and chart repository's containers. This is usually needed when the user hosts a internal storage with self signed certificate.
|
||||
# ca_bundle:
|
||||
|
||||
# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss
|
||||
# # for more info about this configuration please refer https://distribution.github.io/distribution/about/configuration/
|
||||
# # and https://distribution.github.io/distribution/storage-drivers/
|
||||
# filesystem:
|
||||
# maxthreads: 100
|
||||
# # set disable to true when you want to disable registry redirect
|
||||
# redirect:
|
||||
# disable: false
|
||||
{% endif %}
|
||||
|
||||
# Trivy configuration
|
||||
#
|
||||
# Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases.
|
||||
# It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached
|
||||
# in the local file system. In addition, the database contains the update timestamp so Trivy can detect whether it
|
||||
# should download a newer version from the Internet or use the cached one. Currently, the database is updated every
|
||||
# 12 hours and published as a new release to GitHub.
|
||||
{% if trivy is defined %}
|
||||
trivy:
|
||||
# ignoreUnfixed The flag to display only fixed vulnerabilities
|
||||
{% if trivy.ignore_unfixed is defined %}
|
||||
ignore_unfixed: {{ trivy.ignore_unfixed | lower }}
|
||||
{% else %}
|
||||
ignore_unfixed: false
|
||||
{% endif %}
|
||||
# skipUpdate The flag to enable or disable Trivy DB downloads from GitHub
|
||||
#
|
||||
# You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues.
|
||||
# If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and
|
||||
# `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path.
|
||||
{% if trivy.skip_update is defined %}
|
||||
skip_update: {{ trivy.skip_update | lower }}
|
||||
{% else %}
|
||||
skip_update: false
|
||||
{% endif %}
|
||||
{% if trivy.skip_java_db_update is defined %}
|
||||
# skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the
|
||||
# `/home/scanner/.cache/trivy/java-db/trivy-java.db` path
|
||||
skip_java_db_update: {{ trivy.skip_java_db_update | lower }}
|
||||
{% else %}
|
||||
skip_java_db_update: false
|
||||
{% endif %}
|
||||
#
|
||||
{% if trivy.offline_scan is defined %}
|
||||
offline_scan: {{ trivy.offline_scan | lower }}
|
||||
{% else %}
|
||||
offline_scan: false
|
||||
{% endif %}
|
||||
#
|
||||
# Comma-separated list of what security issues to detect. Possible values are `vuln`, `config` and `secret`. Defaults to `vuln`.
|
||||
{% if trivy.security_check is defined %}
|
||||
security_check: {{ trivy.security_check }}
|
||||
{% else %}
|
||||
security_check: vuln
|
||||
{% endif %}
|
||||
#
|
||||
# insecure The flag to skip verifying registry certificate
|
||||
{% if trivy.insecure is defined %}
|
||||
insecure: {{ trivy.insecure | lower }}
|
||||
{% else %}
|
||||
insecure: false
|
||||
{% endif %}
|
||||
#
|
||||
{% if trivy.timeout is defined %}
|
||||
# timeout The duration to wait for scan completion.
|
||||
# There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s.
|
||||
timeout: {{ trivy.timeout}}
|
||||
{% else %}
|
||||
timeout: 5m0s
|
||||
{% endif %}
|
||||
#
|
||||
# github_token The GitHub access token to download Trivy DB
|
||||
#
|
||||
# Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough
|
||||
# for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000
|
||||
# requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult
|
||||
# https://developer.github.com/v3/#rate-limiting
|
||||
#
|
||||
# You can create a GitHub token by following the instructions in
|
||||
# https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
|
||||
#
|
||||
{% if trivy.github_token is defined %}
|
||||
github_token: {{ trivy.github_token }}
|
||||
{% else %}
|
||||
# github_token: xxx
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# trivy:
|
||||
# # ignoreUnfixed The flag to display only fixed vulnerabilities
|
||||
# ignore_unfixed: false
|
||||
# # skipUpdate The flag to enable or disable Trivy DB downloads from GitHub
|
||||
# #
|
||||
# # You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues.
|
||||
# # If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and
|
||||
# # `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path.
|
||||
# skip_update: false
|
||||
# #
|
||||
# # skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the
|
||||
# # `/home/scanner/.cache/trivy/java-db/trivy-java.db` path
|
||||
# skip_java_db_update: false
|
||||
# #
|
||||
# #The offline_scan option prevents Trivy from sending API requests to identify dependencies.
|
||||
# # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it.
|
||||
# # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't
|
||||
# # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode.
|
||||
# # It would work if all the dependencies are in local.
|
||||
# # This option doesn’t affect DB download. You need to specify "skip-update" as well as "offline-scan" in an air-gapped environment.
|
||||
# offline_scan: false
|
||||
# #
|
||||
# # insecure The flag to skip verifying registry certificate
|
||||
# insecure: false
|
||||
# # github_token The GitHub access token to download Trivy DB
|
||||
# #
|
||||
# # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough
|
||||
# # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000
|
||||
# # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult
|
||||
# # https://developer.github.com/v3/#rate-limiting
|
||||
# #
|
||||
# # timeout The duration to wait for scan completion.
|
||||
# # There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s.
|
||||
# timeout: 5m0s
|
||||
# #
|
||||
# # You can create a GitHub token by following the instructions in
|
||||
# # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
|
||||
# #
|
||||
# # github_token: xxx
|
||||
{% endif %}
|
||||
|
||||
jobservice:
|
||||
# Maximum number of job workers in job service
|
||||
{% if jobservice is defined %}
|
||||
max_job_workers: {{ jobservice.max_job_workers }}
|
||||
# Maximum hours of task duration in job service, default 24
|
||||
{% if jobservice.max_job_duration_hours is defined %}
|
||||
max_job_duration_hours: {{ jobservice.max_job_duration_hours }}
|
||||
{% else %}
|
||||
max_job_duration_hours: 24
|
||||
{% endif %}
|
||||
# The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB"
|
||||
{% if jobservice.job_loggers is defined %}
|
||||
job_loggers:
|
||||
{% for job_logger in jobservice.job_loggers %}
|
||||
- {{job_logger}}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
job_loggers:
|
||||
- STD_OUTPUT
|
||||
- FILE
|
||||
# - DB
|
||||
{% endif %}
|
||||
# The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`)
|
||||
{% if jobservice.logger_sweeper_duration is defined %}
|
||||
logger_sweeper_duration: {{ jobservice.logger_sweeper_duration }}
|
||||
{% else %}
|
||||
logger_sweeper_duration: 1
|
||||
{% endif %}
|
||||
{% else %}
|
||||
max_job_workers: 10
|
||||
max_job_duration_hours: 24
|
||||
# The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB"
|
||||
job_loggers:
|
||||
- STD_OUTPUT
|
||||
- FILE
|
||||
# - DB
|
||||
# The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`)
|
||||
logger_sweeper_duration: 1
|
||||
{% endif %}
|
||||
|
||||
notification:
|
||||
# Maximum retry count for webhook job
|
||||
{% if notification is defined %}
|
||||
webhook_job_max_retry: {{ notification.webhook_job_max_retry}}
|
||||
# HTTP client timeout for webhook job
|
||||
{% if notification.webhook_job_http_client_timeout is defined %}
|
||||
webhook_job_http_client_timeout: {{ notification.webhook_job_http_client_timeout }}
|
||||
{% else %}
|
||||
webhook_job_http_client_timeout: 3 #seconds
|
||||
{% endif %}
|
||||
{% else %}
|
||||
webhook_job_max_retry: 3
|
||||
# HTTP client timeout for webhook job
|
||||
webhook_job_http_client_timeout: 3 #seconds
|
||||
{% endif %}
|
||||
|
||||
# Log configurations
|
||||
log:
|
||||
# options are debug, info, warning, error, fatal
|
||||
{% if log is defined %}
|
||||
level: {{ log.level }}
|
||||
# configs for logs in local storage
|
||||
local:
|
||||
# Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated.
|
||||
rotate_count: {{ log.local.rotate_count }}
|
||||
# Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes.
|
||||
# If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G
|
||||
# are all valid.
|
||||
rotate_size: {{ log.local.rotate_size }}
|
||||
# The directory on your host that store log
|
||||
location: {{ log.local.location }}
|
||||
{% if log.external_endpoint is defined %}
|
||||
external_endpoint:
|
||||
# protocol used to transmit log to external endpoint, options is tcp or udp
|
||||
protocol: {{ log.external_endpoint.protocol }}
|
||||
# The host of external endpoint
|
||||
host: {{ log.external_endpoint.host }}
|
||||
# Port of external endpoint
|
||||
port: {{ log.external_endpoint.port }}
|
||||
{% else %}
|
||||
# Uncomment following lines to enable external syslog endpoint.
|
||||
# external_endpoint:
|
||||
# # protocol used to transmit log to external endpoint, options is tcp or udp
|
||||
# protocol: tcp
|
||||
# # The host of external endpoint
|
||||
# host: localhost
|
||||
# # Port of external endpoint
|
||||
# port: 5140
|
||||
{% endif %}
|
||||
{% else %}
|
||||
level: info
|
||||
# configs for logs in local storage
|
||||
local:
|
||||
# Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated.
|
||||
rotate_count: 50
|
||||
# Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes.
|
||||
# If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G
|
||||
# are all valid.
|
||||
rotate_size: 200M
|
||||
# The directory on your host that store log
|
||||
location: /var/log/harbor
|
||||
|
||||
# Uncomment following lines to enable external syslog endpoint.
|
||||
# external_endpoint:
|
||||
# # protocol used to transmit log to external endpoint, options is tcp or udp
|
||||
# protocol: tcp
|
||||
# # The host of external endpoint
|
||||
# host: localhost
|
||||
# # Port of external endpoint
|
||||
# port: 5140
|
||||
{% endif %}
|
||||
|
||||
|
||||
#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY!
|
||||
_version: 2.14.0
|
||||
{% if external_database is defined %}
|
||||
# Uncomment external_database if using external database.
|
||||
external_database:
|
||||
harbor:
|
||||
host: {{ external_database.harbor.host }}
|
||||
port: {{ external_database.harbor.port }}
|
||||
db_name: {{ external_database.harbor.db_name }}
|
||||
username: {{ external_database.harbor.username }}
|
||||
password: {{ external_database.harbor.password }}
|
||||
ssl_mode: {{ external_database.harbor.ssl_mode }}
|
||||
max_idle_conns: {{ external_database.harbor.max_idle_conns}}
|
||||
max_open_conns: {{ external_database.harbor.max_open_conns}}
|
||||
{% else %}
|
||||
# Uncomment external_database if using external database.
|
||||
# external_database:
|
||||
# harbor:
|
||||
# host: harbor_db_host
|
||||
# port: harbor_db_port
|
||||
# db_name: harbor_db_name
|
||||
# username: harbor_db_username
|
||||
# password: harbor_db_password
|
||||
# ssl_mode: disable
|
||||
# max_idle_conns: 2
|
||||
# max_open_conns: 0
|
||||
{% endif %}
|
||||
|
||||
{% if redis is defined %}
|
||||
redis:
|
||||
# # db_index 0 is for core, it's unchangeable
|
||||
{% if redis.registry_db_index is defined %}
|
||||
registry_db_index: {{ redis.registry_db_index }}
|
||||
{% else %}
|
||||
# # registry_db_index: 1
|
||||
{% endif %}
|
||||
{% if redis.jobservice_db_index is defined %}
|
||||
jobservice_db_index: {{ redis.jobservice_db_index }}
|
||||
{% else %}
|
||||
# # jobservice_db_index: 2
|
||||
{% endif %}
|
||||
{% if redis.trivy_db_index is defined %}
|
||||
trivy_db_index: {{ redis.trivy_db_index }}
|
||||
{% else %}
|
||||
# # trivy_db_index: 5
|
||||
{% endif %}
|
||||
{% if redis.harbor_db_index is defined %}
|
||||
harbor_db_index: {{ redis.harbor_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
{% endif %}
|
||||
{% if redis.cache_layer_db_index is defined %}
|
||||
cache_layer_db_index: {{ redis.cache_layer_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# Uncomment redis if need to customize redis db
|
||||
# redis:
|
||||
# # db_index 0 is for core, it's unchangeable
|
||||
# # registry_db_index: 1
|
||||
# # jobservice_db_index: 2
|
||||
# # trivy_db_index: 5
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
|
||||
{% if external_redis is defined %}
|
||||
external_redis:
|
||||
# support redis, redis+sentinel
|
||||
# host for redis: <host_redis>:<port_redis>
|
||||
# host for redis+sentinel:
|
||||
# <host_sentinel1>:<port_sentinel1>,<host_sentinel2>:<port_sentinel2>,<host_sentinel3>:<port_sentinel3>
|
||||
host: {{ external_redis.host }}
|
||||
password: {{ external_redis.password }}
|
||||
# Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH <username> <password> form.
|
||||
{% if external_redis.username is defined %}
|
||||
username: {{ external_redis.username }}
|
||||
{% else %}
|
||||
# username:
|
||||
{% endif %}
|
||||
# sentinel_master_set must be set to support redis+sentinel
|
||||
#sentinel_master_set:
|
||||
{% if external_redis.tlsOptions is defined %}
|
||||
# # tls configuration for redis connection
|
||||
# # only server-authentication is supported
|
||||
# # mtls for redis connection is not supported
|
||||
# # tls connection will be disable by default
|
||||
tlsOptions:
|
||||
enable: {{ external_redis.tlsOptions.enable }}
|
||||
# if it is a self-signed ca, please set the ca path specifically.
|
||||
{% if external_redis.tlsOptions.rootCA is defined %}
|
||||
rootCA: {{ external_redis.tlsOptions.rootCA }}
|
||||
{% else %}
|
||||
# rootCA:
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# # tls configuration for redis connection
|
||||
# # only server-authentication is supported
|
||||
# # mtls for redis connection is not supported
|
||||
# # tls connection will be disable by default
|
||||
# tlsOptions:
|
||||
# enable: false
|
||||
# # if it is a self-signed ca, please set the ca path specifically.
|
||||
# rootCA:
|
||||
{% endif %}
|
||||
# db_index 0 is for core, it's unchangeable
|
||||
registry_db_index: {{ external_redis.registry_db_index }}
|
||||
jobservice_db_index: {{ external_redis.jobservice_db_index }}
|
||||
trivy_db_index: 5
|
||||
idle_timeout_seconds: 30
|
||||
{% if external_redis.harbor_db_index is defined %}
|
||||
harbor_db_index: {{ redis.harbor_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
{% endif %}
|
||||
{% if external_redis.cache_layer_db_index is defined %}
|
||||
cache_layer_db_index: {{ redis.cache_layer_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# Uncomments external_redis if using external Redis server
|
||||
# external_redis:
|
||||
# # support redis, redis+sentinel
|
||||
# # host for redis: <host_redis>:<port_redis>
|
||||
# # host for redis+sentinel:
|
||||
# # <host_sentinel1>:<port_sentinel1>,<host_sentinel2>:<port_sentinel2>,<host_sentinel3>:<port_sentinel3>
|
||||
# host: redis:6379
|
||||
# password:
|
||||
# # Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH <username> <password> form.
|
||||
# # username:
|
||||
# # sentinel_master_set must be set to support redis+sentinel
|
||||
# #sentinel_master_set:
|
||||
# # tls configuration for redis connection
|
||||
# # only server-authentication is supported
|
||||
# # mtls for redis connection is not supported
|
||||
# # tls connection will be disable by default
|
||||
# tlsOptions:
|
||||
# enable: false
|
||||
# # if it is a self-signed ca, please set the ca path specifically.
|
||||
# rootCA:
|
||||
# # db_index 0 is for core, it's unchangeable
|
||||
# registry_db_index: 1
|
||||
# jobservice_db_index: 2
|
||||
# trivy_db_index: 5
|
||||
# idle_timeout_seconds: 30
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
|
||||
{% if uaa is defined %}
|
||||
# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert.
|
||||
uaa:
|
||||
ca_file: {{ uaa.ca_file }}
|
||||
{% else %}
|
||||
# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert.
|
||||
# uaa:
|
||||
# ca_file: /path/to/ca
|
||||
{% endif %}
|
||||
|
||||
|
||||
# Global proxy
|
||||
# Config http proxy for components, e.g. http://my.proxy.com:3128
|
||||
# Components doesn't need to connect to each others via http proxy.
|
||||
# Remove component from `components` array if want disable proxy
|
||||
# for it. If you want use proxy for replication, MUST enable proxy
|
||||
# for core and jobservice, and set `http_proxy` and `https_proxy`.
|
||||
# Add domain to the `no_proxy` field, when you want disable proxy
|
||||
# for some special registry.
|
||||
{% if proxy is defined %}
|
||||
proxy:
|
||||
http_proxy: {{ proxy.http_proxy or ''}}
|
||||
https_proxy: {{ proxy.https_proxy or ''}}
|
||||
no_proxy: {{ proxy.no_proxy or ''}}
|
||||
{% if proxy.components is defined %}
|
||||
components:
|
||||
{% for component in proxy.components %}
|
||||
{% if component != 'clair' %}
|
||||
- {{component}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
proxy:
|
||||
http_proxy:
|
||||
https_proxy:
|
||||
no_proxy:
|
||||
components:
|
||||
- core
|
||||
- jobservice
|
||||
- trivy
|
||||
{% endif %}
|
||||
|
||||
{% if metric is defined %}
|
||||
metric:
|
||||
enabled: {{ metric.enabled }}
|
||||
port: {{ metric.port }}
|
||||
path: {{ metric.path }}
|
||||
{% else %}
|
||||
# metric:
|
||||
# enabled: false
|
||||
# port: 9090
|
||||
# path: /metrics
|
||||
{% endif %}
|
||||
|
||||
# Trace related config
|
||||
# only can enable one trace provider(jaeger or otel) at the same time,
|
||||
# and when using jaeger as provider, can only enable it with agent mode or collector mode.
|
||||
# if using jaeger collector mode, uncomment endpoint and uncomment username, password if needed
|
||||
# if using jaeger agetn mode uncomment agent_host and agent_port
|
||||
{% if trace is defined %}
|
||||
trace:
|
||||
enabled: {{ trace.enabled | lower}}
|
||||
sample_rate: {{ trace.sample_rate }}
|
||||
# # namespace used to differentiate different harbor services
|
||||
{% if trace.namespace is defined %}
|
||||
namespace: {{ trace.namespace }}
|
||||
{% else %}
|
||||
# namespace:
|
||||
{% endif %}
|
||||
# # attributes is a key value dict contains user defined attributes used to initialize trace provider
|
||||
{% if trace.attributes is defined%}
|
||||
attributes:
|
||||
{% for name, value in trace.attributes.items() %}
|
||||
{{name}}: {{value}}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
# attributes:
|
||||
# application: harbor
|
||||
{% endif %}
|
||||
{% if trace.jaeger is defined%}
|
||||
jaeger:
|
||||
endpoint: {{trace.jaeger.endpoint or '' }}
|
||||
username: {{trace.jaeger.username or ''}}
|
||||
password: {{trace.jaeger.password or ''}}
|
||||
agent_host: {{trace.jaeger.agent_host or ''}}
|
||||
agent_port: {{trace.jaeger.agent_port or ''}}
|
||||
{% else %}
|
||||
# jaeger:
|
||||
# endpoint:
|
||||
# username:
|
||||
# password:
|
||||
# agent_host:
|
||||
# agent_port:
|
||||
{% endif %}
|
||||
{% if trace. otel is defined %}
|
||||
otel:
|
||||
endpoint: {{trace.otel.endpoint or '' }}
|
||||
url_path: {{trace.otel.url_path or '' }}
|
||||
compression: {{trace.otel.compression | lower }}
|
||||
insecure: {{trace.otel.insecure | lower }}
|
||||
timeout: {{trace.otel.timeout or '' }}
|
||||
{% else %}
|
||||
# otel:
|
||||
# endpoint: hostname:4318
|
||||
# url_path: /v1/traces
|
||||
# compression: false
|
||||
# insecure: true
|
||||
# # timeout is in seconds
|
||||
# timeout: 10
|
||||
{% endif%}
|
||||
{% else %}
|
||||
# trace:
|
||||
# enabled: true
|
||||
# # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth
|
||||
# sample_rate: 1
|
||||
# # # namespace used to differentiate different harbor services
|
||||
# # namespace:
|
||||
# # # attributes is a key value dict contains user defined attributes used to initialize trace provider
|
||||
# # attributes:
|
||||
# # application: harbor
|
||||
# # jaeger:
|
||||
# # endpoint: http://hostname:14268/api/traces
|
||||
# # username:
|
||||
# # password:
|
||||
# # agent_host: hostname
|
||||
# # agent_port: 6831
|
||||
# # otel:
|
||||
# # endpoint: hostname:4318
|
||||
# # url_path: /v1/traces
|
||||
# # compression: false
|
||||
# # insecure: true
|
||||
# # # timeout is in seconds
|
||||
# # timeout: 10
|
||||
{% endif %}
|
||||
|
||||
# enable purge _upload directories
|
||||
{% if upload_purging is defined %}
|
||||
upload_purging:
|
||||
enabled: {{ upload_purging.enabled | lower}}
|
||||
age: {{ upload_purging.age }}
|
||||
interval: {{ upload_purging.interval }}
|
||||
dryrun: {{ upload_purging.dryrun | lower}}
|
||||
{% else %}
|
||||
upload_purging:
|
||||
enabled: true
|
||||
# remove files in _upload directories which exist for a period of time, default is one week.
|
||||
age: 168h
|
||||
# the interval of the purge operations
|
||||
interval: 24h
|
||||
dryrun: false
|
||||
{% endif %}
|
||||
|
||||
# Cache layer related config
|
||||
{% if cache is defined %}
|
||||
cache:
|
||||
enabled: {{ cache.enabled | lower}}
|
||||
expire_hours: {{ cache.expire_hours }}
|
||||
{% else %}
|
||||
cache:
|
||||
enabled: false
|
||||
expire_hours: 24
|
||||
{% endif %}
|
||||
|
||||
# Harbor core configurations
|
||||
# Uncomment to enable the following harbor core related configuration items.
|
||||
{% if core is defined %}
|
||||
core:
|
||||
# The provider for updating project quota(usage), there are 2 options, redis or db,
|
||||
# by default is implemented by db but you can switch the updation via redis which
|
||||
# can improve the performance of high concurrent pushing to the same project,
|
||||
# and reduce the database connections spike and occupies.
|
||||
# By redis will bring up some delay for quota usage updation for display, so only
|
||||
# suggest switch provider to redis if you were ran into the db connections spike aroud
|
||||
# the scenario of high concurrent pushing to same project, no improvment for other scenes.
|
||||
quota_update_provider: {{ core.quota_update_provider }}
|
||||
{% else %}
|
||||
# core:
|
||||
# # The provider for updating project quota(usage), there are 2 options, redis or db,
|
||||
# # by default is implemented by db but you can switch the updation via redis which
|
||||
# # can improve the performance of high concurrent pushing to the same project,
|
||||
# # and reduce the database connections spike and occupies.
|
||||
# # By redis will bring up some delay for quota usage updation for display, so only
|
||||
# # suggest switch provider to redis if you were ran into the db connections spike around
|
||||
# # the scenario of high concurrent pushing to same project, no improvement for other scenes.
|
||||
# quota_update_provider: redis # Or db
|
||||
{% endif %}
|
|
@ -4,7 +4,6 @@ _REDIS_URL_CORE={{redis_url_core}}
|
|||
{% if redis_url_harbor %}
|
||||
_REDIS_URL_HARBOR={{redis_url_harbor}}
|
||||
{% endif %}
|
||||
SYNC_QUOTA=true
|
||||
_REDIS_URL_REG={{redis_url_reg}}
|
||||
|
||||
LOG_LEVEL={{log_level}}
|
||||
|
@ -40,7 +39,8 @@ REGISTRY_CREDENTIAL_USERNAME={{registry_username}}
|
|||
REGISTRY_CREDENTIAL_PASSWORD={{registry_password}}
|
||||
CSRF_KEY={{csrf_key}}
|
||||
ROBOT_SCANNER_NAME_PREFIX={{scan_robot_prefix}}
|
||||
PERMITTED_REGISTRY_TYPES_FOR_PROXY_CACHE=docker-hub,harbor,azure-acr,ali-acr,aws-ecr,google-gcr,quay,docker-registry,github-ghcr,jfrog-artifactory
|
||||
PERMITTED_REGISTRY_TYPES_FOR_PROXY_CACHE=docker-hub,harbor,azure-acr,ali-acr,aws-ecr,google-gcr,docker-registry,github-ghcr,jfrog-artifactory
|
||||
REPLICATION_ADAPTER_WHITELIST=ali-acr,aws-ecr,azure-acr,docker-hub,docker-registry,github-ghcr,google-gcr,harbor,huawei-SWR,jfrog-artifactory,tencent-tcr,volcengine-cr
|
||||
|
||||
HTTP_PROXY={{core_http_proxy}}
|
||||
HTTPS_PROXY={{core_https_proxy}}
|
||||
|
@ -96,4 +96,4 @@ CACHE_EXPIRE_HOURS={{ cache.expire_hours }}
|
|||
|
||||
{% if core.quota_update_provider %}
|
||||
QUOTA_UPDATE_PROVIDER={{ core.quota_update_provider }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -252,4 +252,7 @@ const (
|
|||
|
||||
// Global Leeway used for token validation
|
||||
JwtLeeway = 60 * time.Second
|
||||
|
||||
// The replication adapter whitelist
|
||||
ReplicationAdapterWhiteList = "REPLICATION_ADAPTER_WHITELIST"
|
||||
)
|
||||
|
|
|
@ -162,6 +162,7 @@ var (
|
|||
{Resource: rbac.ResourceRobot, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRobot, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceNotificationPolicy, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceScan, Action: rbac.ActionCreate},
|
||||
|
|
|
@ -72,7 +72,7 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
|||
|
||||
// Can returns true only when requesting pull/push operation against the specific project
|
||||
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||
if !(action == rbac.ActionPull || action == rbac.ActionPush) {
|
||||
if !(action == rbac.ActionPull || action == rbac.ActionPush || action == rbac.ActionDelete) {
|
||||
log.Debugf("unauthorized for action %s", action)
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -63,8 +63,8 @@ func (p *proxyCacheSecretTestSuite) TestIsSolutionUser() {
|
|||
}
|
||||
|
||||
func (p *proxyCacheSecretTestSuite) TestCan() {
|
||||
// the action isn't pull/push
|
||||
action := rbac.ActionDelete
|
||||
// the action isn't pull/push/delete
|
||||
action := rbac.ActionUpdate
|
||||
resource := project.NewNamespace(1).Resource(rbac.ResourceRepository)
|
||||
p.False(p.sc.Can(context.TODO(), action, resource))
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ package parser
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
|
@ -40,6 +42,11 @@ const (
|
|||
|
||||
// defaultFileSizeLimit is the default file size limit.
|
||||
defaultFileSizeLimit = 1024 * 1024 * 4 // 4MB
|
||||
|
||||
// formatTar is the format of tar file.
|
||||
formatTar = ".tar"
|
||||
// formatRaw is the format of raw file.
|
||||
formatRaw = ".raw"
|
||||
)
|
||||
|
||||
// newBase creates a new base parser.
|
||||
|
@ -70,10 +77,23 @@ func (b *base) Parse(_ context.Context, artifact *artifact.Artifact, layer *ocis
|
|||
}
|
||||
|
||||
defer stream.Close()
|
||||
content, err := untar(stream)
|
||||
|
||||
content, err := decodeContent(layer.MediaType, stream)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to untar the content: %w", err)
|
||||
return "", nil, fmt.Errorf("failed to decode content: %w", err)
|
||||
}
|
||||
|
||||
return contentTypeTextPlain, content, nil
|
||||
}
|
||||
|
||||
func decodeContent(mediaType string, reader io.Reader) ([]byte, error) {
|
||||
format := filepath.Ext(mediaType)
|
||||
switch format {
|
||||
case formatTar:
|
||||
return untar(reader)
|
||||
case formatRaw:
|
||||
return io.ReadAll(reader)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format: %s", format)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,10 +63,11 @@ func TestBaseParse(t *testing.T) {
|
|||
expectedError: "failed to pull blob from registry: registry error",
|
||||
},
|
||||
{
|
||||
name: "successful parse",
|
||||
name: "successful parse (tar format)",
|
||||
artifact: &artifact.Artifact{RepositoryName: "test/repo"},
|
||||
layer: &v1.Descriptor{
|
||||
Digest: "sha256:1234",
|
||||
MediaType: "vnd.foo.bar.tar",
|
||||
Digest: "sha256:1234",
|
||||
},
|
||||
mockSetup: func(m *mock.Client) {
|
||||
var buf bytes.Buffer
|
||||
|
@ -82,6 +83,34 @@ func TestBaseParse(t *testing.T) {
|
|||
},
|
||||
expectedType: contentTypeTextPlain,
|
||||
},
|
||||
{
|
||||
name: "successful parse (raw format)",
|
||||
artifact: &artifact.Artifact{RepositoryName: "test/repo"},
|
||||
layer: &v1.Descriptor{
|
||||
MediaType: "vnd.foo.bar.raw",
|
||||
Digest: "sha256:1234",
|
||||
},
|
||||
mockSetup: func(m *mock.Client) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte("test content"))
|
||||
m.On("PullBlob", "test/repo", "sha256:1234").Return(int64(0), io.NopCloser(bytes.NewReader(buf.Bytes())), nil)
|
||||
},
|
||||
expectedType: contentTypeTextPlain,
|
||||
},
|
||||
{
|
||||
name: "error parse (unsupported format)",
|
||||
artifact: &artifact.Artifact{RepositoryName: "test/repo"},
|
||||
layer: &v1.Descriptor{
|
||||
MediaType: "vnd.foo.bar.unknown",
|
||||
Digest: "sha256:1234",
|
||||
},
|
||||
mockSetup: func(m *mock.Client) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte("test content"))
|
||||
m.On("PullBlob", "test/repo", "sha256:1234").Return(int64(0), io.NopCloser(bytes.NewReader(buf.Bytes())), nil)
|
||||
},
|
||||
expectedError: "failed to decode content: unsupported format: .unknown",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -17,6 +17,7 @@ package parser
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
modelspec "github.com/CloudNativeAI/model-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
@ -47,7 +48,10 @@ func (l *license) Parse(ctx context.Context, artifact *artifact.Artifact, manife
|
|||
// lookup the license file layer
|
||||
var layer *ocispec.Descriptor
|
||||
for _, desc := range manifest.Layers {
|
||||
if desc.MediaType == modelspec.MediaTypeModelDoc {
|
||||
if slices.Contains([]string{
|
||||
modelspec.MediaTypeModelDoc,
|
||||
modelspec.MediaTypeModelDocRaw,
|
||||
}, desc.MediaType) {
|
||||
if desc.Annotations != nil {
|
||||
filepath := desc.Annotations[modelspec.AnnotationFilepath]
|
||||
if filepath == "LICENSE" || filepath == "LICENSE.txt" {
|
||||
|
|
|
@ -83,6 +83,29 @@ func TestLicenseParser(t *testing.T) {
|
|||
expectedType: contentTypeTextPlain,
|
||||
expectedOutput: []byte("MIT License"),
|
||||
},
|
||||
{
|
||||
name: "LICENSE parse success (raw)",
|
||||
manifest: &ocispec.Manifest{
|
||||
Layers: []ocispec.Descriptor{
|
||||
{
|
||||
MediaType: modelspec.MediaTypeModelDocRaw,
|
||||
Annotations: map[string]string{
|
||||
modelspec.AnnotationFilepath: "LICENSE",
|
||||
},
|
||||
Digest: "sha256:abc123",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupMockReg: func(mc *mockregistry.Client) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte("MIT License"))
|
||||
|
||||
mc.On("PullBlob", mock.Anything, "sha256:abc123").
|
||||
Return(int64(buf.Len()), io.NopCloser(bytes.NewReader(buf.Bytes())), nil)
|
||||
},
|
||||
expectedType: contentTypeTextPlain,
|
||||
expectedOutput: []byte("MIT License"),
|
||||
},
|
||||
{
|
||||
name: "LICENSE.txt parse success",
|
||||
manifest: &ocispec.Manifest{
|
||||
|
|
|
@ -17,6 +17,7 @@ package parser
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
modelspec "github.com/CloudNativeAI/model-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
@ -47,7 +48,10 @@ func (r *readme) Parse(ctx context.Context, artifact *artifact.Artifact, manifes
|
|||
// lookup the readme file layer.
|
||||
var layer *ocispec.Descriptor
|
||||
for _, desc := range manifest.Layers {
|
||||
if desc.MediaType == modelspec.MediaTypeModelDoc {
|
||||
if slices.Contains([]string{
|
||||
modelspec.MediaTypeModelDoc,
|
||||
modelspec.MediaTypeModelDocRaw,
|
||||
}, desc.MediaType) {
|
||||
if desc.Annotations != nil {
|
||||
filepath := desc.Annotations[modelspec.AnnotationFilepath]
|
||||
if filepath == "README" || filepath == "README.md" {
|
||||
|
|
|
@ -113,6 +113,29 @@ func TestReadmeParser(t *testing.T) {
|
|||
expectedType: contentTypeMarkdown,
|
||||
expectedOutput: []byte("# Test README"),
|
||||
},
|
||||
{
|
||||
name: "README parse success (raw)",
|
||||
manifest: &ocispec.Manifest{
|
||||
Layers: []ocispec.Descriptor{
|
||||
{
|
||||
MediaType: modelspec.MediaTypeModelDocRaw,
|
||||
Annotations: map[string]string{
|
||||
modelspec.AnnotationFilepath: "README",
|
||||
},
|
||||
Digest: "sha256:def456",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupMockReg: func(mc *mockregistry.Client) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte("# Test README"))
|
||||
|
||||
mc.On("PullBlob", mock.Anything, "sha256:def456").
|
||||
Return(int64(buf.Len()), io.NopCloser(bytes.NewReader(buf.Bytes())), nil)
|
||||
},
|
||||
expectedType: contentTypeMarkdown,
|
||||
expectedOutput: []byte("# Test README"),
|
||||
},
|
||||
{
|
||||
name: "registry error",
|
||||
manifest: &ocispec.Manifest{
|
||||
|
|
|
@ -149,6 +149,22 @@ type ManifestList struct {
|
|||
ContentType string
|
||||
}
|
||||
|
||||
// getManifestDigestInLocal get the artifact digest in local
|
||||
func (c *controller) getManifestDigestInLocal(ctx context.Context, art lib.ArtifactInfo) (string, error) {
|
||||
// Get the manifest from local registry
|
||||
a, err := c.local.GetManifest(ctx, art)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if a == nil {
|
||||
return "", errors.NotFoundError(fmt.Errorf("manifest %v not found in local registry", art.Repository))
|
||||
}
|
||||
if len(a.Digest) == 0 {
|
||||
return "", errors.NotFoundError(fmt.Errorf("manifest %v not found in local registry", art.Repository))
|
||||
}
|
||||
return a.Digest, nil
|
||||
}
|
||||
|
||||
// UseLocalManifest check if these manifest could be found in local registry,
|
||||
// the return error should be nil when it is not found in local and need to delegate to remote registry
|
||||
// the return error should be NotFoundError when it is not found in remote registry
|
||||
|
@ -172,6 +188,16 @@ func (c *controller) UseLocalManifest(ctx context.Context, art lib.ArtifactInfo,
|
|||
return false, nil, err
|
||||
}
|
||||
if !exist || desc == nil {
|
||||
dig, err := c.getManifestDigestInLocal(ctx, art)
|
||||
if err != nil {
|
||||
// skip to delete when error, use debug level log to avoid too many logs when the manifest is removed from upstream
|
||||
log.Debugf("failed to get manifest digest in local, error: %v, skip to delete it, art %+v", err, art)
|
||||
} else {
|
||||
go func() {
|
||||
c.local.DeleteManifest(art.Repository, dig)
|
||||
log.Infof("delete manifest %s with digest %s", art.Repository, dig)
|
||||
}()
|
||||
}
|
||||
return false, nil, errors.NotFoundError(fmt.Errorf("repo %v, tag %v not found", art.Repository, art.Tag))
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,12 @@ package registry
|
|||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
|
@ -205,12 +208,49 @@ func (c *controller) GetInfo(ctx context.Context, id int64) (*model.RegistryInfo
|
|||
return info, nil
|
||||
}
|
||||
|
||||
func getWhitelistedAdapters(ctx context.Context) map[string]struct{} {
|
||||
adapterWhitelistRaw := config.GetCfgManager(ctx).Get(ctx, common.ReplicationAdapterWhiteList).GetString()
|
||||
if adapterWhitelistRaw == "" {
|
||||
return nil
|
||||
}
|
||||
adapterWhitelist := make(map[string]struct{})
|
||||
for _, adapter := range strings.Split(adapterWhitelistRaw, ",") {
|
||||
adapter = strings.TrimSpace(adapter)
|
||||
if adapter != "" {
|
||||
adapterWhitelist[adapter] = struct{}{}
|
||||
}
|
||||
}
|
||||
return adapterWhitelist
|
||||
}
|
||||
|
||||
func (c *controller) ListRegistryProviderTypes(ctx context.Context) ([]string, error) {
|
||||
return c.regMgr.ListRegistryProviderTypes(ctx)
|
||||
allAdapters, err := c.regMgr.ListRegistryProviderTypes(ctx)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
whitelistedAdapters := getWhitelistedAdapters(ctx)
|
||||
var filtered []string
|
||||
for _, t := range allAdapters {
|
||||
if _, ok := whitelistedAdapters[t]; ok {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (c *controller) ListRegistryProviderInfos(ctx context.Context) (map[string]*model.AdapterPattern, error) {
|
||||
return c.regMgr.ListRegistryProviderInfos(ctx)
|
||||
allAdaptersInfo, err := c.regMgr.ListRegistryProviderInfos(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
whitelistedAdapters := getWhitelistedAdapters(ctx)
|
||||
filtered := make(map[string]*model.AdapterPattern)
|
||||
for k, v := range allAdaptersInfo {
|
||||
if _, ok := whitelistedAdapters[k]; ok {
|
||||
filtered[k] = v
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (c *controller) StartRegularHealthCheck(ctx context.Context, closing, done chan struct{}) {
|
||||
|
|
|
@ -15,10 +15,14 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
|
||||
"github.com/goharbor/harbor/src/pkg/reg/model"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
testingproject "github.com/goharbor/harbor/src/testing/pkg/project"
|
||||
|
@ -174,6 +178,64 @@ func (r *registryTestSuite) TestDelete() {
|
|||
r.proMgr.AssertExpectations(r.T())
|
||||
}
|
||||
|
||||
func (r *registryTestSuite) TestGetWhitelistedAdapters() {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected map[string]struct{}
|
||||
}{
|
||||
{
|
||||
name: "adapter empty",
|
||||
input: "",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "adapters with spaces",
|
||||
input: "dockerhub, aws, gcr ",
|
||||
expected: map[string]struct{}{
|
||||
"dockerhub": {},
|
||||
"aws": {},
|
||||
"gcr": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "adapters with empty entries",
|
||||
input: "harbor, , quay,",
|
||||
expected: map[string]struct{}{
|
||||
"harbor": {},
|
||||
"quay": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "adapters all",
|
||||
input: "ali-acr,aws-ecr,azure-acr,docker-hub,google-gcr,harbor,huawei-SWR,jfrog-artifactory,tencent-tcr,volcengine-cr",
|
||||
expected: map[string]struct{}{
|
||||
"ali-acr": {},
|
||||
"aws-ecr": {},
|
||||
"azure-acr": {},
|
||||
"docker-hub": {},
|
||||
"google-gcr": {},
|
||||
"harbor": {},
|
||||
"huawei-SWR": {},
|
||||
"jfrog-artifactory": {},
|
||||
"tencent-tcr": {},
|
||||
"volcengine-cr": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
r.Run(tt.name, func() {
|
||||
conf := map[string]any{
|
||||
common.ReplicationAdapterWhiteList: tt.input,
|
||||
}
|
||||
config.InitWithSettings(conf)
|
||||
result := getWhitelistedAdapters(context.TODO())
|
||||
r.Equal(tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryTestSuite(t *testing.T) {
|
||||
suite.Run(t, ®istryTestSuite{})
|
||||
}
|
||||
|
|
|
@ -109,10 +109,37 @@ func (c *controller) Start(ctx context.Context, policy *replicationmodel.Policy,
|
|||
if op := operator.FromContext(ctx); op != "" {
|
||||
extra["operator"] = op
|
||||
}
|
||||
|
||||
var count int64
|
||||
// If running executions are found, skip the current execution and mark it as error.
|
||||
if policy.SingleActiveReplication {
|
||||
var err error
|
||||
count, err = c.execMgr.Count(ctx, &q.Query{
|
||||
Keywords: map[string]any{
|
||||
"VendorType": job.ReplicationVendorType,
|
||||
"VendorID": policy.ID,
|
||||
"Status": job.RunningStatus.String(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to count running executions for policy ID: %d: %v", policy.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
id, err := c.execMgr.Create(ctx, job.ReplicationVendorType, policy.ID, trigger, extra)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if policy.SingleActiveReplication {
|
||||
if count > 0 {
|
||||
if err = c.execMgr.MarkError(ctx, id, "Execution skipped: active replication still in progress."); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
|
||||
// start the replication flow in background
|
||||
// as the process runs inside a goroutine, the transaction in the outer ctx
|
||||
// may be submitted already when the process starts, so create an new context
|
||||
|
|
|
@ -101,6 +101,38 @@ func (r *replicationTestSuite) TestStart() {
|
|||
r.execMgr.AssertExpectations(r.T())
|
||||
r.flowCtl.AssertExpectations(r.T())
|
||||
r.ormCreator.AssertExpectations(r.T())
|
||||
|
||||
r.SetupTest()
|
||||
|
||||
// run replication flow with SingleActiveReplication, flow should not start
|
||||
r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
r.execMgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
r.execMgr.On("Count", mock.Anything, mock.Anything).Return(int64(1), nil) // Simulate an existing running execution
|
||||
id, err = r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: true, SingleActiveReplication: true}, nil, task.ExecutionTriggerManual)
|
||||
r.Require().Nil(err)
|
||||
r.Equal(int64(1), id)
|
||||
time.Sleep(1 * time.Second) // wait the functions called in the goroutine
|
||||
r.flowCtl.AssertNumberOfCalls(r.T(), "Start", 0)
|
||||
r.execMgr.AssertNumberOfCalls(r.T(), "MarkError", 1) // Ensure execution marked as final status error
|
||||
r.execMgr.AssertExpectations(r.T())
|
||||
r.flowCtl.AssertExpectations(r.T())
|
||||
r.ormCreator.AssertExpectations(r.T())
|
||||
|
||||
r.SetupTest()
|
||||
|
||||
// no error when running the replication flow with SingleActiveReplication
|
||||
r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil)
|
||||
r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
r.ormCreator.On("Create").Return(nil)
|
||||
r.execMgr.On("Count", mock.Anything, mock.Anything).Return(int64(0), nil) // Simulate no running execution
|
||||
id, err = r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: true, SingleActiveReplication: true}, nil, task.ExecutionTriggerManual)
|
||||
r.Require().Nil(err)
|
||||
r.Equal(int64(1), id)
|
||||
time.Sleep(1 * time.Second) // wait the functions called in the goroutine
|
||||
r.execMgr.AssertExpectations(r.T())
|
||||
r.flowCtl.AssertExpectations(r.T())
|
||||
r.ormCreator.AssertExpectations(r.T())
|
||||
}
|
||||
|
||||
func (r *replicationTestSuite) TestStop() {
|
||||
|
|
|
@ -47,6 +47,7 @@ type Policy struct {
|
|||
UpdateTime time.Time `json:"update_time"`
|
||||
Speed int32 `json:"speed"`
|
||||
CopyByChunk bool `json:"copy_by_chunk"`
|
||||
SingleActiveReplication bool `json:"single_active_replication"`
|
||||
}
|
||||
|
||||
// IsScheduledTrigger returns true when the policy is scheduled trigger and enabled
|
||||
|
@ -141,6 +142,7 @@ func (p *Policy) From(policy *replicationmodel.Policy) error {
|
|||
p.UpdateTime = policy.UpdateTime
|
||||
p.Speed = policy.Speed
|
||||
p.CopyByChunk = policy.CopyByChunk
|
||||
p.SingleActiveReplication = policy.SingleActiveReplication
|
||||
|
||||
if policy.SrcRegistryID > 0 {
|
||||
p.SrcRegistry = &model.Registry{
|
||||
|
@ -186,6 +188,7 @@ func (p *Policy) To() (*replicationmodel.Policy, error) {
|
|||
UpdateTime: p.UpdateTime,
|
||||
Speed: p.Speed,
|
||||
CopyByChunk: p.CopyByChunk,
|
||||
SingleActiveReplication: p.SingleActiveReplication,
|
||||
}
|
||||
if p.SrcRegistry != nil {
|
||||
policy.SrcRegistryID = p.SrcRegistry.ID
|
||||
|
|
|
@ -597,7 +597,9 @@ func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if !scannable {
|
||||
// When the scanner is unhealthy, the artifact will be recognized as "not scannable", in this case we will not return the error
|
||||
// but return the scanner report with the best effort.
|
||||
if !scannable && r.Health == sc.StatusHealthy {
|
||||
return nil, errors.NotFoundError(nil).WithMessagef("report not found for %s@%s", artifact.RepositoryName, artifact.Digest)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ import (
|
|||
|
||||
const (
|
||||
proScannerMetaKey = "projectScanner"
|
||||
statusUnhealthy = "unhealthy"
|
||||
statusHealthy = "healthy"
|
||||
StatusUnhealthy = "unhealthy"
|
||||
StatusHealthy = "healthy"
|
||||
// RetrieveCapFailMsg the message indicate failed to retrieve the scanner capabilities
|
||||
RetrieveCapFailMsg = "failed to retrieve scanner capabilities, error %v"
|
||||
)
|
||||
|
@ -287,9 +287,9 @@ func (bc *basicController) GetRegistrationByProject(ctx context.Context, project
|
|||
if err != nil {
|
||||
// Not blocked, just logged it
|
||||
log.Error(errors.Wrap(err, "api controller: get project scanner"))
|
||||
registration.Health = statusUnhealthy
|
||||
registration.Health = StatusUnhealthy
|
||||
} else {
|
||||
registration.Health = statusHealthy
|
||||
registration.Health = StatusHealthy
|
||||
// Fill in some metadata
|
||||
registration.Adapter = meta.Scanner.Name
|
||||
registration.Vendor = meta.Scanner.Vendor
|
||||
|
|
45
src/go.mod
45
src/go.mod
|
@ -1,20 +1,20 @@
|
|||
module github.com/goharbor/harbor/src
|
||||
|
||||
go 1.24.3
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
github.com/CloudNativeAI/model-spec v0.0.3
|
||||
github.com/CloudNativeAI/model-spec v0.0.5
|
||||
github.com/FZambia/sentinel v1.1.0
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
github.com/beego/beego/v2 v2.3.6
|
||||
github.com/beego/beego/v2 v2.3.8
|
||||
github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0
|
||||
github.com/bmatcuk/doublestar v1.3.4
|
||||
github.com/casbin/casbin v1.9.1
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/cloudevents/sdk-go/v2 v2.15.2
|
||||
github.com/cloudevents/sdk-go/v2 v2.16.1
|
||||
github.com/coreos/go-oidc/v3 v3.12.0
|
||||
github.com/dghubble/sling v1.4.2
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
|
@ -26,7 +26,7 @@ require (
|
|||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0
|
||||
github.com/go-openapi/swag v0.23.0
|
||||
github.com/go-openapi/swag v0.23.1
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.4
|
||||
github.com/gocarina/gocsv v0.0.0-20210516172204-ca9e8a8ddea8
|
||||
|
@ -50,13 +50,13 @@ require (
|
|||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/prometheus/client_model v0.6.1
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.19
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.29
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.59.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
|
||||
go.opentelemetry.io/otel v1.35.0
|
||||
|
@ -66,19 +66,19 @@ require (
|
|||
go.opentelemetry.io/otel/trace v1.35.0
|
||||
go.pinniped.dev v0.37.0
|
||||
go.uber.org/ratelimit v0.3.1
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/net v0.38.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/oauth2 v0.28.0
|
||||
golang.org/x/sync v0.14.0
|
||||
golang.org/x/text v0.24.0
|
||||
golang.org/x/time v0.11.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/text v0.27.0
|
||||
golang.org/x/time v0.12.0
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
helm.sh/helm/v3 v3.18.2
|
||||
k8s.io/api v0.33.1
|
||||
k8s.io/apimachinery v0.33.1
|
||||
k8s.io/client-go v0.33.1
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
helm.sh/helm/v3 v3.18.5
|
||||
k8s.io/api v0.33.3
|
||||
k8s.io/apimachinery v0.33.3
|
||||
k8s.io/client-go v0.33.3
|
||||
sigs.k8s.io/yaml v1.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -138,7 +138,7 @@ require (
|
|||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
|
@ -161,7 +161,7 @@ require (
|
|||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
|
@ -177,15 +177,16 @@ require (
|
|||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
google.golang.org/api v0.171.0 // indirect
|
||||
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.69.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
|
99
src/go.sum
99
src/go.sum
|
@ -40,8 +40,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzS
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CloudNativeAI/model-spec v0.0.3 h1:5mvgFQ+3pyupzxYjtV5XAeg9zRe6+46pLPBFNHlOrqE=
|
||||
github.com/CloudNativeAI/model-spec v0.0.3/go.mod h1:3U/4zubBfbUkW59ATSg41HnkYyKrKUcKFH/cVdoPQnk=
|
||||
github.com/CloudNativeAI/model-spec v0.0.5 h1:gVwvpiVJgGZynvIi+xy0Hp/zh+g1EuWa6x6Czm4P0uA=
|
||||
github.com/CloudNativeAI/model-spec v0.0.5/go.mod h1:3U/4zubBfbUkW59ATSg41HnkYyKrKUcKFH/cVdoPQnk=
|
||||
github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8=
|
||||
github.com/FZambia/sentinel v1.1.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
|
@ -68,8 +68,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W
|
|||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
||||
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/beego/beego/v2 v2.3.6 h1:VorPq190QdbA6ZSCM4K901gBkjcpN3csi7ReDk63ClI=
|
||||
github.com/beego/beego/v2 v2.3.6/go.mod h1:5cqHsOHJIxkq44tBpRvtDe59GuVRVv/9/tyVDxd5ce4=
|
||||
github.com/beego/beego/v2 v2.3.8 h1:wplhB1pF4TxR+2SS4PUej8eDoH4xGfxuHfS7wAk9VBc=
|
||||
github.com/beego/beego/v2 v2.3.8/go.mod h1:8vl9+RrXqvodrl9C8yivX1e6le6deCK6RWeq8R7gTTg=
|
||||
github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0 h1:fQaDnUQvBXHHQdGBu9hz8nPznB4BeiPQokvmQVjmNEw=
|
||||
github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0/go.mod h1:KLeFCpAMq2+50NkXC8iiJxLLiiTfTqrGtKEVm+2fk7s=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
|
@ -89,8 +89,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc=
|
||||
github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE=
|
||||
github.com/cloudevents/sdk-go/v2 v2.16.1 h1:G91iUdqvl88BZ1GYYr9vScTj5zzXSyEuqbfE63gbu9Q=
|
||||
github.com/cloudevents/sdk-go/v2 v2.16.1/go.mod h1:v/kVOaWjNfbvc6tkhhlkhvLapj8Aa8kvXiH5GiOHCKI=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
|
@ -185,8 +185,8 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z
|
|||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
|
||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
|
||||
|
@ -393,8 +393,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
|
@ -402,8 +402,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
|
||||
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
|
@ -472,8 +472,8 @@ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8
|
|||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
|
@ -522,8 +522,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
|||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -564,8 +564,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
|
|||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.19 h1:+jLVMqDtdtiAt8QGBk6AMiEg22Br5SZGu2FSHUrIcU0=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.19/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.29 h1:wAc8VbZvCAHsE6KqVawvZA4epB6k+eVVUvso9h0n274=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.29/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
|
||||
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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -621,6 +621,10 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
|
||||
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
|
@ -638,8 +642,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
|||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -666,8 +670,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -693,8 +697,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
|
@ -710,8 +714,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -746,8 +750,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
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=
|
||||
|
@ -758,8 +762,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/text v0.3.0/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=
|
||||
|
@ -772,10 +776,10 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -798,8 +802,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -839,8 +843,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
|||
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.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -874,17 +878,17 @@ 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.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
helm.sh/helm/v3 v3.18.2 h1:mPQP/HHYjNEDAztAK50dD6uxTCNV1zSVU38WwSVdw9M=
|
||||
helm.sh/helm/v3 v3.18.2/go.mod h1:43QHS1W97RcoFJRk36ZBhHdTfykqBlJdsWp3yhzdq8w=
|
||||
helm.sh/helm/v3 v3.18.5 h1:Cc3Z5vd6kDrZq9wO9KxKLNEickiTho6/H/dBNRVSos4=
|
||||
helm.sh/helm/v3 v3.18.5/go.mod h1:L/dXDR2r539oPlFP1PJqKAC1CUgqHJDLkxKpDGrWnyg=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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=
|
||||
k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw=
|
||||
k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
|
||||
k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
|
||||
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
|
||||
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
|
||||
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
|
||||
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
|
||||
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
|
||||
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
|
||||
k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
|
@ -899,5 +903,6 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
|||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
|
||||
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
|
||||
|
|
|
@ -203,5 +203,7 @@ var (
|
|||
|
||||
{Name: common.BeegoMaxMemoryBytes, Scope: SystemScope, Group: BasicGroup, EnvKey: "BEEGO_MAX_MEMORY_BYTES", DefaultValue: fmt.Sprintf("%d", common.DefaultBeegoMaxMemoryBytes), ItemType: &Int64Type{}, Editable: false, Description: `The bytes for limiting the beego max memory, default is 128GB`},
|
||||
{Name: common.BeegoMaxUploadSizeBytes, Scope: SystemScope, Group: BasicGroup, EnvKey: "BEEGO_MAX_UPLOAD_SIZE_BYTES", DefaultValue: fmt.Sprintf("%d", common.DefaultBeegoMaxUploadSizeBytes), ItemType: &Int64Type{}, Editable: false, Description: `The bytes for limiting the beego max upload size, default it 128GB`},
|
||||
|
||||
{Name: common.ReplicationAdapterWhiteList, Scope: SystemScope, Group: BasicGroup, EnvKey: "REPLICATION_ADAPTER_WHITELIST", DefaultValue: "", ItemType: &StringType{}, Editable: false},
|
||||
}
|
||||
)
|
||||
|
|
|
@ -161,17 +161,36 @@ func setFilters(ctx context.Context, qs orm.QuerySeter, query *q.Query, meta *me
|
|||
log.Warningf("The separator '%s' is not valid in the query parameter '%s__%s'. Please use the correct field name.", orm.ExprSep, keyPieces[0], keyPieces[1])
|
||||
continue
|
||||
}
|
||||
k := keyPieces[0]
|
||||
mk, filterable := meta.Filterable(k)
|
||||
fieldKey := keyPieces[0]
|
||||
mk, filterable := meta.Filterable(fieldKey)
|
||||
if !filterable {
|
||||
// This is a workaround for the unsuitable usage of query, the keyword format for field and method should be consistent
|
||||
// e.g. "ArtifactDigest" or the snake case format "artifact_digest" should be used instead:
|
||||
// https://github.com/goharbor/harbor/blob/v2.2.0/src/controller/blob/controller.go#L233
|
||||
mk, filterable = meta.Filterable(snakeCase(k))
|
||||
mk, filterable = meta.Filterable(snakeCase(fieldKey))
|
||||
if mk == nil || !filterable {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// only accept the below operators
|
||||
if len(keyPieces) == 2 {
|
||||
operator := orm.ExprSep + keyPieces[1]
|
||||
allowedOperators := map[string]struct{}{
|
||||
"__icontains": {},
|
||||
"__in": {},
|
||||
"__gte": {},
|
||||
"__lte": {},
|
||||
"__gt": {},
|
||||
"__lt": {},
|
||||
"__exact": {},
|
||||
}
|
||||
if _, ok := allowedOperators[operator]; !ok {
|
||||
log.Warningf("the operator '%s' in query parameter '%s' is not supported, the query will be skipped.", operator, key)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// filter function defined, use it directly
|
||||
if mk.FilterFunc != nil {
|
||||
qs = mk.FilterFunc(ctx, qs, key, value)
|
||||
|
|
|
@ -43,6 +43,7 @@ type Policy struct {
|
|||
UpdateTime time.Time `orm:"column(update_time);auto_now"`
|
||||
Speed int32 `orm:"column(speed_kb)"`
|
||||
CopyByChunk bool `orm:"column(copy_by_chunk)"`
|
||||
SingleActiveReplication bool `orm:"column(single_active_replication)"`
|
||||
}
|
||||
|
||||
// TableName set table name for ORM
|
||||
|
|
|
@ -159,7 +159,7 @@ func (d *dao) NonEmptyRepos(ctx context.Context) ([]*model.RepoRecord, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
sql := `select * from repository where repository_id in (select distinct repository_id from tag)`
|
||||
sql := `select * from repository where exists (select 1 from tag where tag.repository_id = repository.repository_id)`
|
||||
_, err = ormer.Raw(sql).QueryRows(&repos)
|
||||
if err != nil {
|
||||
return repos, err
|
||||
|
|
|
@ -825,6 +825,39 @@
|
|||
'REPLICATION.ENABLED_RULE' | translate
|
||||
}}</label>
|
||||
</div>
|
||||
<div
|
||||
class="clr-checkbox-wrapper"
|
||||
[hidden]="!isNotEventBased()">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="clr-checkbox"
|
||||
[checked]="true"
|
||||
id="singleActiveReplication"
|
||||
formControlName="single_active_replication" />
|
||||
<label
|
||||
for="singleActiveReplication"
|
||||
class="clr-control-label single-active"
|
||||
>{{
|
||||
'REPLICATION.SINGLE_ACTIVE_REPLICATION'
|
||||
| translate
|
||||
}}
|
||||
<clr-tooltip class="override-tooltip">
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-left"
|
||||
clrSize="md"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'TOOLTIP.SINGLE_ACTIVE_REPLICATION'
|
||||
| translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -246,6 +246,10 @@ clr-modal {
|
|||
width: 8.6rem;
|
||||
}
|
||||
|
||||
.single-active {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.des-tooltip {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
|
|
@ -334,6 +334,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||
override: true,
|
||||
speed: -1,
|
||||
copy_by_chunk: false,
|
||||
single_active_replication: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -367,6 +368,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||
dest_namespace_replace_count: Flatten_Level.FLATTEN_LEVEl_1,
|
||||
speed: -1,
|
||||
copy_by_chunk: false,
|
||||
single_active_replication: false,
|
||||
});
|
||||
this.isPushMode = true;
|
||||
this.selectedUnit = BandwidthUnit.KB;
|
||||
|
@ -410,6 +412,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||
override: rule.override,
|
||||
speed: speed,
|
||||
copy_by_chunk: rule.copy_by_chunk,
|
||||
single_active_replication: rule.single_active_replication,
|
||||
});
|
||||
let filtersArray = this.getFilterArray(rule);
|
||||
this.noSelectedEndpoint = false;
|
||||
|
@ -510,6 +513,9 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.ruleForm.value.trigger.type === 'event_based') {
|
||||
this.ruleForm.get('single_active_replication').setValue(false);
|
||||
}
|
||||
if (this.ruleForm.value.trigger.type !== 'scheduled') {
|
||||
this.ruleForm
|
||||
.get('trigger')
|
||||
|
|
|
@ -138,7 +138,7 @@
|
|||
}}</a>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
{{ getStatusStr(j.status) }}
|
||||
{{ getStatusStr(j.status, j.status_text) }}
|
||||
<clr-tooltip>
|
||||
<clr-icon
|
||||
*ngIf="j.status_text"
|
||||
|
|
|
@ -644,7 +644,11 @@ export class ReplicationComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
getStatusStr(status: string): string {
|
||||
getStatusStr(status: string, status_text: string): string {
|
||||
// If status is Failed and status_text has 'Execution skipped', it means the replication task is skipped.
|
||||
if (status === 'Failed' && status_text.startsWith('Execution skipped'))
|
||||
return 'Skipped';
|
||||
|
||||
if (STATUS_MAP && STATUS_MAP[status]) {
|
||||
return STATUS_MAP[status];
|
||||
}
|
||||
|
|
|
@ -196,7 +196,7 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
|||
msg.includes('old_password_is_not_correct')
|
||||
) {
|
||||
this.inlineAlert.showInlineError(
|
||||
'INCONRRECT_OLD_PWD'
|
||||
'INCORRECT_OLD_PWD'
|
||||
);
|
||||
} else {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
}}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
<clr-control-helper class="config-subtext">
|
||||
{{ 'PROJECT_CONFIG.CONTENT_TRUST_POLCIY' | translate }}
|
||||
{{ 'PROJECT_CONFIG.CONTENT_TRUST_POLICY' | translate }}
|
||||
</clr-control-helper>
|
||||
</clr-checkbox-container>
|
||||
<clr-checkbox-container
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "Lade die Ressourcen von der entfernten Registry auf den lokalen Harbor runter.",
|
||||
"DESTINATION_NAMESPACE": "Spezifizieren des Ziel-Namespace. Wenn das Feld leer ist, werden die Ressourcen unter dem gleichen Namespace abgelegt wie in der Quelle.",
|
||||
"OVERRIDE": "Spezifizieren, ob die Ressourcen am Ziel überschrieben werden sollen, falls eine Ressource mit gleichem Namen existiert.",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "E-Mail sollte eine gültige E-Mail-Adresse wie name@example.com sein.",
|
||||
"USER_NAME": "Darf keine Sonderzeichen enthalten und sollte kürzer als 255 Zeichen sein.",
|
||||
"FULL_NAME": "Maximale Länge soll 20 Zeichen sein.",
|
||||
|
@ -293,7 +294,7 @@
|
|||
"PUBLIC_POLICY": "Ein Projekt öffentlich einzustellen, macht die Repositories für alle zugreifbar.",
|
||||
"SECURITY": "Deployment Sicherheit",
|
||||
"CONTENT_TRUST_TOGGLE": "Aktiviere Content Trust",
|
||||
"CONTENT_TRUST_POLCIY": "Erlaube ausschließlich verifizierte Images.",
|
||||
"CONTENT_TRUST_POLICY": "Erlaube ausschließlich verifizierte Images.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "Verhindere den Download von Images mit Schwachstellen.",
|
||||
"PREVENT_VULNERABLE_1": "Verhindere den Download von Images mit Schwachstellen des Schweregrads ",
|
||||
"PREVENT_VULNERABLE_2": "und darüber.",
|
||||
|
@ -578,6 +579,7 @@
|
|||
"ALLOWED_CHARACTERS": "Erlaubte Sonderzeichen",
|
||||
"TOTAL": "Gesamt",
|
||||
"OVERRIDE": "Überschreiben",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "Aktiviere Regel",
|
||||
"OVERRIDE_INFO": "Überschreiben",
|
||||
"OPERATION": "Operation",
|
||||
|
@ -1309,7 +1311,7 @@
|
|||
"CONFLICT_ERROR": "Der Befehl kann nicht ausgeführt werden, da es Konflikte gibt.",
|
||||
"PRECONDITION_FAILED": "Der Befehl kann wegen eines Überprüfungsfehlers nicht ausgeführt werden.",
|
||||
"SERVER_ERROR": "Der Befehl kann wegen einem internen Serverfehler nicht ausgeführt werden.",
|
||||
"INCONRRECT_OLD_PWD": "Das alte Passwort ist nicht korrekt.",
|
||||
"INCORRECT_OLD_PWD": "Das alte Passwort ist nicht korrekt.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS": "Status",
|
||||
"START_TIME": "Startzeit",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "Pull the resources from the remote registry to the local Harbor.",
|
||||
"DESTINATION_NAMESPACE": "Specify the destination namespace. If empty, the resources will be put under the same namespace as the source.",
|
||||
"OVERRIDE": "Specify whether to override the resources at the destination if a resource with the same name exists.",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "Email should be a valid email address like name@example.com.",
|
||||
"USER_NAME": "Cannot contain special characters and maximum length should be 255 characters.",
|
||||
"FULL_NAME": "Maximum length should be 20 characters.",
|
||||
|
@ -293,7 +294,7 @@
|
|||
"PUBLIC_POLICY": "Making a project registry public will make all repositories accessible to everyone.",
|
||||
"SECURITY": "Deployment security",
|
||||
"CONTENT_TRUST_TOGGLE": "Enable content trust",
|
||||
"CONTENT_TRUST_POLCIY": "Allow only verified images to be deployed.",
|
||||
"CONTENT_TRUST_POLICY": "Allow only verified images to be deployed.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "Prevent vulnerable images from running.",
|
||||
"PREVENT_VULNERABLE_1": "Prevent images with vulnerability severity of",
|
||||
"PREVENT_VULNERABLE_2": "and above from being deployed.",
|
||||
|
@ -578,6 +579,7 @@
|
|||
"ALLOWED_CHARACTERS": "Allowed special characters",
|
||||
"TOTAL": "Total",
|
||||
"OVERRIDE": "Override",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "Enable rule",
|
||||
"OVERRIDE_INFO": "Override",
|
||||
"OPERATION": "Operation",
|
||||
|
@ -800,7 +802,7 @@
|
|||
"TAGS_NO_DELETE": "Delete is prohibited in read only mode.",
|
||||
"FILTER_FOR_REPOSITORIES": "Filter Repositories",
|
||||
"TAG": "Tag",
|
||||
"ARTIFACT": "Aarifact",
|
||||
"ARTIFACT": "Artifact",
|
||||
"ARTIFACTS": "Artifacts",
|
||||
"SIZE": "Size",
|
||||
"VULNERABILITY": "Vulnerabilities",
|
||||
|
@ -1312,7 +1314,7 @@
|
|||
"CONFLICT_ERROR": "We are unable to perform your action because your submission has conflicts.",
|
||||
"PRECONDITION_FAILED": "We are unable to perform your action because of a precondition failure.",
|
||||
"SERVER_ERROR": "We are unable to perform your action because internal server errors have occurred.",
|
||||
"INCONRRECT_OLD_PWD": "The old password is incorrect.",
|
||||
"INCORRECT_OLD_PWD": "The old password is incorrect.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS": "Status",
|
||||
"START_TIME": "Start Time",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "Pull de recursos del remote registry al local Harbor.",
|
||||
"DESTINATION_NAMESPACE": "Especificar el namespace de destino. Si esta vacio, los recursos se colocan en el mismo namespace del recurso.",
|
||||
"OVERRIDE": "Especifique si desea anular los recursos en el destino si existe un recurso con el mismo nombre.",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "El email debe ser una dirección válida como nombre@ejemplo.com.",
|
||||
"USER_NAME": "Debe tener una longitud máxima de 255 caracteres y no puede contener caracteres especiales.",
|
||||
"FULL_NAME": "La longitud máxima debería ser de 20 caracteres.",
|
||||
|
@ -294,7 +295,7 @@
|
|||
"PUBLIC_POLICY": "Hacer público un registro de proyecto hará que todos los repositorios sean accesibles para todos.",
|
||||
"SECURITY": "Seguridad Despliegues",
|
||||
"CONTENT_TRUST_TOGGLE": "Habilitar la confianza de contenido",
|
||||
"CONTENT_TRUST_POLCIY": "Solo permita la implementación de imágenes verificadas.",
|
||||
"CONTENT_TRUST_POLICY": "Solo permita la implementación de imágenes verificadas.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "Evitar que se ejecuten imágenes vulnerables.",
|
||||
"PREVENT_VULNERABLE_1": "Impedir imágenes con la gravedad de la vulnerabilidad de",
|
||||
"PREVENT_VULNERABLE_2": "y más arriba de ser desplegado.",
|
||||
|
@ -578,6 +579,7 @@
|
|||
"ALLOWED_CHARACTERS": "Caracteres Especiales Permitidos",
|
||||
"TOTAL": "Total",
|
||||
"OVERRIDE": "Sobreescribir",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "Activar regla",
|
||||
"OVERRIDE_INFO": "Sobreescribir",
|
||||
"CURRENT": "Actual",
|
||||
|
@ -1306,7 +1308,7 @@
|
|||
"CONFLICT_ERROR": "No hemos podido llevar a cabo la acción debido a un conflicto.",
|
||||
"PRECONDITION_FAILED": "No hemos podido llevar a cabo la acción debido a un error de precondición.",
|
||||
"SERVER_ERROR": "No hemos podido llevar a cabo la acción debido a un error interno.",
|
||||
"INCONRRECT_OLD_PWD": "La contraseña antigua no es correcta.",
|
||||
"INCORRECT_OLD_PWD": "La contraseña antigua no es correcta.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS": "Estatus",
|
||||
"START_TIME": "Tiempo Inicio",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "Pull les ressources du registre distant vers le Harbor local.",
|
||||
"DESTINATION_NAMESPACE": "Spécifier l'espace de nom de destination. Si vide, les ressources seront placées sous le même espace de nom que la source.",
|
||||
"OVERRIDE": "Spécifier s'il faut remplacer les ressources dans la destination si une ressource avec le même nom existe.",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "L'e-mail doit être une adresse e-mail valide comme name@example.com.",
|
||||
"USER_NAME": "Ne peut pas contenir de caractères spéciaux et la longueur maximale est de 255 caractères.",
|
||||
"FULL_NAME": "La longueur maximale est de 20 caractères.",
|
||||
|
@ -293,7 +294,7 @@
|
|||
"PUBLIC_POLICY": "Rendre public un registre de projets rendra tous les dépôts accessibles à tous.",
|
||||
"SECURITY": "Sécurité de déploiement",
|
||||
"CONTENT_TRUST_TOGGLE": "Activer la confiance du contenu",
|
||||
"CONTENT_TRUST_POLCIY": "Autoriser uniquement le déploiement d'images vérifiées.",
|
||||
"CONTENT_TRUST_POLICY": "Autoriser uniquement le déploiement d'images vérifiées.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "Empêche les images vulnérables de fonctionner.",
|
||||
"PREVENT_VULNERABLE_1": "Empêcher les images présentant une vulnérabilité grave de",
|
||||
"PREVENT_VULNERABLE_2": "et au-dessus d'être déployées.",
|
||||
|
@ -578,6 +579,7 @@
|
|||
"ALLOWED_CHARACTERS": "Caractères spéciaux autorisés",
|
||||
"TOTAL": "Total",
|
||||
"OVERRIDE": "Surcharger",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "Activer la règle",
|
||||
"OVERRIDE_INFO": "Surcharger",
|
||||
"OPERATION": "Opération",
|
||||
|
@ -1311,7 +1313,7 @@
|
|||
"CONFLICT_ERROR": "Nous ne sommes pas en mesure d'exécuter votre action parce que votre soumission a des conflits.",
|
||||
"PRECONDITION_FAILED": "Nous ne pouvons pas exécuter votre action en raison d'un échec de conditions préalables.",
|
||||
"SERVER_ERROR": "Nous ne sommes pas en mesure d'exécuter votre action parce que des erreurs internes de serveur se sont produites.",
|
||||
"INCONRRECT_OLD_PWD": "L'ancien mot de passe est incorrect.",
|
||||
"INCORRECT_OLD_PWD": "L'ancien mot de passe est incorrect.",
|
||||
"UNKNOWN": "n. d.",
|
||||
"STATUS": "Statut",
|
||||
"START_TIME": "Date/Heure de début",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "원격 레지스트리의 리소스를 로컬 'Harbor'로 가져옵니다.",
|
||||
"DESTINATION_NAMESPACE": "대상 네임스페이스를 지정합니다. 비어 있으면 리소스는 소스와 동일한 네임스페이스에 배치됩니다.",
|
||||
"OVERRIDE": "동일한 이름의 리소스가 있는 경우 대상의 리소스를 재정의할지 여부를 지정합니다.",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "이메일은 name@example.com과 같은 유효한 이메일 주소여야 합니다.",
|
||||
"USER_NAME": "특수 문자를 포함할 수 없으며 최대 길이는 255자입니다.",
|
||||
"FULL_NAME": "최대 길이는 20자입니다.",
|
||||
|
@ -293,7 +294,7 @@
|
|||
"PUBLIC_POLICY": "프로젝트 레지스트리를 공개하면 모든 사람이 모든 저장소에 액세스할 수 있습니다.",
|
||||
"SECURITY": "배포 보안",
|
||||
"CONTENT_TRUST_TOGGLE": "콘텐츠 신뢰 활성화",
|
||||
"CONTENT_TRUST_POLCIY": "확인된 이미지만 배포되도록 허용합니다.",
|
||||
"CONTENT_TRUST_POLICY": "확인된 이미지만 배포되도록 허용합니다.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "취약한 이미지가 실행되지 않도록 방지합니다.",
|
||||
"PREVENT_VULNERABLE_1": "Prevent images with vulnerability severity of",
|
||||
"PREVENT_VULNERABLE_2": "and above from being deployed.",
|
||||
|
@ -575,6 +576,7 @@
|
|||
"ALLOWED_CHARACTERS": "허용되는 특수 문자",
|
||||
"TOTAL": "총",
|
||||
"OVERRIDE": "Override",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "규칙 활성화",
|
||||
"OVERRIDE_INFO": "Override",
|
||||
"OPERATION": "작업",
|
||||
|
@ -1304,7 +1306,7 @@
|
|||
"CONFLICT_ERROR": "귀하의 제출 내용이 충돌하기 때문에 귀하의 작업을 수행할 수 없습니다.",
|
||||
"PRECONDITION_FAILED": "전제 조건 요류로 인해 작업을 수행할 수 없습니다.",
|
||||
"SERVER_ERROR": "내부 서버 오류가 발생하여 귀하의 작업을 수행할 수 없습니다.",
|
||||
"INCONRRECT_OLD_PWD": "이전 비밀번호가 올바르지 않습니다.",
|
||||
"INCORRECT_OLD_PWD": "이전 비밀번호가 올바르지 않습니다.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS": "상태",
|
||||
"START_TIME": "시작 시간",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "Trazer recursos do repositório remoto para o Harbor local.",
|
||||
"DESTINATION_NAMESPACE": "Especificar o namespace de destino. Se vazio, os recursos serão colocados no mesmo namespace que a fonte.",
|
||||
"OVERRIDE": "Sobrescrever recursos no destino se já existir com o mesmo nome.",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "Deve ser um endereço de e-mail válido como nome@exemplo.com.",
|
||||
"USER_NAME": "Não pode conter caracteres especiais. Tamanho máximo de 255 caracteres.",
|
||||
"FULL_NAME": "Tamanho máximo de 20 caracteres.",
|
||||
|
@ -291,7 +292,7 @@
|
|||
"PUBLIC_POLICY": "Tornar o repositório do projeto público tornará todos os artefatos acessíveis sem autenticação.",
|
||||
"SECURITY": "Segurança de implantação",
|
||||
"CONTENT_TRUST_TOGGLE": "Habilitar regras de confiança",
|
||||
"CONTENT_TRUST_POLCIY": "Permitir implantação apenas de imagens verificadas.",
|
||||
"CONTENT_TRUST_POLICY": "Permitir implantação apenas de imagens verificadas.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "Prevenir execução de imagens vulneráveis.",
|
||||
"PREVENT_VULNERABLE_1": "Prevenir imagens com vulnerabilidades de severidade",
|
||||
"PREVENT_VULNERABLE_2": "e acima de serem utilizadas.",
|
||||
|
@ -576,6 +577,7 @@
|
|||
"ALLOWED_CHARACTERS": "Símbolos permitidos",
|
||||
"TOTAL": "Total",
|
||||
"OVERRIDE": "Sobrescrever",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "Habiltar regra",
|
||||
"OVERRIDE_INFO": "Sobrescrever",
|
||||
"CURRENT": "atual",
|
||||
|
@ -1302,7 +1304,7 @@
|
|||
"CONFLICT_ERROR": "Não foi possível executar suas ações pois existem conflitos de envio.",
|
||||
"PRECONDITION_FAILED": "Não foi possível executar suas ações pois ocorreram falhas de pré-condição.",
|
||||
"SERVER_ERROR": "Não foi possível executar suas ações pois ocorreram erros internos.",
|
||||
"INCONRRECT_OLD_PWD": "A senha antiga está incorreta.",
|
||||
"INCORRECT_OLD_PWD": "A senha antiga está incorreta.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS": "Status",
|
||||
"START_TIME": "Início",
|
||||
|
|
|
@ -279,7 +279,7 @@
|
|||
"PUBLIC_POLICY": "Сделать реестр проекта публичным сделает все репозитории доступными для всех.",
|
||||
"SECURITY": "Безопасность развертывания",
|
||||
"CONTENT_TRUST_TOGGLE": "Включить доверие к контенту",
|
||||
"CONTENT_TRUST_POLCIY": "Разрешить развертывание только проверенных образов.",
|
||||
"CONTENT_TRUST_POLICY": "Разрешить развертывание только проверенных образов.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "Предотвращать запуск уязвимых образов.",
|
||||
"PREVENT_VULNERABLE_1": "Предотвращать развертывание образов с уязвимостью серьезности",
|
||||
"PREVENT_VULNERABLE_2": "и выше.",
|
||||
|
@ -1230,7 +1230,7 @@
|
|||
"CONFLICT_ERROR": "Мы не можем выполнить ваш запрос из-за конфликта.",
|
||||
"PRECONDITION_FAILED": "Мы не можем выполнить ваш запрос из-за невыполнения предварительного условия.",
|
||||
"SERVER_ERROR": "Мы не можем выполнить ваш запрос из-за внутренних ошибок сервера.",
|
||||
"INCONRRECT_OLD_PWD": "Старый пароль неверный.",
|
||||
"INCORRECT_OLD_PWD": "Старый пароль неверный.",
|
||||
"UNKNOWN": "н/д",
|
||||
"STATUS": "Статус",
|
||||
"START_TIME": "Время начала",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "Kaynakları uzak kayıt defterinden yerel Harbora çekin.",
|
||||
"DESTINATION_NAMESPACE": "Hedef ad alanını belirtin. Boşsa, kaynaklar, kaynak ile aynı ad alanına yerleştirilir.",
|
||||
"OVERRIDE": "Aynı adı taşıyan bir kaynak varsa, hedefteki kaynakları geçersiz kılmayacağınızı belirtin.",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "E-posta, ad@example.com gibi geçerli bir e-posta adresi olmalıdır.",
|
||||
"USER_NAME": "Özel karakterler içeremez ve maksimum uzunluk 255 karakter olmalıdır.",
|
||||
"FULL_NAME": "Maksimum uzunluk 20 karakter olmalıdır.",
|
||||
|
@ -294,7 +295,7 @@
|
|||
"PUBLIC_POLICY": "Bir proje sicilini halka açmak, bütün depoları herkesin erişebileceği hale getirecektir.",
|
||||
"SECURITY": "Dağıtım güvenliği",
|
||||
"CONTENT_TRUST_TOGGLE": "İçerik güvenini etkinleştir",
|
||||
"CONTENT_TRUST_POLCIY": "Yalnızca doğrulanmış imajların yüklenilmesine izin verin.",
|
||||
"CONTENT_TRUST_POLICY": "Yalnızca doğrulanmış imajların yüklenilmesine izin verin.",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "Güvenlik açığı bulunan görüntülerin çalışmasını önleyin.",
|
||||
"PREVENT_VULNERABLE_1": "Güvenlik açığı ciddiyeti olan imajları önleyin",
|
||||
"PREVENT_VULNERABLE_2": "ve yukarıda yüklenilmekte.",
|
||||
|
@ -579,6 +580,7 @@
|
|||
"ALLOWED_CHARACTERS": "İzin verilen özel karakterler",
|
||||
"TOTAL": "Toplam",
|
||||
"OVERRIDE": "Geçersiz Kıl",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "Kuralı etkinleştir",
|
||||
"OVERRIDE_INFO": "Geçersiz Kıl",
|
||||
"OPERATION": "Operasyon",
|
||||
|
@ -801,7 +803,7 @@
|
|||
"TAGS_NO_DELETE": "Salt okunur modda silmek yasaktır.",
|
||||
"FILTER_FOR_REPOSITORIES": "Depoları Filtrele",
|
||||
"TAG": "Etiket",
|
||||
"ARTIFACT": "Aarifact",
|
||||
"ARTIFACT": "Artifact",
|
||||
"ARTIFACTS": "Artifacts",
|
||||
"SIZE": "Boyut",
|
||||
"VULNERABILITY": "Güvenlik Açığı",
|
||||
|
@ -1310,7 +1312,7 @@
|
|||
"CONFLICT_ERROR": "Gönderiminizde çakışmalar olduğundan eyleminizi gerçekleştiremiyoruz.",
|
||||
"PRECONDITION_FAILED": "Önkoşul arızası nedeniyle işleminizi gerçekleştiremiyoruz.",
|
||||
"SERVER_ERROR": "Dahili sunucu hataları oluştuğundan işleminizi gerçekleştiremiyoruz.",
|
||||
"INCONRRECT_OLD_PWD": "Eski şifre yanlış.",
|
||||
"INCORRECT_OLD_PWD": "Eski şifre yanlış.",
|
||||
"UNKNOWN": "n/a",
|
||||
"STATUS": "Durum",
|
||||
"START_TIME": "Başlama Zamanı",
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"PULL_BASED": "把资源由远端仓库拉取到本地Harbor。",
|
||||
"DESTINATION_NAMESPACE": "指定目标名称空间。如果不填,资源会被放到和源相同的名称空间下。",
|
||||
"OVERRIDE": "如果存在具有相同名称的资源,请指定是否覆盖目标上的资源。",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Specify whether to skip execution until the previous active execution finishes, avoiding the execution of the same replication rules multiple times in parallel.",
|
||||
"EMAIL": "请使用正确的邮箱地址,比如name@example.com。",
|
||||
"USER_NAME": "不能包含特殊字符且长度不能超过255。",
|
||||
"FULL_NAME": "长度不能超过20。",
|
||||
|
@ -292,7 +293,7 @@
|
|||
"PUBLIC_POLICY": "所有人都可访问公开的项目仓库。",
|
||||
"SECURITY": "部署安全",
|
||||
"CONTENT_TRUST_TOGGLE": "内容信任",
|
||||
"CONTENT_TRUST_POLCIY": "仅允许部署通过认证的镜像。",
|
||||
"CONTENT_TRUST_POLICY": "仅允许部署通过认证的镜像。",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "阻止潜在漏洞镜像",
|
||||
"PREVENT_VULNERABLE_1": "阻止危害级别",
|
||||
"PREVENT_VULNERABLE_2": "以上的镜像运行。",
|
||||
|
@ -576,6 +577,7 @@
|
|||
"ALLOWED_CHARACTERS": "允许的特殊字符",
|
||||
"TOTAL": "总数",
|
||||
"OVERRIDE": "覆盖",
|
||||
"SINGLE_ACTIVE_REPLICATION": "Single active replication",
|
||||
"ENABLED_RULE": "启用规则",
|
||||
"OVERRIDE_INFO": "覆盖",
|
||||
"CURRENT": "当前仓库",
|
||||
|
@ -799,7 +801,7 @@
|
|||
"TAGS_NO_DELETE": "在只读模式下删除是被禁止的",
|
||||
"FILTER_FOR_REPOSITORIES": "过滤镜像仓库",
|
||||
"TAG": "Tag",
|
||||
"ARTIFACT": "Aarifact",
|
||||
"ARTIFACT": "Artifact",
|
||||
"ARTIFACTS": "Artifacts",
|
||||
"SIZE": "大小",
|
||||
"VULNERABILITY": "漏洞",
|
||||
|
@ -1308,7 +1310,7 @@
|
|||
"CONFLICT_ERROR": "请求包含冲突, 操作无法完成。",
|
||||
"PRECONDITION_FAILED": "验证前置条件失败, 无法执行操作。",
|
||||
"SERVER_ERROR": "服务器出现内部错误,请求无法完成。",
|
||||
"INCONRRECT_OLD_PWD": "旧密码不正确。",
|
||||
"INCORRECT_OLD_PWD": "旧密码不正确。",
|
||||
"UNKNOWN": "未知",
|
||||
"STATUS": "状态",
|
||||
"START_TIME": "创建时间",
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
"PUBLIC_POLICY": "將專案儲存庫設為公開會讓所有人都能夠存取所有儲存庫。",
|
||||
"SECURITY": "部署安全",
|
||||
"CONTENT_TRUST_TOGGLE": "啟用內容信任",
|
||||
"CONTENT_TRUST_POLCIY": "只允許部署已驗證的映像檔。",
|
||||
"CONTENT_TRUST_POLICY": "只允許部署已驗證的映像檔。",
|
||||
"PREVENT_VULNERABLE_TOGGLE": "阻止有弱點的映像檔執行。",
|
||||
"PREVENT_VULNERABLE_1": "阻止具有",
|
||||
"PREVENT_VULNERABLE_2": "或更高危險級別的映像檔部署。",
|
||||
|
@ -1307,7 +1307,7 @@
|
|||
"CONFLICT_ERROR": "請求包含衝突,操作無法完成。",
|
||||
"PRECONDITION_FAILED": "驗證前置條件失敗,無法執行操作。",
|
||||
"SERVER_ERROR": "伺服器出現內部錯誤,請求無法完成。",
|
||||
"INCONRRECT_OLD_PWD": "舊密碼不正確。",
|
||||
"INCORRECT_OLD_PWD": "舊密碼不正確。",
|
||||
"UNKNOWN": "未知",
|
||||
"STATUS": "狀態",
|
||||
"START_TIME": "建立時間",
|
||||
|
|
|
@ -77,33 +77,29 @@ func Middleware() func(http.Handler) http.Handler {
|
|||
logger.Debugf("artifact %s@%s is pulling by the scanner/cosign, skip the checking", info.Repository, info.Digest)
|
||||
return nil
|
||||
}
|
||||
|
||||
checker := scanChecker()
|
||||
scannable, err := checker.IsScannable(ctx, art)
|
||||
if err != nil {
|
||||
logger.Errorf("check the scannable status of the artifact %s@%s failed, error: %v", art.RepositoryName, art.Digest, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !scannable {
|
||||
// the artifact is not scannable, skip the checking
|
||||
logger.Debugf("artifact %s@%s is not scannable, skip the checking", art.RepositoryName, art.Digest)
|
||||
return nil
|
||||
}
|
||||
|
||||
allowlist := proj.CVEAllowlist.CVESet()
|
||||
|
||||
projectSeverity := vuln.ParseSeverityVersion3(proj.Severity())
|
||||
|
||||
vulnerable, err := scanController.GetVulnerable(ctx, art, allowlist, proj.CVEAllowlist.IsExpired())
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
// No report yet?
|
||||
msg := fmt.Sprintf(`current image without vulnerability scanning cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "%s" or higher from running.' `+
|
||||
`To continue with pull, please contact your project administrator for help.`, projectSeverity)
|
||||
return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg)
|
||||
if errors.IsNotFoundErr(err) {
|
||||
// When the scanner is disconnected the artifact will be considered not scannable.
|
||||
// We'll try to check the existing scan report even when it's not scannable, and only if there is no report, we will skip checking the vulnerability.
|
||||
checker := scanChecker()
|
||||
scannable, err := checker.IsScannable(ctx, art)
|
||||
if err != nil {
|
||||
logger.Errorf("check the scannable status of the artifact %s@%s failed, error: %v", art.RepositoryName, art.Digest, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !scannable {
|
||||
logger.Debugf("artifact %s@%s does not have a scan report, and it is not scannable, skip the checking", art.RepositoryName, art.Digest)
|
||||
return nil
|
||||
}
|
||||
// If the artifact is scannable but there's no report, it's a violation.
|
||||
msg := fmt.Sprintf(`current image without vulnerability scanning cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "%s" or higher from running.' `+
|
||||
`To continue with pull, please contact your project administrator for help.`, projectSeverity)
|
||||
return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg)
|
||||
} else if err != nil {
|
||||
logger.Errorf("get vulnerability summary of the artifact %s@%s failed, error: %v", art.RepositoryName, art.Digest, err)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -184,6 +184,7 @@ func (suite *MiddlewareTestSuite) TestNonScannerPulling() {
|
|||
mock.OnAnything(securityCtx, "Name").Return("local")
|
||||
mock.OnAnything(securityCtx, "Can").Return(true, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(false, nil)
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(nil, errors.NotFoundError(nil))
|
||||
|
||||
req := suite.makeRequest()
|
||||
req = req.WithContext(security.NewContext(req.Context(), securityCtx))
|
||||
|
@ -213,6 +214,7 @@ func (suite *MiddlewareTestSuite) TestCheckIsScannableFailed() {
|
|||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(false, fmt.Errorf("error"))
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(nil, errors.NotFoundError(nil))
|
||||
mock.OnAnything(suite.accessMgr, "List").Return([]accessorymodel.Accessory{}, nil)
|
||||
|
||||
req := suite.makeRequest()
|
||||
|
@ -222,11 +224,29 @@ func (suite *MiddlewareTestSuite) TestCheckIsScannableFailed() {
|
|||
suite.Equal(rr.Code, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) TestArtifactIsNotScannable() {
|
||||
func (suite *MiddlewareTestSuite) TestArtifactIsNotScannableButScanned() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(false, nil)
|
||||
mock.OnAnything(suite.accessMgr, "List").Return([]accessorymodel.Accessory{}, nil)
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
|
||||
ScanStatus: "Success",
|
||||
CVEBypassed: []string{"cve-2020"},
|
||||
}, nil)
|
||||
|
||||
req := suite.makeRequest()
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
Middleware()(suite.next).ServeHTTP(rr, req)
|
||||
suite.Equal(rr.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) TestArtifactIsNotScannableNotScanned() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(false, nil)
|
||||
mock.OnAnything(suite.accessMgr, "List").Return([]accessorymodel.Accessory{}, nil)
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(nil, errors.NotFoundError(nil))
|
||||
|
||||
req := suite.makeRequest()
|
||||
rr := httptest.NewRecorder()
|
||||
|
|
|
@ -49,17 +49,17 @@ type auditlogAPI struct {
|
|||
projectCtl project.Controller
|
||||
}
|
||||
|
||||
func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAuditLogsParams) middleware.Responder {
|
||||
func (a *auditlogAPI) checkPermissionAndBuildQuery(ctx context.Context, qs *string, sort *string, page *int64, pageSize *int64) (*q.Query, error) {
|
||||
secCtx, ok := security.FromContext(ctx)
|
||||
if !ok {
|
||||
return a.SendError(ctx, errors.UnauthorizedError(errors.New("security context not found")))
|
||||
return nil, errors.UnauthorizedError(errors.New("security context not found"))
|
||||
}
|
||||
if !secCtx.IsAuthenticated() {
|
||||
return a.SendError(ctx, errors.UnauthorizedError(nil).WithMessage(secCtx.GetUsername()))
|
||||
return nil, errors.UnauthorizedError(nil).WithMessage(secCtx.GetUsername())
|
||||
}
|
||||
query, err := a.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||
query, err := a.BuildQuery(ctx, qs, sort, page, pageSize)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceAuditLog); err != nil {
|
||||
|
@ -73,11 +73,16 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud
|
|||
|
||||
projects, err := a.projectCtl.List(ctx, q.New(q.KeyWords{"member": member}), project.Metadata(false))
|
||||
if err != nil {
|
||||
return a.SendError(ctx, fmt.Errorf(
|
||||
"failed to get projects of user %s: %v", secCtx.GetUsername(), err))
|
||||
return nil, errors.Errorf(
|
||||
"failed to get projects of user %s: %v", secCtx.GetUsername(), err)
|
||||
}
|
||||
for _, project := range projects {
|
||||
if a.HasProjectPermission(ctx, project.ProjectID, rbac.ActionList, rbac.ResourceLog) {
|
||||
hasPerm, err := a.HasProjectPermission(ctx, project.ProjectID, rbac.ActionList, rbac.ResourceLog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to get project permission of user %s: %v", secCtx.GetUsername(), err)
|
||||
}
|
||||
if hasPerm {
|
||||
ol.Values = append(ol.Values, project.ProjectID)
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +93,14 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud
|
|||
}
|
||||
query.Keywords["ProjectID"] = ol
|
||||
}
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAuditLogsParams) middleware.Responder {
|
||||
query, err := a.checkPermissionAndBuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
total, err := a.auditMgr.Count(ctx, query)
|
||||
if err != nil {
|
||||
|
@ -114,46 +127,13 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud
|
|||
WithLink(a.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(auditLogs)
|
||||
}
|
||||
|
||||
func (a *auditlogAPI) ListAuditLogExts(ctx context.Context, params auditlog.ListAuditLogExtsParams) middleware.Responder {
|
||||
secCtx, ok := security.FromContext(ctx)
|
||||
if !ok {
|
||||
return a.SendError(ctx, errors.UnauthorizedError(errors.New("security context not found")))
|
||||
}
|
||||
if !secCtx.IsAuthenticated() {
|
||||
return a.SendError(ctx, errors.UnauthorizedError(nil).WithMessage(secCtx.GetUsername()))
|
||||
}
|
||||
query, err := a.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||
query, err := a.checkPermissionAndBuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := a.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceAuditLog); err != nil {
|
||||
ol := &q.OrList{}
|
||||
if sc, ok := secCtx.(*local.SecurityContext); ok && sc.IsAuthenticated() {
|
||||
user := sc.User()
|
||||
member := &project.MemberQuery{
|
||||
UserID: user.UserID,
|
||||
GroupIDs: user.GroupIDs,
|
||||
}
|
||||
|
||||
projects, err := a.projectCtl.List(ctx, q.New(q.KeyWords{"member": member}), project.Metadata(false))
|
||||
if err != nil {
|
||||
return a.SendError(ctx, fmt.Errorf(
|
||||
"failed to get projects of user %s: %v", secCtx.GetUsername(), err))
|
||||
}
|
||||
for _, project := range projects {
|
||||
if a.HasProjectPermission(ctx, project.ProjectID, rbac.ActionList, rbac.ResourceLog) {
|
||||
ol.Values = append(ol.Values, project.ProjectID)
|
||||
}
|
||||
}
|
||||
}
|
||||
// make sure no project will be selected with the query
|
||||
if len(ol.Values) == 0 {
|
||||
ol.Values = append(ol.Values, -1)
|
||||
}
|
||||
query.Keywords["ProjectID"] = ol
|
||||
}
|
||||
|
||||
total, err := a.auditextMgr.Count(ctx, query)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
|
|
|
@ -74,11 +74,11 @@ func (b *BaseAPI) HasPermission(ctx context.Context, action rbac.Action, resourc
|
|||
return s.Can(ctx, action, resource)
|
||||
}
|
||||
|
||||
// HasProjectPermission returns true when the request has action permission on project subresource
|
||||
func (b *BaseAPI) HasProjectPermission(ctx context.Context, projectIDOrName any, action rbac.Action, subresource ...rbac.Resource) bool {
|
||||
// HasProjectPermission returns true, nil when the request has action permission on project subresource, and return false, error when the request does not have permission or an error occurs.
|
||||
func (b *BaseAPI) HasProjectPermission(ctx context.Context, projectIDOrName any, action rbac.Action, subresource ...rbac.Resource) (bool, error) {
|
||||
projectID, projectName, err := utils.ParseProjectIDOrName(projectIDOrName)
|
||||
if err != nil {
|
||||
return false
|
||||
return false, err
|
||||
}
|
||||
|
||||
if projectName != "" {
|
||||
|
@ -88,25 +88,29 @@ func (b *BaseAPI) HasProjectPermission(ctx context.Context, projectIDOrName any,
|
|||
if errors.IsNotFoundErr(err) {
|
||||
p = &project.Project{}
|
||||
} else {
|
||||
return false
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if p == nil {
|
||||
log.Warningf("project %s not found", projectName)
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
projectID = p.ProjectID
|
||||
}
|
||||
|
||||
resource := rbac_project.NewNamespace(projectID).Resource(subresource...)
|
||||
return b.HasPermission(ctx, action, resource)
|
||||
return b.HasPermission(ctx, action, resource), nil
|
||||
}
|
||||
|
||||
// RequireProjectAccess checks the permission against the resources according to the context
|
||||
// An error will be returned if it doesn't meet the requirement
|
||||
func (b *BaseAPI) RequireProjectAccess(ctx context.Context, projectIDOrName any, action rbac.Action, subresource ...rbac.Resource) error {
|
||||
if b.HasProjectPermission(ctx, projectIDOrName, action, subresource...) {
|
||||
has, err := b.HasProjectPermission(ctx, projectIDOrName, action, subresource...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
return nil
|
||||
}
|
||||
secCtx, err := b.GetSecurityContext(ctx)
|
||||
|
|
|
@ -387,12 +387,18 @@ func (a *projectAPI) GetProjectSummary(ctx context.Context, params operation.Get
|
|||
}
|
||||
|
||||
var fetchSummaries []func(context.Context, *project.Project, *models.ProjectSummary)
|
||||
|
||||
if hasPerm := a.HasProjectPermission(ctx, p.ProjectID, rbac.ActionRead, rbac.ResourceQuota); hasPerm {
|
||||
hasPerm, err := a.HasProjectPermission(ctx, p.ProjectID, rbac.ActionRead, rbac.ResourceQuota)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
if hasPerm {
|
||||
fetchSummaries = append(fetchSummaries, getProjectQuotaSummary)
|
||||
}
|
||||
|
||||
if hasPerm := a.HasProjectPermission(ctx, p.ProjectID, rbac.ActionList, rbac.ResourceMember); hasPerm {
|
||||
hasPerm, err = a.HasProjectPermission(ctx, p.ProjectID, rbac.ActionList, rbac.ResourceMember)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
if hasPerm {
|
||||
fetchSummaries = append(fetchSummaries, a.getProjectMemberSummary)
|
||||
}
|
||||
|
||||
|
|
|
@ -113,6 +113,14 @@ func (r *replicationAPI) CreateReplicationPolicy(ctx context.Context, params ope
|
|||
policy.CopyByChunk = *params.Policy.CopyByChunk
|
||||
}
|
||||
|
||||
if params.Policy.SingleActiveReplication != nil {
|
||||
// Validate and assign SingleActiveReplication only for non-event_based triggers
|
||||
if params.Policy.Trigger != nil && params.Policy.Trigger.Type == model.TriggerTypeEventBased && *params.Policy.SingleActiveReplication {
|
||||
return r.SendError(ctx, fmt.Errorf("single active replication is not allowed for event_based triggers"))
|
||||
}
|
||||
policy.SingleActiveReplication = *params.Policy.SingleActiveReplication
|
||||
}
|
||||
|
||||
id, err := r.ctl.CreatePolicy(ctx, policy)
|
||||
if err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
|
@ -181,6 +189,14 @@ func (r *replicationAPI) UpdateReplicationPolicy(ctx context.Context, params ope
|
|||
policy.CopyByChunk = *params.Policy.CopyByChunk
|
||||
}
|
||||
|
||||
if params.Policy.SingleActiveReplication != nil {
|
||||
// Validate and assign SingleActiveReplication only for non-event_based triggers
|
||||
if params.Policy.Trigger != nil && params.Policy.Trigger.Type == model.TriggerTypeEventBased && *params.Policy.SingleActiveReplication {
|
||||
return r.SendError(ctx, fmt.Errorf("single active replication is not allowed for event_based triggers"))
|
||||
}
|
||||
policy.SingleActiveReplication = *params.Policy.SingleActiveReplication
|
||||
}
|
||||
|
||||
if err := r.ctl.UpdatePolicy(ctx, policy); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
|
@ -446,6 +462,7 @@ func convertReplicationPolicy(policy *repctlmodel.Policy) *models.ReplicationPol
|
|||
Speed: &policy.Speed,
|
||||
UpdateTime: strfmt.DateTime(policy.UpdateTime),
|
||||
CopyByChunk: &policy.CopyByChunk,
|
||||
SingleActiveReplication: &policy.SingleActiveReplication,
|
||||
}
|
||||
if policy.SrcRegistry != nil {
|
||||
p.SrcRegistry = convertRegistry(policy.SrcRegistry)
|
||||
|
|
|
@ -151,7 +151,8 @@ replication_policy_payload = {
|
|||
"deletion": False,
|
||||
"override": True,
|
||||
"speed": -1,
|
||||
"copy_by_chunk": False
|
||||
"copy_by_chunk": False,
|
||||
"single_active_replication": False
|
||||
}
|
||||
create_replication_policy = Permission("{}/replication/policies".format(harbor_base_url), "POST", 201, replication_policy_payload, "id", id_from_header=True)
|
||||
list_replication_policy = Permission("{}/replication/policies".format(harbor_base_url), "GET", 200, replication_policy_payload)
|
||||
|
@ -204,7 +205,8 @@ if "replication" in resources or "all" == resources:
|
|||
"deletion": False,
|
||||
"override": True,
|
||||
"speed": -1,
|
||||
"copy_by_chunk": False
|
||||
"copy_by_chunk": False,
|
||||
"single_active_replication": False
|
||||
}
|
||||
response = requests.post("{}/replication/policies".format(harbor_base_url), data=json.dumps(replication_policy_payload), verify=False, auth=(admin_user_name, admin_password), headers={"Content-Type": "application/json"})
|
||||
replication_policy_id = int(response.headers["Location"].split("/")[-1])
|
||||
|
|
|
@ -39,19 +39,9 @@ else
|
|||
rc=999
|
||||
fi
|
||||
rc=$?
|
||||
## --------------------------------------------- Upload Harbor CI Logs -------------------------------------------
|
||||
#timestamp=$(date +%s)
|
||||
#GIT_COMMIT=$(git rev-parse --short "$GITHUB_SHA")
|
||||
#outfile="integration_logs_$timestamp$GIT_COMMIT.tar.gz"
|
||||
#sudo tar -zcvf $outfile output.xml log.html /var/log/harbor/*
|
||||
#if [ -f "$outfile" ]; then
|
||||
# uploader $outfile $harbor_logs_bucket
|
||||
# echo "----------------------------------------------"
|
||||
# echo "Download test logs:"
|
||||
# echo "https://storage.googleapis.com/harbor-ci-logs/$outfile"
|
||||
# echo "----------------------------------------------"
|
||||
#else
|
||||
# echo "No log output file to upload"
|
||||
#fi
|
||||
|
||||
## --------------------------------------------- Package Harbor CI Logs -------------------------------------------
|
||||
outfile="integration_logs.tar.gz"
|
||||
sudo tar -zcvf $outfile output.xml log.html /var/log/harbor/*
|
||||
pwd
|
||||
ls -lh $outfile
|
||||
exit $rc
|
||||
|
|
|
@ -3,7 +3,25 @@ set -x
|
|||
|
||||
set -e
|
||||
|
||||
function s3_to_https() {
|
||||
local s3_url="$1"
|
||||
|
||||
if [[ "$s3_url" =~ ^s3://([^/]+)/(.+)$ ]]; then
|
||||
local bucket="${BASH_REMATCH[1]}"
|
||||
local path="${BASH_REMATCH[2]}"
|
||||
# current s3 bucket is create in this region
|
||||
local region="us-west-1"
|
||||
echo "https://${bucket}.s3.${region}.amazonaws.com/${path}"
|
||||
else
|
||||
echo "Invalid S3 URL: $s3_url" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
function uploader {
|
||||
converted_url=$(s3_to_https "s3://$2/$1")
|
||||
echo "download url $converted_url"
|
||||
aws s3 cp $1 s3://$2/$1
|
||||
}
|
||||
|
||||
|
|
|
@ -3,5 +3,5 @@ set -x
|
|||
|
||||
set -e
|
||||
|
||||
sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.24.3 COMPILETAG=compile_golangimage TRIVYFLAG=true EXPORTERFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.24.3 COMPILETAG=compile_golangimage TRIVYFLAG=true EXPORTERFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.24.6 COMPILETAG=compile_golangimage TRIVYFLAG=true EXPORTERFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.24.6 COMPILETAG=compile_golangimage TRIVYFLAG=true EXPORTERFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
|
|
|
@ -96,7 +96,7 @@ Check The Search By One Condition
|
|||
Select From List By Value ${vulnerabilities_filter_select} package
|
||||
Retry Text Input ${vulnerabilities_filter_input} ${package}
|
||||
Retry Button Click ${security_hub_search_btn}
|
||||
Retry Wait Element Count //div[@class='datagrid']//clr-dg-cell[7][@title='${package}'] 15
|
||||
Retry Wait Element Count //div[@class='datagrid']//clr-dg-cell[8][@title='${package}'] 15
|
||||
# Check the search by tag
|
||||
Select From List By Value ${vulnerabilities_filter_select} tag
|
||||
Retry Text Input ${vulnerabilities_filter_input} ${tag}
|
||||
|
@ -189,7 +189,6 @@ Check The Search By All Condition
|
|||
Retry Text Input ${cvss3_to_input} ${cvss_score_v3_to}
|
||||
# severity
|
||||
Retry Element Click ${add_search_criteria_icon}
|
||||
Retry Wait Element ${add_search_criteria_icon_disabled}
|
||||
Retry Wait Element ${remove_search_criteria_icon}
|
||||
${severity_select}= Format String {}{} ${vulnerabilities_filter_select} [8]
|
||||
${severity_input}= Format String {}{} (//form[contains(@class,'clr-form')]//select) [9]
|
||||
|
@ -198,7 +197,7 @@ Check The Search By All Condition
|
|||
# search
|
||||
Retry Button Click ${security_hub_search_btn}
|
||||
Retry Wait Element Count ${vulnerabilities_datagrid_row} 1
|
||||
${target_row_xpath}= Set Variable //div[@class='datagrid'][..//clr-dg-cell[2][@title='${repository_name}'] and ..//clr-dg-cell[3][@title='${digest}'] and ..//clr-dg-cell[1]//a[text()='${cve_id}'] and ..//clr-dg-cell[7][@title='${package}'] and ..//clr-dg-cell[4][text()='${tag}'] and ..//clr-dg-cell[5][text()>=${cvss_score_v3_from} and text()<=${cvss_score_v3_to}] and ..//clr-dg-cell[6]//span[text()='${severity}']]
|
||||
${target_row_xpath}= Set Variable //div[@class='datagrid'][..//clr-dg-cell[2][@title='${repository_name}'] and ..//clr-dg-cell[3][@title='${digest}'] and ..//clr-dg-cell[1]//a[text()='${cve_id}'] and ..//clr-dg-cell[8][@title='${package}'] and ..//clr-dg-cell[4][text()='${tag}'] and ..//clr-dg-cell[5][text()>=${cvss_score_v3_from} and text()<=${cvss_score_v3_to}] and ..//clr-dg-cell[6]//span[text()='${severity}']]
|
||||
Log ${target_row_xpath}
|
||||
Retry Wait Element ${target_row_xpath}
|
||||
FOR ${index} IN RANGE 7
|
||||
|
|
|
@ -124,7 +124,7 @@ Check Listed In CVE Allowlist
|
|||
Go Into Artifact ${tag}
|
||||
|
||||
Scroll Element Into View //clr-dg-row[contains(.,'${cve_id}')]
|
||||
${text}= Get Text //clr-dg-row[contains(.,'${cve_id}')]//clr-dg-cell[7]
|
||||
${text}= Get Text //clr-dg-row[contains(.,'${cve_id}')]//clr-dg-cell[8]
|
||||
Capture Page Screenshot
|
||||
|
||||
Log All is_in_allow_list:${text}
|
||||
|
|
|
@ -16,10 +16,16 @@
|
|||
Documentation This resource wrap test case body
|
||||
Library ../apitests/python/testutils.py
|
||||
Library ../apitests/python/library/repository.py
|
||||
Library String
|
||||
|
||||
*** Variables ***
|
||||
|
||||
*** Keywords ***
|
||||
Remove Port
|
||||
[Arguments] ${address}
|
||||
${result}= Replace String ${address} :9443 ${EMPTY}
|
||||
[Return] ${result}
|
||||
|
||||
Body Of Manage project publicity
|
||||
Init Chrome Driver
|
||||
${d}= Get Current Date result_format=%m%s
|
||||
|
@ -578,7 +584,8 @@ Verify Webhook By Tag Retention Finished Event
|
|||
Verify Webhook By Replication Status Changed Event
|
||||
[Arguments] ${project_name} ${webhook_name} ${project_dest_name} ${replication_rule_name} ${user} ${harbor_handle} ${webhook_handle} ${payload_format}=Default
|
||||
&{replication_finished_property}= Create Dictionary
|
||||
Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${replication_finished_property} type=REPLICATION operator=${user} registry_type=harbor harbor_hostname=${ip}
|
||||
${cleaned_ip}= Remove Port ${ip}
|
||||
Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${replication_finished_property} type=REPLICATION operator=${user} registry_type=harbor harbor_hostname=${cleaned_ip}
|
||||
... ELSE Set To Dictionary ${replication_finished_property} specversion=1.0 type=harbor.replication.status.changed datacontenttype=application/json operator=${user} trigger_type=MANUAL namespace=${project_name}
|
||||
Switch Window ${webhook_handle}
|
||||
Delete All Requests
|
||||
|
|
|
@ -266,23 +266,6 @@ Test Case - Replication Of Pull Images from AWS-ECR To Self
|
|||
Image Should Be Replicated To Project project${d} hello-world
|
||||
Close Browser
|
||||
|
||||
Test Case - Replication Of Pull Images from Google-GCR To Self
|
||||
Init Chrome Driver
|
||||
${d}= Get Current Date result_format=%m%s
|
||||
#login source
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Create An New Project And Go Into Project project${d}
|
||||
Switch To Registries
|
||||
Create A New Endpoint google-gcr e${d} asia.gcr.io ${null} ${gcr_ac_key} Y
|
||||
Switch To Replication Manage
|
||||
Create A Rule With Existing Endpoint rule${d} pull eminent-nation-87317/* image e${d} project${d}
|
||||
Filter Replication Rule rule${d}
|
||||
Select Rule And Replicate rule${d}
|
||||
Check Latest Replication Job Status Succeeded
|
||||
Image Should Be Replicated To Project project${d} httpd
|
||||
Image Should Be Replicated To Project project${d} tomcat
|
||||
Close Browser
|
||||
|
||||
Test Case - Replication Of Push Images to DockerHub Triggered By Event
|
||||
Body Of Replication Of Push Images to Registry Triggered By Event docker-hub https://hub.docker.com/ ${DOCKER_USER} ${DOCKER_PWD} ${DOCKER_USER}
|
||||
|
||||
|
@ -293,18 +276,6 @@ Test Case - Replication Of Push Images to DockerHub Triggered By Event
|
|||
Test Case - Replication Of Push Images to AWS-ECR Triggered By Event
|
||||
Body Of Replication Of Push Images to Registry Triggered By Event aws-ecr us-east-2 ${ecr_ac_id} ${ecr_ac_key} harbor-nightly-replication
|
||||
|
||||
Test Case - Replication Of Pull Images from Gitlab To Self
|
||||
&{image1_with_tag}= Create Dictionary image=photon tag=1.0
|
||||
&{image2_with_tag}= Create Dictionary image=alpine tag=latest
|
||||
${image1}= Get From Dictionary ${image1_with_tag} image
|
||||
${image2}= Get From Dictionary ${image2_with_tag} image
|
||||
@{target_images}= Create List '&{image1_with_tag}' '&{image2_with_tag}'
|
||||
#harbor424542/harbor-ci is the project created in gitlab by user stonezdj, change it when the gitlab user changed
|
||||
Body Of Replication Of Pull Images from Registry To Self gitlab https://registry.gitlab.com ${gitlab_id} ${gitlab_key} harbor424542/harbor-ci/{${image1},${image2}} ${null} N Flatten All Levels @{target_images}
|
||||
|
||||
Test Case - Replication Of Push Images to Gitlab Triggered By Event
|
||||
Body Of Replication Of Push Images to Registry Triggered By Event gitlab https://registry.gitlab.com ${gitlab_id} ${gitlab_key} harbor424542/harbor-ci
|
||||
|
||||
Test Case - Replication Of Pull Manifest List and CNAB from Harbor To Self
|
||||
&{image1_with_tag}= Create Dictionary image=busybox tag=1.32.0 total_artifact_count=9 archive_count=0
|
||||
&{image2_with_tag}= Create Dictionary image=index tag=index_tag total_artifact_count=2 archive_count=0
|
||||
|
|
|
@ -102,12 +102,3 @@ Test Case - Open CVE Details Page
|
|||
Go Into Artifact ${sha256}
|
||||
Retry Double Keywords When Error Click Link New Tab And Switch //hbr-artifact-vulnerabilities//clr-dg-row//a[contains(.,'${cve}')] Retry Wait Element //h1[contains(.,'${cve}')]
|
||||
Close Browser
|
||||
|
||||
Test Case - Open Image Scanners Documentation Page
|
||||
[Tags] image_scanners_documentation_page
|
||||
Init Chrome Driver
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Switch To Scanners Page
|
||||
Retry Element Click ${view_scanner_icon_xpath}
|
||||
Retry Double Keywords When Error Click Link New Tab And Switch ${view_scanner_doc_xpath} Retry Wait Until Page Contains Vulnerability Scanning with Pluggable Scanners
|
||||
Close Browser
|
||||
|
|
Loading…
Reference in New Issue