diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 56ef0fb858..5ed78ff1be 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -41,10 +41,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.22.3 id: go - uses: actions/checkout@v3 with: @@ -89,7 +89,7 @@ jobs: bash ./tests/showtime.sh ./tests/ci/ut_run.sh $IP df -h - name: Codecov For BackEnd - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./src/github.com/goharbor/harbor/profile.cov flags: unittests @@ -102,10 +102,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.22.3 id: go - uses: actions/checkout@v3 with: @@ -157,10 +157,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.22.3 id: go - uses: actions/checkout@v3 with: @@ -212,10 +212,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.22.3 id: go - uses: actions/checkout@v3 with: @@ -265,10 +265,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.22.3 id: go - uses: actions/checkout@v3 with: @@ -331,7 +331,7 @@ jobs: bash ./tests/showtime.sh ./tests/ci/ui_ut_run.sh df -h - name: Codecov For UI - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./src/github.com/goharbor/harbor/src/portal/coverage/lcov.info flags: unittests diff --git a/.github/workflows/auto_assign_prs.yml b/.github/workflows/auto_assign_prs.yml index 711eba7c50..7331fd620e 100644 --- a/.github/workflows/auto_assign_prs.yml +++ b/.github/workflows/auto_assign_prs.yml @@ -13,6 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Set the author of a PR as the assignee - uses: kentaro-m/auto-assign-action@v1.2.6 + uses: kentaro-m/auto-assign-action@v2.0.0 with: configuration-path: ".github/auto-assignees.yml" diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index 6d2867a8c2..febfd494e2 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -23,10 +23,10 @@ jobs: with: version: '430.0.0' - run: gcloud info - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.22.3 id: go - name: Setup Docker uses: docker-practice/actions-setup-docker@master diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9dfb264730..f97fcebcda 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,5 +47,8 @@ jobs: # make bootstrap # make release + # to make sure autobuild success, specifify golang version in go.mod + # https://github.com/github/codeql/issues/15647#issuecomment-2003768106 + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/conformance_test.yml b/.github/workflows/conformance_test.yml index 2e8521250c..8866a46be5 100644 --- a/.github/workflows/conformance_test.yml +++ b/.github/workflows/conformance_test.yml @@ -28,7 +28,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.22.3 id: go - uses: actions/checkout@v3 with: diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index c57863f9f8..7ba4df99c2 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -68,7 +68,7 @@ jobs: source tools/release/release_utils.sh && generateReleaseNotes ${{ env.CUR_TAG }} ${{ env.PRE_TAG }} ${{ secrets.GITHUB_TOKEN }} $release_notes_path echo "RELEASE_NOTES_PATH=$release_notes_path" >> $GITHUB_ENV - name: RC Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: ${{ env.PRERELEASE == 'true' }} with: body_path: ${{ env.RELEASE_NOTES_PATH }} @@ -77,7 +77,7 @@ jobs: ${{ env.OFFLINE_PACKAGE_PATH }}.asc ${{ env.MD5SUM_PATH }} - name: GA Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: ${{ env.PRERELEASE == 'false' }} with: body_path: ${{ env.RELEASE_NOTES_PATH }} diff --git a/.gitignore b/.gitignore index ef34ce1bf7..f2b08ad4aa 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ src/server/v2.0/models/ src/server/v2.0/restapi/ .editorconfig +harborclient/ +openapi-generator-cli.jar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 107ac61a84..6f1f5bc2a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,8 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo | 2.7 | 1.19.4 | | 2.8 | 1.20.6 | | 2.9 | 1.21.3 | -| 2.10 | 1.21.5 | +| 2.10 | 1.21.8 | +| 2.11 | 1.22.3 | Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions. diff --git a/Makefile b/Makefile index 857db70f67..375f74f7db 100644 --- a/Makefile +++ b/Makefile @@ -104,8 +104,8 @@ PREPARE_VERSION_NAME=versions #versions REGISTRYVERSION=v2.8.3-patch-redis -TRIVYVERSION=v0.47.0 -TRIVYADAPTERVERSION=v0.30.19 +TRIVYVERSION=v0.50.4 +TRIVYADAPTERVERSION=v0.31.1 # version of registry for pulling the source code REGISTRY_SRC_TAG=v2.8.3 @@ -140,7 +140,7 @@ GOINSTALL=$(GOCMD) install GOTEST=$(GOCMD) test GODEP=$(GOTEST) -i GOFMT=gofmt -w -GOBUILDIMAGE=golang:1.21.5 +GOBUILDIMAGE=golang:1.22.3 GOBUILDPATHINCONTAINER=/harbor # go build @@ -312,7 +312,7 @@ gen_apis: lint_apis MOCKERY_IMAGENAME=$(IMAGENAMESPACE)/mockery -MOCKERY_VERSION=v2.35.4 +MOCKERY_VERSION=v2.42.2 MOCKERY=$(RUNCONTAINER) ${MOCKERY_IMAGENAME}:${MOCKERY_VERSION} MOCKERY_IMAGE_BUILD_CMD=${DOCKERBUILD} -f ${TOOLSPATH}/mockery/Dockerfile --build-arg GOLANG=${GOBUILDIMAGE} --build-arg MOCKERY_VERSION=${MOCKERY_VERSION} -t ${MOCKERY_IMAGENAME}:$(MOCKERY_VERSION) . diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 523aaad623..c9e1e8a50f 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -991,6 +991,12 @@ paths: type: boolean required: false default: false + - name: with_sbom_overview + in: query + description: Specify whether the SBOM overview is included in returning artifacts, when this option is true, the SBOM overview will be included in the response + type: boolean + required: false + default: false - name: with_signature in: query description: Specify whether the signature is included inside the tags of the returning artifacts. Only works when setting "with_tag=true" @@ -1096,6 +1102,12 @@ paths: type: boolean required: false default: false + - name: with_sbom_overview + in: query + description: Specify whether the SBOM overview is included in returning artifact, when this option is true, the SBOM overview will be included in the response + type: boolean + required: false + default: false - name: with_accessory in: query description: Specify whether the accessories are included of the returning artifacts. @@ -1164,6 +1176,11 @@ paths: - $ref: '#/parameters/projectName' - $ref: '#/parameters/repositoryName' - $ref: '#/parameters/reference' + - name: scanType + in: body + required: false + schema: + $ref: '#/definitions/ScanType' responses: '202': $ref: '#/responses/202' @@ -1175,6 +1192,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/stop: @@ -1189,6 +1208,12 @@ paths: - $ref: '#/parameters/projectName' - $ref: '#/parameters/repositoryName' - $ref: '#/parameters/reference' + - name: scanType + in: body + required: true + schema: + $ref: '#/definitions/ScanType' + description: 'The scan type: Vulnerabilities, SBOM' responses: '202': $ref: '#/responses/202' @@ -1200,6 +1225,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/{report_id}/log: @@ -1226,6 +1253,8 @@ paths: description: Successfully get scan log file schema: type: string + '400': + $ref: '#/responses/400' '401': $ref: '#/responses/401' '403': @@ -1432,7 +1461,7 @@ paths: in: path description: The type of addition. type: string - enum: [build_history, values.yaml, readme.md, dependencies] + enum: [build_history, values.yaml, readme.md, dependencies, sbom] required: true responses: '200': @@ -1451,6 +1480,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/labels: @@ -4798,6 +4829,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /schedules: @@ -6431,6 +6464,14 @@ responses: type: string schema: $ref: '#/definitions/Errors' + '422': + description: Unsupported Type + headers: + X-Request-Id: + description: The ID of the corresponding request for the response + type: string + schema: + $ref: '#/definitions/Errors' '500': description: Internal server error headers: @@ -6592,6 +6633,9 @@ definitions: scan_overview: $ref: '#/definitions/ScanOverview' description: The overview of the scan result. + sbom_overview: + $ref: '#/definitions/SBOMOverview' + description: The overview of the generating SBOM progress accessories: type: array items: @@ -6743,6 +6787,37 @@ definitions: description: 'The scan overview attached in the metadata of tag' additionalProperties: $ref: '#/definitions/NativeReportSummary' + SBOMOverview: + type: object + description: 'The generate SBOM overview information' + properties: + start_time: + type: string + format: date-time + description: 'The start time of the generating sbom report task' + example: '2006-01-02T14:04:05Z' + end_time: + type: string + format: date-time + description: 'The end time of the generating sbom report task' + example: '2006-01-02T15:04:05Z' + scan_status: + type: string + description: 'The status of the generating SBOM task' + sbom_digest: + type: string + description: 'The digest of the generated SBOM accessory' + report_id: + type: string + description: 'id of the native scan report' + example: '5f62c830-f996-11e9-957f-0242c0a89008' + duration: + type: integer + format: int64 + description: 'Time in seconds required to create the report' + example: 300 + scanner: + $ref: '#/definitions/Scanner' NativeReportSummary: type: object description: 'The summary for the native report' @@ -7164,6 +7239,10 @@ definitions: type: string description: 'Whether scan images automatically when pushing. The valid values are "true", "false".' x-nullable: true + auto_sbom_generation: + type: string + description: 'Whether generating SBOM automatically when pushing a subject artifact. The valid values are "true", "false".' + x-nullable: true reuse_sys_cve_allowlist: type: string description: 'Whether this project reuse the system level CVE allowlist as the allowlist of its own. The valid values are "true", "false". @@ -7757,7 +7836,7 @@ definitions: properties: resource: type: string - description: The resource of the access. Possible resources are *, artifact, artifact-addition, artifact-label, audit-log, catalog, configuration, distribution, garbage-collection, helm-chart, helm-chart-version, helm-chart-version-label, immutable-tag, label, ldap-user, log, member, metadata, notification-policy, preheat-instance, preheat-policy, project, quota, registry, replication, replication-adapter, replication-policy, repository, robot, scan, scan-all, scanner, system-volumes, tag, tag-retention, user, user-group or "" (for self-reference). + description: The resource of the access. Possible resources are listed here for system and project level https://github.com/goharbor/harbor/blob/main/src/common/rbac/const.go action: type: string description: The action of the access. Possible actions are *, pull, push, create, read, update, delete, list, operate, scanner-pull and stop. @@ -8364,6 +8443,11 @@ definitions: default: "" description: Indicate the healthy of the registration example: "healthy" + capabilities: + type: object + description: Indicates the capabilities of the scanner, e.g. support_vulnerability or support_sbom. + additionalProperties: True + example: {"support_vulnerability": true, "support_sbom": true} ScannerRegistrationReq: type: object @@ -8446,6 +8530,12 @@ definitions: ScannerCapability: type: object properties: + type: + type: string + description: | + Specify the type of scanner capability, like vulnerability or sbom + x-omitempty: false + example: "sbom" consumes_mime_types: type: array items: @@ -9907,3 +9997,10 @@ definitions: items: type: string description: Links of the vulnerability + ScanType: + type: object + properties: + scan_type: + type: string + description: 'The scan type for the scan request. Two options are currently supported, vulnerability and sbom' + enum: [ vulnerability, sbom ] \ No newline at end of file diff --git a/icons/sbom.png b/icons/sbom.png new file mode 100644 index 0000000000..8903142fb2 Binary files /dev/null and b/icons/sbom.png differ diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 19f1c17ec3..e81abfc43e 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -16,6 +16,18 @@ https: # 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 + +# # 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 +# 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 # # Uncomment following will enable tls communication between all harbor components # internal_tls: @@ -23,8 +35,7 @@ https: # enabled: true # # put your cert and key files on dir # dir: /etc/harbor/tls/internal -# # enable strong ssl ciphers (default: false) -# strong_ssl_ciphers: false + # Uncomment external_url if you want to enable external proxy # And when it enabled the hostname will no longer used @@ -87,6 +98,10 @@ trivy: # `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 @@ -100,6 +115,11 @@ trivy: # # insecure The flag to skip verifying registry certificate insecure: false + # + # 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 + # # 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 @@ -154,7 +174,7 @@ log: # port: 5140 #This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY! -_version: 2.10.0 +_version: 2.11.0 # Uncomment external_database if using external database. # external_database: @@ -238,7 +258,7 @@ proxy: # 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 differenciate different harbor services +# # # namespace used to differentiate different harbor services # # namespace: # # # attributes is a key value dict contains user defined attributes used to initialize trace provider # # attributes: @@ -291,6 +311,6 @@ cache: # # 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. +# # 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 diff --git a/make/migrations/postgresql/0140_2.11.0_schema.up.sql b/make/migrations/postgresql/0140_2.11.0_schema.up.sql new file mode 100644 index 0000000000..fd6fb16eda --- /dev/null +++ b/make/migrations/postgresql/0140_2.11.0_schema.up.sql @@ -0,0 +1,31 @@ +/* +table artifact: + id SERIAL PRIMARY KEY NOT NULL, + type varchar(255) NOT NULL, + media_type varchar(255) NOT NULL, + manifest_media_type varchar(255) NOT NULL, + artifact_type varchar(255) NOT NULL, + project_id int NOT NULL, + repository_id int NOT NULL, + repository_name varchar(255) NOT NULL, + digest varchar(255) NOT NULL, + size bigint, + push_time timestamp default CURRENT_TIMESTAMP, + pull_time timestamp, + extra_attrs text, + annotations jsonb, + CONSTRAINT unique_artifact UNIQUE (repository_id, digest) +*/ + +/* +Add new column artifact_type for artifact table to work with oci-spec v1.1.0 list referrer api +*/ +ALTER TABLE artifact ADD COLUMN IF NOT EXISTS artifact_type varchar(255); + +/* +set value for artifact_type +then set column artifact_type as not null +*/ +UPDATE artifact SET artifact_type = media_type WHERE artifact_type IS NULL; + +ALTER TABLE artifact ALTER COLUMN artifact_type SET NOT NULL; \ No newline at end of file diff --git a/make/photon/prepare/commands/migrate.py b/make/photon/prepare/commands/migrate.py index 6808aa18f6..6ec8d7a28b 100644 --- a/make/photon/prepare/commands/migrate.py +++ b/make/photon/prepare/commands/migrate.py @@ -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.10.0', help="target version of input path") +@click.option('-t', '--target', default='2.11.0', help="target version of input path") def migrate(input_, output, target): """ migrate command will migrate config file style to specific version diff --git a/make/photon/prepare/migrations/__init__.py b/make/photon/prepare/migrations/__init__.py index 4ecb468a37..14b50018a1 100644 --- a/make/photon/prepare/migrations/__init__.py +++ b/make/photon/prepare/migrations/__init__.py @@ -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'} \ No newline at end of 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'} \ No newline at end of file diff --git a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja index 44a46968ff..a6e07e915f 100644 --- a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja @@ -23,6 +23,12 @@ https: # 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: @@ -31,6 +37,8 @@ https: # # 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 %} {% if internal_tls is defined %} @@ -38,13 +46,9 @@ https: 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 }} - # enable strong ssl ciphers (default: false) - {% if internal_tls.strong_ssl_ciphers is defined %} - strong_ssl_ciphers: {{ internal_tls.strong_ssl_ciphers | lower }} - {% else %} - strong_ssl_ciphers: false {% endif %} {% else %} # internal_tls: @@ -52,8 +56,6 @@ internal_tls: # enabled: true # # put your cert and key files on dir # dir: /etc/harbor/tls/internal -# # enable strong ssl ciphers (default: false) -# strong_ssl_ciphers: false {% endif %} # Uncomment external_url if you want to enable external proxy diff --git a/make/photon/prepare/migrations/version_2_11_0/__init__.py b/make/photon/prepare/migrations/version_2_11_0/__init__.py new file mode 100644 index 0000000000..8f2f64cfa2 --- /dev/null +++ b/make/photon/prepare/migrations/version_2_11_0/__init__.py @@ -0,0 +1,21 @@ +import os +from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape +from utils.migration import read_conf + +revision = '2.11.0' +down_revisions = ['2.10.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)) diff --git a/make/photon/prepare/migrations/version_2_11_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_11_0/harbor.yml.jinja new file mode 100644 index 0000000000..ef0be73dbf --- /dev/null +++ b/make/photon/prepare/migrations/version_2_11_0/harbor.yml.jinja @@ -0,0 +1,737 @@ +# 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 }} + # 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 + # 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.11.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 for redis+sentinel: + # :,:,: + 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 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: + # 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 for redis+sentinel: +# # :,:,: +# host: redis:6379 +# password: +# # Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH form. +# # username: +# # sentinel_master_set must be set to support redis+sentinel +# #sentinel_master_set: +# # 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 %} diff --git a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja index 29a57b44c3..6fa2bae788 100644 --- a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja +++ b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja @@ -50,7 +50,12 @@ http { include /etc/nginx/conf.d/*.server.conf; server { + {% if ip_family.ipv4.enabled %} listen 8443 ssl; + {% endif %} + {% if ip_family.ipv6.enabled %} + listen [::]:8443 ssl; + {% endif %} # server_name harbordomain.com; server_tokens off; # SSL @@ -59,7 +64,7 @@ http { # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html ssl_protocols TLSv1.2 TLSv1.3; -{% if internal_tls.strong_ssl_ciphers %} +{% if strong_ssl_ciphers %} ssl_ciphers ECDHE+AESGCM:DHE+AESGCM:ECDHE+RSA+SHA256:DHE+RSA+SHA256:!AES128; {% else %} ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; diff --git a/make/photon/prepare/templates/portal/nginx.conf.jinja b/make/photon/prepare/templates/portal/nginx.conf.jinja index 89dc14bc8b..85a68a094c 100644 --- a/make/photon/prepare/templates/portal/nginx.conf.jinja +++ b/make/photon/prepare/templates/portal/nginx.conf.jinja @@ -16,13 +16,19 @@ http { server { {% if internal_tls.enabled %} + #ip_family + {% if ip_family.ipv4.enabled %} listen 8443 ssl; + {% endif %} + {% if ip_family.ipv6.enabled %} + listen [::]:8443 ssl; + {% endif %} # SSL ssl_certificate /etc/harbor/tls/portal.crt; ssl_certificate_key /etc/harbor/tls/portal.key; ssl_protocols TLSv1.2 TLSv1.3; - {% if internal_tls.strong_ssl_ciphers %} + {% if strong_ssl_ciphers %} ssl_ciphers ECDHE+AESGCM:DHE+AESGCM:ECDHE+RSA+SHA256:DHE+RSA+SHA256:!AES128; {% else %} ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; diff --git a/make/photon/prepare/templates/trivy-adapter/env.jinja b/make/photon/prepare/templates/trivy-adapter/env.jinja index c9402a136b..406e6a91a5 100644 --- a/make/photon/prepare/templates/trivy-adapter/env.jinja +++ b/make/photon/prepare/templates/trivy-adapter/env.jinja @@ -10,6 +10,7 @@ SCANNER_TRIVY_VULN_TYPE=os,library SCANNER_TRIVY_SEVERITY=UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL SCANNER_TRIVY_IGNORE_UNFIXED={{trivy_ignore_unfixed}} SCANNER_TRIVY_SKIP_UPDATE={{trivy_skip_update}} +SCANNER_TRIVY_SKIP_JAVA_DB_UPDATE={{trivy_skip_java_db_update}} SCANNER_TRIVY_OFFLINE_SCAN={{trivy_offline_scan}} SCANNER_TRIVY_SECURITY_CHECKS={{trivy_security_check}} SCANNER_TRIVY_GITHUB_TOKEN={{trivy_github_token}} diff --git a/make/photon/prepare/utils/configs.py b/make/photon/prepare/utils/configs.py index bb30249353..aff7867295 100644 --- a/make/photon/prepare/utils/configs.py +++ b/make/photon/prepare/utils/configs.py @@ -212,6 +212,7 @@ def parse_yaml_config(config_file_path, with_trivy): trivy_configs = configs.get("trivy") or {} config_dict['trivy_github_token'] = trivy_configs.get("github_token") or '' config_dict['trivy_skip_update'] = trivy_configs.get("skip_update") or False + config_dict['trivy_skip_java_db_update'] = trivy_configs.get("skip_java_db_update") or False config_dict['trivy_offline_scan'] = trivy_configs.get("offline_scan") or False config_dict['trivy_security_check'] = trivy_configs.get("security_check") or 'vuln' config_dict['trivy_ignore_unfixed'] = trivy_configs.get("ignore_unfixed") or False @@ -298,6 +299,20 @@ def parse_yaml_config(config_file_path, with_trivy): external_database=config_dict['external_database']) else: config_dict['internal_tls'] = InternalTLS() + # the configure item apply to internal and external tls communication + # for compatibility, user could configure the strong_ssl_ciphers either in https section or under internal_tls section, + # but it is more reasonable to configure it in https_config + if https_config: + config_dict['strong_ssl_ciphers'] = https_config.get('strong_ssl_ciphers') + else: + config_dict['strong_ssl_ciphers'] = False + + if internal_tls_config: + config_dict['strong_ssl_ciphers'] = config_dict['strong_ssl_ciphers'] or internal_tls_config.get('strong_ssl_ciphers') + + + # ip_family config + config_dict['ip_family'] = configs.get('ip_family') or {'ipv4': {'enabled': True}, 'ipv6': {'enabled': False}} # metric configs metric_config = configs.get('metric') diff --git a/make/photon/prepare/utils/migration.py b/make/photon/prepare/utils/migration.py index 1389bae454..a29b1b9df9 100644 --- a/make/photon/prepare/utils/migration.py +++ b/make/photon/prepare/utils/migration.py @@ -27,6 +27,12 @@ def read_conf(path): with open(path) as f: try: d = yaml.safe_load(f) + # the strong_ssl_ciphers configure item apply to internal and external tls communication + # for compatibility, user could configure the strong_ssl_ciphers either in https section or under internal_tls section, + # but it will move to https section after migration + https_config = d.get("https") or {} + internal_tls = d.get('internal_tls') or {} + d['strong_ssl_ciphers'] = https_config.get('strong_ssl_ciphers') or internal_tls.get('strong_ssl_ciphers') except Exception as e: click.echo("parse config file err, make sure your harbor config version is above 1.8.0", e) exit(-1) diff --git a/make/photon/prepare/utils/nginx.py b/make/photon/prepare/utils/nginx.py index 54d4305d41..2872bafbe7 100644 --- a/make/photon/prepare/utils/nginx.py +++ b/make/photon/prepare/utils/nginx.py @@ -63,7 +63,9 @@ def render_nginx_template(config_dict): ssl_cert=SSL_CERT_PATH, ssl_cert_key=SSL_CERT_KEY_PATH, internal_tls=config_dict['internal_tls'], - metric=config_dict['metric']) + metric=config_dict['metric'], + strong_ssl_ciphers=config_dict['strong_ssl_ciphers'], + ip_family=config_dict['ip_family']) location_file_pattern = CUSTOM_NGINX_LOCATION_FILE_PATTERN_HTTPS else: diff --git a/make/photon/prepare/utils/portal.py b/make/photon/prepare/utils/portal.py index a2524827b0..9211a5df72 100644 --- a/make/photon/prepare/utils/portal.py +++ b/make/photon/prepare/utils/portal.py @@ -14,5 +14,8 @@ def prepare_portal(config_dict): str(portal_conf_template_path), portal_conf, internal_tls=config_dict['internal_tls'], + ip_family=config_dict['ip_family'], uid=DEFAULT_UID, - gid=DEFAULT_GID) + gid=DEFAULT_GID, + strong_ssl_ciphers=config_dict['strong_ssl_ciphers'] + ) diff --git a/make/photon/registry/Dockerfile.binary b/make/photon/registry/Dockerfile.binary index be98146a6d..0098691bef 100644 --- a/make/photon/registry/Dockerfile.binary +++ b/make/photon/registry/Dockerfile.binary @@ -1,4 +1,4 @@ -FROM golang:1.21.5 +FROM golang:1.22.3 ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution ENV BUILDTAGS include_oss include_gcs diff --git a/make/photon/trivy-adapter/Dockerfile.binary b/make/photon/trivy-adapter/Dockerfile.binary index 94d71551a6..bbfda2e93c 100644 --- a/make/photon/trivy-adapter/Dockerfile.binary +++ b/make/photon/trivy-adapter/Dockerfile.binary @@ -1,4 +1,4 @@ -FROM golang:1.21.5 +FROM golang:1.22.3 ADD . /go/src/github.com/aquasecurity/harbor-scanner-trivy/ WORKDIR /go/src/github.com/aquasecurity/harbor-scanner-trivy/ diff --git a/make/photon/trivy-adapter/builder.sh b/make/photon/trivy-adapter/builder.sh index b696b01bb0..31ae7e2453 100755 --- a/make/photon/trivy-adapter/builder.sh +++ b/make/photon/trivy-adapter/builder.sh @@ -19,7 +19,7 @@ TEMP=$(mktemp -d ${TMPDIR-/tmp}/trivy-adapter.XXXXXX) git clone https://github.com/aquasecurity/harbor-scanner-trivy.git $TEMP cd $TEMP; git checkout $VERSION; cd - -echo "Building Trivy adapter binary based on golang:1.21.5..." +echo "Building Trivy adapter binary based on golang:1.22.3..." cp Dockerfile.binary $TEMP docker build -f $TEMP/Dockerfile.binary -t trivy-adapter-golang $TEMP diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index a23291ee73..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "harbor", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/src/cmd/exporter/main.go b/src/cmd/exporter/main.go index 400d541787..aea7553ced 100644 --- a/src/cmd/exporter/main.go +++ b/src/cmd/exporter/main.go @@ -96,7 +96,7 @@ func main() { ) prometheus.MustRegister(harborExporter) if err := harborExporter.ListenAndServe(); err != nil { - log.Errorf("Error starting Harbor expoter %s", err) + log.Errorf("Error starting Harbor exporter %s", err) os.Exit(1) } } diff --git a/src/cmd/migrate-patch/main.go b/src/cmd/migrate-patch/main.go index cb224ec5df..eb728b3a11 100644 --- a/src/cmd/migrate-patch/main.go +++ b/src/cmd/migrate-patch/main.go @@ -48,20 +48,23 @@ func main() { log.Fatalf("Failed to connect to Database, error: %v\n", err) } defer db.Close() - c := make(chan struct{}, 1) + + c := make(chan struct{}) go func() { + defer close(c) + err := db.Ping() for ; err != nil; err = db.Ping() { log.Println("Failed to Ping DB, sleep for 1 second.") time.Sleep(1 * time.Second) } - c <- struct{}{} }() select { case <-c: case <-time.After(30 * time.Second): log.Fatal("Failed to connect DB after 30 seconds, time out. \n") } + row := db.QueryRow(pgSQLCheckColStmt) var tblCount, colCount int if err := row.Scan(&tblCount, &colCount); err != nil { diff --git a/src/common/const.go b/src/common/const.go index 64b38da33a..aaa3c3fbe0 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -14,6 +14,8 @@ package common +import "time" + type contextKey string // const variables @@ -241,4 +243,7 @@ const ( BeegoMaxUploadSizeBytes = "beego_max_upload_size_bytes" // DefaultBeegoMaxUploadSizeBytes sets default max upload size to 128GB DefaultBeegoMaxUploadSizeBytes = 1 << 37 + + // Global Leeway used for token validation + JwtLeeway = 60 * time.Second ) diff --git a/src/common/rbac/const.go b/src/common/rbac/const.go index ff49ec3fdc..a783e71d4a 100644 --- a/src/common/rbac/const.go +++ b/src/common/rbac/const.go @@ -51,6 +51,7 @@ const ( ResourceRobot = Resource("robot") ResourceNotificationPolicy = Resource("notification-policy") ResourceScan = Resource("scan") + ResourceSBOM = Resource("sbom") ResourceScanner = Resource("scanner") ResourceArtifact = Resource("artifact") ResourceTag = Resource("tag") @@ -182,6 +183,10 @@ var ( {Resource: ResourceScan, Action: ActionRead}, {Resource: ResourceScan, Action: ActionStop}, + {Resource: ResourceSBOM, Action: ActionCreate}, + {Resource: ResourceSBOM, Action: ActionStop}, + {Resource: ResourceSBOM, Action: ActionRead}, + {Resource: ResourceTag, Action: ActionCreate}, {Resource: ResourceTag, Action: ActionList}, {Resource: ResourceTag, Action: ActionDelete}, diff --git a/src/common/rbac/project/rbac_role.go b/src/common/rbac/project/rbac_role.go index fa618b9827..961f004869 100644 --- a/src/common/rbac/project/rbac_role.go +++ b/src/common/rbac/project/rbac_role.go @@ -86,6 +86,9 @@ var ( {Resource: rbac.ResourceScan, Action: rbac.ActionCreate}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, {Resource: rbac.ResourceScan, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionCreate}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionCreate}, @@ -122,10 +125,7 @@ var ( {Resource: rbac.ResourceMember, Action: rbac.ActionRead}, {Resource: rbac.ResourceMember, Action: rbac.ActionList}, - {Resource: rbac.ResourceMetadata, Action: rbac.ActionCreate}, {Resource: rbac.ResourceMetadata, Action: rbac.ActionRead}, - {Resource: rbac.ResourceMetadata, Action: rbac.ActionUpdate}, - {Resource: rbac.ResourceMetadata, Action: rbac.ActionDelete}, {Resource: rbac.ResourceLog, Action: rbac.ActionList}, @@ -169,6 +169,9 @@ var ( {Resource: rbac.ResourceScan, Action: rbac.ActionCreate}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, {Resource: rbac.ResourceScan, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionCreate}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, @@ -223,6 +226,7 @@ var ( {Resource: rbac.ResourceRobot, Action: rbac.ActionList}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, @@ -267,6 +271,7 @@ var ( {Resource: rbac.ResourceRobot, Action: rbac.ActionList}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, @@ -290,6 +295,7 @@ var ( {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, diff --git a/src/common/utils/utils.go b/src/common/utils/utils.go index ab07c3ed3e..9a7a1f07c3 100644 --- a/src/common/utils/utils.go +++ b/src/common/utils/utils.go @@ -313,11 +313,11 @@ func ValidateCronString(cron string) error { // sort.Slice(input, func(i, j int) bool { // return MostMatchSorter(input[i].GroupName, input[j].GroupName, matchWord) // }) +// // a is the field to be used for sorting, b is the other field, matchWord is the word to be matched // the return value is true if a is less than b // for example, search with "user", input is {"harbor_user", "user", "users, "admin_user"} // it returns with this order {"user", "users", "admin_user", "harbor_user"} - func MostMatchSorter(a, b string, matchWord string) bool { // exact match always first if a == matchWord { @@ -332,3 +332,8 @@ func MostMatchSorter(a, b string, matchWord string) bool { } return len(a) < len(b) } + +// IsLocalPath checks if path is local, includes the empty path +func IsLocalPath(path string) bool { + return len(path) == 0 || (strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "//")) +} diff --git a/src/common/utils/utils_test.go b/src/common/utils/utils_test.go index 73d9fb28b6..4e1ab2ef35 100644 --- a/src/common/utils/utils_test.go +++ b/src/common/utils/utils_test.go @@ -486,3 +486,26 @@ func TestValidateCronString(t *testing.T) { } } } + +func TestIsLocalPath(t *testing.T) { + type args struct { + path string + } + tests := []struct { + name string + args args + want bool + }{ + {"normal test", args{"/harbor/project"}, true}, + {"failed", args{"www.myexample.com"}, false}, + {"other_site1", args{"//www.myexample.com"}, false}, + {"other_site2", args{"https://www.myexample.com"}, false}, + {"other_site", args{"http://www.myexample.com"}, false}, + {"empty_path", args{""}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, IsLocalPath(tt.args.path), "IsLocalPath(%v)", tt.args.path) + }) + } +} diff --git a/src/controller/artifact/abstractor.go b/src/controller/artifact/abstractor.go index 233008157d..bbf75a1fa1 100644 --- a/src/controller/artifact/abstractor.go +++ b/src/controller/artifact/abstractor.go @@ -127,10 +127,18 @@ func (a *abstractor) abstractManifestV2Metadata(artifact *artifact.Artifact, con } // use the "manifest.config.mediatype" as the media type of the artifact artifact.MediaType = manifest.Config.MediaType - if manifest.Annotations[wasm.AnnotationVariantKey] == wasm.AnnotationVariantValue || manifest.Annotations[wasm.AnnotationHandlerKey] == wasm.AnnotationHandlerValue { artifact.MediaType = wasm.MediaType } + /* + https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers + For referrers list, if the artifactType is empty or missing in the image manifest, the value of artifactType MUST be set to the config descriptor mediaType value + */ + if manifest.ArtifactType != "" { + artifact.ArtifactType = manifest.ArtifactType + } else { + artifact.ArtifactType = manifest.Config.MediaType + } // set size artifact.Size = int64(len(content)) + manifest.Config.Size @@ -153,6 +161,16 @@ func (a *abstractor) abstractIndexMetadata(ctx context.Context, art *artifact.Ar return err } + /* + https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers + For referrers list, If the artifactType is empty or missing in an index, the artifactType MUST be omitted. + */ + if index.ArtifactType != "" { + art.ArtifactType = index.ArtifactType + } else { + art.ArtifactType = "" + } + // set annotations art.Annotations = index.Annotations diff --git a/src/controller/artifact/abstractor_test.go b/src/controller/artifact/abstractor_test.go index 47b340f04a..e7955ed1c3 100644 --- a/src/controller/artifact/abstractor_test.go +++ b/src/controller/artifact/abstractor_test.go @@ -15,6 +15,7 @@ package artifact import ( + "context" "testing" "github.com/docker/distribution" @@ -175,7 +176,66 @@ var ( "com.example.key1": "value1" } }` - + OCIManifest = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.example.config.v1+json", + "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", + "size": 123 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "com.example.key1": "value1" + } +}` + OCIManifestWithArtifactType = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.example+type", + "config": { + "mediaType": "application/vnd.example.config.v1+json", + "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", + "size": 123 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "com.example.key1": "value1" + } +}` + OCIManifestWithEmptyConfig = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.example+type", + "config": { + "mediaType": "application/vnd.oci.empty.v1+json", + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "size": 2 + }, + "layers": [ + { + "mediaType": "application/vnd.example+type", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "oci.opencontainers.image.created": "2023-01-02T03:04:05Z", + "com.example.data": "payload" + } +}` index = `{ "schemaVersion": 2, "manifests": [ @@ -202,6 +262,34 @@ var ( "com.example.key1": "value1" } }` + indexWithArtifactType = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "artifactType": "application/vnd.food.stand", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 7143, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + "platform": { + "architecture": "ppc64le", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 7682, + "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", + "platform": { + "architecture": "amd64", + "os": "linux" + } + } + ], + "annotations": { + "com.example.key1": "value1" + } + }` ) type abstractorTestSuite struct { @@ -267,6 +355,67 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() { a.Equal("value1", artifact.Annotations["com.example.key1"]) } +// oci-spec v1 +func (a *abstractorTestSuite) TestAbstractMetadataOfOCIManifest() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifest)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + artifact := &artifact.Artifact{ + ID: 1, + } + a.processor.On("AbstractMetadata", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageManifest, artifact.ManifestMediaType) + a.Assert().Equal("application/vnd.example.config.v1+json", artifact.MediaType) + a.Assert().Equal("application/vnd.example.config.v1+json", artifact.ArtifactType) + a.Assert().Equal(int64(1916), artifact.Size) + a.Require().Len(artifact.Annotations, 1) + a.Equal("value1", artifact.Annotations["com.example.key1"]) +} + +// oci-spec v1.1.0 with artifactType +func (a *abstractorTestSuite) TestAbstractMetadataOfOCIManifestWithArtifactType() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithArtifactType)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + artifact := &artifact.Artifact{ + ID: 1, + } + a.processor.On("AbstractMetadata", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageManifest, artifact.ManifestMediaType) + a.Assert().Equal("application/vnd.example.config.v1+json", artifact.MediaType) + a.Assert().Equal("application/vnd.example+type", artifact.ArtifactType) + a.Assert().Equal(int64(1966), artifact.Size) + a.Require().Len(artifact.Annotations, 1) + a.Equal("value1", artifact.Annotations["com.example.key1"]) +} + +// empty config with artifactType +func (a *abstractorTestSuite) TestAbstractMetadataOfV2ManifestWithEmptyConfig() { + // v1.MediaTypeImageManifest + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithEmptyConfig)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + artifact := &artifact.Artifact{ + ID: 1, + } + a.processor.On("AbstractMetadata", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageManifest, artifact.ManifestMediaType) + a.Assert().Equal(v1.MediaTypeEmptyJSON, artifact.MediaType) + a.Assert().Equal("application/vnd.example+type", artifact.ArtifactType) + a.Assert().Equal(int64(1880), artifact.Size) + a.Require().Len(artifact.Annotations, 2) + a.Equal("payload", artifact.Annotations["com.example.data"]) +} + // OCI index func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() { manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageIndex, []byte(index)) @@ -279,17 +428,41 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() { artifact := &artifact.Artifact{ ID: 1, } - err = a.abstractor.AbstractMetadata(nil, artifact) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) a.Require().Nil(err) a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType) a.Assert().Equal(v1.MediaTypeImageIndex, artifact.MediaType) + a.Assert().Equal("", artifact.ArtifactType) a.Assert().Equal(int64(668), artifact.Size) a.Require().Len(artifact.Annotations, 1) a.Assert().Equal("value1", artifact.Annotations["com.example.key1"]) a.Len(artifact.References, 2) } +func (a *abstractorTestSuite) TestAbstractMetadataOfIndexWithArtifactType() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageIndex, []byte(indexWithArtifactType)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + a.argMgr.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything).Return(&artifact.Artifact{ + ID: 2, + Size: 10, + }, nil) + artifact := &artifact.Artifact{ + ID: 1, + } + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType) + a.Assert().Equal(v1.MediaTypeImageIndex, artifact.MediaType) + a.Assert().Equal("application/vnd.food.stand", artifact.ArtifactType) + a.Assert().Equal(int64(801), artifact.Size) + a.Require().Len(artifact.Annotations, 1) + a.Assert().Equal("value1", artifact.Annotations["com.example.key1"]) + a.Len(artifact.References, 2) +} + type unknownManifest struct{} func (u *unknownManifest) References() []distribution.Descriptor { diff --git a/src/controller/artifact/annotation/v1alpha1.go b/src/controller/artifact/annotation/v1alpha1.go index 15c464ab21..6ca4605e13 100644 --- a/src/controller/artifact/annotation/v1alpha1.go +++ b/src/controller/artifact/annotation/v1alpha1.go @@ -92,6 +92,7 @@ func parseV1alpha1Icon(artifact *artifact.Artifact, manifest *v1.Manifest, reg r if err != nil { return err } + defer icon.Close() // check the size of the size <= 1MB data, err := io.ReadAll(io.LimitReader(icon, 1<<20)) if err != nil { diff --git a/src/controller/artifact/controller.go b/src/controller/artifact/controller.go index 0e710c064e..45f6e4f59c 100644 --- a/src/controller/artifact/controller.go +++ b/src/controller/artifact/controller.go @@ -29,6 +29,7 @@ import ( "github.com/goharbor/harbor/src/controller/artifact/processor/chart" "github.com/goharbor/harbor/src/controller/artifact/processor/cnab" "github.com/goharbor/harbor/src/controller/artifact/processor/image" + "github.com/goharbor/harbor/src/controller/artifact/processor/sbom" "github.com/goharbor/harbor/src/controller/artifact/processor/wasm" "github.com/goharbor/harbor/src/controller/event/metadata" "github.com/goharbor/harbor/src/controller/tag" @@ -57,7 +58,10 @@ import ( var ( // Ctl is a global artifact controller instance - Ctl = NewController() + Ctl = NewController() + skippedContentTypes = map[string]struct{}{ + "application/vnd.in-toto+json": {}, + } ) var ( @@ -73,6 +77,7 @@ var ( chart.ArtifactTypeChart: icon.DigestOfIconChart, cnab.ArtifactTypeCNAB: icon.DigestOfIconCNAB, wasm.ArtifactTypeWASM: icon.DigestOfIconWASM, + sbom.ArtifactTypeSBOM: icon.DigestOfIconAccSBOM, } ) @@ -111,6 +116,8 @@ type Controller interface { RemoveLabel(ctx context.Context, artifactID int64, labelID int64) (err error) // Walk walks the artifact tree rooted at root, calling walkFn for each artifact in the tree, including root. Walk(ctx context.Context, root *Artifact, walkFn func(*Artifact) error, option *Option) error + // HasUnscannableLayer check artifact with digest if has unscannable layer + HasUnscannableLayer(ctx context.Context, dgst string) (bool, error) } // NewController creates an instance of the default artifact controller @@ -324,12 +331,6 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces return err } - if isAccessory { - if err := c.accessoryMgr.DeleteAccessories(ctx, q.New(q.KeyWords{"ArtifactID": art.ID, "Digest": art.Digest})); err != nil && !errors.IsErr(err, errors.NotFoundCode) { - return err - } - } - // the child artifact is referenced by some tags, skip if !isRoot && len(art.Tags) > 0 { return nil @@ -352,11 +353,26 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces return nil } + if isAccessory { + if err := c.accessoryMgr.DeleteAccessories(ctx, q.New(q.KeyWords{"ArtifactID": art.ID, "Digest": art.Digest})); err != nil && !errors.IsErr(err, errors.NotFoundCode) { + return err + } + } + // delete accessories if contains any for _, acc := range art.Accessories { // only hard ref accessory should be removed if acc.IsHard() { - if err = c.deleteDeeply(ctx, acc.GetData().ArtifactID, true, true); err != nil { + // if this acc artifact has parent(is child), set isRoot to false + parents, err := c.artMgr.ListReferences(ctx, &q.Query{ + Keywords: map[string]interface{}{ + "ChildID": acc.GetData().ArtifactID, + }, + }) + if err != nil { + return err + } + if err = c.deleteDeeply(ctx, acc.GetData().ArtifactID, len(parents) == 0, true); err != nil { return err } } @@ -369,7 +385,12 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces !errors.IsErr(err, errors.NotFoundCode) { return err } - if err = c.deleteDeeply(ctx, reference.ChildID, false, false); err != nil { + // if the child artifact is an accessory, set isAccessory to true + accs, err := c.accessoryMgr.List(ctx, q.New(q.KeyWords{"ArtifactID": reference.ChildID})) + if err != nil { + return err + } + if err = c.deleteDeeply(ctx, reference.ChildID, false, len(accs) > 0); err != nil { return err } } @@ -743,3 +764,21 @@ func (c *controller) populateAccessories(ctx context.Context, art *Artifact) { } art.Accessories = accs } + +// HasUnscannableLayer check if it is a in-toto sbom, if it contains any blob with a content_type is application/vnd.in-toto+json, then consider as in-toto sbom +func (c *controller) HasUnscannableLayer(ctx context.Context, dgst string) (bool, error) { + if len(dgst) == 0 { + return false, nil + } + blobs, err := c.blobMgr.GetByArt(ctx, dgst) + if err != nil { + return false, err + } + for _, b := range blobs { + if _, exist := skippedContentTypes[b.ContentType]; exist { + log.Debugf("the artifact with digest %v is unscannable, because it contains content type: %v", dgst, b.ContentType) + return true, nil + } + } + return false, nil +} diff --git a/src/controller/artifact/controller_test.go b/src/controller/artifact/controller_test.go index ce58b5d280..0e6ed458a2 100644 --- a/src/controller/artifact/controller_test.go +++ b/src/controller/artifact/controller_test.go @@ -35,6 +35,7 @@ import ( accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model" basemodel "github.com/goharbor/harbor/src/pkg/accessory/model/base" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/blob/models" "github.com/goharbor/harbor/src/pkg/label/model" repomodel "github.com/goharbor/harbor/src/pkg/repository/model" model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag" @@ -678,6 +679,29 @@ func (c *controllerTestSuite) TestWalk() { } } +func (c *controllerTestSuite) TestIsIntoto() { + blobs := []*models.Blob{ + {Digest: "sha256:00000", ContentType: "application/vnd.oci.image.manifest.v1+json"}, + {Digest: "sha256:22222", ContentType: "application/vnd.oci.image.config.v1+json"}, + {Digest: "sha256:11111", ContentType: "application/vnd.in-toto+json"}, + } + c.blobMgr.On("GetByArt", mock.Anything, mock.Anything).Return(blobs, nil).Once() + isIntoto, err := c.ctl.HasUnscannableLayer(context.Background(), "sha256: 77777") + c.Nil(err) + c.True(isIntoto) + + blobs2 := []*models.Blob{ + {Digest: "sha256:00000", ContentType: "application/vnd.oci.image.manifest.v1+json"}, + {Digest: "sha256:22222", ContentType: "application/vnd.oci.image.config.v1+json"}, + {Digest: "sha256:11111", ContentType: "application/vnd.oci.image.layer.v1.tar+gzip"}, + } + + c.blobMgr.On("GetByArt", mock.Anything, mock.Anything).Return(blobs2, nil).Once() + isIntoto2, err := c.ctl.HasUnscannableLayer(context.Background(), "sha256: 8888") + c.Nil(err) + c.False(isIntoto2) +} + func TestControllerTestSuite(t *testing.T) { suite.Run(t, &controllerTestSuite{}) } diff --git a/src/controller/artifact/model.go b/src/controller/artifact/model.go index b1bb377954..0953fd0690 100644 --- a/src/controller/artifact/model.go +++ b/src/controller/artifact/model.go @@ -80,6 +80,19 @@ func (artifact *Artifact) SetAdditionLink(addition, version string) { artifact.AdditionLinks[addition] = &AdditionLink{HREF: href, Absolute: false} } +func (artifact *Artifact) SetSBOMAdditionLink(sbomDgst string, version string) { + if artifact.AdditionLinks == nil { + artifact.AdditionLinks = make(map[string]*AdditionLink) + } + addition := "sboms" + projectName, repo := utils.ParseRepository(artifact.RepositoryName) + // encode slash as %252F + repo = repository.Encode(repo) + href := fmt.Sprintf("/api/%s/projects/%s/repositories/%s/artifacts/%s/additions/%s", version, projectName, repo, sbomDgst, addition) + + artifact.AdditionLinks[addition] = &AdditionLink{HREF: href, Absolute: false} +} + // AdditionLink is a link via that the addition can be fetched type AdditionLink struct { HREF string `json:"href"` diff --git a/src/controller/artifact/processor/chart/chart.go b/src/controller/artifact/processor/chart/chart.go index 059d47bbfb..e7df72b562 100644 --- a/src/controller/artifact/processor/chart/chart.go +++ b/src/controller/artifact/processor/chart/chart.go @@ -85,11 +85,11 @@ func (p *processor) AbstractAddition(_ context.Context, artifact *artifact.Artif if err != nil { return nil, err } + defer blob.Close() content, err := io.ReadAll(blob) if err != nil { return nil, err } - blob.Close() chartDetails, err := p.chartOperator.GetDetails(content) if err != nil { return nil, err diff --git a/src/controller/artifact/processor/default_test.go b/src/controller/artifact/processor/default_test.go index 0f6af77347..4899415395 100644 --- a/src/controller/artifact/processor/default_test.go +++ b/src/controller/artifact/processor/default_test.go @@ -117,7 +117,31 @@ var ( } ] }` - v2ManifestWithUnknownConfig = `{ + OCIManifestWithUnknownJsonConfig = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.exmaple.config.v1+json", + "digest": "sha256:48ef4a53c0770222d9752cd0588431dbda54667046208c79804e34c15c1579cd", + "size": 129 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "com.example.key1": "value1" + } + }` + UnknownJsonConfig = `{ + "author": "yminer", + "architecture": "amd64", + "selfdefined": "true" +}` + OCIManifestWithUnknownConfig = `{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { @@ -141,7 +165,30 @@ var ( "newUnspecifiedField": null } }` - unknownConfig = `{NHL Peanut Butter on my NHL bagel}` + UnknownConfig = `{NHL Peanut Butter on my NHL bagel}` + + OCIManifestWithEmptyConfig = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.example+type", + "config": { + "mediaType": "application/vnd.oci.empty.v1+json", + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "size": 2 + }, + "layers": [ + { + "mediaType": "application/vnd.example+type", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "oci.opencontainers.image.created": "2023-01-02T03:04:05Z", + "com.example.data": "payload" + } + }` + emptyConfig = `{}` ) type defaultProcessorTestSuite struct { @@ -190,6 +237,12 @@ func (d *defaultProcessorTestSuite) TestGetArtifactType() { typee = processor.GetArtifactType(nil, art) d.Equal("IMAGE", typee) + mediaType = "application/vnd.example.config.v1+json" + art = &artifact.Artifact{MediaType: mediaType} + processor = &defaultProcessor{} + typee = processor.GetArtifactType(nil, art) + d.Equal(ArtifactTypeUnknown, typee) + mediaType = "application/vnd.cncf.helm.chart.config.v1+json" art = &artifact.Artifact{MediaType: mediaType} processor = &defaultProcessor{} @@ -229,19 +282,53 @@ func (d *defaultProcessorTestSuite) TestAbstractMetadata() { d.Len(art.ExtraAttrs, 12) } -func (d *defaultProcessorTestSuite) TestAbstractMetadataWithUnknownConfig() { - manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(v2ManifestWithUnknownConfig)) +func (d *defaultProcessorTestSuite) TestAbstractMetadataOfOCIManifesttWithUnknownJsonConfig() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithUnknownJsonConfig)) d.Require().Nil(err) manifestMediaType, content, err := manifest.Payload() d.Require().Nil(err) - configBlob := io.NopCloser(strings.NewReader(unknownConfig)) - d.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(0), configBlob, nil) - art := &artifact.Artifact{ManifestMediaType: manifestMediaType} - err = d.processor.AbstractMetadata(nil, art, content) + configBlob := io.NopCloser(strings.NewReader(UnknownJsonConfig)) + metadata := map[string]interface{}{} + err = json.NewDecoder(configBlob).Decode(&metadata) + d.Require().Nil(err) + + art := &artifact.Artifact{ManifestMediaType: manifestMediaType, MediaType: "application/vnd.example.config.v1+json"} + + d.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(129), configBlob, nil) + d.parser.On("Parse", context.TODO(), mock.AnythingOfType("*artifact.Artifact"), mock.AnythingOfType("[]byte")).Return(nil) + err = d.processor.AbstractMetadata(context.TODO(), art, content) d.Require().Nil(err) d.Len(art.ExtraAttrs, 0) - d.Len(unknownConfig, 35) + d.NotEqual(art.ExtraAttrs, len(metadata)) + +} + +func (d *defaultProcessorTestSuite) TestAbstractMetadataWithUnknownConfig() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithUnknownConfig)) + d.Require().Nil(err) + manifestMediaType, content, err := manifest.Payload() + d.Require().Nil(err) + + configBlob := io.NopCloser(strings.NewReader(UnknownConfig)) + d.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(0), configBlob, nil) + art := &artifact.Artifact{ManifestMediaType: manifestMediaType, MediaType: "application/vnd.nhl.peanut.butter.bagel"} + err = d.processor.AbstractMetadata(context.TODO(), art, content) + d.Require().Nil(err) + d.Len(art.ExtraAttrs, 0) +} + +func (d *defaultProcessorTestSuite) TestAbstractMetadataWithEmptyConfig() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithEmptyConfig)) + d.Require().Nil(err) + manifestMediaType, content, err := manifest.Payload() + d.Require().Nil(err) + + art := &artifact.Artifact{ManifestMediaType: manifestMediaType, MediaType: "application/vnd.oci.empty.v1+json"} + err = d.processor.AbstractMetadata(context.TODO(), art, content) + d.Assert().Equal(0, len(art.ExtraAttrs)) + d.Assert().Equal(2, len(emptyConfig)) + d.Require().Nil(err) } func TestDefaultProcessorTestSuite(t *testing.T) { diff --git a/src/controller/artifact/processor/sbom/sbom.go b/src/controller/artifact/processor/sbom/sbom.go new file mode 100644 index 0000000000..4eb11f4bd2 --- /dev/null +++ b/src/controller/artifact/processor/sbom/sbom.go @@ -0,0 +1,89 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "context" + "encoding/json" + "io" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/goharbor/harbor/src/controller/artifact/processor" + "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/artifact" +) + +const ( + // ArtifactTypeSBOM is the artifact type for SBOM, it's scope is only used in the processor + ArtifactTypeSBOM = "SBOM" + // processorMediaType is the media type for SBOM, it's scope is only used to register the processor + processorMediaType = "application/vnd.goharbor.harbor.sbom.v1" +) + +func init() { + pc := &Processor{} + pc.ManifestProcessor = base.NewManifestProcessor() + if err := processor.Register(pc, processorMediaType); err != nil { + log.Errorf("failed to register processor for media type %s: %v", processorMediaType, err) + return + } +} + +// Processor is the processor for SBOM +type Processor struct { + *base.ManifestProcessor +} + +// AbstractAddition returns the addition for SBOM +func (m *Processor) AbstractAddition(_ context.Context, art *artifact.Artifact, _ string) (*processor.Addition, error) { + man, _, err := m.RegCli.PullManifest(art.RepositoryName, art.Digest) + if err != nil { + return nil, errors.Wrap(err, "failed to pull manifest") + } + _, payload, err := man.Payload() + if err != nil { + return nil, errors.Wrap(err, "failed to get payload") + } + manifest := &v1.Manifest{} + if err := json.Unmarshal(payload, manifest); err != nil { + return nil, err + } + // SBOM artifact should only have one layer + if len(manifest.Layers) != 1 { + return nil, errors.New(nil).WithCode(errors.NotFoundCode).WithMessage("The sbom is not found") + } + layerDgst := manifest.Layers[0].Digest.String() + _, blob, err := m.RegCli.PullBlob(art.RepositoryName, layerDgst) + if err != nil { + return nil, errors.Wrap(err, "failed to pull the blob") + } + defer blob.Close() + content, err := io.ReadAll(blob) + if err != nil { + return nil, err + } + return &processor.Addition{ + Content: content, + ContentType: processorMediaType, + }, nil +} + +// GetArtifactType the artifact type is used to display the artifact type in the UI +func (m *Processor) GetArtifactType(_ context.Context, _ *artifact.Artifact) string { + return ArtifactTypeSBOM +} diff --git a/src/controller/artifact/processor/sbom/sbom_test.go b/src/controller/artifact/processor/sbom/sbom_test.go new file mode 100644 index 0000000000..33889591a3 --- /dev/null +++ b/src/controller/artifact/processor/sbom/sbom_test.go @@ -0,0 +1,166 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "context" + "fmt" + "io" + "strings" + "testing" + + "github.com/docker/distribution" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + + "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/testing/pkg/registry" +) + +type SBOMProcessorTestSuite struct { + suite.Suite + processor *Processor + regCli *registry.Client +} + +func (suite *SBOMProcessorTestSuite) SetupSuite() { + suite.regCli = ®istry.Client{} + suite.processor = &Processor{ + &base.ManifestProcessor{ + RegCli: suite.regCli, + }, + } +} + +func (suite *SBOMProcessorTestSuite) TearDownSuite() { +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionNormal() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abc" + }] +}` + sbomContent := "this is a sbom content" + reader := strings.NewReader(sbomContent) + blobReader := io.NopCloser(reader) + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(123), blobReader, nil).Once() + addition, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.Nil(err) + suite.Equal(sbomContent, string(addition.Content)) +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionMultiLayer() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abc" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 843, + "digest": "sha256:def" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 531, + "digest": "sha256:123" + } + ] +}` + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + _, err = suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionPullBlobError() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abc" + } + ] +}` + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(123), nil, errors.NotFoundError(fmt.Errorf("not found"))).Once() + addition, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) + suite.Nil(addition) +} +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionNoSBOMLayer() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + } +}` + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + _, err = suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionPullManifestError() { + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(nil, "sha256:123", errors.NotFoundError(fmt.Errorf("not found"))).Once() + _, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) + +} + +func (suite *SBOMProcessorTestSuite) TestGetArtifactType() { + suite.Equal(ArtifactTypeSBOM, suite.processor.GetArtifactType(context.Background(), &artifact.Artifact{})) +} + +func TestSBOMProcessorTestSuite(t *testing.T) { + suite.Run(t, &SBOMProcessorTestSuite{}) +} diff --git a/src/controller/event/handler/internal/artifact.go b/src/controller/event/handler/internal/artifact.go index fb212ac87d..0e9b3f5bba 100644 --- a/src/controller/event/handler/internal/artifact.go +++ b/src/controller/event/handler/internal/artifact.go @@ -24,6 +24,7 @@ import ( "time" "github.com/goharbor/harbor/src/controller/artifact" + "github.com/goharbor/harbor/src/controller/artifact/processor/sbom" "github.com/goharbor/harbor/src/controller/event" "github.com/goharbor/harbor/src/controller/event/operator" "github.com/goharbor/harbor/src/controller/repository" @@ -36,6 +37,7 @@ import ( "github.com/goharbor/harbor/src/pkg" pkgArt "github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/scan/report" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/task" ) @@ -258,6 +260,11 @@ func (a *ArtifactEventHandler) onPush(ctx context.Context, event *event.Artifact if err := autoScan(ctx, &artifact.Artifact{Artifact: *event.Artifact}, event.Tags...); err != nil { log.Errorf("scan artifact %s@%s failed, error: %v", event.Artifact.RepositoryName, event.Artifact.Digest, err) } + + log.Debugf("auto generate sbom is triggered for artifact event %+v", event) + if err := autoGenSBOM(ctx, &artifact.Artifact{Artifact: *event.Artifact}); err != nil { + log.Errorf("generate sbom for artifact %s@%s failed, error: %v", event.Artifact.RepositoryName, event.Artifact.Digest, err) + } }() return nil @@ -314,6 +321,11 @@ func (a *ArtifactEventHandler) onDelete(ctx context.Context, event *event.Artifa log.Errorf("failed to delete scan reports of artifact %v, error: %v", unrefDigests, err) } + if event.Artifact.Type == sbom.ArtifactTypeSBOM && len(event.Artifact.Digest) > 0 { + if err := reportMgr.DeleteByExtraAttr(ctx, v1.MimeTypeSBOMReport, "sbom_digest", event.Artifact.Digest); err != nil { + log.Errorf("failed to delete scan reports of with sbom digest %v, error: %v", event.Artifact.Digest, err) + } + } return nil } diff --git a/src/controller/event/handler/internal/util.go b/src/controller/event/handler/internal/util.go index c7cf512430..51085a6f17 100644 --- a/src/controller/event/handler/internal/util.go +++ b/src/controller/event/handler/internal/util.go @@ -20,7 +20,9 @@ import ( "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/project" "github.com/goharbor/harbor/src/controller/scan" + "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" ) // autoScan scan artifact when the project of the artifact enable auto scan @@ -37,9 +39,26 @@ func autoScan(ctx context.Context, a *artifact.Artifact, tags ...string) error { return orm.WithTransaction(func(ctx context.Context) error { options := []scan.Option{} if len(tags) > 0 { - options = append(options, scan.WithTag(tags[0])) + options = append(options, scan.WithTag(tags[0]), scan.WithFromEvent(true)) } return scan.DefaultController.Scan(ctx, a, options...) })(orm.SetTransactionOpNameToContext(ctx, "tx-auto-scan")) } + +func autoGenSBOM(ctx context.Context, a *artifact.Artifact) error { + proj, err := project.Ctl.Get(ctx, a.ProjectID) + if err != nil { + return err + } + if !proj.AutoSBOMGen() { + return nil + } + // transaction here to work with the image index + return orm.WithTransaction(func(ctx context.Context) error { + options := []scan.Option{} + options = append(options, scan.WithScanType(v1.ScanTypeSbom), scan.WithFromEvent(true)) + log.Debugf("sbom scan controller artifact %+v, options %+v", a, options) + return scan.DefaultController.Scan(ctx, a, options...) + })(orm.SetTransactionOpNameToContext(ctx, "tx-auto-gen-sbom")) +} diff --git a/src/controller/event/handler/internal/util_test.go b/src/controller/event/handler/internal/util_test.go index 615fad9f6d..158bcc2fef 100644 --- a/src/controller/event/handler/internal/util_test.go +++ b/src/controller/event/handler/internal/util_test.go @@ -95,6 +95,34 @@ func (suite *AutoScanTestSuite) TestAutoScan() { suite.Nil(autoScan(ctx, art)) } +func (suite *AutoScanTestSuite) TestAutoScanSBOM() { + mock.OnAnything(suite.projectController, "Get").Return(&proModels.Project{ + Metadata: map[string]string{ + proModels.ProMetaAutoSBOMGen: "true", + }, + }, nil) + suite.scanController.On("Scan", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + art := &artifact.Artifact{} + + suite.Nil(autoGenSBOM(ctx, art)) +} + +func (suite *AutoScanTestSuite) TestAutoScanSBOMFalse() { + mock.OnAnything(suite.projectController, "Get").Return(&proModels.Project{ + Metadata: map[string]string{ + proModels.ProMetaAutoSBOMGen: "false", + }, + }, nil) + + suite.scanController.On("Scan", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + + ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + art := &artifact.Artifact{} + + suite.Nil(autoGenSBOM(ctx, art)) +} + func (suite *AutoScanTestSuite) TestAutoScanFailed() { mock.OnAnything(suite.projectController, "Get").Return(&proModels.Project{ Metadata: map[string]string{ diff --git a/src/controller/event/handler/webhook/artifact/replication.go b/src/controller/event/handler/webhook/artifact/replication.go index fab71978bb..56ceb0bb1f 100644 --- a/src/controller/event/handler/webhook/artifact/replication.go +++ b/src/controller/event/handler/webhook/artifact/replication.go @@ -216,7 +216,9 @@ func constructReplicationPayload(ctx context.Context, event *event.ReplicationEv func getMetadataFromResource(resource string) (namespace, nameAndTag string) { // Usually resource format likes 'library/busybox:v1', but it could be 'busybox:v1' in docker registry - meta := strings.Split(resource, "/") + // It also could be 'library/bitnami/fluentd:1.13.3-debian-10-r0' so we need to split resource to only 2 parts + // possible namespace and image name which may include slashes for example: bitnami/fluentd:1.13.3-debian-10-r0 + meta := strings.SplitN(resource, "/", 2) if len(meta) == 1 { return "", meta[0] } diff --git a/src/controller/event/handler/webhook/artifact/replication_test.go b/src/controller/event/handler/webhook/artifact/replication_test.go index f920ff25d0..8a8d1bca22 100644 --- a/src/controller/event/handler/webhook/artifact/replication_test.go +++ b/src/controller/event/handler/webhook/artifact/replication_test.go @@ -146,3 +146,21 @@ func TestIsLocalRegistry(t *testing.T) { } assert.False(t, isLocalRegistry(reg2)) } + +func TestReplicationHandler_ShortResourceName(t *testing.T) { + namespace, resource := getMetadataFromResource("busybox:v1") + assert.Equal(t, "", namespace) + assert.Equal(t, "busybox:v1", resource) +} + +func TestReplicationHandler_NormalResourceName(t *testing.T) { + namespace, resource := getMetadataFromResource("library/busybox:v1") + assert.Equal(t, "library", namespace) + assert.Equal(t, "busybox:v1", resource) +} + +func TestReplicationHandler_LongResourceName(t *testing.T) { + namespace, resource := getMetadataFromResource("library/bitnami/fluentd:1.13.3-debian-10-r0") + assert.Equal(t, "library", namespace) + assert.Equal(t, "bitnami/fluentd:1.13.3-debian-10-r0", resource) +} diff --git a/src/controller/event/handler/webhook/scan/scan.go b/src/controller/event/handler/webhook/scan/scan.go index fda487a07b..04b3fffb51 100644 --- a/src/controller/event/handler/webhook/scan/scan.go +++ b/src/controller/event/handler/webhook/scan/scan.go @@ -21,6 +21,7 @@ import ( "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/event" "github.com/goharbor/harbor/src/controller/event/handler/util" + eventModel "github.com/goharbor/harbor/src/controller/event/model" "github.com/goharbor/harbor/src/controller/project" "github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/lib/errors" @@ -104,6 +105,9 @@ func constructScanImagePayload(ctx context.Context, event *event.ScanImageEvent, RepoFullName: event.Artifact.Repository, RepoType: repoType, }, + Scan: &eventModel.Scan{ + ScanType: event.ScanType, + }, }, Operator: event.Operator, } @@ -138,17 +142,29 @@ func constructScanImagePayload(ctx context.Context, event *event.ScanImageEvent, time.Sleep(500 * time.Millisecond) } - // Add scan overview - summaries, err := scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}) - if err != nil { - return nil, errors.Wrap(err, "construct scan payload") + scanSummaries := map[string]interface{}{} + if event.ScanType == v1.ScanTypeVulnerability { + scanSummaries, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}) + if err != nil { + return nil, errors.Wrap(err, "construct scan payload") + } } + sbomOverview := map[string]interface{}{} + if event.ScanType == v1.ScanTypeSbom { + sbomOverview, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeSBOMReport}) + if err != nil { + return nil, errors.Wrap(err, "construct scan payload") + } + } + + // Add scan overview and sbom overview resource := &model.Resource{ Tag: event.Artifact.Tag, Digest: event.Artifact.Digest, ResourceURL: resURL, - ScanOverview: summaries, + ScanOverview: scanSummaries, + SBOMOverview: sbomOverview, } payload.EventData.Resources = append(payload.EventData.Resources, resource) diff --git a/src/controller/event/metadata/scan.go b/src/controller/event/metadata/scan.go index a05cc3d8f9..588a9e3e1d 100644 --- a/src/controller/event/metadata/scan.go +++ b/src/controller/event/metadata/scan.go @@ -27,6 +27,7 @@ import ( // ScanImageMetaData defines meta data of image scanning event type ScanImageMetaData struct { Artifact *v1.Artifact + ScanType string Status string Operator string } @@ -55,6 +56,7 @@ func (si *ScanImageMetaData) Resolve(evt *event.Event) error { Artifact: si.Artifact, OccurAt: time.Now(), Operator: si.Operator, + ScanType: si.ScanType, } evt.Topic = topic diff --git a/src/controller/event/model/event.go b/src/controller/event/model/event.go index 6782b152d7..2e7021bc36 100644 --- a/src/controller/event/model/event.go +++ b/src/controller/event/model/event.go @@ -74,3 +74,9 @@ type RetentionRule struct { // Selector attached to the rule for filtering scope (e.g: repositories or namespaces) ScopeSelectors map[string][]*rule.Selector `json:"scope_selectors,omitempty"` } + +// Scan describes scan infos +type Scan struct { + // ScanType the scan type + ScanType string `json:"scan_type,omitempty"` +} diff --git a/src/controller/event/topic.go b/src/controller/event/topic.go index 3514bb9dc4..08e133e1a4 100644 --- a/src/controller/event/topic.go +++ b/src/controller/event/topic.go @@ -159,7 +159,7 @@ func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { ResourceType: "artifact"} if len(p.Tags) == 0 { - auditLog.Resource = fmt.Sprintf("%s:%s", + auditLog.Resource = fmt.Sprintf("%s@%s", p.Artifact.RepositoryName, p.Artifact.Digest) } else { auditLog.Resource = fmt.Sprintf("%s:%s", @@ -188,7 +188,7 @@ func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { ResourceType: "artifact"} if len(p.Tags) == 0 { - auditLog.Resource = fmt.Sprintf("%s:%s", + auditLog.Resource = fmt.Sprintf("%s@%s", p.Artifact.RepositoryName, p.Artifact.Digest) } else { auditLog.Resource = fmt.Sprintf("%s:%s", @@ -222,7 +222,7 @@ func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { Operation: rbac.ActionDelete.String(), Username: d.Operator, ResourceType: "artifact", - Resource: fmt.Sprintf("%s:%s", d.Artifact.RepositoryName, d.Artifact.Digest)} + Resource: fmt.Sprintf("%s@%s", d.Artifact.RepositoryName, d.Artifact.Digest)} return auditLog, nil } @@ -289,6 +289,7 @@ func (d *DeleteTagEvent) String() string { // ScanImageEvent is scanning image related event data to publish type ScanImageEvent struct { EventType string + ScanType string Artifact *v1.Artifact OccurAt time.Time Operator string diff --git a/src/controller/icon/controller.go b/src/controller/icon/controller.go index 81af7f6f7b..c2f950544c 100644 --- a/src/controller/icon/controller.go +++ b/src/controller/icon/controller.go @@ -69,6 +69,10 @@ var ( path: "./icons/wasm.png", resize: true, }, + icon.DigestOfIconAccSBOM: { + path: "./icons/sbom.png", + resize: true, + }, icon.DigestOfIconDefault: { path: "./icons/default.png", resize: true, diff --git a/src/controller/member/controller.go b/src/controller/member/controller.go index 80212d9c52..c53d49232d 100644 --- a/src/controller/member/controller.go +++ b/src/controller/member/controller.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/goharbor/harbor/src/common" + commonmodels "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/core/auth" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/q" @@ -45,7 +46,7 @@ type Controller interface { // Count get the total amount of project members Count(ctx context.Context, projectNameOrID interface{}, query *q.Query) (int, error) // IsProjectAdmin judges if the user is a project admin of any project - IsProjectAdmin(ctx context.Context, memberID int) (bool, error) + IsProjectAdmin(ctx context.Context, member commonmodels.User) (bool, error) } // Request - Project Member Request @@ -261,8 +262,8 @@ func (c *controller) Delete(ctx context.Context, projectNameOrID interface{}, me return c.mgr.Delete(ctx, p.ProjectID, memberID) } -func (c *controller) IsProjectAdmin(ctx context.Context, memberID int) (bool, error) { - members, err := c.projectMgr.ListAdminRolesOfUser(ctx, memberID) +func (c *controller) IsProjectAdmin(ctx context.Context, member commonmodels.User) (bool, error) { + members, err := c.projectMgr.ListAdminRolesOfUser(ctx, member) if err != nil { return false, err } diff --git a/src/controller/member/controller_test.go b/src/controller/member/controller_test.go index 5634816606..fe54875092 100644 --- a/src/controller/member/controller_test.go +++ b/src/controller/member/controller_test.go @@ -98,7 +98,7 @@ func (suite *MemberControllerTestSuite) TestAddProjectMemberWithUserGroup() { func (suite *MemberControllerTestSuite) TestIsProjectAdmin() { mock.OnAnything(suite.projectMgr, "ListAdminRolesOfUser").Return([]models.Member{models.Member{ID: 2, ProjectID: 2}}, nil) - ok, err := suite.controller.IsProjectAdmin(context.Background(), 2) + ok, err := suite.controller.IsProjectAdmin(context.Background(), comModels.User{UserID: 1}) suite.NoError(err) suite.True(ok) } diff --git a/src/controller/replication/execution.go b/src/controller/replication/execution.go index f6facc02a0..95136d9d84 100644 --- a/src/controller/replication/execution.go +++ b/src/controller/replication/execution.go @@ -154,11 +154,8 @@ func (c *controller) Start(ctx context.Context, policy *replicationmodel.Policy, func (c *controller) markError(ctx context.Context, executionID int64, err error) { logger := log.GetLogger(ctx) // try to stop the execution first in case that some tasks are already created - if err := c.execMgr.StopAndWait(ctx, executionID, 10*time.Second); err != nil { - logger.Errorf("failed to stop the execution %d: %v", executionID, err) - } - if err := c.execMgr.MarkError(ctx, executionID, err.Error()); err != nil { - logger.Errorf("failed to mark error for the execution %d: %v", executionID, err) + if e := c.execMgr.StopAndWaitWithError(ctx, executionID, 10*time.Second, err); e != nil { + logger.Errorf("failed to stop the execution %d: %v", executionID, e) } } diff --git a/src/controller/replication/execution_test.go b/src/controller/replication/execution_test.go index 62b0dd549c..de4791f09a 100644 --- a/src/controller/replication/execution_test.go +++ b/src/controller/replication/execution_test.go @@ -75,8 +75,7 @@ func (r *replicationTestSuite) TestStart() { // got error when running the replication flow 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.execMgr.On("StopAndWait", mock.Anything, mock.Anything, mock.Anything).Return(nil) - r.execMgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil) + r.execMgr.On("StopAndWaitWithError", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("error")) r.ormCreator.On("Create").Return(nil) id, err = r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: true}, nil, task.ExecutionTriggerManual) diff --git a/src/controller/replication/flow/mock_adapter_factory_test.go b/src/controller/replication/flow/mock_adapter_factory_test.go index e1d069b072..e11e8baca7 100644 --- a/src/controller/replication/flow/mock_adapter_factory_test.go +++ b/src/controller/replication/flow/mock_adapter_factory_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package flow @@ -18,6 +18,10 @@ type mockFactory struct { func (_m *mockFactory) AdapterPattern() *model.AdapterPattern { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for AdapterPattern") + } + var r0 *model.AdapterPattern if rf, ok := ret.Get(0).(func() *model.AdapterPattern); ok { r0 = rf() @@ -34,6 +38,10 @@ func (_m *mockFactory) AdapterPattern() *model.AdapterPattern { func (_m *mockFactory) Create(_a0 *model.Registry) (adapter.Adapter, error) { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 adapter.Adapter var r1 error if rf, ok := ret.Get(0).(func(*model.Registry) (adapter.Adapter, error)); ok { diff --git a/src/controller/replication/flow/mock_adapter_test.go b/src/controller/replication/flow/mock_adapter_test.go index f5fd791584..331b98e2ff 100644 --- a/src/controller/replication/flow/mock_adapter_test.go +++ b/src/controller/replication/flow/mock_adapter_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package flow @@ -21,6 +21,10 @@ type mockAdapter struct { func (_m *mockAdapter) BlobExist(repository string, digest string) (bool, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for BlobExist") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(string, string) (bool, error)); ok { @@ -45,6 +49,10 @@ func (_m *mockAdapter) BlobExist(repository string, digest string) (bool, error) func (_m *mockAdapter) CanBeMount(digest string) (bool, string, error) { ret := _m.Called(digest) + if len(ret) == 0 { + panic("no return value specified for CanBeMount") + } + var r0 bool var r1 string var r2 error @@ -76,6 +84,10 @@ func (_m *mockAdapter) CanBeMount(digest string) (bool, string, error) { func (_m *mockAdapter) DeleteManifest(repository string, reference string) error { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for DeleteManifest") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, reference) @@ -90,6 +102,10 @@ func (_m *mockAdapter) DeleteManifest(repository string, reference string) error func (_m *mockAdapter) DeleteTag(repository string, tag string) error { ret := _m.Called(repository, tag) + if len(ret) == 0 { + panic("no return value specified for DeleteTag") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, tag) @@ -104,6 +120,10 @@ func (_m *mockAdapter) DeleteTag(repository string, tag string) error { func (_m *mockAdapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resource, error) { ret := _m.Called(filters) + if len(ret) == 0 { + panic("no return value specified for FetchArtifacts") + } + var r0 []*model.Resource var r1 error if rf, ok := ret.Get(0).(func([]*model.Filter) ([]*model.Resource, error)); ok { @@ -130,6 +150,10 @@ func (_m *mockAdapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resourc func (_m *mockAdapter) HealthCheck() (string, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for HealthCheck") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func() (string, error)); ok { @@ -154,6 +178,10 @@ func (_m *mockAdapter) HealthCheck() (string, error) { func (_m *mockAdapter) Info() (*model.RegistryInfo, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Info") + } + var r0 *model.RegistryInfo var r1 error if rf, ok := ret.Get(0).(func() (*model.RegistryInfo, error)); ok { @@ -180,6 +208,10 @@ func (_m *mockAdapter) Info() (*model.RegistryInfo, error) { func (_m *mockAdapter) ListTags(repository string) ([]string, error) { ret := _m.Called(repository) + if len(ret) == 0 { + panic("no return value specified for ListTags") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { @@ -206,6 +238,10 @@ func (_m *mockAdapter) ListTags(repository string) ([]string, error) { func (_m *mockAdapter) ManifestExist(repository string, reference string) (bool, *distribution.Descriptor, error) { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for ManifestExist") + } + var r0 bool var r1 *distribution.Descriptor var r2 error @@ -239,6 +275,10 @@ func (_m *mockAdapter) ManifestExist(repository string, reference string) (bool, func (_m *mockAdapter) MountBlob(srcRepository string, digest string, dstRepository string) error { ret := _m.Called(srcRepository, digest, dstRepository) + if len(ret) == 0 { + panic("no return value specified for MountBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string) error); ok { r0 = rf(srcRepository, digest, dstRepository) @@ -253,6 +293,10 @@ func (_m *mockAdapter) MountBlob(srcRepository string, digest string, dstReposit func (_m *mockAdapter) PrepareForPush(_a0 []*model.Resource) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for PrepareForPush") + } + var r0 error if rf, ok := ret.Get(0).(func([]*model.Resource) error); ok { r0 = rf(_a0) @@ -267,6 +311,10 @@ func (_m *mockAdapter) PrepareForPush(_a0 []*model.Resource) error { func (_m *mockAdapter) PullBlob(repository string, digest string) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for PullBlob") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -300,6 +348,10 @@ func (_m *mockAdapter) PullBlob(repository string, digest string) (int64, io.Rea func (_m *mockAdapter) PullBlobChunk(repository string, digest string, blobSize int64, start int64, end int64) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest, blobSize, start, end) + if len(ret) == 0 { + panic("no return value specified for PullBlobChunk") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -340,6 +392,10 @@ func (_m *mockAdapter) PullManifest(repository string, reference string, acceptt _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for PullManifest") + } + var r0 distribution.Manifest var r1 string var r2 error @@ -373,6 +429,10 @@ func (_m *mockAdapter) PullManifest(repository string, reference string, acceptt func (_m *mockAdapter) PushBlob(repository string, digest string, size int64, blob io.Reader) error { ret := _m.Called(repository, digest, size, blob) + if len(ret) == 0 { + panic("no return value specified for PushBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, int64, io.Reader) error); ok { r0 = rf(repository, digest, size, blob) @@ -387,6 +447,10 @@ func (_m *mockAdapter) PushBlob(repository string, digest string, size int64, bl func (_m *mockAdapter) PushBlobChunk(repository string, digest string, size int64, chunk io.Reader, start int64, end int64, location string) (string, int64, error) { ret := _m.Called(repository, digest, size, chunk, start, end, location) + if len(ret) == 0 { + panic("no return value specified for PushBlobChunk") + } + var r0 string var r1 int64 var r2 error @@ -418,6 +482,10 @@ func (_m *mockAdapter) PushBlobChunk(repository string, digest string, size int6 func (_m *mockAdapter) PushManifest(repository string, reference string, mediaType string, payload []byte) (string, error) { ret := _m.Called(repository, reference, mediaType, payload) + if len(ret) == 0 { + panic("no return value specified for PushManifest") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(string, string, string, []byte) (string, error)); ok { diff --git a/src/controller/replication/mock_flow_controller_test.go b/src/controller/replication/mock_flow_controller_test.go index cd0728a5cd..a3d6ec854b 100644 --- a/src/controller/replication/mock_flow_controller_test.go +++ b/src/controller/replication/mock_flow_controller_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package replication @@ -21,6 +21,10 @@ type flowController struct { func (_m *flowController) Start(ctx context.Context, executionID int64, policy *model.Policy, resource *regmodel.Resource) error { ret := _m.Called(ctx, executionID, policy, resource) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, *model.Policy, *regmodel.Resource) error); ok { r0 = rf(ctx, executionID, policy, resource) diff --git a/src/controller/retention/controller.go b/src/controller/retention/controller.go index 4ec607a40c..179d8c151b 100644 --- a/src/controller/retention/controller.go +++ b/src/controller/retention/controller.go @@ -280,12 +280,8 @@ func (r *defaultController) TriggerRetentionExec(ctx context.Context, policyID i if num, err := r.launcher.Launch(ctx, p, id, dryRun); err != nil { logger.Errorf("failed to launch the retention jobs, err: %v", err) - if err = r.execMgr.StopAndWait(ctx, id, 10*time.Second); err != nil { - logger.Errorf("failed to stop the retention execution %d: %v", id, err) - } - - if err = r.execMgr.MarkError(ctx, id, err.Error()); err != nil { - logger.Errorf("failed to mark error for the retention execution %d: %v", id, err) + if e := r.execMgr.StopAndWaitWithError(ctx, id, 10*time.Second, err); e != nil { + logger.Errorf("failed to stop the retention execution %d: %v", id, e) } } else if num == 0 { // no candidates, mark the execution as done directly diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index d5e36737fa..14f5a29372 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -17,6 +17,7 @@ package scan import ( "bytes" "context" + "encoding/json" "fmt" "reflect" "strings" @@ -25,7 +26,6 @@ import ( "github.com/google/uuid" - "github.com/goharbor/harbor/src/common/rbac" ar "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/event/operator" "github.com/goharbor/harbor/src/controller/robot" @@ -49,6 +49,7 @@ import ( "github.com/goharbor/harbor/src/pkg/scan/postprocessors" "github.com/goharbor/harbor/src/pkg/scan/report" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model" "github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/task" ) @@ -68,10 +69,11 @@ const ( artfiactKey = "artifact" registrationKey = "registration" - artifactIDKey = "artifact_id" - artifactTagKey = "artifact_tag" - reportUUIDsKey = "report_uuids" - robotIDKey = "robot_id" + artifactIDKey = "artifact_id" + artifactTagKey = "artifact_tag" + reportUUIDsKey = "report_uuids" + robotIDKey = "robot_id" + enabledCapabilities = "enabled_capabilities" ) // uuidGenerator is a func template which is for generating UUID. @@ -91,6 +93,7 @@ type launchScanJobParam struct { Artifact *ar.Artifact Tag string Reports []*scan.Report + Type string } // basicController is default implementation of api.Controller interface @@ -193,6 +196,18 @@ func (bc *basicController) collectScanningArtifacts(ctx context.Context, r *scan return nil } + // because there are lots of in-toto sbom artifacts in dockerhub and replicated to Harbor, they are considered as image type + // when scanning these type of sbom artifact, the scanner might assume it is image layer with tgz format, and if scanner read the layer with a stream of tgz, + // it fail and close the stream abruptly and cause the pannic in the harbor core log + // to avoid pannic, skip scan the in-toto sbom artifact sbom artifact + unscannable, err := bc.ar.HasUnscannableLayer(ctx, a.Digest) + if err != nil { + return err + } + if unscannable { + return nil + } + supported := hasCapability(r, a) if !supported && a.IsImageIndex() { @@ -242,23 +257,26 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti if err != nil { return err } - - if !scannable { - return errors.BadRequestError(nil).WithMessage("the configured scanner %s does not support scanning artifact with mime type %s", r.Name, artifact.ManifestMediaType) - } - // Parse options opts, err := parseOptions(options...) if err != nil { return errors.Wrap(err, "scan controller: scan") } + if !scannable { + if opts.FromEvent { + // skip to return err for event related scan + return nil + } + return errors.BadRequestError(nil).WithMessage("the configured scanner %s does not support scanning artifact with mime type %s", r.Name, artifact.ManifestMediaType) + } + var ( errs []error launchScanJobParams []*launchScanJobParam ) for _, art := range artifacts { - reports, err := bc.makeReportPlaceholder(ctx, r, art) + reports, err := bc.makeReportPlaceholder(ctx, r, art, opts) if err != nil { if errors.IsConflictErr(err) { errs = append(errs, err) @@ -287,6 +305,7 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti Artifact: art, Tag: tag, Reports: reports, + Type: opts.GetScanType(), }) } } @@ -308,6 +327,9 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti "id": r.ID, "name": r.Name, }, + enabledCapabilities: map[string]interface{}{ + "type": opts.GetScanType(), + }, } if op := operator.FromContext(ctx); op != "" { extraAttrs["operator"] = op @@ -324,7 +346,7 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti for _, launchScanJobParam := range launchScanJobParams { launchScanJobParam.ExecutionID = opts.ExecutionID - if err := bc.launchScanJob(ctx, launchScanJobParam); err != nil { + if err := bc.launchScanJob(ctx, launchScanJobParam, opts); err != nil { log.G(ctx).Warningf("scan artifact %s@%s failed, error: %v", artifact.RepositoryName, artifact.Digest, err) errs = append(errs, err) } @@ -339,15 +361,16 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti } // Stop scan job of a given artifact -func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact) error { +func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact, capType string) error { if artifact == nil { return errors.New("nil artifact to stop scan") } - query := q.New(q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest}) + query := q.New(q.KeyWords{"vendor_type": job.ImageScanJobVendorType, "extra_attrs.artifact.digest": artifact.Digest, "extra_attrs.enabled_capabilities.type": capType}) executions, err := bc.execMgr.List(ctx, query) if err != nil { return err } + if len(executions) == 0 { message := fmt.Sprintf("no scan job for artifact digest=%v", artifact.Digest) return errors.BadRequestError(nil).WithMessage(message) @@ -379,7 +402,9 @@ func (bc *basicController) ScanAll(ctx context.Context, trigger string, async bo } err = bc.startScanAll(ctx, executionID) - log.Errorf("failed to start scan all, executionID=%d, error: %v", executionID, err) + if err != nil { + log.Errorf("failed to start scan all, executionID=%d, error: %v", executionID, err) + } }(bc.makeCtx()) } else { if err := bc.startScanAll(ctx, executionID); err != nil { @@ -541,13 +566,15 @@ func (bc *basicController) startScanAll(ctx context.Context, executionID int64) return nil } -func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner.Registration, art *ar.Artifact) ([]*scan.Report, error) { - mimeTypes := r.GetProducesMimeTypes(art.ManifestMediaType) - +func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner.Registration, art *ar.Artifact, opts *Options) ([]*scan.Report, error) { + mimeTypes := r.GetProducesMimeTypes(art.ManifestMediaType, opts.GetScanType()) oldReports, err := bc.manager.GetBy(bc.cloneCtx(ctx), art.Digest, r.UUID, mimeTypes) if err != nil { return nil, err } + if err := bc.deleteArtifactAccessories(ctx, oldReports); err != nil { + return nil, err + } if err := bc.assembleReports(ctx, oldReports...); err != nil { return nil, err @@ -569,7 +596,7 @@ func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner var reports []*scan.Report - for _, pm := range r.GetProducesMimeTypes(art.ManifestMediaType) { + for _, pm := range r.GetProducesMimeTypes(art.ManifestMediaType, opts.GetScanType()) { report := &scan.Report{ Digest: art.Digest, RegistrationUUID: r.UUID, @@ -670,12 +697,23 @@ func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact, return reports, nil } +func isSBOMMimeTypes(mimeTypes []string) bool { + for _, mimeType := range mimeTypes { + if mimeType == v1.MimeTypeSBOMReport { + return true + } + } + return false +} + // GetSummary ... func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) { if artifact == nil { return nil, errors.New("no way to get report summaries for nil artifact") } - + if isSBOMMimeTypes(mimeTypes) { + return bc.GetSBOMSummary(ctx, artifact, mimeTypes) + } // Get reports first rps, err := bc.GetReport(ctx, artifact, mimeTypes) if err != nil { @@ -704,6 +742,52 @@ func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact return summaries, nil } +func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) { + if art == nil { + return nil, errors.New("no way to get report summaries for nil artifact") + } + r, err := bc.sc.GetRegistrationByProject(ctx, art.ProjectID) + if err != nil { + return nil, errors.Wrap(err, "scan controller: get sbom summary") + } + reports, err := bc.manager.GetBy(ctx, art.Digest, r.UUID, mimeTypes) + if err != nil { + return nil, err + } + if len(reports) == 0 { + return map[string]interface{}{}, nil + } + reportContent := reports[0].Report + result := map[string]interface{}{} + if len(reportContent) == 0 { + status := bc.retrieveStatusFromTask(ctx, reports[0].UUID) + if len(status) > 0 { + result[sbomModel.ReportID] = reports[0].UUID + result[sbomModel.ScanStatus] = status + } + log.Debug("no content for current report") + return result, nil + } + err = json.Unmarshal([]byte(reportContent), &result) + return result, err +} + +// retrieve the status from task +func (bc *basicController) retrieveStatusFromTask(ctx context.Context, reportID string) string { + if len(reportID) == 0 { + return "" + } + tasks, err := bc.taskMgr.ListScanTasksByReportUUID(ctx, reportID) + if err != nil { + log.Warningf("can not find the task with report UUID %v, error %v", reportID, err) + return "" + } + if len(tasks) > 0 { + return tasks[0].Status + } + return "" +} + // GetScanLog ... func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact, uuid string) ([]byte, error) { if len(uuid) == 0 { @@ -910,7 +994,7 @@ func (bc *basicController) GetVulnerable(ctx context.Context, artifact *ar.Artif } // makeRobotAccount creates a robot account based on the arguments for scanning. -func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64, repository string, registration *scanner.Registration) (*robot.Robot, error) { +func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64, repository string, registration *scanner.Registration, permission []*types.Policy) (*robot.Robot, error) { // Use uuid as name to avoid duplicated entries. UUID, err := bc.uuid() if err != nil { @@ -932,16 +1016,7 @@ func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64 { Kind: "project", Namespace: projectName, - Access: []*types.Policy{ - { - Resource: rbac.ResourceRepository, - Action: rbac.ActionPull, - }, - { - Resource: rbac.ResourceRepository, - Action: rbac.ActionScannerPull, - }, - }, + Access: permission, }, }, } @@ -960,7 +1035,7 @@ func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64 } // launchScanJob launches a job to run scan -func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJobParam) error { +func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJobParam, opts *Options) error { // don't launch scan job for the artifact which is not supported by the scanner if !hasCapability(param.Registration, param.Artifact) { return nil @@ -978,7 +1053,12 @@ func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJ return errors.Wrap(err, "scan controller: launch scan job") } - robot, err := bc.makeRobotAccount(ctx, param.Artifact.ProjectID, param.Artifact.RepositoryName, param.Registration) + // Get Scanner handler by scan type to separate the scan logic for different scan types + handler := sca.GetScanHandler(param.Type) + if handler == nil { + return fmt.Errorf("failed to get scan handler, type is %v", param.Type) + } + robot, err := bc.makeRobotAccount(ctx, param.Artifact.ProjectID, param.Artifact.RepositoryName, param.Registration, handler.RequiredPermissions()) if err != nil { return errors.Wrap(err, "scan controller: launch scan job") } @@ -994,6 +1074,12 @@ func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJ Digest: param.Artifact.Digest, Tag: param.Tag, MimeType: param.Artifact.ManifestMediaType, + Size: param.Artifact.Size, + }, + RequestType: []*v1.ScanType{ + { + Type: opts.GetScanType(), + }, }, } @@ -1228,3 +1314,48 @@ func parseOptions(options ...Option) (*Options, error) { return ops, nil } + +// deleteArtifactAccessories delete the accessory in reports, only delete sbom accessory +func (bc *basicController) deleteArtifactAccessories(ctx context.Context, reports []*scan.Report) error { + for _, rpt := range reports { + if rpt.MimeType != v1.MimeTypeSBOMReport { + continue + } + if err := bc.deleteArtifactAccessory(ctx, rpt.Report); err != nil { + return err + } + } + return nil +} + +// deleteArtifactAccessory check if current report has accessory info, if there is, delete it +func (bc *basicController) deleteArtifactAccessory(ctx context.Context, report string) error { + if len(report) == 0 { + return nil + } + sbomSummary := sbomModel.Summary{} + if err := json.Unmarshal([]byte(report), &sbomSummary); err != nil { + // it could be a non sbom report, just skip + log.Debugf("fail to unmarshal %v, skip to delete sbom report", err) + return nil + } + repo, dgst := sbomSummary.SBOMAccArt() + if len(repo) == 0 || len(dgst) == 0 { + return nil + } + art, err := bc.ar.GetByReference(ctx, repo, dgst, nil) + if err != nil { + if errors.IsNotFoundErr(err) { + return nil + } + return err + } + if art == nil { + return nil + } + err = bc.ar.Delete(ctx, art.ID) + if errors.IsNotFoundErr(err) { + return nil + } + return err +} diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 2858b7090d..1cd464891d 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -45,6 +45,7 @@ import ( "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/scan/vuln" + _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" "github.com/goharbor/harbor/src/pkg/task" artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact" robottesting "github.com/goharbor/harbor/src/testing/controller/robot" @@ -69,15 +70,16 @@ type ControllerTestSuite struct { tagCtl *tagtesting.FakeController - registration *scanner.Registration - artifact *artifact.Artifact - rawReport string + registration *scanner.Registration + artifact *artifact.Artifact + wrongArtifact *artifact.Artifact + rawReport string execMgr *tasktesting.ExecutionManager taskMgr *tasktesting.Manager reportMgr *reporttesting.Manager ar artifact.Controller - c Controller + c *basicController reportConverter *postprocessorstesting.ScanReportV1ToV2Converter cache *mockcache.Cache } @@ -100,6 +102,9 @@ func (suite *ControllerTestSuite) SetupSuite() { suite.artifact.Digest = "digest-code" suite.artifact.ManifestMediaType = v1.MimeTypeDockerArtifact + suite.wrongArtifact = &artifact.Artifact{Artifact: art.Artifact{ID: 2, ProjectID: 1}} + suite.wrongArtifact.Digest = "digest-wrong" + m := &v1.ScannerAdapterMetadata{ Scanner: &v1.Scanner{ Name: "Trivy", @@ -107,6 +112,7 @@ func (suite *ControllerTestSuite) SetupSuite() { Version: "0.1.0", }, Capabilities: []*v1.ScannerCapability{{ + Type: v1.ScanTypeVulnerability, ConsumesMimeTypes: []string{ v1.MimeTypeOCIArtifact, v1.MimeTypeDockerArtifact, @@ -114,7 +120,17 @@ func (suite *ControllerTestSuite) SetupSuite() { ProducesMimeTypes: []string{ v1.MimeTypeNativeReport, }, - }}, + }, + { + Type: v1.ScanTypeSbom, + ConsumesMimeTypes: []string{ + v1.MimeTypeOCIArtifact, + }, + ProducesMimeTypes: []string{ + v1.MimeTypeSBOMReport, + }, + }, + }, Properties: v1.ScannerProperties{ "extra": "testing", }, @@ -179,7 +195,22 @@ func (suite *ControllerTestSuite) SetupSuite() { }, } + sbomReport := []*scan.Report{ + { + ID: 12, + UUID: "rp-uuid-002", + Digest: "digest-code", + RegistrationUUID: "uuid001", + MimeType: "application/vnd.scanner.adapter.sbom.report.harbor+json; version=1.0", + Status: "Success", + Report: `{"sbom_digest": "sha256:1234567890", "scan_status": "Success", "duration": 3, "start_time": "2021-09-01T00:00:00Z", "end_time": "2021-09-01T00:00:03Z"}`, + }, + } + + emptySBOMReport := []*scan.Report{{Report: ``, UUID: "rp-uuid-004"}} mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeNativeReport}).Return(reports, nil) + mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(sbomReport, nil) + mgr.On("GetBy", mock.Anything, suite.wrongArtifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(emptySBOMReport, nil) mgr.On("Get", mock.Anything, "rp-uuid-001").Return(reports[0], nil) mgr.On("UpdateReportData", "rp-uuid-001", suite.rawReport, (int64)(10000)).Return(nil) mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil) @@ -319,6 +350,7 @@ func (suite *ControllerTestSuite) TestScanControllerScan() { { // artifact not provieded suite.Require().Error(suite.c.Scan(context.TODO(), nil)) + mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Times(3) } { @@ -380,7 +412,7 @@ func (suite *ControllerTestSuite) TestScanControllerScan() { func (suite *ControllerTestSuite) TestScanControllerStop() { { // artifact not provieded - suite.Require().Error(suite.c.Stop(context.TODO(), nil)) + suite.Require().Error(suite.c.Stop(context.TODO(), nil, "vulnerability")) } { @@ -392,7 +424,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) - suite.Require().NoError(suite.c.Stop(ctx, suite.artifact)) + suite.Require().NoError(suite.c.Stop(ctx, suite.artifact, "vulnerability")) } { @@ -402,7 +434,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) - suite.Require().Error(suite.c.Stop(ctx, suite.artifact)) + suite.Require().Error(suite.c.Stop(ctx, suite.artifact, "vulnerability")) } { @@ -411,12 +443,13 @@ func (suite *ControllerTestSuite) TestScanControllerStop() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) - suite.Require().Error(suite.c.Stop(ctx, suite.artifact)) + suite.Require().Error(suite.c.Stop(ctx, suite.artifact, "vulnerability")) } } // TestScanControllerGetReport ... func (suite *ControllerTestSuite) TestScanControllerGetReport() { + mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once() ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) { walkFn := args.Get(2).(func(*artifact.Artifact) error) @@ -435,13 +468,13 @@ func (suite *ControllerTestSuite) TestScanControllerGetReport() { // TestScanControllerGetSummary ... func (suite *ControllerTestSuite) TestScanControllerGetSummary() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once() mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once() mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) { walkFn := args.Get(2).(func(*artifact.Artifact) error) walkFn(suite.artifact) }).Once() mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return(nil, nil).Once() - sum, err := suite.c.GetSummary(ctx, suite.artifact, []string{v1.MimeTypeNativeReport}) require.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(sum)) @@ -449,6 +482,7 @@ func (suite *ControllerTestSuite) TestScanControllerGetSummary() { // TestScanControllerGetScanLog ... func (suite *ControllerTestSuite) TestScanControllerGetScanLog() { + mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once() ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return([]*task.Task{ { @@ -469,6 +503,7 @@ func (suite *ControllerTestSuite) TestScanControllerGetScanLog() { func (suite *ControllerTestSuite) TestScanControllerGetMultiScanLog() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Times(4) suite.taskMgr.On("ListScanTasksByReportUUID", ctx, "rp-uuid-001").Return([]*task.Task{ { ID: 1, @@ -531,7 +566,7 @@ func (suite *ControllerTestSuite) TestScanAll() { { // no artifacts found when scan all executionID := int64(1) - + mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once() suite.execMgr.On( "Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE", mock.Anything).Return(executionID, nil).Once() @@ -619,3 +654,57 @@ func (suite *ControllerTestSuite) makeExtraAttrs(artifactID int64, reportUUIDs . return extraAttrs } + +func (suite *ControllerTestSuite) TestGenerateSBOMSummary() { + sum, err := suite.c.GetSBOMSummary(context.TODO(), suite.artifact, []string{v1.MimeTypeSBOMReport}) + suite.Nil(err) + suite.NotNil(sum) + status := sum["scan_status"] + suite.NotNil(status) + dgst := sum["sbom_digest"] + suite.NotNil(dgst) + suite.Equal("Success", status) + suite.Equal("sha256:1234567890", dgst) + tasks := []*task.Task{{Status: "Error"}} + suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once() + sum2, err := suite.c.GetSummary(context.TODO(), suite.wrongArtifact, []string{v1.MimeTypeSBOMReport}) + suite.Nil(err) + suite.NotNil(sum2) + +} + +func TestIsSBOMMimeTypes(t *testing.T) { + // Test with a slice containing the SBOM mime type + assert.True(t, isSBOMMimeTypes([]string{v1.MimeTypeSBOMReport})) + + // Test with a slice not containing the SBOM mime type + assert.False(t, isSBOMMimeTypes([]string{"application/vnd.oci.image.manifest.v1+json"})) + + // Test with an empty slice + assert.False(t, isSBOMMimeTypes([]string{})) +} + +func (suite *ControllerTestSuite) TestDeleteArtifactAccessories() { + // artifact not provided + suite.Nil(suite.c.deleteArtifactAccessories(context.TODO(), nil)) + + // artifact is provided + art := &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1, RepositoryName: "library/photon"}} + mock.OnAnything(suite.ar, "GetByReference").Return(art, nil).Once() + mock.OnAnything(suite.ar, "Delete").Return(nil).Once() + reportContent := `{"sbom_digest":"sha256:12345", "scan_status":"Success", "duration":3, "sbom_repository":"library/photon"}` + emptyReportContent := `` + reports := []*scan.Report{ + {Report: reportContent}, + {Report: emptyReportContent}, + } + ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + suite.NoError(suite.c.deleteArtifactAccessories(ctx, reports)) +} + +func (suite *ControllerTestSuite) TestRetrieveStatusFromTask() { + tasks := []*task.Task{{Status: "Error"}} + suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once() + status := suite.c.retrieveStatusFromTask(nil, "rp-uuid-004") + suite.Equal("Error", status) +} diff --git a/src/controller/scan/callback.go b/src/controller/scan/callback.go index 978219f261..5229ca0b18 100644 --- a/src/controller/scan/callback.go +++ b/src/controller/scan/callback.go @@ -120,6 +120,13 @@ func scanTaskStatusChange(ctx context.Context, taskID int64, status string) (err if operator, ok := exec.ExtraAttrs["operator"].(string); ok { e.Operator = operator } + + // extract ScanType if exist in ExtraAttrs + if c, ok := exec.ExtraAttrs["enabled_capabilities"].(map[string]interface{}); ok { + if Type, ok := c["type"].(string); ok { + e.ScanType = Type + } + } // fire event notification.AddEvent(ctx, e) } diff --git a/src/controller/scan/checker.go b/src/controller/scan/checker.go index a4d0e3ba34..4b429b2bc3 100644 --- a/src/controller/scan/checker.go +++ b/src/controller/scan/checker.go @@ -86,6 +86,18 @@ func (c *checker) IsScannable(ctx context.Context, art *artifact.Artifact) (bool return artifact.ErrBreak } + // because there are lots of in-toto sbom artifacts in dockerhub and replicated to Harbor, they are considered as image type + // when scanning these type of sbom artifact, the scanner might assume it is image layer with tgz format, and if scanner read the layer with a stream of tgz, + // it fail and close the stream abruptly and cause the pannic in the harbor core log + // to avoid pannic, skip scan the in-toto sbom artifact sbom artifact + unscannable, err := c.artifactCtl.HasUnscannableLayer(ctx, a.Digest) + if err != nil { + return err + } + if unscannable { + return nil + } + return nil } diff --git a/src/controller/scan/checker_test.go b/src/controller/scan/checker_test.go index e96cb1e511..716e574be0 100644 --- a/src/controller/scan/checker_test.go +++ b/src/controller/scan/checker_test.go @@ -81,7 +81,7 @@ func (suite *CheckerTestSuite) TestIsScannable() { walkFn := args.Get(2).(func(*artifact.Artifact) error) walkFn(art) }) - + mock.OnAnything(c.artifactCtl, "HasUnscannableLayer").Return(false, nil).Once() isScannable, err := c.IsScannable(context.TODO(), art) suite.Nil(err) suite.False(isScannable) @@ -97,6 +97,7 @@ func (suite *CheckerTestSuite) TestIsScannable() { walkFn := args.Get(2).(func(*artifact.Artifact) error) walkFn(art) }) + mock.OnAnything(c.artifactCtl, "HasUnscannableLayer").Return(false, nil).Once() isScannable, err := c.IsScannable(context.TODO(), art) suite.Nil(err) diff --git a/src/controller/scan/controller.go b/src/controller/scan/controller.go index b890ff7d3f..625e8f86f6 100644 --- a/src/controller/scan/controller.go +++ b/src/controller/scan/controller.go @@ -55,10 +55,11 @@ type Controller interface { // Arguments: // ctx context.Context : the context for this method // artifact *artifact.Artifact : the artifact whose scan job to be stopped + // capType string : the capability type of the scanner, vulnerability or SBOM. // // Returns: // error : non nil error if any errors occurred - Stop(ctx context.Context, artifact *artifact.Artifact) error + Stop(ctx context.Context, artifact *artifact.Artifact, capType string) error // GetReport gets the reports for the given artifact identified by the digest // diff --git a/src/controller/scan/options.go b/src/controller/scan/options.go index a876448339..c751ee1005 100644 --- a/src/controller/scan/options.go +++ b/src/controller/scan/options.go @@ -14,10 +14,22 @@ package scan +import v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + // Options keep the settings/configurations for scanning. type Options struct { ExecutionID int64 // The execution id to scan artifact Tag string // The tag of the artifact to scan + ScanType string // The scan type could be sbom or vulnerability + FromEvent bool // indicate the current call from event or not +} + +// GetScanType returns the scan type. for backward compatibility, the default type is vulnerability. +func (o *Options) GetScanType() string { + if len(o.ScanType) == 0 { + o.ScanType = v1.ScanTypeVulnerability + } + return o.ScanType } // Option represents an option item by func template. @@ -44,3 +56,19 @@ func WithTag(tag string) Option { return nil } } + +// WithScanType set the scanType +func WithScanType(scanType string) Option { + return func(options *Options) error { + options.ScanType = scanType + return nil + } +} + +// WithFromEvent set the caller's source +func WithFromEvent(fromEvent bool) Option { + return func(options *Options) error { + options.FromEvent = fromEvent + return nil + } +} diff --git a/src/controller/scanner/base_controller.go b/src/controller/scanner/base_controller.go index 2028da3555..d05878288c 100644 --- a/src/controller/scanner/base_controller.go +++ b/src/controller/scanner/base_controller.go @@ -37,6 +37,8 @@ const ( proScannerMetaKey = "projectScanner" statusUnhealthy = "unhealthy" statusHealthy = "healthy" + // RetrieveCapFailMsg the message indicate failed to retrieve the scanner capabilities + RetrieveCapFailMsg = "failed to retrieve scanner capabilities, error %v" ) // DefaultController is a singleton api controller for plug scanners @@ -79,7 +81,12 @@ func (bc *basicController) ListRegistrations(ctx context.Context, query *q.Query if err != nil { return nil, errors.Wrap(err, "api controller: list registrations") } - + for _, r := range l { + if err := bc.RetrieveCap(ctx, r); err != nil { + log.Warningf(RetrieveCapFailMsg, err) + return l, nil + } + } return l, nil } @@ -122,10 +129,26 @@ func (bc *basicController) GetRegistration(ctx context.Context, registrationUUID if err != nil { return nil, errors.Wrap(err, "api controller: get registration") } - + if r == nil { + return nil, nil + } + if err := bc.RetrieveCap(ctx, r); err != nil { + log.Warningf(RetrieveCapFailMsg, err) + return r, nil + } return r, nil } +func (bc *basicController) RetrieveCap(ctx context.Context, r *scanner.Registration) error { + mt, err := bc.Ping(ctx, r) + if err != nil { + logger.Errorf("Get registration error: %s", err) + return err + } + r.Capabilities = mt.ConvertCapability() + return nil +} + // RegistrationExists ... func (bc *basicController) RegistrationExists(ctx context.Context, registrationUUID string) bool { registration, err := bc.manager.Get(ctx, registrationUUID) diff --git a/src/controller/scanner/controller.go b/src/controller/scanner/controller.go index 9df9a6d363..d9558d3e4f 100644 --- a/src/controller/scanner/controller.go +++ b/src/controller/scanner/controller.go @@ -113,7 +113,7 @@ type Controller interface { // Arguments: // ctx context.Context : the context.Context for this method // projectID int64 : the ID of the given project - // scannerID string : the UUID of the the scanner + // scannerID string : the UUID of the scanner // // Returns: // error : non nil error if any errors occurred @@ -154,4 +154,7 @@ type Controller interface { // *v1.ScannerAdapterMetadata : metadata returned by the scanner if successfully ping // error : non nil error if any errors occurred GetMetadata(ctx context.Context, registrationUUID string) (*v1.ScannerAdapterMetadata, error) + + // RetrieveCap retrieve scanner capabilities + RetrieveCap(ctx context.Context, r *scanner.Registration) error } diff --git a/src/controller/systemartifact/execution.go b/src/controller/systemartifact/execution.go index c9f6c896d7..7a575e076c 100644 --- a/src/controller/systemartifact/execution.go +++ b/src/controller/systemartifact/execution.go @@ -119,11 +119,8 @@ func (c *controller) createCleanupTask(ctx context.Context, jobParams job.Parame func (c *controller) markError(ctx context.Context, executionID int64, err error) { // try to stop the execution first in case that some tasks are already created - if err := c.execMgr.StopAndWait(ctx, executionID, 10*time.Second); err != nil { - log.Errorf("failed to stop the execution %d: %v", executionID, err) - } - if err := c.execMgr.MarkError(ctx, executionID, err.Error()); err != nil { - log.Errorf("failed to mark error for the execution %d: %v", executionID, err) + if e := c.execMgr.StopAndWaitWithError(ctx, executionID, 10*time.Second, err); e != nil { + log.Errorf("failed to stop the execution %d: %v", executionID, e) } } diff --git a/src/controller/systemartifact/execution_test.go b/src/controller/systemartifact/execution_test.go index 68ba211919..7656fae376 100644 --- a/src/controller/systemartifact/execution_test.go +++ b/src/controller/systemartifact/execution_test.go @@ -117,7 +117,7 @@ func (suite *SystemArtifactCleanupTestSuite) TestStartCleanupErrorDuringTaskCrea suite.taskMgr.On("Create", ctx, executionID, mock.Anything).Return(taskId, errors.New("test error")).Once() suite.execMgr.On("MarkError", ctx, executionID, mock.Anything).Return(nil).Once() - suite.execMgr.On("StopAndWait", ctx, executionID, mock.Anything).Return(nil).Once() + suite.execMgr.On("StopAndWaitWithError", ctx, executionID, mock.Anything, mock.Anything).Return(nil).Once() err := suite.ctl.Start(ctx, false, "SCHEDULE") suite.Error(err) diff --git a/src/core/controllers/oidc.go b/src/core/controllers/oidc.go index 9987867845..295acb2cf8 100644 --- a/src/core/controllers/oidc.go +++ b/src/core/controllers/oidc.go @@ -63,7 +63,13 @@ func (oc *OIDCController) RedirectLogin() { oc.SendInternalServerError(err) return } - if err := oc.SetSession(redirectURLKey, oc.Ctx.Request.URL.Query().Get("redirect_url")); err != nil { + redirectURL := oc.Ctx.Request.URL.Query().Get("redirect_url") + if !utils.IsLocalPath(redirectURL) { + log.Errorf("invalid redirect url: %v", redirectURL) + oc.SendBadRequestError(fmt.Errorf("cannot redirect to other site")) + return + } + if err := oc.SetSession(redirectURLKey, redirectURL); err != nil { log.Errorf("failed to set session for key: %s, error: %v", redirectURLKey, err) oc.SendInternalServerError(err) return diff --git a/src/core/main.go b/src/core/main.go index b660ea012b..f0bc965645 100644 --- a/src/core/main.go +++ b/src/core/main.go @@ -60,6 +60,7 @@ import ( _ "github.com/goharbor/harbor/src/pkg/accessory/model/cosign" _ "github.com/goharbor/harbor/src/pkg/accessory/model/notation" _ "github.com/goharbor/harbor/src/pkg/accessory/model/nydus" + _ "github.com/goharbor/harbor/src/pkg/accessory/model/sbom" _ "github.com/goharbor/harbor/src/pkg/accessory/model/subject" "github.com/goharbor/harbor/src/pkg/audit" dbCfg "github.com/goharbor/harbor/src/pkg/config/db" @@ -69,6 +70,8 @@ import ( "github.com/goharbor/harbor/src/pkg/oidc" "github.com/goharbor/harbor/src/pkg/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + _ "github.com/goharbor/harbor/src/pkg/scan/sbom" + _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" pkguser "github.com/goharbor/harbor/src/pkg/user" "github.com/goharbor/harbor/src/pkg/version" "github.com/goharbor/harbor/src/server" @@ -102,14 +105,14 @@ func gracefulShutdown(closing, done chan struct{}, shutdowns ...func()) { signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) log.Infof("capture system signal %s, to close \"closing\" channel", <-signals) close(closing) - shutdownChan := make(chan struct{}, 1) + shutdownChan := make(chan struct{}) go func() { + defer close(shutdownChan) for _, s := range shutdowns { s() } <-done log.Infof("Goroutines exited normally") - shutdownChan <- struct{}{} }() select { case <-shutdownChan: diff --git a/src/core/middlewares/middlewares.go b/src/core/middlewares/middlewares.go index 0ae29bb02a..555ca56748 100644 --- a/src/core/middlewares/middlewares.go +++ b/src/core/middlewares/middlewares.go @@ -55,6 +55,7 @@ var ( dbTxSkippers = []middleware.Skipper{ middleware.MethodAndPathSkipper(http.MethodPatch, distribution.BlobUploadURLRegexp), middleware.MethodAndPathSkipper(http.MethodPut, distribution.BlobUploadURLRegexp), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/token")), func(r *http.Request) bool { // skip tx for GET, HEAD and Options requests m := r.Method return m == http.MethodGet || m == http.MethodHead || m == http.MethodOptions diff --git a/src/core/service/token/authutils.go b/src/core/service/token/authutils.go index 2597270896..da392b19b3 100644 --- a/src/core/service/token/authutils.go +++ b/src/core/service/token/authutils.go @@ -22,7 +22,7 @@ import ( "github.com/docker/distribution/registry/auth/token" "github.com/docker/libtrust" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/security" diff --git a/src/core/service/token/token_test.go b/src/core/service/token/token_test.go index 6525df9c61..f0881904e4 100644 --- a/src/core/service/token/token_test.go +++ b/src/core/service/token/token_test.go @@ -27,7 +27,7 @@ import ( "testing" "github.com/docker/distribution/registry/auth/token" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/goharbor/harbor/src/common/rbac" diff --git a/src/go.mod b/src/go.mod index dd2f7adb71..6fbefa1f02 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,83 +1,85 @@ module github.com/goharbor/harbor/src -go 1.21 +go 1.22.3 require ( github.com/FZambia/sentinel v1.1.0 github.com/Masterminds/semver v1.5.0 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 - github.com/aws/aws-sdk-go v1.34.28 + github.com/aws/aws-sdk-go v1.50.24 github.com/beego/beego/v2 v2.0.6 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.2.1 - github.com/cloudevents/sdk-go/v2 v2.14.0 - github.com/coreos/go-oidc/v3 v3.9.0 + github.com/cenkalti/backoff/v4 v4.3.0 + github.com/cloudevents/sdk-go/v2 v2.15.2 + github.com/coreos/go-oidc/v3 v3.10.0 github.com/dghubble/sling v1.1.0 github.com/docker/distribution v2.8.2+incompatible github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 - github.com/go-asn1-ber/asn1-ber v1.5.1 - github.com/go-ldap/ldap/v3 v3.2.4 - github.com/go-openapi/errors v0.20.4 + github.com/go-asn1-ber/asn1-ber v1.5.6 + github.com/go-ldap/ldap/v3 v3.4.6 + github.com/go-openapi/errors v0.22.0 github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/runtime v0.26.2 github.com/go-openapi/spec v0.20.11 // indirect - github.com/go-openapi/strfmt v0.21.8 + github.com/go-openapi/strfmt v0.23.0 github.com/go-openapi/swag v0.22.7 github.com/go-openapi/validate v0.22.3 // indirect github.com/go-redis/redis/v8 v8.11.4 github.com/gocarina/gocsv v0.0.0-20210516172204-ca9e8a8ddea8 github.com/gocraft/work v0.5.1 - github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/golang-migrate/migrate/v4 v4.16.2 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/golang-migrate/migrate/v4 v4.17.1 github.com/gomodule/redigo v2.0.0+incompatible - github.com/google/uuid v1.3.1 - github.com/gorilla/csrf v1.6.2 + github.com/google/go-containerregistry v0.19.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/csrf v1.7.2 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 github.com/graph-gophers/dataloader v5.0.0+incompatible - github.com/jackc/pgconn v1.14.0 - github.com/jackc/pgx/v4 v4.18.1 + github.com/jackc/pgconn v1.14.3 + github.com/jackc/pgx/v4 v4.18.3 github.com/jpillora/backoff v1.0.0 github.com/ncw/swift v1.0.49 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b + github.com/opencontainers/image-spec v1.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/robfig/cron/v3 v3.0.1 github.com/spf13/viper v1.8.1 - github.com/stretchr/testify v1.8.4 - github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 + github.com/stretchr/testify v1.9.0 + github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible github.com/vmihailenco/msgpack/v5 v5.4.1 - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46.1 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 - go.opentelemetry.io/otel v1.21.0 + github.com/volcengine/volcengine-go-sdk v1.0.97 + go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.51.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 + go.opentelemetry.io/otel v1.26.0 go.opentelemetry.io/otel/exporters/jaeger v1.0.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 - go.opentelemetry.io/otel/sdk v1.21.0 - go.opentelemetry.io/otel/trace v1.21.0 - go.uber.org/ratelimit v0.2.0 - golang.org/x/crypto v0.17.0 - golang.org/x/net v0.17.0 - golang.org/x/oauth2 v0.13.0 - golang.org/x/sync v0.3.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 + go.opentelemetry.io/otel/sdk v1.26.0 + go.opentelemetry.io/otel/trace v1.26.0 + go.uber.org/ratelimit v0.3.1 + golang.org/x/crypto v0.22.0 + golang.org/x/net v0.24.0 + golang.org/x/oauth2 v0.19.0 + golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 golang.org/x/time v0.5.0 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.11.3 - k8s.io/api v0.29.0 - k8s.io/apimachinery v0.29.0 + helm.sh/helm/v3 v3.14.4 + k8s.io/api v0.30.0 + k8s.io/apimachinery v0.30.0 k8s.io/client-go v0.29.0 sigs.k8s.io/yaml v1.4.0 ) require ( - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/azure-sdk-for-go v37.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -87,32 +89,37 @@ require ( github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d // indirect - github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denverdino/aliyungo v0.0.0-20191227032621-df38c6fa730c // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dnaeon/go-vcr v1.2.0 // indirect + github.com/docker/cli v24.0.6+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -122,16 +129,19 @@ require ( github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -145,34 +155,35 @@ require ( github.com/robfig/cron v1.0.0 // indirect github.com/satori/go.uuid v1.2.0 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect - github.com/sirupsen/logrus v1.9.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.2.0 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + github.com/volcengine/volc-sdk-golang v1.0.23 // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect - google.golang.org/api v0.126.0 // indirect - google.golang.org/appengine v1.6.8 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect + google.golang.org/api v0.162.0 // indirect google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/src/go.sum b/src/go.sum index e9c9e8e69d..723ed50c22 100644 --- a/src/go.sum +++ b/src/go.sum @@ -5,8 +5,8 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -43,9 +43,10 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 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/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8= github.com/FZambia/sentinel v1.1.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= @@ -54,19 +55,19 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d h1:RjxaKUAINjr+fYbaYjpdBUZc8R3+wF/Yr2XkDHho4Sg= github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 h1:bNE5ID4C3YOkROfvBjXJUG53gyb+8az3TQN02LqnGBk= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= -github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -74,15 +75,17 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= -github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +github.com/aws/aws-sdk-go v1.50.24 h1:3o2Pg7mOoVL0jv54vWtuafoZqAeEXLhm1tltWA2GcEw= +github.com/aws/aws-sdk-go v1.50.24/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beego/beego/v2 v2.0.6 h1:21Aqz3+RzUE1yP9a5xdU6LK54n9Z7NLEJtR4PE7NrPQ= github.com/beego/beego/v2 v2.0.6/go.mod h1:CH2/JIaB4ceGYVQlYqTAFft4pVk/ol1ZkakUrUvAyns= 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.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -93,25 +96,28 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM= github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudevents/sdk-go/v2 v2.14.0 h1:Nrob4FwVgi5L4tV9lhjzZcjYqFVyJzsA56CwPaPfv6s= -github.com/cloudevents/sdk-go/v2 v2.14.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To= +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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 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/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= -github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= +github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -124,14 +130,18 @@ github.com/dghubble/sling v1.1.0/go.mod h1:ZcPRuLm0qrcULW2gOrjXrAWgf76sahqSyxXyV github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= -github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= +github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= +github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= github.com/distribution/distribution v2.8.2+incompatible h1:k9+4DKdOG+quPFZXT/mUsiQrGu9vYCp+dXpuPkuqhk8= github.com/distribution/distribution v2.8.2+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -158,28 +168,29 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= -github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.6 h1:CYsqysemXfEaQbyrLJmdsCRuufHoLa3P/gGWGl5TDrM= +github.com/go-asn1-ber/asn1-ber v1.5.6/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap/v3 v3.2.4 h1:PFavAq2xTgzo/loE8qNXcQaofAaqIpI4WgaLdv+1l3E= -github.com/go-ldap/ldap/v3 v3.2.4/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M= -github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -196,8 +207,8 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6 github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY= github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= -github.com/go-openapi/strfmt v0.21.8 h1:VYBUoKYRLAlgKDrIxR/I0lKrztDQ0tuTDrbhLVP8Erg= -github.com/go-openapi/strfmt v0.21.8/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= +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.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -208,7 +219,6 @@ github.com/go-openapi/validate v0.22.3 h1:KxG9mu5HBRYbecRb37KRCihvGGtND2aXziBAv0 github.com/go-openapi/validate v0.22.3/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -226,13 +236,13 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= -github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -246,10 +256,11 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= @@ -268,6 +279,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= +github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -278,24 +291,26 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/csrf v1.6.2 h1:QqQ/OWwuFp4jMKgBFAzJVW3FMULdyUW7JoM4pEWuqKg= -github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= +github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= +github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -336,8 +351,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= @@ -355,8 +370,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= @@ -370,12 +385,11 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -398,11 +412,14 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -415,8 +432,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +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.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -441,6 +458,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -487,12 +506,12 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= +github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -536,6 +555,7 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -549,8 +569,9 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -571,8 +592,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -582,24 +604,29 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 h1:Vnr3IqaafEuQUciG6D6EaeLJm26Mg8sjAfbI4OoeauM= -github.com/tencentcloud/tencentcloud-sdk-go v1.0.62/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible h1:q+D/Y9jla3afgsIihtyhwyl0c2W+eRWNM9ohVwPiiPw= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 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.0.97 h1:JykYagPlleFuFIrk90uigS1UyIZPRIYX6TnC6FErWP4= +github.com/volcengine/volcengine-go-sdk v1.0.97/go.mod h1:oht5AKDJsk0fY6tV2ViqaVlOO14KSRmXZlI8ikK60Tg= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -610,32 +637,32 @@ go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQc go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= -go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46.1 h1:Ifzy1lucGMQJh6wPRxusde8bWaDhYjSNOqDyn6Hb4TM= -go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46.1/go.mod h1:YfFNem80G9UZ/mL5zd5GGXZSy95eXK+RhzIWBkLjLSc= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.51.0 h1:rXpHmgy1pMXlfv3W1T5ctoDA3QeTFjNq/YwCmwrfr8Q= +go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.51.0/go.mod h1:9uIRD3NZrM7QMQEGeKhr7V4xSDTMku3MPOVs8iZ3VVk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/exporters/jaeger v1.0.0 h1:cLhx8llHw02h5JTqGqaRbYn+QVKHmrzD9vEbKnSPk5U= go.opentelemetry.io/otel/exporters/jaeger v1.0.0/go.mod h1:q10N1AolE1JjqKrFJK2tYw0iZpmX+HBaXBtuCzRnBGQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -649,8 +676,8 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= -go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -665,9 +692,7 @@ golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -675,9 +700,9 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -702,8 +727,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -719,7 +745,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -730,13 +755,14 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -746,8 +772,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -784,15 +811,20 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= 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= @@ -800,8 +832,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -835,8 +868,9 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= 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= @@ -848,8 +882,6 @@ google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 h1:Cpp2P6TPjujNoC5M2KHY6g7wfyLYfIWRZaSdIKfDasA= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -861,12 +893,12 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -875,8 +907,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -888,11 +920,12 @@ 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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -922,22 +955,24 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.11.3 h1:n1X5yaQTP5DYywlBOZMl2gX398Gp6YwFp/IAVj6+5D4= -helm.sh/helm/v3 v3.11.3/go.mod h1:S+sOdQc3BLvt09a9rSlKKVs9x0N/yx+No0y3qFw+FQ8= +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.14.4 h1:6FSpEfqyDalHq3kUr4gOMThhgY55kXUEjdQoyODYnrM= +helm.sh/helm/v3 v3.14.4/go.mod h1:Tje7LL4gprZpuBNTbG34d1Xn5NmRT3OWfBRwpOSer9I= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/src/jobservice/job/impl/replication/replication.go b/src/jobservice/job/impl/replication/replication.go index 740ea37973..453fa5e032 100644 --- a/src/jobservice/job/impl/replication/replication.go +++ b/src/jobservice/job/impl/replication/replication.go @@ -51,6 +51,8 @@ import ( _ "github.com/goharbor/harbor/src/pkg/reg/adapter/quay" // import tencentcr adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/tencentcr" + // register the VolcEngine CR Registry adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/volcenginecr" "github.com/goharbor/harbor/src/pkg/reg/model" ) diff --git a/src/jobservice/main.go b/src/jobservice/main.go index 42d531546b..e00dd4b20c 100644 --- a/src/jobservice/main.go +++ b/src/jobservice/main.go @@ -36,6 +36,8 @@ import ( _ "github.com/goharbor/harbor/src/pkg/accessory/model/subject" _ "github.com/goharbor/harbor/src/pkg/config/inmemory" _ "github.com/goharbor/harbor/src/pkg/config/rest" + _ "github.com/goharbor/harbor/src/pkg/scan/sbom" + _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" ) func main() { diff --git a/src/jobservice/mgt/mock_manager.go b/src/jobservice/mgt/mock_manager.go index 12a12231e5..8f1ccd3a22 100644 --- a/src/jobservice/mgt/mock_manager.go +++ b/src/jobservice/mgt/mock_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package mgt @@ -18,6 +18,10 @@ type MockManager struct { func (_m *MockManager) GetJob(jobID string) (*job.Stats, error) { ret := _m.Called(jobID) + if len(ret) == 0 { + panic("no return value specified for GetJob") + } + var r0 *job.Stats var r1 error if rf, ok := ret.Get(0).(func(string) (*job.Stats, error)); ok { @@ -44,6 +48,10 @@ func (_m *MockManager) GetJob(jobID string) (*job.Stats, error) { func (_m *MockManager) GetJobs(q *query.Parameter) ([]*job.Stats, int64, error) { ret := _m.Called(q) + if len(ret) == 0 { + panic("no return value specified for GetJobs") + } + var r0 []*job.Stats var r1 int64 var r2 error @@ -77,6 +85,10 @@ func (_m *MockManager) GetJobs(q *query.Parameter) ([]*job.Stats, int64, error) func (_m *MockManager) GetPeriodicExecution(pID string, q *query.Parameter) ([]*job.Stats, int64, error) { ret := _m.Called(pID, q) + if len(ret) == 0 { + panic("no return value specified for GetPeriodicExecution") + } + var r0 []*job.Stats var r1 int64 var r2 error @@ -110,6 +122,10 @@ func (_m *MockManager) GetPeriodicExecution(pID string, q *query.Parameter) ([]* func (_m *MockManager) GetScheduledJobs(q *query.Parameter) ([]*job.Stats, int64, error) { ret := _m.Called(q) + if len(ret) == 0 { + panic("no return value specified for GetScheduledJobs") + } + var r0 []*job.Stats var r1 int64 var r2 error @@ -143,6 +159,10 @@ func (_m *MockManager) GetScheduledJobs(q *query.Parameter) ([]*job.Stats, int64 func (_m *MockManager) SaveJob(_a0 *job.Stats) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for SaveJob") + } + var r0 error if rf, ok := ret.Get(0).(func(*job.Stats) error); ok { r0 = rf(_a0) diff --git a/src/jobservice/period/mock_scheduler.go b/src/jobservice/period/mock_scheduler.go index 9a8bb611f2..abbf494b7d 100644 --- a/src/jobservice/period/mock_scheduler.go +++ b/src/jobservice/period/mock_scheduler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package period @@ -13,6 +13,10 @@ type MockScheduler struct { func (_m *MockScheduler) Schedule(policy *Policy) (int64, error) { ret := _m.Called(policy) + if len(ret) == 0 { + panic("no return value specified for Schedule") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(*Policy) (int64, error)); ok { @@ -42,6 +46,10 @@ func (_m *MockScheduler) Start() { func (_m *MockScheduler) UnSchedule(policyID string) error { ret := _m.Called(policyID) + if len(ret) == 0 { + panic("no return value specified for UnSchedule") + } + var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { r0 = rf(policyID) diff --git a/src/lib/cache/mock_cache_test.go b/src/lib/cache/mock_cache_test.go index 02616848a5..62af8f2bb4 100644 --- a/src/lib/cache/mock_cache_test.go +++ b/src/lib/cache/mock_cache_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cache @@ -18,6 +18,10 @@ type mockCache struct { func (_m *mockCache) Contains(ctx context.Context, key string) bool { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Contains") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { r0 = rf(ctx, key) @@ -32,6 +36,10 @@ func (_m *mockCache) Contains(ctx context.Context, key string) bool { func (_m *mockCache) Delete(ctx context.Context, key string) error { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, key) @@ -46,6 +54,10 @@ func (_m *mockCache) Delete(ctx context.Context, key string) error { func (_m *mockCache) Fetch(ctx context.Context, key string, value interface{}) error { ret := _m.Called(ctx, key, value) + if len(ret) == 0 { + panic("no return value specified for Fetch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) error); ok { r0 = rf(ctx, key, value) @@ -60,6 +72,10 @@ func (_m *mockCache) Fetch(ctx context.Context, key string, value interface{}) e func (_m *mockCache) Ping(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -81,6 +97,10 @@ func (_m *mockCache) Save(ctx context.Context, key string, value interface{}, ex _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}, ...time.Duration) error); ok { r0 = rf(ctx, key, value, expiration...) @@ -95,6 +115,10 @@ func (_m *mockCache) Save(ctx context.Context, key string, value interface{}, ex func (_m *mockCache) Scan(ctx context.Context, match string) (Iterator, error) { ret := _m.Called(ctx, match) + if len(ret) == 0 { + panic("no return value specified for Scan") + } + var r0 Iterator var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (Iterator, error)); ok { diff --git a/src/lib/icon/const.go b/src/lib/icon/const.go index adaabc144b..3529cf46e1 100644 --- a/src/lib/icon/const.go +++ b/src/lib/icon/const.go @@ -27,4 +27,5 @@ const ( DigestOfIconAccCosign = "sha256:20401d5b3a0f6dbc607c8d732eb08471af4ae6b19811a4efce8c6a724aed2882" DigestOfIconAccNotation = "sha256:3ac706e102bbe9362b400aa162df58135d35e66b9c3bee2165de92022d25fe34" DigestOfIconAccNydus = "sha256:dfcb6617cd9c144358dc1b305b87bbe34f0b619f1e329116e6aee2e41f2e34cf" + DigestOfIconAccSBOM = "sha256:c19f80c357cd7e90d2a01b9ae3e2eb62ce447a2662bb590a19177d72d550bdae" ) diff --git a/src/pkg/accessory/manager.go b/src/pkg/accessory/manager.go index 0c02908d57..003350e480 100644 --- a/src/pkg/accessory/manager.go +++ b/src/pkg/accessory/manager.go @@ -33,6 +33,7 @@ var ( model.TypeCosignSignature: icon.DigestOfIconAccCosign, model.TypeNotationSignature: icon.DigestOfIconAccNotation, model.TypeNydusAccelerator: icon.DigestOfIconAccNydus, + model.TypeHarborSBOM: icon.DigestOfIconAccSBOM, } ) diff --git a/src/pkg/accessory/model/accessory.go b/src/pkg/accessory/model/accessory.go index 4d60525321..c4a8737f85 100644 --- a/src/pkg/accessory/model/accessory.go +++ b/src/pkg/accessory/model/accessory.go @@ -76,6 +76,9 @@ const ( // TypeSubject ... TypeSubject = "subject.accessory" + + // TypeHarborSBOM identifies sbom.harbor + TypeHarborSBOM = "sbom.harbor" ) // AccessoryData ... diff --git a/src/pkg/accessory/model/sbom/sbom.go b/src/pkg/accessory/model/sbom/sbom.go new file mode 100644 index 0000000000..3e5a5642ac --- /dev/null +++ b/src/pkg/accessory/model/sbom/sbom.go @@ -0,0 +1,46 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "github.com/goharbor/harbor/src/pkg/accessory/model" + "github.com/goharbor/harbor/src/pkg/accessory/model/base" +) + +// HarborSBOM is the sbom accessory for harbor +type HarborSBOM struct { + base.Default +} + +// Kind gives the reference type of accessory. +func (c *HarborSBOM) Kind() string { + return model.RefHard +} + +// IsHard ... +func (c *HarborSBOM) IsHard() bool { + return true +} + +// New returns sbom accessory +func New(data model.AccessoryData) model.Accessory { + return &HarborSBOM{base.Default{ + Data: data, + }} +} + +func init() { + model.Register(model.TypeHarborSBOM, New) +} diff --git a/src/pkg/accessory/model/sbom/sbom_test.go b/src/pkg/accessory/model/sbom/sbom_test.go new file mode 100644 index 0000000000..92f9bda270 --- /dev/null +++ b/src/pkg/accessory/model/sbom/sbom_test.go @@ -0,0 +1,87 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/goharbor/harbor/src/pkg/accessory/model" + htesting "github.com/goharbor/harbor/src/testing" +) + +type SBOMTestSuite struct { + htesting.Suite + accessory model.Accessory + digest string + subDigest string +} + +func (suite *SBOMTestSuite) SetupSuite() { + suite.digest = suite.DigestString() + suite.subDigest = suite.DigestString() + suite.accessory, _ = model.New(model.TypeHarborSBOM, + model.AccessoryData{ + ArtifactID: 1, + SubArtifactDigest: suite.subDigest, + Size: 4321, + Digest: suite.digest, + }) +} + +func (suite *SBOMTestSuite) TestGetID() { + suite.Equal(int64(0), suite.accessory.GetData().ID) +} + +func (suite *SBOMTestSuite) TestGetArtID() { + suite.Equal(int64(1), suite.accessory.GetData().ArtifactID) +} + +func (suite *SBOMTestSuite) TestSubGetArtID() { + suite.Equal(suite.subDigest, suite.accessory.GetData().SubArtifactDigest) +} + +func (suite *SBOMTestSuite) TestSubGetSize() { + suite.Equal(int64(4321), suite.accessory.GetData().Size) +} + +func (suite *SBOMTestSuite) TestSubGetDigest() { + suite.Equal(suite.digest, suite.accessory.GetData().Digest) +} + +func (suite *SBOMTestSuite) TestSubGetType() { + suite.Equal(model.TypeHarborSBOM, suite.accessory.GetData().Type) +} + +func (suite *SBOMTestSuite) TestSubGetRefType() { + suite.Equal(model.RefHard, suite.accessory.Kind()) +} + +func (suite *SBOMTestSuite) TestIsSoft() { + suite.False(suite.accessory.IsSoft()) +} + +func (suite *SBOMTestSuite) TestIsHard() { + suite.True(suite.accessory.IsHard()) +} + +func (suite *SBOMTestSuite) TestDisplay() { + suite.False(suite.accessory.Display()) +} + +func TestSBOMTestSuite(t *testing.T) { + suite.Run(t, new(SBOMTestSuite)) +} diff --git a/src/pkg/artifact/dao/model.go b/src/pkg/artifact/dao/model.go index c9ba1447bc..7bb7c36abc 100644 --- a/src/pkg/artifact/dao/model.go +++ b/src/pkg/artifact/dao/model.go @@ -33,6 +33,7 @@ type Artifact struct { Type string `orm:"column(type)"` // image, chart or other OCI compatible MediaType string `orm:"column(media_type)"` // the media type of artifact ManifestMediaType string `orm:"column(manifest_media_type)"` // the media type of manifest/index + ArtifactType string `orm:"colume(artifact_type)"` // the artifactType of manifest/index ProjectID int64 `orm:"column(project_id)"` // needed for quota RepositoryID int64 `orm:"column(repository_id)"` RepositoryName string `orm:"column(repository_name)"` diff --git a/src/pkg/artifact/model.go b/src/pkg/artifact/model.go index 944e370b4c..464a31924a 100644 --- a/src/pkg/artifact/model.go +++ b/src/pkg/artifact/model.go @@ -34,6 +34,7 @@ type Artifact struct { Type string `json:"type"` // image, chart or other OCI compatible MediaType string `json:"media_type"` // the media type of artifact. Mostly, it's the value of `manifest.config.mediatype` ManifestMediaType string `json:"manifest_media_type"` // the media type of manifest/index + ArtifactType string `json:"artifact_type"` // the artifactType of manifest/index ProjectID int64 `json:"project_id"` RepositoryID int64 `json:"repository_id"` RepositoryName string `json:"repository_name"` @@ -63,6 +64,7 @@ func (a *Artifact) From(art *dao.Artifact) { a.Type = art.Type a.MediaType = art.MediaType a.ManifestMediaType = art.ManifestMediaType + a.ArtifactType = art.ArtifactType a.ProjectID = art.ProjectID a.RepositoryID = art.RepositoryID a.RepositoryName = art.RepositoryName @@ -92,6 +94,7 @@ func (a *Artifact) To() *dao.Artifact { Type: a.Type, MediaType: a.MediaType, ManifestMediaType: a.ManifestMediaType, + ArtifactType: a.ArtifactType, ProjectID: a.ProjectID, RepositoryID: a.RepositoryID, RepositoryName: a.RepositoryName, diff --git a/src/pkg/artifact/model_test.go b/src/pkg/artifact/model_test.go index 537658fafd..24bd77eba0 100644 --- a/src/pkg/artifact/model_test.go +++ b/src/pkg/artifact/model_test.go @@ -37,6 +37,7 @@ func (m *modelTestSuite) TestArtifactFrom() { Type: "IMAGE", MediaType: "application/vnd.oci.image.config.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", + ArtifactType: "application/vnd.example+type", ProjectID: 1, RepositoryID: 1, Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", @@ -52,6 +53,7 @@ func (m *modelTestSuite) TestArtifactFrom() { assert.Equal(t, dbArt.Type, art.Type) assert.Equal(t, dbArt.MediaType, art.MediaType) assert.Equal(t, dbArt.ManifestMediaType, art.ManifestMediaType) + assert.Equal(t, dbArt.ArtifactType, art.ArtifactType) assert.Equal(t, dbArt.ProjectID, art.ProjectID) assert.Equal(t, dbArt.RepositoryID, art.RepositoryID) assert.Equal(t, dbArt.Digest, art.Digest) @@ -71,6 +73,7 @@ func (m *modelTestSuite) TestArtifactTo() { RepositoryID: 1, MediaType: "application/vnd.oci.image.config.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", + ArtifactType: "application/vnd.example+type", Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", Size: 1024, PushTime: time.Now(), @@ -87,6 +90,7 @@ func (m *modelTestSuite) TestArtifactTo() { assert.Equal(t, art.Type, dbArt.Type) assert.Equal(t, art.MediaType, dbArt.MediaType) assert.Equal(t, art.ManifestMediaType, dbArt.ManifestMediaType) + assert.Equal(t, art.ArtifactType, dbArt.ArtifactType) assert.Equal(t, art.ProjectID, dbArt.ProjectID) assert.Equal(t, art.RepositoryID, dbArt.RepositoryID) assert.Equal(t, art.Digest, dbArt.Digest) diff --git a/src/pkg/blob/dao/dao_test.go b/src/pkg/blob/dao/dao_test.go index 00c63e94c6..69771a89fa 100644 --- a/src/pkg/blob/dao/dao_test.go +++ b/src/pkg/blob/dao/dao_test.go @@ -269,7 +269,7 @@ func (suite *DaoTestSuite) TestFindBlobsShouldUnassociatedWithProject() { artifact1 := suite.DigestString() artifact2 := suite.DigestString() - sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world')` + sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name, artifact_type) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world', 'artifact_type')` suite.ExecSQL(sql, artifact1, projectID, 10) suite.ExecSQL(sql, artifact2, projectID, 10) diff --git a/src/pkg/blob/manager_test.go b/src/pkg/blob/manager_test.go index c688bb34e6..8ed541970f 100644 --- a/src/pkg/blob/manager_test.go +++ b/src/pkg/blob/manager_test.go @@ -130,7 +130,7 @@ func (suite *ManagerTestSuite) TestCleanupAssociationsForProject() { artifact1 := suite.DigestString() artifact2 := suite.DigestString() - sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world')` + sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name, artifact_type) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world', 'artifact_type')` suite.ExecSQL(sql, artifact1, projectID, 10) suite.ExecSQL(sql, artifact2, projectID, 10) @@ -200,7 +200,7 @@ func (suite *ManagerTestSuite) TestFindBlobsShouldUnassociatedWithProject() { artifact1 := suite.DigestString() artifact2 := suite.DigestString() - sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world')` + sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name, artifact_type) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world', 'artifact_type')` suite.ExecSQL(sql, artifact1, projectID, 11) suite.ExecSQL(sql, artifact2, projectID, 11) diff --git a/src/pkg/cached/project/redis/manager.go b/src/pkg/cached/project/redis/manager.go index 2fe086b5fe..da945b817d 100644 --- a/src/pkg/cached/project/redis/manager.go +++ b/src/pkg/cached/project/redis/manager.go @@ -18,6 +18,7 @@ import ( "context" "time" + commonmodels "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/log" @@ -75,8 +76,8 @@ func (m *Manager) ListRoles(ctx context.Context, projectID int64, userID int, gr return m.delegator.ListRoles(ctx, projectID, userID, groupIDs...) } -func (m *Manager) ListAdminRolesOfUser(ctx context.Context, userID int) ([]models.Member, error) { - return m.delegator.ListAdminRolesOfUser(ctx, userID) +func (m *Manager) ListAdminRolesOfUser(ctx context.Context, user commonmodels.User) ([]models.Member, error) { + return m.delegator.ListAdminRolesOfUser(ctx, user) } func (m *Manager) Delete(ctx context.Context, id int64) error { diff --git a/src/pkg/joblog/manager.go b/src/pkg/joblog/manager.go index 78ec677ea7..9915598802 100644 --- a/src/pkg/joblog/manager.go +++ b/src/pkg/joblog/manager.go @@ -56,7 +56,7 @@ func (m *manager) Create(ctx context.Context, jobLog *models.JobLog) (id int64, return m.dao.Create(ctx, jobLog) } -// DeleteJobLogsBefore ... +// DeleteBefore ... func (m *manager) DeleteBefore(ctx context.Context, t time.Time) (id int64, err error) { return m.dao.DeleteBefore(ctx, t) } diff --git a/src/pkg/notifier/model/event.go b/src/pkg/notifier/model/event.go index bcdc2a6c13..4bf852df0a 100644 --- a/src/pkg/notifier/model/event.go +++ b/src/pkg/notifier/model/event.go @@ -42,6 +42,7 @@ type EventData struct { Repository *Repository `json:"repository,omitempty"` Replication *model.Replication `json:"replication,omitempty"` Retention *model.Retention `json:"retention,omitempty"` + Scan *model.Scan `json:"scan,omitempty"` Custom map[string]string `json:"custom_attributes,omitempty"` } @@ -51,6 +52,7 @@ type Resource struct { Tag string `json:"tag,omitempty"` ResourceURL string `json:"resource_url,omitempty"` ScanOverview map[string]interface{} `json:"scan_overview,omitempty"` + SBOMOverview map[string]interface{} `json:"sbom_overview,omitempty"` } // Repository info of notification event diff --git a/src/pkg/project/dao/dao.go b/src/pkg/project/dao/dao.go index eb88ccc477..05d9f193bc 100644 --- a/src/pkg/project/dao/dao.go +++ b/src/pkg/project/dao/dao.go @@ -20,6 +20,7 @@ import ( "time" "github.com/goharbor/harbor/src/common" + commonmodels "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" @@ -43,7 +44,7 @@ type DAO interface { // ListRoles the roles of user for the specific project ListRoles(ctx context.Context, projectID int64, userID int, groupIDs ...int) ([]int, error) // ListAdminRolesOfUser returns the roles of user for the all projects - ListAdminRolesOfUser(ctx context.Context, userID int) ([]models.Member, error) + ListAdminRolesOfUser(ctx context.Context, user commonmodels.User) ([]models.Member, error) } // New returns an instance of the default DAO @@ -202,19 +203,39 @@ func (d *dao) ListRoles(ctx context.Context, projectID int64, userID int, groupI return roles, nil } -func (d *dao) ListAdminRolesOfUser(ctx context.Context, userID int) ([]models.Member, error) { +func (d *dao) ListAdminRolesOfUser(ctx context.Context, user commonmodels.User) ([]models.Member, error) { o, err := orm.FromContext(ctx) if err != nil { return nil, err } - sql := `select b.* from project as a left join project_member as b on a.project_id = b.project_id where a.deleted = 'f' and b.entity_id = ? and b.entity_type = 'u' and b.role = 1;` - - var members []models.Member - _, err = o.Raw(sql, userID).QueryRows(&members) + var membersU []models.Member + sqlU := `select b.* from project as a left join project_member as b on a.project_id = b.project_id where a.deleted = 'f' and b.entity_id = ? and b.entity_type = 'u' and b.role = 1;` + _, err = o.Raw(sqlU, user.UserID).QueryRows(&membersU) if err != nil { return nil, err } + var membersG []models.Member + if len(user.GroupIDs) > 0 { + var params []interface{} + params = append(params, user.GroupIDs) + sqlG := fmt.Sprintf(`select b.* from project as a + left join project_member as b on a.project_id = b.project_id + where a.deleted = 'f' and b.entity_id in ( %s ) and b.entity_type = 'g' and b.role = 1;`, orm.ParamPlaceholderForIn(len(user.GroupIDs))) + _, err = o.Raw(sqlG, params).QueryRows(&membersG) + if err != nil { + return nil, err + } + } + + var members []models.Member + if len(membersU) > 0 { + members = append(members, membersU...) + } + if len(membersG) > 0 { + members = append(members, membersG...) + } + return members, nil } diff --git a/src/pkg/project/dao/dao_test.go b/src/pkg/project/dao/dao_test.go index f3baf96221..d054fba2b9 100644 --- a/src/pkg/project/dao/dao_test.go +++ b/src/pkg/project/dao/dao_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/goharbor/harbor/src/common" + commonmodels "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/orm" @@ -341,6 +342,42 @@ func (suite *DaoTestSuite) TestListByMember() { } } +func (suite *DaoTestSuite) TestListAdminRolesOfUser() { + { + // projectAdmin and user groups + suite.WithUser(func(userID int64, username string) { + project := &models.Project{ + Name: utils.GenerateRandomString(), + OwnerID: int(userID), + } + projectID, err := suite.dao.Create(orm.Context(), project) + suite.Nil(err) + + defer suite.dao.Delete(orm.Context(), projectID) + + suite.WithUserGroup(func(groupID int64, groupName string) { + + o, err := orm.FromContext(orm.Context()) + if err != nil { + suite.Fail("got error %v", err) + } + + var pid int64 + suite.Nil(o.Raw("INSERT INTO project_member (project_id, entity_id, role, entity_type) values (?, ?, ?, ?) RETURNING id", projectID, groupID, common.RoleProjectAdmin, "g").QueryRow(&pid)) + defer o.Raw("DELETE FROM project_member WHERE id = ?", pid) + + userTest := commonmodels.User{ + UserID: int(userID), + GroupIDs: []int{int(groupID)}, + } + roles, err := suite.dao.ListAdminRolesOfUser(orm.Context(), userTest) + suite.Nil(err) + suite.Len(roles, 2) + }) + }) + } +} + func (suite *DaoTestSuite) TestListRoles() { { // only projectAdmin diff --git a/src/pkg/project/manager.go b/src/pkg/project/manager.go index 3a03d55d3c..aa4d55bdb9 100644 --- a/src/pkg/project/manager.go +++ b/src/pkg/project/manager.go @@ -19,6 +19,7 @@ import ( "regexp" "strings" + commonmodels "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/q" @@ -47,7 +48,7 @@ type Manager interface { ListRoles(ctx context.Context, projectID int64, userID int, groupIDs ...int) ([]int, error) // ListAdminRolesOfUser returns the roles of user for the all projects - ListAdminRolesOfUser(ctx context.Context, userID int) ([]models.Member, error) + ListAdminRolesOfUser(ctx context.Context, user commonmodels.User) ([]models.Member, error) } // New returns a default implementation of Manager @@ -124,6 +125,6 @@ func (m *manager) ListRoles(ctx context.Context, projectID int64, userID int, gr } // ListAdminRolesOfUser returns the roles of user for the all projects -func (m *manager) ListAdminRolesOfUser(ctx context.Context, userID int) ([]models.Member, error) { - return m.dao.ListAdminRolesOfUser(ctx, userID) +func (m *manager) ListAdminRolesOfUser(ctx context.Context, user commonmodels.User) ([]models.Member, error) { + return m.dao.ListAdminRolesOfUser(ctx, user) } diff --git a/src/pkg/project/models/pro_meta.go b/src/pkg/project/models/pro_meta.go index bc4fe3ca3e..a8a0659fe8 100644 --- a/src/pkg/project/models/pro_meta.go +++ b/src/pkg/project/models/pro_meta.go @@ -23,4 +23,5 @@ const ( ProMetaSeverity = "severity" ProMetaAutoScan = "auto_scan" ProMetaReuseSysCVEAllowlist = "reuse_sys_cve_allowlist" + ProMetaAutoSBOMGen = "auto_sbom_generation" ) diff --git a/src/pkg/project/models/project.go b/src/pkg/project/models/project.go index 5a379468ae..e533bd9112 100644 --- a/src/pkg/project/models/project.go +++ b/src/pkg/project/models/project.go @@ -147,6 +147,15 @@ func (p *Project) AutoScan() bool { return isTrue(auto) } +// AutoSBOMGen ... +func (p *Project) AutoSBOMGen() bool { + auto, exist := p.GetMetadata(ProMetaAutoSBOMGen) + if !exist { + return false + } + return isTrue(auto) +} + // FilterByPublic returns orm.QuerySeter with public filter func (p *Project) FilterByPublic(_ context.Context, qs orm.QuerySeter, _ string, value interface{}) orm.QuerySeter { subQuery := `SELECT project_id FROM project_metadata WHERE name = 'public' AND value = '%s'` diff --git a/src/pkg/reg/adapter/volcenginecr/adapter.go b/src/pkg/reg/adapter/volcenginecr/adapter.go new file mode 100644 index 0000000000..106ba76cb8 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/adapter.go @@ -0,0 +1,167 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "errors" + "path" + "strings" + + volcCR "github.com/volcengine/volcengine-go-sdk/service/cr" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "github.com/volcengine/volcengine-go-sdk/volcengine/credentials" + volcSession "github.com/volcengine/volcengine-go-sdk/volcengine/session" + + "github.com/goharbor/harbor/src/lib/log" + adp "github.com/goharbor/harbor/src/pkg/reg/adapter" + "github.com/goharbor/harbor/src/pkg/reg/adapter/native" + "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/goharbor/harbor/src/pkg/reg/util" + "github.com/goharbor/harbor/src/pkg/registry/auth/bearer" +) + +func init() { + if err := adp.RegisterFactory(model.RegistryTypeVolcCR, new(factory)); err != nil { + log.Errorf("failed to register factory for %s: %v", model.RegistryTypeVolcCR, err) + return + } + log.Infof("the factory for adapter %s registered", model.RegistryTypeVolcCR) +} + +type factory struct{} + +/** + * Implement Factory Interface +**/ +var _ adp.Factory = &factory{} + +type adapter struct { + *native.Adapter + registryName *string + volcCrClient *volcCR.CR + registry *model.Registry +} + +// Create ... +func (f *factory) Create(r *model.Registry) (adp.Adapter, error) { + return newAdapter(r) +} + +// AdapterPattern ... +func (f *factory) AdapterPattern() *model.AdapterPattern { + return getAdapterInfo() +} + +func getAdapterInfo() *model.AdapterPattern { + return &model.AdapterPattern{} +} + +func newAdapter(registry *model.Registry) (a *adapter, err error) { + // get region and registryName from url + region, registryName, err := getRegionRegistryName(registry.URL) + if err != nil { + log.Errorf("getRegion failed. error=%v", err) + return nil, err + } + + // Create VolcCR API client + config := volcengine.NewConfig(). + WithCredentials(credentials.NewStaticCredentials(registry.Credential.AccessKey, registry.Credential.AccessSecret, "")). + WithRegion(region) + sess, err := volcSession.NewSession(config) + if err != nil { + log.Errorf("getSession error. error=%v", err) + return nil, err + } + client := volcCR.New(sess) + + // Get AuthorizationToken for docker login + bearRealm, bearService, err := getRealmService(registry.URL, registry.Insecure) + if err != nil { + log.Error("fail to ping the registry", "url", registry.URL) + return nil, err + } + cred := NewAuth(client, registryName) + var transport = util.GetHTTPTransport(registry.Insecure) + authorizer := bearer.NewAuthorizer(bearRealm, bearService, cred, transport) + + return &adapter{ + registry: registry, + registryName: ®istryName, + volcCrClient: client, + Adapter: native.NewAdapterWithAuthorizer(registry, authorizer), + }, nil +} + +func (a *adapter) Info() (info *model.RegistryInfo, err error) { + info = &model.RegistryInfo{ + Type: model.RegistryTypeVolcCR, + SupportedResourceTypes: []string{ + model.ResourceTypeImage, + }, + SupportedResourceFilters: []*model.FilterStyle{ + { + Type: model.FilterTypeName, + Style: model.FilterStyleTypeText, + }, + { + Type: model.FilterTypeTag, + Style: model.FilterStyleTypeText, + }, + }, + SupportedTriggers: []string{ + model.TriggerTypeManual, + model.TriggerTypeScheduled, + }, + } + return +} + +func (a *adapter) PrepareForPush(resources []*model.Resource) (err error) { + for _, resource := range resources { + if resource == nil { + return errors.New("the resource cannot be null") + } + if resource.Metadata == nil { + return errors.New("[volcengine-cr.PrepareForPush] the metadata of resource cannot be null") + } + if resource.Metadata.Repository == nil { + return errors.New("[volcengine-cr.PrepareForPush] the namespace of resource cannot be null") + } + if len(resource.Metadata.Repository.Name) == 0 { + return errors.New("[volcengine-cr.PrepareForPush] the name of the namespace cannot be null") + } + var paths = strings.Split(resource.Metadata.Repository.Name, "/") + if len(paths) < 2 { + return errors.New("[volcengine-cr.PrepareForPush] the name of the repository and namespace cannot be null") + } + var namespace = paths[0] + var repository = path.Join(paths[1:]...) + + log.Debugf("namespace=%s", namespace) + err = a.createNamespace(namespace) + if err != nil { + log.Errorf("PrepareForPush error :%v", err) + return + } + log.Debugf("namespace=%s, repository=%s", namespace, repository) + err = a.createRepository(namespace, repository) + if err != nil { + log.Errorf("PrepareForPush error :%v", err) + return + } + } + return +} diff --git a/src/pkg/reg/adapter/volcenginecr/adapter_test.go b/src/pkg/reg/adapter/volcenginecr/adapter_test.go new file mode 100644 index 0000000000..591bed14bd --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/adapter_test.go @@ -0,0 +1,189 @@ +package volcenginecr + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + volcCR "github.com/volcengine/volcengine-go-sdk/service/cr" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "github.com/volcengine/volcengine-go-sdk/volcengine/credentials" + volcSession "github.com/volcengine/volcengine-go-sdk/volcengine/session" + + "github.com/goharbor/harbor/src/common/utils/test" + adp "github.com/goharbor/harbor/src/pkg/reg/adapter" + "github.com/goharbor/harbor/src/pkg/reg/adapter/native" + "github.com/goharbor/harbor/src/pkg/reg/model" +) + +func getMockAdapter_withoutCred(t *testing.T, hasCred, health bool) (*adapter, *httptest.Server) { + server := test.NewServer( + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/v2/", + Handler: func(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.Method, r.URL) + if health { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusBadRequest) + } + }, + }, + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/", + Handler: func(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.Method, r.URL) + w.WriteHeader(http.StatusOK) + }, + }, + &test.RequestHandlerMapping{ + Method: http.MethodPost, + Pattern: "/", + Handler: func(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.Method, r.URL) + if buf, e := ioutil.ReadAll(&io.LimitedReader{R: r.Body, N: 80}); e == nil { + fmt.Println("\t", string(buf)) + } + w.WriteHeader(http.StatusOK) + }, + }, + ) + + registry := &model.Registry{ + Type: model.RegistryTypeVolcCR, + URL: server.URL, + } + if hasCred { + registry.Credential = &model.Credential{ + AccessKey: "MockAccessKey", + AccessSecret: "MockAccessSecret", + } + } + name := "test-registry" + config := volcengine.NewConfig(). + WithCredentials(credentials.NewStaticCredentials("", "", "")). + WithRegion("cn-beijing") + sess, _ := volcSession.NewSession(config) + client := volcCR.New(sess) + return &adapter{ + Adapter: native.NewAdapter(registry), + registryName: &name, + volcCrClient: client, + registry: registry, + }, server +} + +func TestAdapter_NewAdapter_InvalidURL(t *testing.T) { + factory, err := adp.GetFactory("BadName") + assert.Nil(t, factory) + assert.Error(t, err) + + factory, err = adp.GetFactory(model.RegistryTypeVolcCR) + assert.NoError(t, err) + assert.NotNil(t, factory) + adapter, err := factory.Create(&model.Registry{ + Type: model.RegistryTypeVolcCR, + Credential: &model.Credential{}, + }) + assert.Error(t, err) + assert.Nil(t, adapter) +} + +// remove it because failed +// func TestAdapter_NewAdapter_PingFailed(t *testing.T) { +// factory, _ := adp.GetFactory(model.RegistryTypeVolcCR) +// adapter, err := factory.Create(&model.Registry{ +// Type: model.RegistryTypeVolcCR, +// Credential: &model.Credential{}, +// URL: "https://cr-test-cn-beijing.cr.volces.com", +// }) +// assert.Error(t, err) +// assert.Nil(t, adapter) +// } + +func TestAdapter_Info(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + info, err := a.Info() + assert.Nil(t, err) + assert.NotNil(t, info) + + assert.EqualValues(t, 1, len(info.SupportedResourceTypes)) + assert.EqualValues(t, model.ResourceTypeImage, info.SupportedResourceTypes[0]) +} + +func TestAdapter_PrepareForPush(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{ + Name: "busybox", + }, + }, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilResource(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + var resources = []*model.Resource{nil} + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilMeta(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilRepository(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + Metadata: &model.ResourceMetadata{}, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilRepositoryName(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{}, + }, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} diff --git a/src/pkg/reg/adapter/volcenginecr/artifact_registry.go b/src/pkg/reg/adapter/volcenginecr/artifact_registry.go new file mode 100644 index 0000000000..ff3d74f777 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/artifact_registry.go @@ -0,0 +1,205 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "fmt" + "strings" + + "github.com/opencontainers/go-digest" + "github.com/volcengine/volcengine-go-sdk/service/cr" + + "github.com/goharbor/harbor/src/common/utils" + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/goharbor/harbor/src/pkg/reg/util" +) + +// DeleteManifest VolcCR will use our own openAPI to delete Manifest +func (a *adapter) DeleteManifest(repository, reference string) (err error) { + parts := strings.SplitN(repository, "/", 2) + if len(parts) != 2 { + return fmt.Errorf("VolcEngineCR only support repo in format /, but got: %s", repository) + } + log.Warningf("namespace=%s, repository=%s, tag=%s", parts[0], parts[1], reference) + + if _, err := digest.Parse(reference); err != nil { + // get digest + resp, err := a.volcCrClient.ListTags(&cr.ListTagsInput{ + Registry: a.registryName, + Namespace: &parts[0], + Repository: &parts[1], + Filter: &cr.FilterForListTagsInput{ + Names: []*string{ + &reference, + }, + }, + }) + if err != nil { + return err + } + if resp == nil || resp.TotalCount == nil { + return fmt.Errorf("[VolcEngineCR.DeleteManifest] ListTags resp nil") + } + if *resp.TotalCount == 0 { + return nil + } + if resp.Items[0] == nil { + return fmt.Errorf("[VolcEngineCR.DeleteManifest] ListTags resp nil") + } + reference = *resp.Items[0].Digest + } + // listCandidateTags based on digest + tags, err := a.listCandidateTags(parts[0], parts[1], reference) + if err != nil { + log.Errorf("DeleteManifest error :%v", err) + return err + } + // deleteTags + err = a.deleteTags(parts[0], parts[1], tags) + if err != nil { + log.Errorf("DeleteManifest error :%v", err) + } + return +} + +// DeleteTag VolcCR will use our own openAPI to delete tag +func (a *adapter) DeleteTag(repository, tag string) (err error) { + parts := strings.SplitN(repository, "/", 2) + if len(parts) != 2 { + return fmt.Errorf("VolcEngineCR only support repo in format /, but got: %s", repository) + } + log.Warningf("namespace=%s, repository=%s, tag=%s", parts[0], parts[1], tag) + + err = a.deleteTags(parts[0], parts[1], []*string{ + &tag, + }) + if err != nil { + log.Errorf("deleteTag error: %v", err) + } + return +} + +// FetchArtifacts VolcCR not support /v2/_catalog of Registry +func (a *adapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resource, error) { + log.Debug("FetchArtifacts filters", "filters", filters) + // 1. get filter pattern + var repoPattern string + var tagsPattern string + for _, filter := range filters { + if filter.Type == model.FilterTypeName { + repoPattern = filter.Value.(string) + } + if filter.Type == model.FilterTypeTag { + tagsPattern = filter.Value.(string) + } + } + namespacePattern := strings.Split(repoPattern, "/")[0] + + log.Debug("read in filter patterns", "repoPattern", repoPattern, "tagsPattern", tagsPattern) + + // 2. list namespace candidtes + namespaces, err := a.listCandidateNamespaces(namespacePattern) + if err != nil { + log.Errorf("FetchArtifacts error: %v", err) + return nil, err + } + log.Debug("FetchArtifacts filtered namespace", "namespace", namespaces) + + // 3. list repos + var nsRepos []string + for _, ns := range namespaces { + repoCandidates, err := a.listRepositories(ns) + if err != nil { + log.Error("FetchArtifacts error", "error", err) + return nil, err + } + log.Debug(" FetchArtifacts list repo", "repos: ", repoCandidates) + for _, r := range repoCandidates { + nsRepoCandidate := fmt.Sprintf("%s/%s", ns, r) + ok, err := util.Match(repoPattern, nsRepoCandidate) + if err != nil { + log.Error("FetchArtifacts error", "error", err) + return nil, err + } + log.Debug("filter namespaced repository", "repoPattern: ", repoPattern, "repo: ", nsRepoCandidate) + if ok { + nsRepos = append(nsRepos, nsRepoCandidate) + } + } + } + log.Debug("filter namespaced repository", "length", len(nsRepos)) + + // 4. list tags + var rawResources = make([]*model.Resource, len(nsRepos)) + resources := make([]*model.Resource, 0) + runner := utils.NewLimitedConcurrentRunner(concurrentLimit) + + for idx, repo := range nsRepos { + i := idx + nsRepo := repo + runner.AddTask(func() error { + repoArr := strings.SplitN(nsRepo, "/", 2) + // note list tag don't tell different oci types now + candidateTags, err := a.listAllTags(repoArr[0], repoArr[1]) + if err != nil { + log.Error("fail to list all tags", "nsRepo", nsRepo) + return fmt.Errorf("volcengineCR fail to list all tags %w", err) + } + + tags := make([]string, 0) + if tagsPattern != "" { + for _, candidateTag := range candidateTags { + ok, err := util.Match(tagsPattern, candidateTag) + if err != nil { + return fmt.Errorf("fail to match tag pattern, error=%w", err) + } + if ok { + tags = append(tags, candidateTag) + } + } + } else { + tags = candidateTags + } + + log.Debug("filter tags") + + if len(tags) > 0 { + rawResources[i] = &model.Resource{ + Type: model.ResourceTypeImage, + Registry: a.registry, + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{ + Name: nsRepo, + }, + Vtags: tags, + }, + } + } + return nil + }) + } + if err = runner.Wait(); err != nil { + return nil, fmt.Errorf("failed to fetch artifacts: %w", err) + } + + for _, res := range rawResources { + if res != nil { + resources = append(resources, res) + } + } + + return resources, nil +} diff --git a/src/pkg/reg/adapter/volcenginecr/artifact_registry_test.go b/src/pkg/reg/adapter/volcenginecr/artifact_registry_test.go new file mode 100644 index 0000000000..a9645ddc77 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/artifact_registry_test.go @@ -0,0 +1,55 @@ +package volcenginecr + +import ( + "github.com/goharbor/harbor/src/pkg/reg/model" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestArtifactRegistry_DeleteManifest(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.DeleteManifest("ut_test/ut_test", "sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc") + assert.Error(t, err) +} + +func TestArtifactRegistry_DeleteTag(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.DeleteTag("ut_test/ut_test", "v1") + assert.Error(t, err) +} + +func TestArtifactRegistry_FetchArtifacts(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + tests := []struct { + name string + filter model.Filter + wantErr bool + }{ + {"filter name", + model.Filter{ + Type: model.FilterTypeName, + Value: "ut_test", + }, + true}, + {"filter tag", + model.Filter{ + Type: model.FilterTypeTag, + Value: "v1", + }, + true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := a.FetchArtifacts([]*model.Filter{&tt.filter}) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/src/pkg/reg/adapter/volcenginecr/auth.go b/src/pkg/reg/adapter/volcenginecr/auth.go new file mode 100644 index 0000000000..b0a08e9240 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/auth.go @@ -0,0 +1,93 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/volcengine/volcengine-go-sdk/service/cr" + + "github.com/goharbor/harbor/src/common/http/modifier" + "github.com/goharbor/harbor/src/lib/log" +) + +// Credential ... +type Credential modifier.Modifier + +type volcCredential struct { + client *cr.CR + registry string + authCache *authCache +} + +type authCache struct { + username string + password string + expireAt *time.Time +} + +var _ Credential = &volcCredential{} + +// NewAuth will get a temporary username and password via cr GetAuthorizationToken action for docker login +func NewAuth(client *cr.CR, registry string) Credential { + return &volcCredential{ + client: client, + registry: registry, + authCache: &authCache{}, + } +} + +func (c *volcCredential) Modify(r *http.Request) (err error) { + if c.client == nil { + return errNilVolcCrClient + } + if !c.isCacheAuthValid() { + log.Debugf("update token %s\n", r.Host) + authResp, err := c.client.GetAuthorizationToken(&cr.GetAuthorizationTokenInput{ + Registry: &c.registry, + }) + if err != nil { + return err + } + if authResp == nil || authResp.Username == nil || authResp.Token == nil || authResp.ExpireTime == nil { + return errors.New("[VolcengineCR] GetAuthorizationToken output nil") + } + c.authCache.username = *authResp.Username + c.authCache.password = *authResp.Token + expireTime, err := time.Parse(time.RFC3339, *authResp.ExpireTime) + if err != nil { + log.Errorf("fail to parse expire time returned: %v", err) + return fmt.Errorf("[VolcengineCR] fail to parse expire time returned: %v", err) + } + c.authCache.expireAt = &expireTime + } else { + log.Debug("token cached") + } + r.SetBasicAuth(c.authCache.username, c.authCache.password) + return nil +} + +func (c *volcCredential) isCacheAuthValid() bool { + if c.authCache == nil || c.authCache.expireAt == nil { + return false + } + if time.Now().After(*c.authCache.expireAt) { + return false + } + return true +} diff --git a/src/pkg/reg/adapter/volcenginecr/auth_test.go b/src/pkg/reg/adapter/volcenginecr/auth_test.go new file mode 100644 index 0000000000..6af5566f6a --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/auth_test.go @@ -0,0 +1,29 @@ +package volcenginecr + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + volcCR "github.com/volcengine/volcengine-go-sdk/service/cr" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "github.com/volcengine/volcengine-go-sdk/volcengine/credentials" + volcSession "github.com/volcengine/volcengine-go-sdk/volcengine/session" +) + +func Test_Modify_nilCR(t *testing.T) { + c := &volcCredential{} + err := c.Modify(&http.Request{}) + assert.Error(t, err) +} + +func Test_Modify(t *testing.T) { + config := volcengine.NewConfig(). + WithCredentials(credentials.NewStaticCredentials("", "", "")). + WithRegion("cn-beijing") + sess, _ := volcSession.NewSession(config) + client := volcCR.New(sess) + c := &volcCredential{client: client} + err := c.Modify(&http.Request{}) + assert.Error(t, err) +} diff --git a/src/pkg/reg/adapter/volcenginecr/consts.go b/src/pkg/reg/adapter/volcenginecr/consts.go new file mode 100644 index 0000000000..d519d2bb0b --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/consts.go @@ -0,0 +1,33 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import "errors" + +var ( + regionRegs = map[string]interface{}{ + "(.*)-(cn-.*)": nil, + } + errListNamespaceResp = errors.New("[VolcengineCR adapt] ListNamespaces resp nil") + errListRepositoriesResp = errors.New("[VolcengineCR adapt] ListRepositories resp nil") + errListTagsResp = errors.New("[VolcengineCR adapt] ListTags resp nil") + errPareseDigest = errors.New("[VolcengineCR adapt] fail to parse reference") + errNilVolcCrClient = errors.New("[volcengine-cr.createRepository] nil volcCr client") +) + +const ( + MaxPageSize int64 = 100 + concurrentLimit int = 3 +) diff --git a/src/pkg/reg/adapter/volcenginecr/helper.go b/src/pkg/reg/adapter/volcenginecr/helper.go new file mode 100644 index 0000000000..8af780f310 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/helper.go @@ -0,0 +1,66 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "errors" + "fmt" + "net/http" + "regexp" + + "github.com/docker/distribution/registry/client/auth/challenge" + + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/reg/util" +) + +func getRegionRegistryName(url string) (string, string, error) { + reg := regexp.MustCompile(`https://(.*)\.cr\.volces|ivolces\.com`) + rs := reg.FindStringSubmatch(url) + if rs == nil || len(rs) != 2 { + return "", "", errors.New("Invalid url") + } + registryNameRegion := rs[1] + for regionReg := range regionRegs { + reg = regexp.MustCompile(regionReg) + res := reg.FindStringSubmatch(registryNameRegion) + if res == nil || len(res) != 3 { + log.Debug("fail to match", "reg", regionReg) + continue + } + return res[2], res[1], nil + } + + return "", "", errors.New("invalid region") +} + +func getRealmService(host string, insecure bool) (string, string, error) { + client := &http.Client{ + Transport: util.GetHTTPTransport(insecure), + } + + resp, err := client.Get(host + "/v2/") + if err != nil { + return "", "", err + } + defer resp.Body.Close() // nolint + challenges := challenge.ResponseChallenges(resp) + for _, challenge := range challenges { + if challenge.Scheme == "bearer" { + return challenge.Parameters["realm"], challenge.Parameters["service"], nil + } + } + return "", "", fmt.Errorf("bearer auth scheme isn't supported: %v", challenges) +} diff --git a/src/pkg/reg/adapter/volcenginecr/helper_test.go b/src/pkg/reg/adapter/volcenginecr/helper_test.go new file mode 100644 index 0000000000..e08f72797d --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/helper_test.go @@ -0,0 +1,56 @@ +package volcenginecr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_getRegionRegistryNamer(t *testing.T) { + tests := []struct { + name string + url string + wantRegion string + wantRegistry string + wantErr bool + }{ + {"registry beijing", "https://enterprise-cn-beijing.cr.volces.com", "cn-beijing", "enterprise", false}, + {"invalid url", "http://enterprise-cn-beijing.cr.volces.com", "", "", true}, + {"invalid region", "https://enterprise-us-test.cr.volces.com", "", "", true}, + {"invalid suffix", "https://enterprise-us-test.cr-test.volces.com", "", "", true}, + {"registry shanghai", "https://cn-beijing-cn-shanghai.cr.volces.com", "cn-shanghai", "cn-beijing", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRegion, gotRegistry, err := getRegionRegistryName(tt.url) + if tt.wantErr { + assert.NotNil(t, err) + } + assert.Equal(t, tt.wantRegion, gotRegion) + assert.Equal(t, tt.wantRegistry, gotRegistry) + }) + } +} + +func Test_getRealmService(t *testing.T) { + tests := []struct { + name string + host string + insecure bool + wantErr bool + }{ + {"ping success", "https://cr-cn-beijing.volces.com", false, false}, + {"ping success", "https://cr-cn-beijing.volces.com", true, false}, + {"ping error", "https://cr-test-cn-beijing.volces.com", true, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, _, err := getRealmService(tt.host, tt.insecure) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/src/pkg/reg/adapter/volcenginecr/volccr.go b/src/pkg/reg/adapter/volcenginecr/volccr.go new file mode 100644 index 0000000000..753cfe599e --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/volccr.go @@ -0,0 +1,334 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "math" + + "github.com/opencontainers/go-digest" + "github.com/volcengine/volcengine-go-sdk/service/cr" + + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/reg/util" +) + +func (a *adapter) createNamespace(namespace string) (err error) { + if a.volcCrClient == nil { + return errNilVolcCrClient + } + // check if exists + exist, err := a.namespaceExist(namespace) + if err != nil { + return + } + // if exists; skip create + if exist { + return nil + } + + // create Namespace + _, err = a.volcCrClient.CreateNamespace(&cr.CreateNamespaceInput{ + Registry: a.registryName, + Name: &namespace, + }) + if err != nil { + log.Debugf("CreateNamespace error:%v", err) + return err + } + return nil +} + +func (a *adapter) createRepository(namespace, repository string) (err error) { + if a.volcCrClient == nil { + return errNilVolcCrClient + } + // check if exists + res, err := a.volcCrClient.ListRepositories(&cr.ListRepositoriesInput{ + Registry: a.registryName, + Filter: &cr.FilterForListRepositoriesInput{ + Names: []*string{ + &repository, + }, + Namespaces: []*string{ + &namespace, + }, + }, + }) + if err != nil { + log.Debugf("ListRepositories error:%v", err) + return err + } + // if exists; skip create + if res != nil && res.TotalCount != nil && *res.TotalCount > 0 { + return nil + } + + // create Repository + _, err = a.volcCrClient.CreateRepository(&cr.CreateRepositoryInput{ + Registry: a.registryName, + Namespace: &namespace, + Name: &repository, + }) + if err != nil { + log.Debugf("CreateRepository error:%v", err) + return err + } + return nil +} + +func (a *adapter) deleteTags(namespace, repository string, tags []*string) error { + _, err := a.volcCrClient.DeleteTags(&cr.DeleteTagsInput{ + Registry: a.registryName, + Namespace: &namespace, + Repository: &repository, + Names: tags, + }) + return err +} + +func (a *adapter) listCandidateNamespaces(namespacePattern string) ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + namespaces := make([]string, 0) + // filter namespaces + if len(namespacePattern) > 0 { + if nms, ok := util.IsSpecificPathComponent(namespacePattern); ok { + // Check if namespace exist + for _, ns := range nms { + exist, err := a.namespaceExist(ns) + if err != nil { + return nil, err + } + if !exist { + continue + } + namespaces = append(namespaces, nms...) + } + } + } + + if len(namespaces) > 0 { + log.Debug("list candidate namespace", "pattern", namespacePattern, "namespaces", namespaces) + return namespaces, nil + } + + // list all + return a.listNamespaces() +} + +func (a *adapter) listNamespaces() ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var nsList []string + + for remain > 0 { + resp, err := a.volcCrClient.ListNamespaces( + &cr.ListNamespacesInput{ + Registry: a.registryName, + PageSize: &pageSize, + PageNumber: &pageNumber, + }) + + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errListNamespaceResp + } + if initCondition { + nsList = make([]string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + // be careful with state machine. + pageNumber++ + for _, nsInfo := range resp.Items { + if nsInfo != nil && nsInfo.Name != nil { + nsList = append(nsList, *nsInfo.Name) + } + } + } + return nsList, nil +} + +func (a *adapter) listRepositories(namespace string) ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var repoList []string + + for remain > 0 { + resp, err := a.volcCrClient.ListRepositories( + &cr.ListRepositoriesInput{ + Registry: a.registryName, + PageSize: &pageSize, + PageNumber: &pageNumber, + Filter: &cr.FilterForListRepositoriesInput{ + Namespaces: []*string{&namespace}, + }, + }) + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errListRepositoriesResp + } + if initCondition { + repoList = make([]string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + // be careful with state machine. + pageNumber++ + for _, repoInfo := range resp.Items { + if repoInfo != nil && repoInfo.Name != nil { + repoList = append(repoList, *repoInfo.Name) + } + } + } + return repoList, nil +} + +// listAllTags list all tags of different artifacts with given namespace and repo +func (a *adapter) listAllTags(namespace, repo string) ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var tagList []string + + for remain > 0 { + resp, err := a.volcCrClient.ListTags( + &cr.ListTagsInput{ + Registry: a.registryName, + Namespace: &namespace, + Repository: &repo, + PageSize: &pageSize, + PageNumber: &pageNumber, + }) + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errListTagsResp + } + if initCondition { + tagList = make([]string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + pageNumber++ + for _, tagInfo := range resp.Items { + tagList = append(tagList, *tagInfo.Name) + } + } + return tagList, nil +} + +func (a *adapter) listCandidateTags(namespace, repository, reference string) ([]*string, error) { + if a.volcCrClient == nil { + return []*string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var tagList []*string + desiredDig, err := digest.Parse(reference) + if err != nil { + return tagList, errPareseDigest + } + + for remain > 0 { + resp, err := a.volcCrClient.ListTags( + &cr.ListTagsInput{ + Registry: a.registryName, + Namespace: &namespace, + Repository: &repository, + PageSize: &pageSize, + PageNumber: &pageNumber, + }) + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errPareseDigest + } + if initCondition { + tagList = make([]*string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + pageNumber++ + for _, tagInfo := range resp.Items { + if tagInfo != nil && tagInfo.Name != nil && tagInfo.Digest != nil { + dig, err := digest.Parse(*tagInfo.Digest) + if err != nil { + log.Debug("fail to parase digest", "tag", tagInfo) + continue + } + if desiredDig.String() == dig.String() { + tagList = append(tagList, tagInfo.Name) + } + } + } + } + return tagList, nil +} + +func (a *adapter) namespaceExist(namespace string) (bool, error) { + if a.volcCrClient == nil { + return false, errNilVolcCrClient + } + resp, err := a.volcCrClient.ListNamespaces(&cr.ListNamespacesInput{ + Registry: a.registryName, + Filter: &cr.FilterForListNamespacesInput{ + Names: []*string{ + &namespace, + }, + }, + }) + if err != nil { + return false, err + } + if resp == nil || resp.TotalCount == nil { + return false, errListNamespaceResp + } + if *resp.TotalCount > 0 { + return true, nil + } + return false, nil +} diff --git a/src/pkg/reg/adapter/volcenginecr/volccr_test.go b/src/pkg/reg/adapter/volcenginecr/volccr_test.go new file mode 100644 index 0000000000..961df39ebc --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/volccr_test.go @@ -0,0 +1,70 @@ +package volcenginecr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVolccr_createNamespace(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.createNamespace("ut_test") + assert.Error(t, err) +} + +func TestVolccr_createRepository(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.createRepository("ut_test", "ut_test") + assert.Error(t, err) +} + +func TestVolccr_deleteTags(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.deleteTags("ut_test", "ut_test", []*string{}) + assert.Error(t, err) +} + +func TestVolccr_listCandidateNamespaces(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listCandidateNamespaces("ut_test") + assert.Error(t, err) +} + +func TestVolccr_listNamespaces(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listNamespaces() + assert.Error(t, err) +} + +func TestVolccr_listRepositories(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listRepositories("ut_test") + assert.Error(t, err) +} + +func TestVolccr_listAllTags(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listAllTags("ut_test", "ut_test") + assert.Error(t, err) +} + +func TestVolccr_listCandidateTags(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listCandidateTags("ut_test", "ut_test", "sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc") + assert.Error(t, err) +} + +func TestVolccr_namespaceExist(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.namespaceExist("ut_test") + assert.Error(t, err) +} diff --git a/src/pkg/reg/manager.go b/src/pkg/reg/manager.go index 251aec47b4..c1d37671d9 100644 --- a/src/pkg/reg/manager.go +++ b/src/pkg/reg/manager.go @@ -22,6 +22,8 @@ import ( "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/reg/adapter" + "github.com/goharbor/harbor/src/pkg/reg/dao" + "github.com/goharbor/harbor/src/pkg/reg/model" // register the AliACR adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/aliacr" @@ -51,8 +53,8 @@ import ( _ "github.com/goharbor/harbor/src/pkg/reg/adapter/quay" // register the TencentCloud TCR adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/tencentcr" - "github.com/goharbor/harbor/src/pkg/reg/dao" - "github.com/goharbor/harbor/src/pkg/reg/model" + // register the VolcEngine CR Registry adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/volcenginecr" ) var ( diff --git a/src/pkg/reg/model/registry.go b/src/pkg/reg/model/registry.go index 6c8a09cc41..1b4f945d71 100644 --- a/src/pkg/reg/model/registry.go +++ b/src/pkg/reg/model/registry.go @@ -34,6 +34,7 @@ const ( RegistryTypeDTR = "dtr" RegistryTypeTencentTcr = "tencent-tcr" RegistryTypeGithubCR = "github-ghcr" + RegistryTypeVolcCR = "volcengine-cr" RegistryTypeHelmHub = "helm-hub" RegistryTypeArtifactHub = "artifact-hub" diff --git a/src/pkg/scan/dao/scan/report.go b/src/pkg/scan/dao/scan/report.go index f30b36ddb7..8da0e67af8 100644 --- a/src/pkg/scan/dao/scan/report.go +++ b/src/pkg/scan/dao/scan/report.go @@ -16,6 +16,7 @@ package scan import ( "context" + "fmt" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/orm" @@ -38,6 +39,8 @@ type DAO interface { UpdateReportData(ctx context.Context, uuid string, report string) error // Update update report Update(ctx context.Context, r *Report, cols ...string) error + // DeleteByExtraAttr delete the scan_report by mimeType and extra attribute + DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error } // New returns an instance of the default DAO @@ -110,3 +113,14 @@ func (d *dao) Update(ctx context.Context, r *Report, cols ...string) error { } return nil } + +func (d *dao) DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error { + o, err := orm.FromContext(ctx) + if err != nil { + return err + } + delReportSQL := "delete from scan_report where mime_type = ? and report::jsonb @> ?" + dgstJSONStr := fmt.Sprintf(`{"%s":"%s"}`, attrName, attrValue) + _, err = o.Raw(delReportSQL, mimeType, dgstJSONStr).Exec() + return err +} diff --git a/src/pkg/scan/dao/scan/report_test.go b/src/pkg/scan/dao/scan/report_test.go index ccda8e02bc..ab46622894 100644 --- a/src/pkg/scan/dao/scan/report_test.go +++ b/src/pkg/scan/dao/scan/report_test.go @@ -53,14 +53,23 @@ func (suite *ReportTestSuite) SetupTest() { RegistrationUUID: "ruuid", MimeType: v1.MimeTypeNativeReport, } - suite.create(r) + sbomReport := &Report{ + UUID: "uuid3", + Digest: "digest1003", + RegistrationUUID: "ruuid", + MimeType: v1.MimeTypeSBOMReport, + Report: `{"sbom_digest": "sha256:abc"}`, + } + suite.create(sbomReport) } // TearDownTest clears enf for test case. func (suite *ReportTestSuite) TearDownTest() { _, err := suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid"}}) require.NoError(suite.T(), err) + _, err = suite.dao.DeleteMany(orm.Context(), q.Query{Keywords: q.KeyWords{"uuid": "uuid3"}}) + require.NoError(suite.T(), err) } // TestReportList tests list reports with query parameters. @@ -95,7 +104,7 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() { err := suite.dao.UpdateReportData(orm.Context(), "uuid", "{}") suite.Require().NoError(err) - l, err := suite.dao.List(orm.Context(), nil) + l, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"uuid": "uuid"})) suite.Require().NoError(err) suite.Require().Equal(1, len(l)) suite.Equal("{}", l[0].Report) @@ -104,6 +113,17 @@ func (suite *ReportTestSuite) TestReportUpdateReportData() { suite.Require().NoError(err) } +func (suite *ReportTestSuite) TestDeleteReportBySBOMDigest() { + l, err := suite.dao.List(orm.Context(), nil) + suite.Require().NoError(err) + suite.Equal(2, len(l)) + err = suite.dao.DeleteByExtraAttr(orm.Context(), v1.MimeTypeSBOMReport, "sbom_digest", "sha256:abc") + suite.Require().NoError(err) + l2, err := suite.dao.List(orm.Context(), nil) + suite.Require().NoError(err) + suite.Equal(1, len(l2)) +} + func (suite *ReportTestSuite) create(r *Report) { id, err := suite.dao.Create(orm.Context(), r) suite.Require().NoError(err) diff --git a/src/pkg/scan/dao/scanner/model.go b/src/pkg/scan/dao/scanner/model.go index dbdcf8b1df..cc418e624f 100644 --- a/src/pkg/scan/dao/scanner/model.go +++ b/src/pkg/scan/dao/scanner/model.go @@ -66,8 +66,9 @@ type Registration struct { Metadata *v1.ScannerAdapterMetadata `orm:"-" json:"-"` // Timestamps - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` + Capabilities map[string]interface{} `orm:"-" json:"capabilities,omitempty"` } // TableName for Endpoint @@ -151,15 +152,20 @@ func (r *Registration) HasCapability(manifestMimeType string) bool { } // GetProducesMimeTypes returns produces mime types for the artifact -func (r *Registration) GetProducesMimeTypes(mimeType string) []string { +func (r *Registration) GetProducesMimeTypes(mimeType string, scanType string) []string { if r.Metadata == nil { return nil } - for _, capability := range r.Metadata.Capabilities { - for _, mt := range capability.ConsumesMimeTypes { - if mt == mimeType { - return capability.ProducesMimeTypes + capType := capability.Type + if len(capType) == 0 { + capType = v1.ScanTypeVulnerability + } + if scanType == capType { + for _, mt := range capability.ConsumesMimeTypes { + if mt == mimeType { + return capability.ProducesMimeTypes + } } } } diff --git a/src/pkg/scan/export/digest_calculator.go b/src/pkg/scan/export/digest_calculator.go index 5c3d50d22c..a5f46f2be1 100644 --- a/src/pkg/scan/export/digest_calculator.go +++ b/src/pkg/scan/export/digest_calculator.go @@ -35,6 +35,7 @@ func (calc *SHA256ArtifactDigestCalculator) Calculate(fileName string) (digest.D if err != nil { return "", err } + defer file.Close() hash := sha256.New() if _, err := io.Copy(hash, file); err != nil { return "", err diff --git a/src/pkg/scan/handler.go b/src/pkg/scan/handler.go new file mode 100644 index 0000000000..f402107c70 --- /dev/null +++ b/src/pkg/scan/handler.go @@ -0,0 +1,51 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scan + +import ( + "time" + + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +var handlerRegistry = map[string]Handler{} + +// RegisterScanHanlder register scanner handler +func RegisterScanHanlder(requestType string, handler Handler) { + handlerRegistry[requestType] = handler +} + +// GetScanHandler get the handler +func GetScanHandler(requestType string) Handler { + return handlerRegistry[requestType] +} + +// Handler handler for scan job, it could be implement by different scan type, such as vulnerability, sbom +type Handler interface { + // RequestProducesMineTypes returns the produces mime types + RequestProducesMineTypes() []string + // RequiredPermissions defines the permission used by the scan robot account + RequiredPermissions() []*types.Policy + // RequestParameters defines the parameters for scan request + RequestParameters() map[string]interface{} + // ReportURLParameter defines the parameters for scan report + ReportURLParameter(sr *v1.ScanRequest) (string, error) + // PostScan defines the operation after scan + PostScan(ctx job.Context, sr *v1.ScanRequest, rp *scan.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) +} diff --git a/src/pkg/scan/job.go b/src/pkg/scan/job.go index b1ad1905d4..c386628724 100644 --- a/src/pkg/scan/job.go +++ b/src/pkg/scan/job.go @@ -16,6 +16,7 @@ package scan import ( "bytes" + "context" "encoding/base64" "encoding/json" "fmt" @@ -34,8 +35,8 @@ import ( "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" - "github.com/goharbor/harbor/src/pkg/scan/postprocessors" "github.com/goharbor/harbor/src/pkg/scan/report" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" ) @@ -145,6 +146,7 @@ func (j *Job) Validate(params job.Parameters) error { func (j *Job) Run(ctx job.Context, params job.Parameters) error { // Get logger myLogger := ctx.GetLogger() + startTime := time.Now() // shouldStop checks if the job should be stopped shouldStop := func() bool { @@ -160,6 +162,11 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { r, _ := extractRegistration(params) req, _ := ExtractScanReq(params) mimeTypes, _ := extractMimeTypes(params) + scanType := v1.ScanTypeVulnerability + if len(req.RequestType) > 0 { + scanType = req.RequestType[0].Type + } + handler := GetScanHandler(scanType) // Print related infos to log printJSONParameter(JobParamRegistration, removeRegistrationAuthInfo(r), myLogger) @@ -236,29 +243,24 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { myLogger.Debugf("check scan report for mime %s at %s", m, t.Format("2006/01/02 15:04:05")) - rawReport, err := client.GetScanReport(resp.ID, m) + reportURLParameter, err := handler.ReportURLParameter(req) + if err != nil { + errs[i] = errors.Wrap(err, "scan job: get report url") + return + } + rawReport, err := fetchScanReportFromScanner(client, resp.ID, m, reportURLParameter) if err != nil { // Not ready yet if notReadyErr, ok := err.(*v1.ReportNotReadyError); ok { // Reset to the new check interval tm.Reset(time.Duration(notReadyErr.RetryAfter) * time.Second) myLogger.Infof("Report with mime type %s is not ready yet, retry after %d seconds", m, notReadyErr.RetryAfter) - continue } - - errs[i] = errors.Wrap(err, fmt.Sprintf("check scan report with mime type %s", m)) + errs[i] = errors.Wrap(err, fmt.Sprintf("scan job: fetch scan report, mimetype %v", m)) return } - - // Make sure the data is aligned with the v1 spec. - if _, err = report.ResolveData(m, []byte(rawReport)); err != nil { - errs[i] = errors.Wrap(err, "scan job: resolve report data") - return - } - rawReports[i] = rawReport - return case <-ctx.SystemContext().Done(): // Terminated by system @@ -292,33 +294,19 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { // Log error to the job log if err != nil { myLogger.Error(err) - return err } for i, mimeType := range mimeTypes { - reports, err := report.Mgr.GetBy(ctx.SystemContext(), req.Artifact.Digest, r.UUID, []string{mimeType}) + rp, err := getReportPlaceholder(ctx.SystemContext(), req.Artifact.Digest, r.UUID, mimeType, myLogger) if err != nil { - myLogger.Error("Failed to get report for artifact %s of mimetype %s, error %v", req.Artifact.Digest, mimeType, err) - return err } + myLogger.Debugf("Converting report ID %s to the new V2 schema", rp.UUID) - if len(reports) == 0 { - myLogger.Error("No report found for artifact %s of mimetype %s, error %v", req.Artifact.Digest, mimeType, err) - - return errors.NotFoundError(nil).WithMessage("no report found to update data") - } - - rp := reports[0] - - logger.Debugf("Converting report ID %s to the new V2 schema", rp.UUID) - - // use a new ormer here to use the short db connection - _, reportData, err := postprocessors.Converter.ToRelationalSchema(ctx.SystemContext(), rp.UUID, rp.RegistrationUUID, rp.Digest, rawReports[i]) + reportData, err := handler.PostScan(ctx, req, rp, rawReports[i], startTime, robotAccount) if err != nil { myLogger.Errorf("Failed to convert vulnerability data to new schema for report %s, error %v", rp.UUID, err) - return err } @@ -328,7 +316,6 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { // would be redundant if err := report.Mgr.UpdateReportData(ctx.SystemContext(), rp.UUID, reportData); err != nil { myLogger.Errorf("Failed to update report data for report %s, error %v", rp.UUID, err) - return err } @@ -338,6 +325,31 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { return nil } +func getReportPlaceholder(ctx context.Context, digest string, reportUUID string, mimeType string, logger logger.Interface) (*scan.Report, error) { + reports, err := report.Mgr.GetBy(ctx, digest, reportUUID, []string{mimeType}) + if err != nil { + logger.Error("Failed to get report for artifact %s of mimetype %s, error %v", digest, mimeType, err) + return nil, err + } + if len(reports) == 0 { + logger.Errorf("No report found for artifact %s of mimetype %s, error %v", digest, mimeType, err) + return nil, errors.NotFoundError(nil).WithMessage("no report found to update data") + } + return reports[0], nil +} + +func fetchScanReportFromScanner(client v1.Client, requestID string, mimType string, urlParameter string) (rawReport string, err error) { + rawReport, err = client.GetScanReport(requestID, mimType, urlParameter) + if err != nil { + return "", err + } + // Make sure the data is aligned with the v1 spec. + if _, err = report.ResolveData(mimType, []byte(rawReport)); err != nil { + return "", err + } + return rawReport, nil +} + // ExtractScanReq extracts the scan request from the job parameters. func ExtractScanReq(params job.Parameters) (*v1.ScanRequest, error) { v, ok := params[JobParameterRequest] @@ -361,7 +373,20 @@ func ExtractScanReq(params job.Parameters) (*v1.ScanRequest, error) { if err := req.Validate(); err != nil { return nil, err } - + reqType := v1.ScanTypeVulnerability + // attach the request with ProducesMimeTypes and Parameters + if len(req.RequestType) > 0 { + // current only support requestType with one element for each request + if len(req.RequestType[0].Type) > 0 { + reqType = req.RequestType[0].Type + } + handler := GetScanHandler(reqType) + if handler == nil { + return nil, errors.Errorf("failed to get scan handler, request type %v", reqType) + } + req.RequestType[0].ProducesMimeTypes = handler.RequestProducesMineTypes() + req.RequestType[0].Parameters = handler.RequestParameters() + } return req, nil } @@ -394,6 +419,7 @@ func removeScanAuthInfo(sr *v1.ScanRequest) string { URL: sr.Registry.URL, Authorization: "[HIDDEN]", }, + RequestType: sr.RequestType, } str, err := req.ToJSON() diff --git a/src/pkg/scan/job_test.go b/src/pkg/scan/job_test.go index d29c35fde4..ff00dd20f5 100644 --- a/src/pkg/scan/job_test.go +++ b/src/pkg/scan/job_test.go @@ -19,15 +19,19 @@ import ( "testing" "time" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/goharbor/harbor/src/controller/robot" "github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + "github.com/goharbor/harbor/src/pkg/scan/report" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/scan/vuln" + htesting "github.com/goharbor/harbor/src/testing" mockjobservice "github.com/goharbor/harbor/src/testing/jobservice" mocktesting "github.com/goharbor/harbor/src/testing/mock" v1testing "github.com/goharbor/harbor/src/testing/pkg/scan/rest/v1" @@ -35,10 +39,11 @@ import ( // JobTestSuite is a test suite to test the scan job. type JobTestSuite struct { - suite.Suite + htesting.Suite defaultClientPool v1.ClientPool mcp *v1testing.ClientPool + reportIDs []string } // TestJob is the entry of JobTestSuite. @@ -48,6 +53,7 @@ func TestJob(t *testing.T) { // SetupSuite sets up test env for JobTestSuite. func (suite *JobTestSuite) SetupSuite() { + suite.Suite.SetupSuite() mcp := &v1testing.ClientPool{} suite.defaultClientPool = v1.DefaultClientPool v1.DefaultClientPool = mcp @@ -55,9 +61,12 @@ func (suite *JobTestSuite) SetupSuite() { suite.mcp = mcp } -// TeraDownSuite clears test env for TeraDownSuite. -func (suite *JobTestSuite) TeraDownSuite() { +// TearDownSuite clears test env for TearDownSuite. +func (suite *JobTestSuite) TearDownSuite() { v1.DefaultClientPool = suite.defaultClientPool + for _, id := range suite.reportIDs { + _ = report.Mgr.Delete(suite.Context(), id) + } } // TestJob tests the scan job @@ -151,3 +160,60 @@ func (suite *JobTestSuite) TestJob() { err = j.Run(ctx, jp) require.NoError(suite.T(), err) } + +func (suite *JobTestSuite) TestgetReportPlaceholder() { + dgst := "sha256:mydigest" + uuid := `7f20b1b9-6117-4a2e-820b-e4cc0401f15e` + scannerUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15f` + rpt := &scan.Report{ + UUID: uuid, + RegistrationUUID: scannerUUID, + Digest: dgst, + MimeType: v1.MimeTypeDockerArtifact, + } + ctx := suite.Context() + rptID, err := report.Mgr.Create(ctx, rpt) + suite.reportIDs = append(suite.reportIDs, rptID) + require.NoError(suite.T(), err) + jobLogger := &mockjobservice.MockJobLogger{} + report, err := getReportPlaceholder(ctx, dgst, scannerUUID, v1.MimeTypeDockerArtifact, jobLogger) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), report) +} + +func (suite *JobTestSuite) TestfetchScanReportFromScanner() { + vulnRpt := &vuln.Report{ + GeneratedAt: time.Now().UTC().String(), + Scanner: &v1.Scanner{ + Name: "Trivy", + Vendor: "Harbor", + Version: "0.1.0", + }, + Severity: vuln.High, + } + rptContent, err := json.Marshal(vulnRpt) + require.NoError(suite.T(), err) + rawContent := string(rptContent) + ctx := suite.Context() + dgst := "sha256:mydigest" + uuid := `7f20b1b9-6117-4a2e-820b-e4cc0401f15a` + scannerUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15b` + rpt := &scan.Report{ + UUID: uuid, + RegistrationUUID: scannerUUID, + Digest: dgst, + MimeType: v1.MimeTypeDockerArtifact, + Report: rawContent, + } + + ctx = suite.Context() + rptID, err := report.Mgr.Create(ctx, rpt) + suite.reportIDs = append(suite.reportIDs, rptID) + require.NoError(suite.T(), err) + client := &v1testing.Client{} + client.On("GetScanReport", mock.Anything, v1.MimeTypeGenericVulnerabilityReport, mock.Anything).Return(rawContent, nil) + parameters := "sbom_media_type=application/spdx+json" + rawRept, err := fetchScanReportFromScanner(client, "abc", v1.MimeTypeGenericVulnerabilityReport, parameters) + require.NoError(suite.T(), err) + require.Equal(suite.T(), rawContent, rawRept) +} diff --git a/src/pkg/scan/postprocessors/report_converters.go b/src/pkg/scan/postprocessors/report_converters.go index 538b75ce7a..0a91f41ad3 100644 --- a/src/pkg/scan/postprocessors/report_converters.go +++ b/src/pkg/scan/postprocessors/report_converters.go @@ -354,25 +354,28 @@ func (c *nativeToRelationalSchemaConverter) updateReport(ctx context.Context, vu return report.Mgr.Update(ctx, r, "CriticalCnt", "HighCnt", "MediumCnt", "LowCnt", "NoneCnt", "UnknownCnt", "FixableCnt") } -// CVSS ... -type CVSS struct { - NVD Nvd `json:"nvd"` +// CVS ... +type CVS struct { + CVSS map[string]map[string]interface{} `json:"CVSS"` } -// Nvd ... -type Nvd struct { - V3Score float64 `json:"V3Score"` -} - -func parseScoreFromVendorAttribute(ctx context.Context, vendorAttribute string) (NvdV3Score float64) { - var data map[string]CVSS +func parseScoreFromVendorAttribute(ctx context.Context, vendorAttribute string) float64 { + var data CVS err := json.Unmarshal([]byte(vendorAttribute), &data) if err != nil { log.G(ctx).Errorf("failed to parse vendor_attribute, error %v", err) return 0 } - if cvss, ok := data["CVSS"]; ok { - return cvss.NVD.V3Score + + // set the nvd as the first priority, if it's unavailable, return the first V3Score available. + if val, ok := data.CVSS["nvd"]["V3Score"]; ok { + return val.(float64) + } + + for vendor := range data.CVSS { + if val, ok := data.CVSS[vendor]["V3Score"]; ok { + return val.(float64) + } } return 0 } diff --git a/src/pkg/scan/postprocessors/report_converters_test.go b/src/pkg/scan/postprocessors/report_converters_test.go index 057113a479..32b7660fa1 100644 --- a/src/pkg/scan/postprocessors/report_converters_test.go +++ b/src/pkg/scan/postprocessors/report_converters_test.go @@ -578,6 +578,8 @@ func Test_parseScoreFromVendorAttribute(t *testing.T) { {"both", args{`{"CVSS":{"nvd":{"V3Score":5.5,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"},"redhat":{"V3Score":6.2,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}}}`}, 5.5}, {"both2", args{`{"CVSS":{"nvd":{"V2Score":7.2,"V2Vector":"AV:L/AC:L/Au:N/C:C/I:C/A:C","V3Score":7.8,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},"redhat":{"V3Score":7.8,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 7.8}, {"none", args{`{"CVSS":{"nvd":{"V2Score":7.2,"V2Vector":"AV:L/AC:L/Au:N/C:C/I:C/A:C","V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},"redhat":{"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 0}, + {"redhatonly", args{`{"CVSS":{"redhat":{"V3Score":8.8, "V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 8.8}, + {"nvdnov3butredhat", args{`{"CVSS":{"nvd":{"V2Score":7.2,"V2Vector":"AV:L/AC:L/Au:N/C:C/I:C/A:C"},"redhat":{"V3Score":7.8,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 7.8}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/src/pkg/scan/report/manager.go b/src/pkg/scan/report/manager.go index fa6415ed0b..3bc2de1f11 100644 --- a/src/pkg/scan/report/manager.go +++ b/src/pkg/scan/report/manager.go @@ -104,6 +104,8 @@ type Manager interface { // Update update report information Update(ctx context.Context, r *scan.Report, cols ...string) error + // DeleteByExtraAttr delete scan_report by sbom_digest + DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error } // basicManager is a default implementation of report manager. @@ -226,3 +228,7 @@ func (bm *basicManager) List(ctx context.Context, query *q.Query) ([]*scan.Repor func (bm *basicManager) Update(ctx context.Context, r *scan.Report, cols ...string) error { return bm.dao.Update(ctx, r, cols...) } + +func (bm *basicManager) DeleteByExtraAttr(ctx context.Context, mimeType, attrName, attrValue string) error { + return bm.dao.DeleteByExtraAttr(ctx, mimeType, attrName, attrValue) +} diff --git a/src/pkg/scan/rest/v1/client.go b/src/pkg/scan/rest/v1/client.go index 251ccef1f8..a5ae04075f 100644 --- a/src/pkg/scan/rest/v1/client.go +++ b/src/pkg/scan/rest/v1/client.go @@ -68,7 +68,7 @@ type Client interface { // Returns: // string : the scan report of the given artifact // error : non nil error if any errors occurred - GetScanReport(scanRequestID, reportMIMEType string) (string, error) + GetScanReport(scanRequestID, reportMIMEType string, urlParameter string) (string, error) } // basicClient is default implementation of the Client interface @@ -97,7 +97,7 @@ func NewClient(url, authType, accessCredential string, skipCertVerify bool) (Cli httpClient: &http.Client{ Timeout: time.Second * 5, Transport: transport, - CheckRedirect: func(req *http.Request, via []*http.Request) error { + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse }, }, @@ -167,7 +167,7 @@ func (c *basicClient) SubmitScan(req *ScanRequest) (*ScanResponse, error) { } // GetScanReport ... -func (c *basicClient) GetScanReport(scanRequestID, reportMIMEType string) (string, error) { +func (c *basicClient) GetScanReport(scanRequestID, reportMIMEType string, urlParameter string) (string, error) { if len(scanRequestID) == 0 { return "", errors.New("empty scan request ID") } @@ -177,8 +177,11 @@ func (c *basicClient) GetScanReport(scanRequestID, reportMIMEType string) (strin } def := c.spec.GetScanReport(scanRequestID, reportMIMEType) - - req, err := http.NewRequest(http.MethodGet, def.URL, nil) + reportURL := def.URL + if len(urlParameter) > 0 { + reportURL = fmt.Sprintf("%s?%s", def.URL, urlParameter) + } + req, err := http.NewRequest(http.MethodGet, reportURL, nil) if err != nil { return "", errors.Wrap(err, "v1 client: get scan report") } diff --git a/src/pkg/scan/rest/v1/client_test.go b/src/pkg/scan/rest/v1/client_test.go index e3ee8ae49f..5893514d63 100644 --- a/src/pkg/scan/rest/v1/client_test.go +++ b/src/pkg/scan/rest/v1/client_test.go @@ -58,6 +58,7 @@ func (suite *ClientTestSuite) TestClientMetadata() { require.NotNil(suite.T(), m) assert.Equal(suite.T(), m.Scanner.Name, "Trivy") + assert.Equal(suite.T(), m.Capabilities[0].Type, "sbom") } // TestClientSubmitScan tests the scan submission of client @@ -71,7 +72,7 @@ func (suite *ClientTestSuite) TestClientSubmitScan() { // TestClientGetScanReportError tests getting report failed func (suite *ClientTestSuite) TestClientGetScanReportError() { - _, err := suite.client.GetScanReport("id1", MimeTypeNativeReport) + _, err := suite.client.GetScanReport("id1", MimeTypeNativeReport, "") require.Error(suite.T(), err) assert.Condition(suite.T(), func() (success bool) { success = strings.Index(err.Error(), "error") != -1 @@ -81,14 +82,14 @@ func (suite *ClientTestSuite) TestClientGetScanReportError() { // TestClientGetScanReport tests getting report func (suite *ClientTestSuite) TestClientGetScanReport() { - res, err := suite.client.GetScanReport("id2", MimeTypeNativeReport) + res, err := suite.client.GetScanReport("id2", MimeTypeNativeReport, "") require.NoError(suite.T(), err) require.NotEmpty(suite.T(), res) } // TestClientGetScanReportNotReady tests the case that the report is not ready func (suite *ClientTestSuite) TestClientGetScanReportNotReady() { - _, err := suite.client.GetScanReport("id3", MimeTypeNativeReport) + _, err := suite.client.GetScanReport("id3", MimeTypeNativeReport, "") require.Error(suite.T(), err) require.Condition(suite.T(), func() (success bool) { _, success = err.(*ReportNotReadyError) @@ -119,6 +120,7 @@ func (mh *mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { Version: "0.1.0", }, Capabilities: []*ScannerCapability{{ + Type: "sbom", ConsumesMimeTypes: []string{ MimeTypeOCIArtifact, MimeTypeDockerArtifact, diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index c7579cd746..06e6fb0a1c 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -21,6 +21,17 @@ import ( "github.com/goharbor/harbor/src/lib/errors" ) +const ( + supportVulnerability = "support_vulnerability" + supportSBOM = "support_sbom" +) + +var supportedMimeTypes = []string{ + MimeTypeNativeReport, + MimeTypeGenericVulnerabilityReport, + MimeTypeSBOMReport, +} + // Scanner represents metadata of a Scanner Adapter which allow Harbor to lookup a scanner capable of // scanning a given Artifact stored in its registry and making sure that it can interpret a // returned result. @@ -37,6 +48,7 @@ type Scanner struct { // report MIME types. For example, a scanner capable of analyzing Docker images and producing // a vulnerabilities report recognizable by Harbor web console might be represented with the // following capability: +// - type: vulnerability // - consumes MIME types: // -- application/vnd.oci.image.manifest.v1+json // -- application/vnd.docker.distribution.manifest.v2+json @@ -44,6 +56,8 @@ type Scanner struct { // -- application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0 // -- application/vnd.scanner.adapter.vuln.report.raw type ScannerCapability struct { + // The type of the scanner capability, vulnerability or sbom + Type string `json:"type"` // The set of MIME types of the artifacts supported by the scanner to produce the reports // specified in the "produces_mime_types". A given mime type should only be present in one // capability item. @@ -95,7 +109,7 @@ func (md *ScannerAdapterMetadata) Validate() error { // either of v1.MimeTypeNativeReport OR v1.MimeTypeGenericVulnerabilityReport is required found = false for _, pm := range ca.ProducesMimeTypes { - if pm == MimeTypeNativeReport || pm == MimeTypeGenericVulnerabilityReport { + if isSupportedMimeType(pm) { found = true break } @@ -109,6 +123,15 @@ func (md *ScannerAdapterMetadata) Validate() error { return nil } +func isSupportedMimeType(mimeType string) bool { + for _, mt := range supportedMimeTypes { + if mt == mimeType { + return true + } + } + return false +} + // HasCapability returns true when mine type of the artifact support by the scanner func (md *ScannerAdapterMetadata) HasCapability(mimeType string) bool { for _, capability := range md.Capabilities { @@ -135,6 +158,28 @@ func (md *ScannerAdapterMetadata) GetCapability(mimeType string) *ScannerCapabil return nil } +// ConvertCapability converts the capability to map, used in get scanner API +func (md *ScannerAdapterMetadata) ConvertCapability() map[string]interface{} { + capabilities := make(map[string]interface{}) + oldScanner := true + for _, c := range md.Capabilities { + if len(c.Type) > 0 { + oldScanner = false + } + if c.Type == ScanTypeVulnerability { + capabilities[supportVulnerability] = true + } else if c.Type == ScanTypeSbom { + capabilities[supportSBOM] = true + } + } + if oldScanner && len(capabilities) == 0 { + // to compatible with old version scanner, suppose they should always support scan vulnerability when capability is empty + capabilities[supportVulnerability] = true + capabilities[supportSBOM] = false + } + return capabilities +} + // Artifact represents an artifact stored in Registry. type Artifact struct { // ID of the namespace (project). It will not be sent to scanner adapter. @@ -150,6 +195,8 @@ type Artifact struct { Digest string `json:"digest"` // The mime type of the scanned artifact MimeType string `json:"mime_type"` + // The size the scanned artifact + Size int64 `json:"size"` } // Registry represents Registry connection settings. @@ -159,6 +206,8 @@ type Registry struct { // An optional value of the HTTP Authorization header sent with each request to the Docker Registry for getting or exchanging token. // For example, `Basic: Base64(username:password)`. Authorization string `json:"authorization"` + // Insecure is an indicator of https or http. + Insecure bool `json:"insecure"` } // ScanRequest represents a structure that is sent to a Scanner Adapter to initiate artifact scanning. @@ -168,6 +217,18 @@ type ScanRequest struct { Registry *Registry `json:"registry"` // Artifact to be scanned. Artifact *Artifact `json:"artifact"` + // RequestType + RequestType []*ScanType `json:"enabled_capabilities"` +} + +// ScanType represent the type of the scan request +type ScanType struct { + // Type sets the type of the scan, it could be sbom or vulnerability, default is vulnerability + Type string `json:"type"` + // ProducesMimeTypes defines scanreport should be + ProducesMimeTypes []string `json:"produces_mime_types"` + // Parameters extra parameters + Parameters map[string]interface{} `json:"parameters"` } // FromJSON parses ScanRequest from json data diff --git a/src/pkg/scan/rest/v1/models_test.go b/src/pkg/scan/rest/v1/models_test.go new file mode 100644 index 0000000000..96590bc4c6 --- /dev/null +++ b/src/pkg/scan/rest/v1/models_test.go @@ -0,0 +1,41 @@ +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsSupportedMimeType(t *testing.T) { + // Test with a supported mime type + assert.True(t, isSupportedMimeType(MimeTypeSBOMReport), "isSupportedMimeType should return true for supported mime types") + + // Test with an unsupported mime type + assert.False(t, isSupportedMimeType("unsupported/mime-type"), "isSupportedMimeType should return false for unsupported mime types") +} + +func TestConvertCapability(t *testing.T) { + md := &ScannerAdapterMetadata{ + Capabilities: []*ScannerCapability{ + {Type: ScanTypeSbom}, + {Type: ScanTypeVulnerability}, + }, + } + result := md.ConvertCapability() + assert.Equal(t, result[supportSBOM], true) + assert.Equal(t, result[supportVulnerability], true) +} + +func TestConvertCapabilityOldScaner(t *testing.T) { + md := &ScannerAdapterMetadata{ + Capabilities: []*ScannerCapability{ + { + ConsumesMimeTypes: []string{"application/vnd.oci.image.manifest.v1+json", "application/vnd.docker.distribution.manifest.v2+json"}, + ProducesMimeTypes: []string{MimeTypeNativeReport}, + }, + }, + } + result := md.ConvertCapability() + assert.Equal(t, result[supportSBOM], false) + assert.Equal(t, result[supportVulnerability], true) +} diff --git a/src/pkg/scan/rest/v1/spec.go b/src/pkg/scan/rest/v1/spec.go index bb46d1c1b9..a867e71674 100644 --- a/src/pkg/scan/rest/v1/spec.go +++ b/src/pkg/scan/rest/v1/spec.go @@ -39,9 +39,14 @@ const ( MimeTypeScanRequest = "application/vnd.scanner.adapter.scan.request+json; version=1.0" // MimeTypeScanResponse defines the mime type for scan response MimeTypeScanResponse = "application/vnd.scanner.adapter.scan.response+json; version=1.0" + // MimeTypeSBOMReport + MimeTypeSBOMReport = "application/vnd.security.sbom.report+json; version=1.0" // MimeTypeGenericVulnerabilityReport defines the MIME type for the generic report with enhanced information MimeTypeGenericVulnerabilityReport = "application/vnd.security.vulnerability.report; version=1.1" + ScanTypeVulnerability = "vulnerability" + ScanTypeSbom = "sbom" + apiPrefix = "/api/v1" ) diff --git a/src/pkg/scan/sbom/model/summary.go b/src/pkg/scan/sbom/model/summary.go new file mode 100644 index 0000000000..0d7e6a2ef2 --- /dev/null +++ b/src/pkg/scan/sbom/model/summary.go @@ -0,0 +1,47 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +const ( + // SBOMRepository ... + SBOMRepository = "sbom_repository" + // SBOMDigest ... + SBOMDigest = "sbom_digest" + // StartTime ... + StartTime = "start_time" + // EndTime ... + EndTime = "end_time" + // Duration ... + Duration = "duration" + // ScanStatus ... + ScanStatus = "scan_status" + // ReportID ... + ReportID = "report_id" + // Scanner ... + Scanner = "scanner" +) + +// Summary includes the sbom summary information +type Summary map[string]interface{} + +// SBOMAccArt returns the repository and digest of the SBOM +func (s Summary) SBOMAccArt() (repo, digest string) { + if repo, ok := s[SBOMRepository].(string); ok { + if digest, ok := s[SBOMDigest].(string); ok { + return repo, digest + } + } + return "", "" +} diff --git a/src/pkg/scan/sbom/sbom.go b/src/pkg/scan/sbom/sbom.go new file mode 100644 index 0000000000..9a819d1236 --- /dev/null +++ b/src/pkg/scan/sbom/sbom.go @@ -0,0 +1,166 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" + + "github.com/goharbor/harbor/src/common" + "github.com/goharbor/harbor/src/lib/config" + scanModel "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + sbom "github.com/goharbor/harbor/src/pkg/scan/sbom/model" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan" + + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + "github.com/goharbor/harbor/src/pkg/scan/vuln" +) + +const ( + sbomMimeType = "application/vnd.goharbor.harbor.sbom.v1" + sbomMediaTypeSpdx = "application/spdx+json" +) + +func init() { + scan.RegisterScanHanlder(v1.ScanTypeSbom, &scanHandler{GenAccessoryFunc: scan.GenAccessoryArt, RegistryServer: registry}) +} + +// ScanHandler defines the Handler to generate sbom +type scanHandler struct { + GenAccessoryFunc func(scanRep v1.ScanRequest, sbomContent []byte, labels map[string]string, mediaType string, robot *model.Robot) (string, error) + RegistryServer func(ctx context.Context) (string, bool) +} + +// RequestProducesMineTypes defines the mine types produced by the scan handler +func (v *scanHandler) RequestProducesMineTypes() []string { + return []string{v1.MimeTypeSBOMReport} +} + +// RequestParameters defines the parameters for scan request +func (v *scanHandler) RequestParameters() map[string]interface{} { + return map[string]interface{}{"sbom_media_types": []string{sbomMediaTypeSpdx}} +} + +// ReportURLParameter defines the parameters for scan report url +func (v *scanHandler) ReportURLParameter(_ *v1.ScanRequest) (string, error) { + return fmt.Sprintf("sbom_media_type=%s", url.QueryEscape(sbomMediaTypeSpdx)), nil +} + +// RequiredPermissions defines the permission used by the scan robot account +func (v *scanHandler) RequiredPermissions() []*types.Policy { + return []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPush, + }, + } +} + +// PostScan defines task specific operations after the scan is complete +func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) { + sbomContent, s, err := retrieveSBOMContent(rawReport) + if err != nil { + return "", err + } + scanReq := v1.ScanRequest{ + Registry: sr.Registry, + Artifact: sr.Artifact, + } + // the registry server url is core by default, need to replace it with real registry server url + scanReq.Registry.URL, scanReq.Registry.Insecure = v.RegistryServer(ctx.SystemContext()) + if len(scanReq.Registry.URL) == 0 { + return "", fmt.Errorf("empty registry server") + } + myLogger := ctx.GetLogger() + myLogger.Debugf("Pushing accessory artifact to %s/%s", scanReq.Registry.URL, scanReq.Artifact.Repository) + dgst, err := v.GenAccessoryFunc(scanReq, sbomContent, v.annotations(), sbomMimeType, robot) + if err != nil { + myLogger.Errorf("error when create accessory from image %v", err) + return "", err + } + return v.generateReport(startTime, sr.Artifact.Repository, dgst, "Success", s) +} + +// annotations defines the annotations for the accessory artifact +func (v *scanHandler) annotations() map[string]string { + t := time.Now().Format(time.RFC3339) + return map[string]string{ + "created": t, + "created-by": "Harbor", + "org.opencontainers.artifact.created": t, + "org.opencontainers.artifact.description": "SPDX JSON SBOM", + } +} + +func (v *scanHandler) generateReport(startTime time.Time, repository, digest, status string, scanner *v1.Scanner) (string, error) { + summary := sbom.Summary{} + endTime := time.Now() + summary[sbom.StartTime] = startTime + summary[sbom.EndTime] = endTime + summary[sbom.Duration] = int64(endTime.Sub(startTime).Seconds()) + summary[sbom.SBOMRepository] = repository + summary[sbom.SBOMDigest] = digest + summary[sbom.ScanStatus] = status + summary[sbom.Scanner] = scanner + rep, err := json.Marshal(summary) + if err != nil { + return "", err + } + return string(rep), nil +} + +// extract server name from config, and remove the protocol prefix +func registry(ctx context.Context) (string, bool) { + cfgMgr, ok := config.FromContext(ctx) + if ok { + extURL := cfgMgr.Get(context.Background(), common.ExtEndpoint).GetString() + insecure := strings.HasPrefix(extURL, "http://") + server := strings.TrimPrefix(extURL, "https://") + server = strings.TrimPrefix(server, "http://") + return server, insecure + } + return "", false +} + +// retrieveSBOMContent retrieves the "sbom" field from the raw report +func retrieveSBOMContent(rawReport string) ([]byte, *v1.Scanner, error) { + rpt := vuln.Report{} + err := json.Unmarshal([]byte(rawReport), &rpt) + if err != nil { + return nil, nil, err + } + sbomContent, err := json.Marshal(rpt.SBOM) + if err != nil { + return nil, nil, err + } + return sbomContent, rpt.Scanner, nil +} diff --git a/src/pkg/scan/sbom/sbom_test.go b/src/pkg/scan/sbom/sbom_test.go new file mode 100644 index 0000000000..c1e0cd9721 --- /dev/null +++ b/src/pkg/scan/sbom/sbom_test.go @@ -0,0 +1,139 @@ +package sbom + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + "github.com/goharbor/harbor/src/testing/jobservice" + + "github.com/stretchr/testify/suite" +) + +func Test_scanHandler_ReportURLParameter(t *testing.T) { + type args struct { + in0 *v1.ScanRequest + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {"normal test", args{&v1.ScanRequest{}}, "sbom_media_type=application%2Fspdx%2Bjson", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &scanHandler{} + got, err := v.ReportURLParameter(tt.args.in0) + if (err != nil) != tt.wantErr { + t.Errorf("ReportURLParameter() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ReportURLParameter() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_scanHandler_RequiredPermissions(t *testing.T) { + tests := []struct { + name string + want []*types.Policy + }{ + {"normal test", []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPush, + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &scanHandler{} + if got := v.RequiredPermissions(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RequiredPermissions() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_scanHandler_RequestProducesMineTypes(t *testing.T) { + tests := []struct { + name string + want []string + }{ + {"normal test", []string{v1.MimeTypeSBOMReport}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &scanHandler{} + if got := v.RequestProducesMineTypes(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RequestProducesMineTypes() = %v, want %v", got, tt.want) + } + }) + } +} + +func mockGetRegistry(ctx context.Context) (string, bool) { + return "myharbor.example.com", false +} + +func mockGenAccessory(scanRep v1.ScanRequest, sbomContent []byte, labels map[string]string, mediaType string, robot *model.Robot) (string, error) { + return "sha256:1234567890", nil +} + +type ExampleTestSuite struct { + handler *scanHandler + suite.Suite +} + +func (suite *ExampleTestSuite) SetupSuite() { + suite.handler = &scanHandler{ + GenAccessoryFunc: mockGenAccessory, + RegistryServer: mockGetRegistry, + } +} + +func (suite *ExampleTestSuite) TearDownSuite() { +} + +func (suite *ExampleTestSuite) TestPostScan() { + req := &v1.ScanRequest{ + Registry: &v1.Registry{ + URL: "myregistry.example.com", + }, + Artifact: &v1.Artifact{ + Repository: "library/nosql", + }, + } + robot := &model.Robot{ + Name: "robot", + Secret: "mysecret", + } + startTime := time.Now() + rawReport := `{"sbom": { "key": "value" }}` + ctx := &jobservice.MockJobContext{} + ctx.On("GetLogger").Return(&jobservice.MockJobLogger{}) + accessory, err := suite.handler.PostScan(ctx, req, nil, rawReport, startTime, robot) + suite.Require().NoError(err) + suite.Require().NotEmpty(accessory) +} + +func TestExampleTestSuite(t *testing.T) { + suite.Run(t, &ExampleTestSuite{}) +} diff --git a/src/pkg/scan/util.go b/src/pkg/scan/util.go new file mode 100644 index 0000000000..e66e657f40 --- /dev/null +++ b/src/pkg/scan/util.go @@ -0,0 +1,100 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scan + +import ( + "crypto/tls" + "fmt" + "net/http" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/static" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/goharbor/harbor/src/pkg/robot/model" + v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +// Insecure ... +type Insecure bool + +// RemoteOptions ... +func (i Insecure) RemoteOptions() []remote.Option { + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: bool(i)} + return []remote.Option{remote.WithTransport(tr)} +} + +type referrer struct { + Insecure +} + +// GenAccessoryArt composes the accessory oci object and push it back to harbor core as an accessory of the scanned artifact. +func GenAccessoryArt(sq v1sq.ScanRequest, accData []byte, accAnnotations map[string]string, mediaType string, robot *model.Robot) (string, error) { + accArt, err := mutate.Append(empty.Image, mutate.Addendum{ + Layer: static.NewLayer(accData, ocispec.MediaTypeImageLayer), + History: v1.History{ + Author: "harbor", + CreatedBy: "harbor", + Created: v1.Time{}, // static + }, + }) + if err != nil { + return "", err + } + + dg, err := digest.Parse(sq.Artifact.Digest) + if err != nil { + return "", err + } + accSubArt := &v1.Descriptor{ + MediaType: types.MediaType(sq.Artifact.MimeType), + Size: sq.Artifact.Size, + Digest: v1.Hash{ + Algorithm: dg.Algorithm().String(), + Hex: dg.Hex(), + }, + } + // TODO to leverage the artifactType of distribution spec v1.1 to specify the sbom type. + // https://github.com/google/go-containerregistry/issues/1832 + accArt = mutate.MediaType(accArt, ocispec.MediaTypeImageManifest) + accArt = mutate.ConfigMediaType(accArt, types.MediaType(mediaType)) + accArt = mutate.Annotations(accArt, accAnnotations).(v1.Image) + accArt = mutate.Subject(accArt, *accSubArt).(v1.Image) + + dgst, err := accArt.Digest() + if err != nil { + return "", err + } + accRef, err := name.ParseReference(fmt.Sprintf("%s/%s@%s", sq.Registry.URL, sq.Artifact.Repository, dgst.String())) + if sq.Registry.Insecure { + accRef, err = name.ParseReference(fmt.Sprintf("%s/%s@%s", sq.Registry.URL, sq.Artifact.Repository, dgst.String()), name.Insecure) + } + if err != nil { + return "", err + } + opts := append(referrer{Insecure: true}.RemoteOptions(), remote.WithAuth(&authn.Basic{Username: robot.Name, Password: robot.Secret})) + if err := remote.Write(accRef, accArt, opts...); err != nil { + return "", err + } + return dgst.String(), nil +} diff --git a/src/pkg/scan/util_test.go b/src/pkg/scan/util_test.go new file mode 100644 index 0000000000..b53fad8348 --- /dev/null +++ b/src/pkg/scan/util_test.go @@ -0,0 +1,61 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scan + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/google/go-containerregistry/pkg/registry" + "github.com/stretchr/testify/assert" + + "github.com/goharbor/harbor/src/pkg/robot/model" + v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +func TestGenAccessoryArt(t *testing.T) { + server := httptest.NewServer(registry.New(registry.WithReferrersSupport(true))) + defer server.Close() + u, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + + sq := v1sq.ScanRequest{ + Registry: &v1sq.Registry{ + URL: u.Host, + }, + Artifact: &v1sq.Artifact{ + Repository: "library/hello-world", + Tag: "latest", + Size: 1234, + MimeType: "application/vnd.docker.distribution.manifest.v2+json", + Digest: "sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7", + }, + } + r := &model.Robot{ + Name: "admin", + Secret: "Harbor12345", + } + + annotations := map[string]string{ + "created-by": "trivy", + "org.opencontainers.artifact.description": "SPDX JSON SBOM", + } + s, err := GenAccessoryArt(sq, []byte(`{"name": "harborAccTest", "version": "1.0"}`), annotations, "application/vnd.goharbor.harbor.main.v1", r) + assert.Nil(t, err) + assert.Equal(t, "sha256:a39c6456d3cd1d87b7ee5706f67133d7a6d27a2dbc9ed66d50e504ff8920efc3", s) +} diff --git a/src/pkg/scan/vuln/report.go b/src/pkg/scan/vuln/report.go index 0f24737b9a..fbc119f644 100644 --- a/src/pkg/scan/vuln/report.go +++ b/src/pkg/scan/vuln/report.go @@ -33,6 +33,9 @@ type Report struct { Vulnerabilities []*VulnerabilityItem `json:"vulnerabilities"` vulnerabilityItemList *VulnerabilityItemList + + // SBOM sbom content + SBOM map[string]interface{} `json:"sbom,omitempty"` } // GetVulnerabilityItemList returns VulnerabilityItemList from the Vulnerabilities of report diff --git a/src/pkg/scan/vulnerability/vul.go b/src/pkg/scan/vulnerability/vul.go new file mode 100644 index 0000000000..2e9194c4a0 --- /dev/null +++ b/src/pkg/scan/vulnerability/vul.go @@ -0,0 +1,72 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vulnerability + +import ( + "time" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + scanJob "github.com/goharbor/harbor/src/pkg/scan" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/scan/postprocessors" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +func init() { + scanJob.RegisterScanHanlder(v1.ScanTypeVulnerability, &ScanHandler{}) +} + +// ScanHandler defines the handler for scan vulnerability +type ScanHandler struct { +} + +// RequestProducesMineTypes returns the produces mime types +func (v *ScanHandler) RequestProducesMineTypes() []string { + return []string{v1.MimeTypeGenericVulnerabilityReport} +} + +// RequestParameters defines the parameters for scan request +func (v *ScanHandler) RequestParameters() map[string]interface{} { + return nil +} + +// RequiredPermissions defines the permission used by the scan robot account +func (v *ScanHandler) RequiredPermissions() []*types.Policy { + return []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + } +} + +// ReportURLParameter vulnerability doesn't require any scan report parameters +func (v *ScanHandler) ReportURLParameter(_ *v1.ScanRequest) (string, error) { + return "", nil +} + +// PostScan ... +func (v *ScanHandler) PostScan(ctx job.Context, _ *v1.ScanRequest, origRp *scan.Report, rawReport string, _ time.Time, _ *model.Robot) (string, error) { + // use a new ormer here to use the short db connection + _, refreshedReport, err := postprocessors.Converter.ToRelationalSchema(ctx.SystemContext(), origRp.UUID, origRp.RegistrationUUID, origRp.Digest, rawReport) + return refreshedReport, err +} diff --git a/src/pkg/scan/vulnerability/vul_test.go b/src/pkg/scan/vulnerability/vul_test.go new file mode 100644 index 0000000000..003e15a0da --- /dev/null +++ b/src/pkg/scan/vulnerability/vul_test.go @@ -0,0 +1,116 @@ +package vulnerability + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/scan/postprocessors" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + "github.com/goharbor/harbor/src/testing/jobservice" + postprocessorstesting "github.com/goharbor/harbor/src/testing/pkg/scan/postprocessors" +) + +func TestRequiredPermissions(t *testing.T) { + v := &ScanHandler{} + expected := []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + } + + result := v.RequiredPermissions() + + assert.Equal(t, expected, result, "RequiredPermissions should return correct permissions") +} + +func TestPostScan(t *testing.T) { + v := &ScanHandler{} + ctx := &jobservice.MockJobContext{} + artifact := &v1.Artifact{} + origRp := &scan.Report{} + rawReport := "" + + mocker := &postprocessorstesting.ScanReportV1ToV2Converter{} + mocker.On("ToRelationalSchema", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, "original report", nil) + postprocessors.Converter = mocker + sr := &v1.ScanRequest{Artifact: artifact} + refreshedReport, err := v.PostScan(ctx, sr, origRp, rawReport, time.Now(), &model.Robot{}) + assert.Equal(t, "", refreshedReport, "PostScan should return the refreshed report") + assert.Nil(t, err, "PostScan should not return an error") +} + +func TestScanHandler_RequiredPermissions(t *testing.T) { + tests := []struct { + name string + want []*types.Policy + }{ + {"normal", []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &ScanHandler{} + assert.Equalf(t, tt.want, v.RequiredPermissions(), "RequiredPermissions()") + }) + } +} + +func TestScanHandler_ReportURLParameter(t *testing.T) { + type args struct { + in0 *v1.ScanRequest + } + tests := []struct { + name string + args args + want string + wantErr assert.ErrorAssertionFunc + }{ + {"normal", args{&v1.ScanRequest{}}, "", assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &ScanHandler{} + got, err := v.ReportURLParameter(tt.args.in0) + if !tt.wantErr(t, err, fmt.Sprintf("ReportURLParameter(%v)", tt.args.in0)) { + return + } + assert.Equalf(t, tt.want, got, "ReportURLParameter(%v)", tt.args.in0) + }) + } +} + +func TestScanHandler_RequestProducesMineTypes(t *testing.T) { + tests := []struct { + name string + want []string + }{ + {"normal", []string{v1.MimeTypeGenericVulnerabilityReport}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &ScanHandler{} + assert.Equalf(t, tt.want, v.RequestProducesMineTypes(), "RequestProducesMineTypes()") + }) + } +} diff --git a/src/pkg/scheduler/mock_dao_test.go b/src/pkg/scheduler/mock_dao_test.go index 69783d30bc..8fc6ea3c58 100644 --- a/src/pkg/scheduler/mock_dao_test.go +++ b/src/pkg/scheduler/mock_dao_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scheduler @@ -18,6 +18,10 @@ type mockDAO struct { func (_m *mockDAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -42,6 +46,10 @@ func (_m *mockDAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *mockDAO) Create(ctx context.Context, s *schedule) (int64, error) { ret := _m.Called(ctx, s) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *schedule) (int64, error)); ok { @@ -66,6 +74,10 @@ func (_m *mockDAO) Create(ctx context.Context, s *schedule) (int64, error) { func (_m *mockDAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -80,6 +92,10 @@ func (_m *mockDAO) Delete(ctx context.Context, id int64) error { func (_m *mockDAO) Get(ctx context.Context, id int64) (*schedule, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*schedule, error)); ok { @@ -106,6 +122,10 @@ func (_m *mockDAO) Get(ctx context.Context, id int64) (*schedule, error) { func (_m *mockDAO) List(ctx context.Context, query *q.Query) ([]*schedule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*schedule, error)); ok { @@ -139,6 +159,10 @@ func (_m *mockDAO) Update(ctx context.Context, s *schedule, props ...string) err _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *schedule, ...string) error); ok { r0 = rf(ctx, s, props...) @@ -153,6 +177,10 @@ func (_m *mockDAO) Update(ctx context.Context, s *schedule, props ...string) err func (_m *mockDAO) UpdateRevision(ctx context.Context, id int64, revision int64) (int64, error) { ret := _m.Called(ctx, id, revision) + if len(ret) == 0 { + panic("no return value specified for UpdateRevision") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (int64, error)); ok { diff --git a/src/pkg/securityhub/dao/security_test.go b/src/pkg/securityhub/dao/security_test.go index f6033b75f2..ae6025b308 100644 --- a/src/pkg/securityhub/dao/security_test.go +++ b/src/pkg/securityhub/dao/security_test.go @@ -49,12 +49,12 @@ func (suite *SecurityDaoTestSuite) SetupTest() { `delete from artifact_accessory`, `delete from artifact`, `insert into scan_report(uuid, digest, registration_uuid, mime_type, critical_cnt, high_cnt, medium_cnt, low_cnt, unknown_cnt, fixable_cnt) values('uuid', 'digest1001', 'ruuid', 'application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0', 50, 50, 50, 0, 0, 20)`, - `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon) -values (1001, 1, 'library/hello-world', 'digest1001', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.docker.distribution.manifest.v2+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '');`, - `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon) -values (1002, 1, 'library/hello-world', 'digest1002', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '');`, - `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon) -values (1003, 1, 'library/hello-world', 'digest1003', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '');`, + `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon, artifact_type) +values (1001, 1, 'library/hello-world', 'digest1001', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.docker.distribution.manifest.v2+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '', 'application/vnd.docker.container.image.v1+json');`, + `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon, artifact_type) +values (1002, 1, 'library/hello-world', 'digest1002', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '', 'application/vnd.docker.container.image.v1+json');`, + `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon, artifact_type) +values (1003, 1, 'library/hello-world', 'digest1003', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '', 'application/vnd.docker.container.image.v1+json');`, `insert into tag (id, repository_id, artifact_id, name, push_time, pull_time) values (1001, 1742, 1001, 'latest', '2023-06-02 01:45:55.050785', '2023-06-02 09:16:47.838778')`, `INSERT INTO artifact_accessory (id, artifact_id, subject_artifact_id, type, size, digest, creation_time, subject_artifact_digest, subject_artifact_repo) VALUES (1001, 1002, 1, 'signature.cosign', 2109, 'sha256:08c64c0de2667abcf3974b4b75b82903f294680b81584318adc4826d0dcb7a9c', '2023-08-03 04:54:32.102928', 'sha256:a97a153152fcd6410bdf4fb64f5622ecf97a753f07dcc89dab14509d059736cf', 'library/nuxeo')`, `INSERT INTO artifact_reference (id, parent_id, child_id, child_digest, platform, urls, annotations) VALUES (1001, 1001, 1003, 'sha256:d2b2f2980e9ccc570e5726b56b54580f23a018b7b7314c9eaff7e5e479c78657', '{"architecture":"amd64","os":"linux"}', '', null)`, diff --git a/src/pkg/task/dao/execution.go b/src/pkg/task/dao/execution.go index cd26e792e5..8cd66e75aa 100644 --- a/src/pkg/task/dao/execution.go +++ b/src/pkg/task/dao/execution.go @@ -343,6 +343,12 @@ func (e *executionDAO) refreshStatus(ctx context.Context, id int64) (bool, strin return status != execution.Status, status, false, err } +type jsonbStru struct { + keyPrefix string + key string + value interface{} +} + func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.QuerySeter, error) { qs, err := orm.QuerySetter(ctx, &Execution{}, query) if err != nil { @@ -352,39 +358,32 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que // append the filter for "extra attrs" if query != nil && len(query.Keywords) > 0 { var ( - key string - keyPrefix string - value interface{} + jsonbStrus []jsonbStru + args []interface{} ) - for key, value = range query.Keywords { - if strings.HasPrefix(key, "ExtraAttrs.") { - keyPrefix = "ExtraAttrs." - break + + for key, value := range query.Keywords { + if strings.HasPrefix(key, "ExtraAttrs.") && key != "ExtraAttrs." { + jsonbStrus = append(jsonbStrus, jsonbStru{ + keyPrefix: "ExtraAttrs.", + key: key, + value: value, + }) } - if strings.HasPrefix(key, "extra_attrs.") { - keyPrefix = "extra_attrs." - break + if strings.HasPrefix(key, "extra_attrs.") && key != "extra_attrs." { + jsonbStrus = append(jsonbStrus, jsonbStru{ + keyPrefix: "extra_attrs.", + key: key, + value: value, + }) } } - if len(keyPrefix) == 0 || keyPrefix == key { + if len(jsonbStrus) == 0 { return qs, nil } - // key with keyPrefix supports multi-level query operator on PostgreSQL JSON data - // examples: - // key = extra_attrs.id, - // ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value} - // key = extra_attrs.artifact.digest - // ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value} - // key = extra_attrs.a.b.c - // ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value} - keys := strings.Split(strings.TrimPrefix(key, keyPrefix), ".") - var args []interface{} - for _, item := range keys { - args = append(args, item) - } - args = append(args, value) - inClause, err := orm.CreateInClause(ctx, buildInClauseSQLForExtraAttrs(keys), args...) + idSQL, args := buildInClauseSQLForExtraAttrs(jsonbStrus) + inClause, err := orm.CreateInClause(ctx, idSQL, args...) if err != nil { return nil, err } @@ -395,23 +394,60 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que } // Param keys is strings.Split() after trim "extra_attrs."/"ExtraAttrs." prefix -func buildInClauseSQLForExtraAttrs(keys []string) string { - switch len(keys) { - case 0: - // won't fall into this case, as the if condition on "keyPrefix == key" - // act as a place holder to ensure "default" is equivalent to "len(keys) >= 2" - return "" - case 1: - return "select id from execution where extra_attrs->>?=?" - default: - // len(keys) >= 2 - elements := make([]string, len(keys)-1) - for i := range elements { - elements[i] = "?" - } - s := strings.Join(elements, "->") - return fmt.Sprintf("select id from execution where extra_attrs->%s->>?=?", s) +// key with keyPrefix supports multi-level query operator on PostgreSQL JSON data +// examples: +// key = extra_attrs.id, +// +// ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value} +// +// key = extra_attrs.artifact.digest +// +// ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value} +// +// key = extra_attrs.a.b.c +// +// ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value} +func buildInClauseSQLForExtraAttrs(jsonbStrus []jsonbStru) (string, []interface{}) { + if len(jsonbStrus) == 0 { + return "", nil } + + var cond string + var args []interface{} + sql := "select id from execution where" + + for i, jsonbStr := range jsonbStrus { + if jsonbStr.key == "" || jsonbStr.value == "" { + return "", nil + } + keys := strings.Split(strings.TrimPrefix(jsonbStr.key, jsonbStr.keyPrefix), ".") + if len(keys) == 1 { + if i == 0 { + cond += "extra_attrs->>?=?" + } else { + cond += " and extra_attrs->>?=?" + } + } + if len(keys) >= 2 { + elements := make([]string, len(keys)-1) + for i := range elements { + elements[i] = "?" + } + s := strings.Join(elements, "->") + if i == 0 { + cond += fmt.Sprintf("extra_attrs->%s->>?=?", s) + } else { + cond += fmt.Sprintf(" and extra_attrs->%s->>?=?", s) + } + } + + for _, item := range keys { + args = append(args, item) + } + args = append(args, jsonbStr.value) + } + + return fmt.Sprintf("%s %s", sql, cond), args } func buildExecStatusOutdateKey(id int64, vendor string) string { diff --git a/src/pkg/task/dao/execution_test.go b/src/pkg/task/dao/execution_test.go index 732286eabd..edf711d846 100644 --- a/src/pkg/task/dao/execution_test.go +++ b/src/pkg/task/dao/execution_test.go @@ -395,22 +395,36 @@ func TestExecutionDAOSuite(t *testing.T) { } func Test_buildInClauseSQLForExtraAttrs(t *testing.T) { - type args struct { - keys []string - } tests := []struct { name string - args args + args []jsonbStru want string }{ - {"extra_attrs.", args{[]string{}}, ""}, - {"extra_attrs.id", args{[]string{"id"}}, "select id from execution where extra_attrs->>?=?"}, - {"extra_attrs.artifact.digest", args{[]string{"artifact", "digest"}}, "select id from execution where extra_attrs->?->>?=?"}, - {"extra_attrs.a.b.c", args{[]string{"a", "b", "c"}}, "select id from execution where extra_attrs->?->?->>?=?"}, + {"extra_attrs.", []jsonbStru{}, ""}, + {"extra_attrs.", []jsonbStru{{}}, ""}, + {"extra_attrs.id", []jsonbStru{{ + keyPrefix: "extra_attrs.", + key: "extra_attrs.id", + value: "1", + }}, "select id from execution where extra_attrs->>?=?"}, + {"extra_attrs.artifact.digest", []jsonbStru{{ + keyPrefix: "extra_attrs.", + key: "extra_attrs.artifact.digest", + value: "sha256:1234", + }}, "select id from execution where extra_attrs->?->>?=?"}, + {"extra_attrs.a.b.c", []jsonbStru{{ + keyPrefix: "extra_attrs.", + key: "extra_attrs.a.b.c", + value: "test_value_1", + }, { + keyPrefix: "extra_attrs.", + key: "extra_attrs.d.e", + value: "test_value_2", + }}, "select id from execution where extra_attrs->?->?->>?=? and extra_attrs->?->>?=?"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := buildInClauseSQLForExtraAttrs(tt.args.keys); got != tt.want { + if got, _ := buildInClauseSQLForExtraAttrs(tt.args); got != tt.want { t.Errorf("buildInClauseSQLForExtraAttrs() = %v, want %v", got, tt.want) } }) diff --git a/src/pkg/task/dao/task.go b/src/pkg/task/dao/task.go index de3c71bd22..1777385618 100644 --- a/src/pkg/task/dao/task.go +++ b/src/pkg/task/dao/task.go @@ -25,6 +25,8 @@ import ( "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" + + "github.com/google/uuid" ) // TaskDAO is the data access object interface for task @@ -91,22 +93,33 @@ func (t *taskDAO) List(ctx context.Context, query *q.Query) ([]*Task, error) { return tasks, nil } +func isValidUUID(id string) bool { + if len(id) == 0 { + return false + } + if _, err := uuid.Parse(id); err != nil { + return false + } + return true +} + func (t *taskDAO) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*Task, error) { ormer, err := orm.FromContext(ctx) if err != nil { return nil, err } - tasks := []*Task{} - // Due to the limitation of the beego's orm, the SQL cannot be converted by orm framework, - // so we can only execute the query by raw SQL, the SQL filters the task contains the report uuid in the column extra_attrs, - // consider from performance side which can using indexes to speed up queries. - sql := fmt.Sprintf(`SELECT * FROM task WHERE extra_attrs::jsonb->'report_uuids' @> '["%s"]'`, uuid) - _, err = ormer.Raw(sql).QueryRows(&tasks) + if !isValidUUID(uuid) { + return nil, errors.BadRequestError(fmt.Errorf("invalid UUID %v", uuid)) + } + + var tasks []*Task + param := fmt.Sprintf(`{"report_uuids":["%s"]}`, uuid) + sql := `SELECT * FROM task WHERE extra_attrs::jsonb @> cast( ? as jsonb )` + _, err = ormer.Raw(sql, param).QueryRows(&tasks) if err != nil { return nil, err } - return tasks, nil } diff --git a/src/pkg/task/dao/task_test.go b/src/pkg/task/dao/task_test.go index aeca41a1ca..58a41e5519 100644 --- a/src/pkg/task/dao/task_test.go +++ b/src/pkg/task/dao/task_test.go @@ -113,8 +113,9 @@ func (t *taskDAOTestSuite) TestList() { } func (t *taskDAOTestSuite) TestListScanTasksByReportUUID() { + reportUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15e` // should not exist if non set - tasks, err := t.taskDAO.ListScanTasksByReportUUID(t.ctx, "fake-report-uuid") + tasks, err := t.taskDAO.ListScanTasksByReportUUID(t.ctx, reportUUID) t.Require().Nil(err) t.Require().Len(tasks, 0) // create one with report uuid @@ -122,12 +123,12 @@ func (t *taskDAOTestSuite) TestListScanTasksByReportUUID() { ExecutionID: t.executionID, Status: "success", StatusCode: 1, - ExtraAttrs: `{"report_uuids": ["fake-report-uuid"]}`, + ExtraAttrs: fmt.Sprintf(`{"report_uuids": ["%s"]}`, reportUUID), }) t.Require().Nil(err) defer t.taskDAO.Delete(t.ctx, taskID) // should exist as created - tasks, err = t.taskDAO.ListScanTasksByReportUUID(t.ctx, "fake-report-uuid") + tasks, err = t.taskDAO.ListScanTasksByReportUUID(t.ctx, reportUUID) t.Require().Nil(err) t.Require().Len(tasks, 1) t.Equal(taskID, tasks[0].ID) @@ -299,6 +300,29 @@ func (t *taskDAOTestSuite) TestExecutionIDsByVendorAndStatus() { defer t.taskDAO.Delete(t.ctx, tid) } +func TestIsValidUUID(t *testing.T) { + tests := []struct { + name string + uuid string + expected bool + }{ + {"Valid UUID", "7f20b1b9-6117-4a2e-820b-e4cc0401f15f", true}, + {"Invalid UUID - Short", "7f20b1b9-6117-4a2e-820b", false}, + {"Invalid UUID - Long", "7f20b1b9-6117-4a2e-820b-e4cc0401f15f-extra", false}, + {"Invalid UUID - Invalid Characters", "7f20b1b9-6117-4z2e-820b-e4cc0401f15f", false}, + {"Empty String", "", false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := isValidUUID(test.uuid) + if result != test.expected { + t.Errorf("Expected isValidUUID(%s) to be %t, got %t", test.uuid, test.expected, result) + } + }) + } +} + func TestTaskDAOSuite(t *testing.T) { suite.Run(t, &taskDAOTestSuite{}) } diff --git a/src/pkg/task/execution.go b/src/pkg/task/execution.go index 49fee7f96f..e2160cd898 100644 --- a/src/pkg/task/execution.go +++ b/src/pkg/task/execution.go @@ -59,6 +59,8 @@ type ExecutionManager interface { // StopAndWait stops all linked tasks of the specified execution and waits until all tasks are stopped // or get an error StopAndWait(ctx context.Context, id int64, timeout time.Duration) (err error) + // StopAndWaitWithError calls the StopAndWait first, if it doesn't return error, then it call MarkError if the origError is not empty + StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) (err error) // Delete the specified execution and its tasks Delete(ctx context.Context, id int64) (err error) // Delete all executions and tasks of the specific vendor. They can be deleted only when all the executions/tasks @@ -250,6 +252,16 @@ func (e *executionManager) StopAndWait(ctx context.Context, id int64, timeout ti } } +func (e *executionManager) StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) error { + if err := e.StopAndWait(ctx, id, timeout); err != nil { + return err + } + if origError != nil { + return e.MarkError(ctx, id, origError.Error()) + } + return nil +} + func (e *executionManager) Delete(ctx context.Context, id int64) error { tasks, err := e.taskDAO.List(ctx, &q.Query{ Keywords: map[string]interface{}{ diff --git a/src/pkg/task/mock_execution_dao_test.go b/src/pkg/task/mock_execution_dao_test.go index 0f80511697..f26a035a62 100644 --- a/src/pkg/task/mock_execution_dao_test.go +++ b/src/pkg/task/mock_execution_dao_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type mockExecutionDAO struct { func (_m *mockExecutionDAO) AsyncRefreshStatus(ctx context.Context, id int64, vendor string) error { ret := _m.Called(ctx, id, vendor) + if len(ret) == 0 { + panic("no return value specified for AsyncRefreshStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, id, vendor) @@ -34,6 +38,10 @@ func (_m *mockExecutionDAO) AsyncRefreshStatus(ctx context.Context, id int64, ve func (_m *mockExecutionDAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -58,6 +66,10 @@ func (_m *mockExecutionDAO) Count(ctx context.Context, query *q.Query) (int64, e func (_m *mockExecutionDAO) Create(ctx context.Context, execution *dao.Execution) (int64, error) { ret := _m.Called(ctx, execution) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Execution) (int64, error)); ok { @@ -82,6 +94,10 @@ func (_m *mockExecutionDAO) Create(ctx context.Context, execution *dao.Execution func (_m *mockExecutionDAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -96,6 +112,10 @@ func (_m *mockExecutionDAO) Delete(ctx context.Context, id int64) error { func (_m *mockExecutionDAO) Get(ctx context.Context, id int64) (*dao.Execution, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Execution, error)); ok { @@ -122,6 +142,10 @@ func (_m *mockExecutionDAO) Get(ctx context.Context, id int64) (*dao.Execution, func (_m *mockExecutionDAO) GetMetrics(ctx context.Context, id int64) (*dao.Metrics, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetMetrics") + } + var r0 *dao.Metrics var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Metrics, error)); ok { @@ -148,6 +172,10 @@ func (_m *mockExecutionDAO) GetMetrics(ctx context.Context, id int64) (*dao.Metr func (_m *mockExecutionDAO) List(ctx context.Context, query *q.Query) ([]*dao.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Execution, error)); ok { @@ -174,6 +202,10 @@ func (_m *mockExecutionDAO) List(ctx context.Context, query *q.Query) ([]*dao.Ex func (_m *mockExecutionDAO) RefreshStatus(ctx context.Context, id int64) (bool, string, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for RefreshStatus") + } + var r0 bool var r1 string var r2 error @@ -212,6 +244,10 @@ func (_m *mockExecutionDAO) Update(ctx context.Context, execution *dao.Execution _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Execution, ...string) error); ok { r0 = rf(ctx, execution, props...) diff --git a/src/pkg/task/mock_jobservice_client_test.go b/src/pkg/task/mock_jobservice_client_test.go index 4aee354ea2..0d9ecfeca8 100644 --- a/src/pkg/task/mock_jobservice_client_test.go +++ b/src/pkg/task/mock_jobservice_client_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -18,6 +18,10 @@ type mockJobserviceClient struct { func (_m *mockJobserviceClient) GetExecutions(uuid string) ([]job.Stats, error) { ret := _m.Called(uuid) + if len(ret) == 0 { + panic("no return value specified for GetExecutions") + } + var r0 []job.Stats var r1 error if rf, ok := ret.Get(0).(func(string) ([]job.Stats, error)); ok { @@ -44,6 +48,10 @@ func (_m *mockJobserviceClient) GetExecutions(uuid string) ([]job.Stats, error) func (_m *mockJobserviceClient) GetJobLog(uuid string) ([]byte, error) { ret := _m.Called(uuid) + if len(ret) == 0 { + panic("no return value specified for GetJobLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(string) ([]byte, error)); ok { @@ -70,6 +78,10 @@ func (_m *mockJobserviceClient) GetJobLog(uuid string) ([]byte, error) { func (_m *mockJobserviceClient) GetJobServiceConfig() (*job.Config, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetJobServiceConfig") + } + var r0 *job.Config var r1 error if rf, ok := ret.Get(0).(func() (*job.Config, error)); ok { @@ -96,6 +108,10 @@ func (_m *mockJobserviceClient) GetJobServiceConfig() (*job.Config, error) { func (_m *mockJobserviceClient) PostAction(uuid string, action string) error { ret := _m.Called(uuid, action) + if len(ret) == 0 { + panic("no return value specified for PostAction") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(uuid, action) @@ -110,6 +126,10 @@ func (_m *mockJobserviceClient) PostAction(uuid string, action string) error { func (_m *mockJobserviceClient) SubmitJob(_a0 *models.JobData) (string, error) { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for SubmitJob") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(*models.JobData) (string, error)); ok { diff --git a/src/pkg/task/mock_sweep_manager_test.go b/src/pkg/task/mock_sweep_manager_test.go index 4ca1a88d1a..735c34304c 100644 --- a/src/pkg/task/mock_sweep_manager_test.go +++ b/src/pkg/task/mock_sweep_manager_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -17,6 +17,10 @@ type mockSweepManager struct { func (_m *mockSweepManager) Clean(ctx context.Context, execID []int64) error { ret := _m.Called(ctx, execID) + if len(ret) == 0 { + panic("no return value specified for Clean") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []int64) error); ok { r0 = rf(ctx, execID) @@ -31,6 +35,10 @@ func (_m *mockSweepManager) Clean(ctx context.Context, execID []int64) error { func (_m *mockSweepManager) FixDanglingStateExecution(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for FixDanglingStateExecution") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -45,6 +53,10 @@ func (_m *mockSweepManager) FixDanglingStateExecution(ctx context.Context) error func (_m *mockSweepManager) ListCandidates(ctx context.Context, vendorType string, retainCnt int64) ([]int64, error) { ret := _m.Called(ctx, vendorType, retainCnt) + if len(ret) == 0 { + panic("no return value specified for ListCandidates") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]int64, error)); ok { diff --git a/src/pkg/task/mock_task_dao_test.go b/src/pkg/task/mock_task_dao_test.go index 49ae17e9f2..357353bfa2 100644 --- a/src/pkg/task/mock_task_dao_test.go +++ b/src/pkg/task/mock_task_dao_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -22,6 +22,10 @@ type mockTaskDAO struct { func (_m *mockTaskDAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -46,6 +50,10 @@ func (_m *mockTaskDAO) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *mockTaskDAO) Create(ctx context.Context, _a1 *dao.Task) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Task) (int64, error)); ok { @@ -70,6 +78,10 @@ func (_m *mockTaskDAO) Create(ctx context.Context, _a1 *dao.Task) (int64, error) func (_m *mockTaskDAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -84,6 +96,10 @@ func (_m *mockTaskDAO) Delete(ctx context.Context, id int64) error { func (_m *mockTaskDAO) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType string, status string) ([]int64, error) { ret := _m.Called(ctx, vendorType, status) + if len(ret) == 0 { + panic("no return value specified for ExecutionIDsByVendorAndStatus") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]int64, error)); ok { @@ -110,6 +126,10 @@ func (_m *mockTaskDAO) ExecutionIDsByVendorAndStatus(ctx context.Context, vendor func (_m *mockTaskDAO) Get(ctx context.Context, id int64) (*dao.Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Task, error)); ok { @@ -136,6 +156,10 @@ func (_m *mockTaskDAO) Get(ctx context.Context, id int64) (*dao.Task, error) { func (_m *mockTaskDAO) GetMaxEndTime(ctx context.Context, executionID int64) (time.Time, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetMaxEndTime") + } + var r0 time.Time var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (time.Time, error)); ok { @@ -160,6 +184,10 @@ func (_m *mockTaskDAO) GetMaxEndTime(ctx context.Context, executionID int64) (ti func (_m *mockTaskDAO) List(ctx context.Context, query *q.Query) ([]*dao.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Task, error)); ok { @@ -186,6 +214,10 @@ func (_m *mockTaskDAO) List(ctx context.Context, query *q.Query) ([]*dao.Task, e func (_m *mockTaskDAO) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*dao.Task, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for ListScanTasksByReportUUID") + } + var r0 []*dao.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*dao.Task, error)); ok { @@ -212,6 +244,10 @@ func (_m *mockTaskDAO) ListScanTasksByReportUUID(ctx context.Context, uuid strin func (_m *mockTaskDAO) ListStatusCount(ctx context.Context, executionID int64) ([]*dao.StatusCount, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for ListStatusCount") + } + var r0 []*dao.StatusCount var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*dao.StatusCount, error)); ok { @@ -245,6 +281,10 @@ func (_m *mockTaskDAO) Update(ctx context.Context, _a1 *dao.Task, props ...strin _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Task, ...string) error); ok { r0 = rf(ctx, _a1, props...) @@ -259,6 +299,10 @@ func (_m *mockTaskDAO) Update(ctx context.Context, _a1 *dao.Task, props ...strin func (_m *mockTaskDAO) UpdateStatus(ctx context.Context, id int64, status string, statusRevision int64) error { ret := _m.Called(ctx, id, status, statusRevision) + if len(ret) == 0 { + panic("no return value specified for UpdateStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string, int64) error); ok { r0 = rf(ctx, id, status, statusRevision) @@ -273,6 +317,10 @@ func (_m *mockTaskDAO) UpdateStatus(ctx context.Context, id int64, status string func (_m *mockTaskDAO) UpdateStatusInBatch(ctx context.Context, jobIDs []string, status string, batchSize int) error { ret := _m.Called(ctx, jobIDs, status, batchSize) + if len(ret) == 0 { + panic("no return value specified for UpdateStatusInBatch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string, int) error); ok { r0 = rf(ctx, jobIDs, status, batchSize) diff --git a/src/pkg/task/mock_task_manager_test.go b/src/pkg/task/mock_task_manager_test.go index 6e740ab0f5..bca9c411a0 100644 --- a/src/pkg/task/mock_task_manager_test.go +++ b/src/pkg/task/mock_task_manager_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -18,6 +18,10 @@ type mockTaskManager struct { func (_m *mockTaskManager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -49,6 +53,10 @@ func (_m *mockTaskManager) Create(ctx context.Context, executionID int64, job *J _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *Job, ...map[string]interface{}) (int64, error)); ok { @@ -73,6 +81,10 @@ func (_m *mockTaskManager) Create(ctx context.Context, executionID int64, job *J func (_m *mockTaskManager) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType string, status string) ([]int64, error) { ret := _m.Called(ctx, vendorType, status) + if len(ret) == 0 { + panic("no return value specified for ExecutionIDsByVendorAndStatus") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]int64, error)); ok { @@ -99,6 +111,10 @@ func (_m *mockTaskManager) ExecutionIDsByVendorAndStatus(ctx context.Context, ve func (_m *mockTaskManager) Get(ctx context.Context, id int64) (*Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*Task, error)); ok { @@ -125,6 +141,10 @@ func (_m *mockTaskManager) Get(ctx context.Context, id int64) (*Task, error) { func (_m *mockTaskManager) GetLog(ctx context.Context, id int64) ([]byte, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -151,6 +171,10 @@ func (_m *mockTaskManager) GetLog(ctx context.Context, id int64) ([]byte, error) func (_m *mockTaskManager) GetLogByJobID(ctx context.Context, jobID string) ([]byte, error) { ret := _m.Called(ctx, jobID) + if len(ret) == 0 { + panic("no return value specified for GetLogByJobID") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { @@ -177,6 +201,10 @@ func (_m *mockTaskManager) GetLogByJobID(ctx context.Context, jobID string) ([]b func (_m *mockTaskManager) List(ctx context.Context, query *q.Query) ([]*Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*Task, error)); ok { @@ -203,6 +231,10 @@ func (_m *mockTaskManager) List(ctx context.Context, query *q.Query) ([]*Task, e func (_m *mockTaskManager) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*Task, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for ListScanTasksByReportUUID") + } + var r0 []*Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*Task, error)); ok { @@ -229,6 +261,10 @@ func (_m *mockTaskManager) ListScanTasksByReportUUID(ctx context.Context, uuid s func (_m *mockTaskManager) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -250,6 +286,10 @@ func (_m *mockTaskManager) Update(ctx context.Context, task *Task, props ...stri _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *Task, ...string) error); ok { r0 = rf(ctx, task, props...) @@ -264,6 +304,10 @@ func (_m *mockTaskManager) Update(ctx context.Context, task *Task, props ...stri func (_m *mockTaskManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error { ret := _m.Called(ctx, id, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for UpdateExtraAttrs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]interface{}) error); ok { r0 = rf(ctx, id, extraAttrs) @@ -278,6 +322,10 @@ func (_m *mockTaskManager) UpdateExtraAttrs(ctx context.Context, id int64, extra func (_m *mockTaskManager) UpdateStatusInBatch(ctx context.Context, jobIDs []string, status string, batchSize int) error { ret := _m.Called(ctx, jobIDs, status, batchSize) + if len(ret) == 0 { + panic("no return value specified for UpdateStatusInBatch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string, int) error); ok { r0 = rf(ctx, jobIDs, status, batchSize) diff --git a/src/pkg/token/claims/robot/robot.go b/src/pkg/token/claims/robot/robot.go index 11de0bd7ea..323a84ee17 100644 --- a/src/pkg/token/claims/robot/robot.go +++ b/src/pkg/token/claims/robot/robot.go @@ -17,8 +17,9 @@ package robot import ( "errors" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" + "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/pkg/permission/types" ) @@ -45,8 +46,9 @@ func (rc Claim) Valid() error { if rc.Access == nil { return errors.New("the access info cannot be nil") } - stdErr := rc.RegisteredClaims.Valid() - if stdErr != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway)) + + if stdErr := v.Validate(rc.RegisteredClaims); stdErr != nil { return stdErr } return nil diff --git a/src/pkg/token/claims/v2/claims.go b/src/pkg/token/claims/v2/claims.go index 687c6af61b..9558b9b85c 100644 --- a/src/pkg/token/claims/v2/claims.go +++ b/src/pkg/token/claims/v2/claims.go @@ -15,11 +15,10 @@ package v2 import ( - "crypto/subtle" - "fmt" + "github.com/goharbor/harbor/src/common" "github.com/docker/distribution/registry/auth/token" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" ) func init() { @@ -39,11 +38,10 @@ type Claims struct { // Valid checks if the issuer is harbor func (c *Claims) Valid() error { - if err := c.RegisteredClaims.Valid(); err != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway), jwt.WithIssuer(Issuer)) + + if err := v.Validate(c.RegisteredClaims); err != nil { return err } - if subtle.ConstantTimeCompare([]byte(c.Issuer), []byte(Issuer)) == 0 { - return fmt.Errorf("invalid token issuer: %s", c.Issuer) - } return nil } diff --git a/src/pkg/token/claims/v2/claims_test.go b/src/pkg/token/claims/v2/claims_test.go index 6af09ae228..6d107b3cc3 100644 --- a/src/pkg/token/claims/v2/claims_test.go +++ b/src/pkg/token/claims/v2/claims_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/docker/distribution/registry/auth/token" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) diff --git a/src/pkg/token/option_test.go b/src/pkg/token/option_test.go index 5139afbd10..2d23c10401 100644 --- a/src/pkg/token/option_test.go +++ b/src/pkg/token/option_test.go @@ -3,7 +3,7 @@ package token import ( "testing" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) diff --git a/src/pkg/token/options.go b/src/pkg/token/options.go index 0c8fe614b1..7f639dfdc3 100644 --- a/src/pkg/token/options.go +++ b/src/pkg/token/options.go @@ -19,7 +19,7 @@ import ( "fmt" "os" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/log" diff --git a/src/pkg/token/token.go b/src/pkg/token/token.go index fe95421a8d..920b587afe 100644 --- a/src/pkg/token/token.go +++ b/src/pkg/token/token.go @@ -20,8 +20,9 @@ import ( "errors" "fmt" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" + "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/lib/log" ) @@ -34,8 +35,8 @@ type Token struct { // New ... func New(opt *Options, claims jwt.Claims) (*Token, error) { - err := claims.Valid() - if err != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway)) + if err := v.Validate(claims); err != nil { return nil, err } return &Token{ @@ -65,10 +66,8 @@ func Parse(opt *Options, rawToken string, claims jwt.Claims) (*Token, error) { if err != nil { return nil, err } - token, err := jwt.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (interface{}, error) { - if token.Method.Alg() != opt.SignMethod.Alg() { - return nil, errors.New("invalid signing method") - } + var parser = jwt.NewParser(jwt.WithLeeway(common.JwtLeeway), jwt.WithValidMethods([]string{opt.SignMethod.Alg()})) + token, err := parser.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (interface{}, error) { switch k := key.(type) { case *rsa.PrivateKey: return &k.PublicKey, nil diff --git a/src/pkg/token/token_test.go b/src/pkg/token/token_test.go index fa87d75182..b3bd3a9cff 100644 --- a/src/pkg/token/token_test.go +++ b/src/pkg/token/token_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/goharbor/harbor/src/lib/config" @@ -92,6 +92,42 @@ func TestRaw(t *testing.T) { assert.NotNil(t, rawTk) } +func TestNewWithClockSkew(t *testing.T) { + rbacPolicy := &types.Policy{ + Resource: "/project/library/repository", + Action: "pull", + } + var policies []*types.Policy + policies = append(policies, rbacPolicy) + + tokenID := int64(123) + projectID := int64(321) + + expiresAt := time.Now().UTC().Add(-50 * time.Second) + robot := robot_claim.Claim{ + TokenID: tokenID, + ProjectID: projectID, + Access: policies, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expiresAt), + }, + } + defaultOpt := DefaultTokenOptions() + if defaultOpt == nil { + assert.NotNil(t, defaultOpt) + return + } + token, err := New(defaultOpt, robot) + if err != nil { + assert.Nil(t, err) + return + } + + rawTk, err := token.Raw() + assert.Nil(t, err) + assert.NotNil(t, rawTk) +} + func TestParseWithClaims(t *testing.T) { rawTk := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MTIzLCJQcm9qZWN0SUQiOjAsIkFjY2VzcyI6W3siUmVzb3VyY2UiOiIvcHJvamVjdC9saWJyYXkvcmVwb3NpdG9yeSIsIkFjdGlvbiI6InB1bGwiLCJFZmZlY3QiOiIifV0sIlN0YW5kYXJkQ2xhaW1zIjp7ImV4cCI6MTU0ODE0MDIyOSwiaXNzIjoiaGFyYm9yLXRva2VuLWlzc3VlciJ9fQ.Jc3qSKN4SJVUzAvBvemVpRcSOZaHlu0Avqms04qzPm4ru9-r9IRIl3mnSkI6m9XkzLUeJ7Kiwyw63ghngnVKw_PupeclOGC6s3TK5Cfmo4h-lflecXjZWwyy-dtH_e7Us_ItS-R3nXDJtzSLEpsGHCcAj-1X2s93RB2qD8LNSylvYeDezVkTzqRzzfawPJheKKh9JTrz-3eUxCwQard9-xjlwvfUYULoHTn9npNAUq4-jqhipW4uE8HL-ym33AGF57la8U0RO11hmDM5K8-PiYknbqJ_oONeS3HBNym2pEFeGjtTv2co213wl4T5lemlg4SGolMBuJ03L7_beVZ0o-MKTkKDqDwJalb6_PM-7u3RbxC9IzJMiwZKIPnD3FvV10iPxUUQHaH8Jz5UZ2pFIhi_8BNnlBfT0JOPFVYATtLjHMczZelj2YvAeR1UHBzq3E0jPpjjwlqIFgaHCaN_KMwEvadTo_Fi2sEH4pNGP7M3yehU_72oLJQgF4paJarsmEoij6ZtPs6xekBz1fccVitq_8WNIz9aeCUdkUBRwI5QKw1RdW4ua-w74ld5MZStWJA8veyoLkEb_Q9eq2oAj5KWFjJbW5-ltiIfM8gxKflsrkWAidYGcEIYcuXr7UdqEKXxtPiWM0xb3B91ovYvO5402bn3f9-UGtlcestxNHA" rClaims := &robot_claim.Claim{} @@ -104,3 +140,47 @@ func TestParseWithClaims(t *testing.T) { assert.Equal(t, int64(0), rClaims.ProjectID) assert.Equal(t, "/project/libray/repository", rClaims.Access[0].Resource.String()) } + +func TestParseWithClaimsWithClockSkew(t *testing.T) { + rbacPolicy := &types.Policy{ + Resource: "/project/library/repository", + Action: "push", + } + var policies []*types.Policy + policies = append(policies, rbacPolicy) + + tokenID := int64(123) + projectID := int64(321) + + now := time.Now().UTC() + expiresAt := jwt.NewNumericDate(now.Add(time.Duration(10) * 24 * time.Hour)) + notBefore := jwt.NewNumericDate(now.Add(50 * time.Second)) + issuedAt := jwt.NewNumericDate(now.Add(50 * time.Second)) + robot := robot_claim.Claim{ + TokenID: tokenID, + ProjectID: projectID, + Access: policies, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: expiresAt, + NotBefore: notBefore, + IssuedAt: issuedAt, + }, + } + defaultOpt := DefaultTokenOptions() + if defaultOpt == nil { + assert.NotNil(t, defaultOpt) + return + } + token, err := New(defaultOpt, robot) + if err != nil { + assert.Nil(t, err) + return + } + rawTk, err := token.Raw() + assert.Nil(t, err) + rClaims := &robot_claim.Claim{} + token, err = Parse(defaultOpt, rawTk, rClaims) + assert.Nil(t, err) + assert.Equal(t, token.Token.Claims.(*robot_claim.Claim).Access[0].Resource, types.Resource("/project/library/repository")) + assert.Equal(t, token.Token.Claims.(*robot_claim.Claim).Access[0].Action, types.Action("push")) +} diff --git a/src/portal/package-lock.json b/src/portal/package-lock.json index 68ae5a7093..aa3796d1aa 100644 --- a/src/portal/package-lock.json +++ b/src/portal/package-lock.json @@ -35,7 +35,7 @@ "zone.js": "~0.13.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.9", + "@angular-devkit/build-angular": "^16.2.12", "@angular-eslint/builder": "16.1.2", "@angular-eslint/eslint-plugin": "16.1.2", "@angular-eslint/eslint-plugin-template": "16.1.2", @@ -53,7 +53,7 @@ "eslint": "^8.41.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", - "express": "^4.17.1", + "express": "^4.19.2", "https-proxy-agent": "^5.0.1", "jasmine-core": "~4.5.0", "jasmine-spec-reporter": "~7.0.0", @@ -101,15 +101,15 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", - "integrity": "sha512-S1C4UYxRVyNt3C0wCxbT2jZ1dN5i37kS0mol3PQjbR8gQ0GQzHmzhjTBl1oImo8aouET9yhrk9etk65oat4mBQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.12.tgz", + "integrity": "sha512-VVGKZ0N3gyR0DP7VrcZl4io3ruWYT94mrlyJsJMLlrYy/EX8JCvqrJC9c+dscrtKjhZzjwdyhszkJQY4JfwACA==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.1", - "@angular-devkit/architect": "0.1602.9", - "@angular-devkit/build-webpack": "0.1602.9", - "@angular-devkit/core": "16.2.9", + "@angular-devkit/architect": "0.1602.12", + "@angular-devkit/build-webpack": "0.1602.12", + "@angular-devkit/core": "16.2.12", "@babel/core": "7.22.9", "@babel/generator": "7.22.9", "@babel/helper-annotate-as-pure": "7.22.5", @@ -121,7 +121,7 @@ "@babel/runtime": "7.22.6", "@babel/template": "7.22.5", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "16.2.9", + "@ngtools/webpack": "16.2.12", "@vitejs/plugin-basic-ssl": "1.0.1", "ansi-colors": "4.1.3", "autoprefixer": "10.4.14", @@ -164,7 +164,7 @@ "text-table": "0.2.0", "tree-kill": "1.2.2", "tslib": "2.6.1", - "vite": "4.4.7", + "vite": "4.5.2", "webpack": "5.88.2", "webpack-dev-middleware": "6.1.1", "webpack-dev-server": "4.15.1", @@ -222,6 +222,48 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1602.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.12.tgz", + "integrity": "sha512-19Fwwfx+KvJ01SyI6cstRgqT9+cwer8Ro1T27t1JqlGyOX8tY3pV78ulwxy2+wCzPjR18V6W7cb7Cv6fyK4xog==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.12", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.12.tgz", + "integrity": "sha512-o6ziQs+EcEonFezrsA46jbZqkQrs4ckS1bAQj93g5ZjGtieUz8l/U3lclvKpL/iEzWkGVViSYuP2KyW2oqTDiQ==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { "version": "7.22.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", @@ -701,12 +743,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1602.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.9.tgz", - "integrity": "sha512-+3IxovfBPR2Vy730mGa0SVKkd5LQVom85gjXOs7WcnnnZmfc1q/BtFlqTgW1UWvTxP8IQdm7UYWVclQfL/WExw==", + "version": "0.1602.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.12.tgz", + "integrity": "sha512-1lmR4jCkxPJuAFXReesEY3CB+/5jSebGE5ry6qJJvNm6kuSc9bzfTytrcwosVY+Q7kAA2ij7kAYw0loGbTjLWA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/architect": "0.1602.12", "rxjs": "7.8.1" }, "engines": { @@ -719,6 +761,48 @@ "webpack-dev-server": "^4.0.0" } }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1602.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.12.tgz", + "integrity": "sha512-19Fwwfx+KvJ01SyI6cstRgqT9+cwer8Ro1T27t1JqlGyOX8tY3pV78ulwxy2+wCzPjR18V6W7cb7Cv6fyK4xog==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.12", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.12.tgz", + "integrity": "sha512-o6ziQs+EcEonFezrsA46jbZqkQrs4ckS1bAQj93g5ZjGtieUz8l/U3lclvKpL/iEzWkGVViSYuP2KyW2oqTDiQ==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/core": { "version": "16.2.9", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", @@ -3912,9 +3996,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.9.tgz", - "integrity": "sha512-rOclD7FfT4OSwVA0nDnULbJS6TORJ0+sQiuT2ebaNFErYr3LOm6Zut05tnmzFw8q1cePrILbG+xpnbggNr9Pyw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.12.tgz", + "integrity": "sha512-f9R9Qsk8v+ffDxryl6PQ7Wnf2JCNd4dDXOH+d/AuF06VFiwcwGDRDZpmqkAXbFxQfcWTbT1FFvfoJ+SFcJgXLA==", "dev": true, "engines": { "node": "^16.14.0 || >=18.10.0", @@ -4504,9 +4588,9 @@ } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.3.tgz", - "integrity": "sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "dependencies": { "@types/express-serve-static-core": "*", @@ -4623,9 +4707,9 @@ "dev": true }, "node_modules/@types/node-forge": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.9.tgz", - "integrity": "sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -4735,9 +4819,9 @@ "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" }, "node_modules/@types/ws": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", - "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dev": true, "dependencies": { "@types/node": "*" @@ -6196,13 +6280,13 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -6210,7 +6294,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -6235,23 +6319,15 @@ "dev": true }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dev": true, "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, - "node_modules/bonjour-service/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -7143,9 +7219,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -8633,12 +8709,6 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -9671,17 +9741,17 @@ "dev": true }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -16266,9 +16336,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -19006,14 +19076,14 @@ } }, "node_modules/vite": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", - "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", "dev": true, "dependencies": { "esbuild": "^0.18.10", - "postcss": "^8.4.26", - "rollup": "^3.25.2" + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -19484,9 +19554,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -20043,4 +20113,4 @@ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } -} \ No newline at end of file +} diff --git a/src/portal/package.json b/src/portal/package.json index 6a25a0b2a0..14a48ff888 100644 --- a/src/portal/package.json +++ b/src/portal/package.json @@ -53,7 +53,7 @@ "zone.js": "~0.13.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.9", + "@angular-devkit/build-angular": "^16.2.12", "@angular-eslint/builder": "16.1.2", "@angular-eslint/eslint-plugin": "16.1.2", "@angular-eslint/eslint-plugin-template": "16.1.2", @@ -71,7 +71,7 @@ "eslint": "^8.41.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", - "express": "^4.17.1", + "express": "^4.19.2", "https-proxy-agent": "^5.0.1", "jasmine-core": "~4.5.0", "jasmine-spec-reporter": "~7.0.0", diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html index 46ea92d7e4..b050c24eaa 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html @@ -3,6 +3,14 @@ diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts index 5883717903..e75db841d3 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts @@ -50,7 +50,7 @@ const routes: Routes = [ }, { path: '', - redirectTo: 'scanners', + redirectTo: 'security-hub', pathMatch: 'full', }, ], diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html index d60260878c..d25f74c39a 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html @@ -139,9 +139,21 @@ {{ 'SCANNER.ENDPOINT' | translate }} - {{ 'SCANNER.HEALTH' | translate }} - {{ 'SCANNER.ENABLED' | translate }} - {{ 'SCANNER.AUTH' | translate }} + {{ + 'SCANNER.HEALTH' | translate + }} + {{ + 'SCANNER.ENABLED' | translate + }} + {{ + 'SCANNER.AUTH' | translate + }} + {{ + 'SCANNER.VULNERABILITY' | translate + }} + {{ + 'SCANNER.SBOM' | translate + }} {{ 'SCANNER.DESCRIPTION' | translate }} @@ -198,6 +210,18 @@ {{ scanner.auth ? scanner.auth : 'None' }} + {{ + (supportCapability(scanner, 'vulnerability') + ? 'SCANNER.SUPPORTED' + : 'SCANNER.NOT_SUPPORTED' + ) | translate + }} + {{ + (supportCapability(scanner, 'sbom') + ? 'SCANNER.SUPPORTED' + : 'SCANNER.NOT_SUPPORTED' + ) | translate + }} {{ scanner.description }} {{ 'SCANNER.CAPABILITIES' | translate }} -
- {{ 'SCANNER.CONSUMES_MIME_TYPES_COLON' | translate }} - -
-
- {{ 'SCANNER.PRODUCTS_MIME_TYPES_COLON' | translate }} - +
+ {{ i }}: +
+ {{ 'SCANNER.CONSUMES_MIME_TYPES_COLON' | translate }} + +
+
+ {{ 'SCANNER.PRODUCTS_MIME_TYPES_COLON' | translate }} + +
+
+ {{ 'SCANNER.CAPABILITIES_TYPE' | translate }} + + {{ + (scannerMetadata?.capabilities[i]?.type === 'sbom' + ? 'SCANNER.SBOM' + : scannerMetadata?.capabilities[i]?.type === + 'vulnerability' + ? 'SCANNER.VULNERABILITY' + : scannerMetadata?.capabilities[i]?.type + ) | translate + }} + +
{{ 'SCANNER.PROPERTIES' | translate }}
{{ - 'REPLICATION.BANDWIDTH_TOOLTIP' | translate + 'REPLICATION.BANDWIDTH_TOOLTIP' + | translate + : { max_job_workers: maxJobWorkers } }} diff --git a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts index 3bdf95fa58..19e47f9bc9 100644 --- a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts @@ -281,4 +281,13 @@ describe('CreateEditRuleComponent (inline template)', () => { expect(ruleNameInput).toBeTruthy(); expect(ruleNameInput.value.trim()).toEqual('sync_01'); })); + + it('List all Registries Response', fakeAsync(() => { + fixture.detectChanges(); + comp.ngOnInit(); + comp.getAllRegistries(); + fixture.whenStable(); + tick(5000); + expect(comp.targetList.length).toBe(4); + })); }); diff --git a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts index 4b3e567259..5b66125146 100644 --- a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts +++ b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts @@ -41,6 +41,7 @@ import { ErrorHandler } from '../../../../../shared/units/error-handler'; import { TranslateService } from '@ngx-translate/core'; import { cronRegex } from '../../../../../shared/units/utils'; import { FilterType } from '../../../../../shared/entities/shared.const'; +import { JobserviceService } from 'ng-swagger-gen/services'; import { RegistryService } from '../../../../../../../ng-swagger-gen/services/registry.service'; import { Registry } from '../../../../../../../ng-swagger-gen/models/registry'; import { Label } from '../../../../../../../ng-swagger-gen/models/label'; @@ -80,6 +81,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { headerTitle = 'REPLICATION.ADD_POLICY'; createEditRuleOpened: boolean; + maxJobWorkers = 10; inProgress = false; onGoing = false; inNameChecking = false; @@ -125,6 +127,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { private endpointService: RegistryService, private errorHandler: ErrorHandler, private translateService: TranslateService, + private jobServiceService: JobserviceService, private labelService: LabelService ) { this.createForm(); @@ -238,6 +241,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { } } }); + this.initMaxJobWorkers(); } trimText(event) { if (event.target.value) { @@ -906,4 +910,14 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { } return false; } + + initMaxJobWorkers() { + this.jobServiceService.getWorkerPools().subscribe({ + next: pools => { + if ((pools ?? []).length > 0) { + this.maxJobWorkers = pools[0].concurrency; + } + }, + }); + } } diff --git a/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts b/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts index f693753ad3..6e3b4097c5 100644 --- a/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts +++ b/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts @@ -78,6 +78,7 @@ export const ACTION_RESOURCE_I18N_MAP = { log: 'ROBOT_ACCOUNT.LOG', 'notification-policy': 'ROBOT_ACCOUNT.NOTIFICATION_POLICY', quota: 'ROBOT_ACCOUNT.QUOTA', + sbom: 'ROBOT_ACCOUNT.SBOM', }; export function convertKey(key: string) { diff --git a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html index 4daac1a794..c0092c78bb 100644 --- a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html +++ b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html @@ -121,6 +121,23 @@ {{ 'PROJECT_CONFIG.AUTOSCAN_POLICY' | translate }} + + + + + + + + {{ 'PROJECT_CONFIG.AUTOSBOM_POLICY' | translate }} + +
{ expect( component.projectPolicyConfigComponent.projectPolicy.ScanImgOnPush ).toBeTruthy(); + expect( + component.projectPolicyConfigComponent.projectPolicy + .GenerateSbomOnPush + ).toBeTruthy(); }); it('should get hasChangeConfigRole', () => { expect( diff --git a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts index 60d30e2515..2be4f34077 100644 --- a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts +++ b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts @@ -32,6 +32,7 @@ export class ProjectPolicy { PreventVulImg: boolean; PreventVulImgSeverity: string; ScanImgOnPush: boolean; + GenerateSbomOnPush: boolean; constructor() { this.Public = false; @@ -40,6 +41,7 @@ export class ProjectPolicy { this.PreventVulImg = false; this.PreventVulImgSeverity = LOW; this.ScanImgOnPush = false; + this.GenerateSbomOnPush = false; } initByProject(pro: Project) { @@ -52,6 +54,7 @@ export class ProjectPolicy { this.PreventVulImgSeverity = pro.metadata.severity; } this.ScanImgOnPush = pro.metadata.auto_scan === 'true'; + this.GenerateSbomOnPush = pro.metadata.auto_sbom_generation === 'true'; } } diff --git a/src/portal/src/app/base/project/project-config/project-policy-config/project.ts b/src/portal/src/app/base/project/project-config/project-policy-config/project.ts index e94749945e..8772eab01a 100644 --- a/src/portal/src/app/base/project/project-config/project-policy-config/project.ts +++ b/src/portal/src/app/base/project/project-config/project-policy-config/project.ts @@ -19,6 +19,7 @@ export class Project { prevent_vul: string | boolean; severity: string; auto_scan: string | boolean; + auto_sbom_generation: string | boolean; reuse_sys_cve_allowlist?: string; }; cve_allowlist?: object; @@ -28,5 +29,6 @@ export class Project { this.metadata.prevent_vul = false; this.metadata.severity = 'low'; this.metadata.auto_scan = false; + this.metadata.auto_sbom_generation = false; } } diff --git a/src/portal/src/app/base/project/project.ts b/src/portal/src/app/base/project/project.ts index 495edf664d..d72d22f115 100644 --- a/src/portal/src/app/base/project/project.ts +++ b/src/portal/src/app/base/project/project.ts @@ -33,6 +33,7 @@ export class Project { prevent_vul: string | boolean; severity: string; auto_scan: string | boolean; + auto_sbom_generation: string | boolean; retention_id: number; }; constructor() { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html index 20d2950b8c..1a71ebf3a5 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html @@ -1,62 +1,114 @@

{{ 'ARTIFACT.ADDITIONS' | translate }}

- + - - - - + + + + + + + + + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + +
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts index 0f8a801e42..c4147cb577 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts @@ -4,6 +4,8 @@ import { AdditionLinks } from '../../../../../../../ng-swagger-gen/models/additi import { CURRENT_BASE_HREF } from '../../../../../shared/units/utils'; import { SharedTestingModule } from '../../../../../shared/shared.module'; import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ArtifactListPageService } from '../artifact-list-page/artifact-list-page.service'; +import { ClrLoadingState } from '@clr/angular'; describe('ArtifactAdditionsComponent', () => { const mockedAdditionLinks: AdditionLinks = { @@ -12,6 +14,18 @@ describe('ArtifactAdditionsComponent', () => { href: CURRENT_BASE_HREF + '/test', }, }; + const mockedArtifactListPageService = { + hasScannerSupportSBOM(): boolean { + return true; + }, + hasEnabledScanner(): boolean { + return true; + }, + getScanBtnState(): ClrLoadingState { + return ClrLoadingState.SUCCESS; + }, + init() {}, + }; let component: ArtifactAdditionsComponent; let fixture: ComponentFixture; @@ -20,6 +34,12 @@ describe('ArtifactAdditionsComponent', () => { imports: [SharedTestingModule], declarations: [ArtifactAdditionsComponent], schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: ArtifactListPageService, + useValue: mockedArtifactListPageService, + }, + ], }).compileComponents(); }); @@ -27,6 +47,7 @@ describe('ArtifactAdditionsComponent', () => { fixture = TestBed.createComponent(ArtifactAdditionsComponent); component = fixture.componentInstance; component.additionLinks = mockedAdditionLinks; + component.tab = 'vulnerability'; fixture.detectChanges(); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts index b31ef5df4e..a0f5007b87 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts @@ -1,15 +1,24 @@ -import { Component, Input } from '@angular/core'; +import { + AfterViewChecked, + ChangeDetectorRef, + Component, + Input, + OnInit, + ViewChild, +} from '@angular/core'; import { ADDITIONS } from './models'; import { AdditionLinks } from '../../../../../../../ng-swagger-gen/models/addition-links'; import { AdditionLink } from '../../../../../../../ng-swagger-gen/models/addition-link'; import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact'; +import { ClrLoadingState, ClrTabs } from '@clr/angular'; +import { ArtifactListPageService } from '../artifact-list-page/artifact-list-page.service'; @Component({ selector: 'artifact-additions', templateUrl: './artifact-additions.component.html', styleUrls: ['./artifact-additions.component.scss'], }) -export class ArtifactAdditionsComponent { +export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { @Input() artifact: Artifact; @Input() additionLinks: AdditionLinks; @Input() projectName: string; @@ -19,7 +28,39 @@ export class ArtifactAdditionsComponent { repoName: string; @Input() digest: string; - constructor() {} + @Input() + sbomDigest: string; + @Input() + tab: string; + + @Input() currentTabLinkId: string = ''; + activeTab: string = null; + + @ViewChild('additionsTab') tabs: ClrTabs; + constructor( + private ref: ChangeDetectorRef, + private artifactListPageService: ArtifactListPageService + ) {} + + ngOnInit(): void { + this.activeTab = this.tab; + if (!this.activeTab) { + this.currentTabLinkId = 'vulnerability'; + } + this.artifactListPageService.init(this.projectId); + } + + ngAfterViewChecked() { + if (this.activeTab) { + this.currentTabLinkId = this.activeTab; + this.activeTab = null; + } + this.ref.detectChanges(); + } + + hasScannerSupportSBOM(): boolean { + return this.artifactListPageService.hasScannerSupportSBOM(); + } getVulnerability(): AdditionLink { if ( @@ -30,6 +71,7 @@ export class ArtifactAdditionsComponent { } return null; } + getBuildHistory(): AdditionLink { if (this.additionLinks && this.additionLinks[ADDITIONS.BUILD_HISTORY]) { return this.additionLinks[ADDITIONS.BUILD_HISTORY]; @@ -54,4 +96,16 @@ export class ArtifactAdditionsComponent { } return null; } + + actionTab(tab: string): void { + this.currentTabLinkId = tab; + } + + getScanBtnState(): ClrLoadingState { + return this.artifactListPageService.getScanBtnState(); + } + + hasEnabledScanner(): boolean { + return this.artifactListPageService.hasEnabledScanner(); + } } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html new file mode 100644 index 0000000000..577711f332 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html @@ -0,0 +1,98 @@ +
+
+
+
+ +
+
+
+
+ + +
+
+ +
+
+
+ {{ 'SBOM.GRID.COLUMN_PACKAGE' | translate }} + {{ 'SBOM.GRID.COLUMN_VERSION' | translate }} + {{ + 'SBOM.GRID.COLUMN_LICENSE' | translate + }} + + {{ + 'SBOM.STATE.OTHER_STATUS' | translate + }} + + {{ 'SBOM.CHART.TOOLTIPS_TITLE_ZERO' | translate }} + + + + {{ + res.name ?? '' + }} + {{ + res.versionInfo ?? '' + }} + {{ res.licenseConcluded ?? '' }} + + + +
+ {{ + 'SBOM.REPORTED_BY' + | translate + : { + scanner: getScannerInfo( + artifact?.sbom_overview?.scanner + ) + } + }} +
+ + {{ + 'PAGINATION.PAGE_SIZE' | translate + }} + {{ pagination.firstItem + 1 }} - + {{ pagination.lastItem + 1 }} + {{ 'SBOM.GRID.FOOT_OF' | translate }} + {{ artifactSbomPackages().length }} + {{ 'SBOM.GRID.FOOT_ITEMS' | translate }} + +
+
+
+
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.scss b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.scss new file mode 100644 index 0000000000..9ec2fa9190 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.scss @@ -0,0 +1,74 @@ +.result-row { + position: relative; +} +/* stylelint-disable */ +.rightPos{ + position: absolute; + z-index: 100; + right: 0; + margin-top: 1.25rem; +} + +.option-right { + padding-right: 16px; + margin-top: 5px; +} + +.center { + align-items: center; +} + +.label-critical { + background:#ff4d2e; + color:#000; +} + +.label-danger { + background:#ff8f3d!important; + color:#000!important; +} + +.label-medium { + background-color: #ffce66; + color:#000; +} + +.label-low { + background: #fff1ad; + color:#000; +} + +.label-none { + background-color: #2ec0ff; + color:#000; +} + +.no-border { + border: none; +} + +.ml-05 { + margin-left: 0.5rem; +} + +.report { + text-align: left; +} + +.mt-5px { + margin-top: 5px; +} + +.label { + min-width: 3rem; +} + +.package-medium { + max-width: 25rem; + text-overflow: ellipsis; +} + +.version-medium { + min-width: 22rem; + text-overflow: ellipsis; +} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts new file mode 100644 index 0000000000..09e68430a4 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts @@ -0,0 +1,193 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ArtifactSbomComponent } from './artifact-sbom.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ClarityModule } from '@clr/angular'; +import { of } from 'rxjs'; +import { + TranslateFakeLoader, + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { UserPermissionService } from '../../../../../../shared/services'; +import { ErrorHandler } from '../../../../../../shared/units/error-handler'; +import { SessionService } from '../../../../../../shared/services/session.service'; +import { SessionUser } from '../../../../../../shared/entities/session-user'; +import { AppConfigService } from 'src/app/services/app-config.service'; +import { ArtifactSbomPackageItem } from '../../artifact'; +import { ArtifactService } from 'ng-swagger-gen/services'; +import { ArtifactListPageService } from '../../artifact-list-page/artifact-list-page.service'; + +describe('ArtifactSbomComponent', () => { + let component: ArtifactSbomComponent; + let fixture: ComponentFixture; + const artifactSbomPackages: ArtifactSbomPackageItem[] = [ + { + name: 'alpine-baselayout', + SPDXID: 'SPDXRef-Package-5b53573c19a59415', + versionInfo: '3.2.0-r18', + supplier: 'NOASSERTION', + downloadLocation: 'NONE', + checksums: [ + { + algorithm: 'SHA1', + checksumValue: '132992eab020986b3b5d886a77212889680467a0', + }, + ], + sourceInfo: 'built package from: alpine-baselayout 3.2.0-r18', + licenseConcluded: 'GPL-2.0-only', + licenseDeclared: 'GPL-2.0-only', + copyrightText: '', + externalRefs: [ + { + referenceCategory: 'PACKAGE-MANAGER', + referenceType: 'purl', + referenceLocator: + 'pkg:apk/alpine/alpine-baselayout@3.2.0-r18?arch=x86_64\u0026distro=3.15.5', + }, + ], + attributionTexts: [ + 'PkgID: alpine-baselayout@3.2.0-r18', + 'LayerDiffID: sha256:ad543cd673bd9de2bac48599da992506dcc37a183179302ea934853aaa92cb84', + ], + primaryPackagePurpose: 'LIBRARY', + }, + { + name: 'alpine-keys', + SPDXID: 'SPDXRef-Package-7e5952f7a76e9643', + versionInfo: '2.4-r1', + supplier: 'NOASSERTION', + downloadLocation: 'NONE', + checksums: [ + { + algorithm: 'SHA1', + checksumValue: '903176b2d2a8ddefd1ba6940f19ad17c2c1d4aff', + }, + ], + sourceInfo: 'built package from: alpine-keys 2.4-r1', + licenseConcluded: 'MIT', + licenseDeclared: 'MIT', + copyrightText: '', + externalRefs: [ + { + referenceCategory: 'PACKAGE-MANAGER', + referenceType: 'purl', + referenceLocator: + 'pkg:apk/alpine/alpine-keys@2.4-r1?arch=x86_64\u0026distro=3.15.5', + }, + ], + attributionTexts: [ + 'PkgID: alpine-keys@2.4-r1', + 'LayerDiffID: sha256:ad543cd673bd9de2bac48599da992506dcc37a183179302ea934853aaa92cb84', + ], + primaryPackagePurpose: 'LIBRARY', + }, + ]; + const artifactSbomJson = { + spdxVersion: 'SPDX-2.3', + dataLicense: 'CC0-1.0', + SPDXID: 'SPDXRef-DOCUMENT', + name: 'alpine:3.15.5', + documentNamespace: + 'http://aquasecurity.github.io/trivy/container_image/alpine:3.15.5-7ead854c-7340-44c9-bbbf-5403c21cc9b6', + creationInfo: { + licenseListVersion: '', + creators: ['Organization: aquasecurity', 'Tool: trivy-0.47.0'], + created: '2023-11-29T07:06:22Z', + }, + packages: artifactSbomPackages, + }; + const fakedArtifactService = { + getAddition() { + return of(JSON.stringify(artifactSbomJson)); + }, + }; + const fakedUserPermissionService = { + hasProjectPermissions() { + return of(true); + }, + }; + const fakedAppConfigService = { + getConfig() { + return of({ sbom_enabled: true }); + }, + }; + const mockedUser: SessionUser = { + user_id: 1, + username: 'admin', + email: 'harbor@vmware.com', + realname: 'admin', + has_admin_role: true, + comment: 'no comment', + }; + const fakedSessionService = { + getCurrentUser() { + return mockedUser; + }, + }; + const mockedSbomDigest = + 'sha256:51a41cec9de9d62ee60e206f5a8a615a028a65653e45539990867417cb486285'; + const mockedArtifactListPageService = { + hasScannerSupportSBOM(): boolean { + return true; + }, + init() {}, + }; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + ClarityModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateFakeLoader, + }, + }), + ], + declarations: [ArtifactSbomComponent], + providers: [ + ErrorHandler, + { provide: AppConfigService, useValue: fakedAppConfigService }, + { provide: ArtifactService, useValue: fakedArtifactService }, + { + provide: UserPermissionService, + useValue: fakedUserPermissionService, + }, + { provide: SessionService, useValue: fakedSessionService }, + { + provide: ArtifactListPageService, + useValue: mockedArtifactListPageService, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ArtifactSbomComponent); + component = fixture.componentInstance; + component.hasSbomPermission = true; + component.hasScannerSupportSBOM = true; + component.sbomDigest = mockedSbomDigest; + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + it('should get sbom list and render', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row'); + expect(rows.length).toEqual(2); + }); + + it('download button should show the right text', async () => { + fixture.autoDetectChanges(true); + const scanBtn: HTMLButtonElement = + fixture.nativeElement.querySelector('#sbom-btn'); + expect(scanBtn.innerText).toContain('SBOM.DOWNLOAD'); + }); +}); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts new file mode 100644 index 0000000000..c37ee3c161 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts @@ -0,0 +1,238 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ClrDatagridStateInterface, ClrLoadingState } from '@clr/angular'; +import { finalize } from 'rxjs/operators'; +import { + ScannerVo, + UserPermissionService, + USERSTATICPERMISSION, +} from '../../../../../../shared/services'; +import { ErrorHandler } from '../../../../../../shared/units/error-handler'; +import { + dbEncodeURIComponent, + downloadJson, + getPageSizeFromLocalStorage, + PageSizeMapKeys, + SBOM_SCAN_STATUS, + setPageSizeToLocalStorage, +} from '../../../../../../shared/units/utils'; +import { Subscription } from 'rxjs'; +import { Artifact } from '../../../../../../../../ng-swagger-gen/models/artifact'; +import { SessionService } from '../../../../../../shared/services/session.service'; +import { + EventService, + HarborEvent, +} from '../../../../../../services/event-service/event.service'; +import { severityText } from '../../../../../left-side-nav/interrogation-services/vulnerability-database/security-hub.interface'; + +import { + ArtifactSbom, + ArtifactSbomPackageItem, + getArtifactSbom, +} from '../../artifact'; +import { ArtifactService } from 'ng-swagger-gen/services'; +import { ScanTypes } from '../../../../../../shared/entities/shared.const'; + +@Component({ + selector: 'hbr-artifact-sbom', + templateUrl: './artifact-sbom.component.html', + styleUrls: ['./artifact-sbom.component.scss'], +}) +export class ArtifactSbomComponent implements OnInit, OnDestroy { + @Input() + projectName: string; + @Input() + projectId: number; + @Input() + repoName: string; + @Input() + sbomDigest: string; + @Input() artifact: Artifact; + @Input() hasScannerSupportSBOM: boolean = false; + + artifactSbom: ArtifactSbom; + loading: boolean = false; + downloadSbomBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; + hasSbomPermission: boolean = false; + hasShowLoading: boolean = false; + sub: Subscription; + hasViewInitWithDelay: boolean = false; + pageSize: number = getPageSizeFromLocalStorage( + PageSizeMapKeys.ARTIFACT_SBOM_COMPONENT, + 25 + ); + readonly severityText = severityText; + constructor( + private errorHandler: ErrorHandler, + private artifactService: ArtifactService, + private userPermissionService: UserPermissionService, + private eventService: EventService, + private session: SessionService + ) {} + + ngOnInit() { + this.getSbom(); + this.getSbomPermission(); + if (!this.sub) { + this.sub = this.eventService.subscribe( + HarborEvent.UPDATE_SBOM_INFO, + (artifact: Artifact) => { + if (artifact?.digest === this.artifact?.digest) { + if (artifact.sbom_overview) { + const sbomDigest = Object.values( + artifact.sbom_overview + )?.[0]?.sbom_digest; + if (sbomDigest) { + this.sbomDigest = sbomDigest; + } + } + this.getSbom(); + } + } + ); + } + setTimeout(() => { + this.hasViewInitWithDelay = true; + }, 0); + } + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } + } + + getSbom() { + if (this.sbomDigest) { + if (!this.hasShowLoading) { + this.loading = true; + this.hasShowLoading = true; + } + const sbomAdditionParams = { + repositoryName: dbEncodeURIComponent(this.repoName), + reference: this.sbomDigest, + projectName: this.projectName, + addition: ScanTypes.SBOM, + }; + this.artifactService + .getAddition(sbomAdditionParams) + .pipe( + finalize(() => { + this.loading = false; + this.hasShowLoading = false; + }) + ) + .subscribe( + res => { + if (res) { + this.artifactSbom = getArtifactSbom( + JSON.parse(res) + ); + } else { + this.loading = false; + this.hasShowLoading = false; + } + }, + error => { + this.errorHandler.error(error); + } + ); + } + } + + getSbomPermission(): void { + const permissions = [ + { + resource: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.KEY, + action: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.VALUE.READ, + }, + ]; + this.userPermissionService + .hasProjectPermissions(this.projectId, permissions) + .subscribe( + (results: Array) => { + this.hasSbomPermission = results[0]; + // only has label permission + }, + error => this.errorHandler.error(error) + ); + } + + refresh(): void { + this.getSbom(); + } + + hasGeneratedSbom(): boolean { + return this.hasViewInitWithDelay; + } + + isSystemAdmin(): boolean { + const account = this.session.getCurrentUser(); + return account && account.has_admin_role; + } + + getScannerInfo(scanner: ScannerVo): string { + if (scanner) { + if (scanner.name && scanner.version) { + return `${scanner.name}@${scanner.version}`; + } + if (scanner.name && !scanner.version) { + return `${scanner.name}`; + } + } + return ''; + } + + isRunningState(): boolean { + return ( + this.hasViewInitWithDelay && + this.artifact.sbom_overview && + (this.artifact.sbom_overview.scan_status === + SBOM_SCAN_STATUS.PENDING || + this.artifact.sbom_overview.scan_status === + SBOM_SCAN_STATUS.RUNNING) + ); + } + + downloadSbom() { + this.downloadSbomBtnState = ClrLoadingState.LOADING; + if ( + this.artifact?.sbom_overview?.scan_status === + SBOM_SCAN_STATUS.SUCCESS + ) { + downloadJson( + this.artifactSbom.sbomJsonRaw, + `${this.artifactSbom.sbomName}.json` + ); + } + this.downloadSbomBtnState = ClrLoadingState.DEFAULT; + } + + canDownloadSbom(): boolean { + return ( + this.hasScannerSupportSBOM && + //this.hasSbomPermission && + this.sbomDigest && + this.downloadSbomBtnState !== ClrLoadingState.LOADING && + this.artifactSbom !== undefined + ); + } + + artifactSbomPackages(): ArtifactSbomPackageItem[] { + return ( + this.artifactSbom?.sbomPackage?.packages?.filter( + item => + item?.name || item?.versionInfo || item?.licenseConcluded + ) ?? [] + ); + } + + load(state: ClrDatagridStateInterface) { + if (state?.page?.size) { + setPageSizeToLocalStorage( + PageSizeMapKeys.ARTIFACT_SBOM_COMPONENT, + state.page.size + ); + } + } +} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts index 02ad708eab..9d83d167cb 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts @@ -50,14 +50,13 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { @Input() digest: string; @Input() artifact: Artifact; + @Input() scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; + @Input() hasEnabledScanner: boolean = false; scan_overview: any; scanner: ScannerVo; - projectScanner: ScannerVo; scanningResults: VulnerabilityItem[] = []; loading: boolean = false; - hasEnabledScanner: boolean = false; - scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; severitySort: ClrDatagridComparatorInterface; cvssSort: ClrDatagridComparatorInterface; hasScanningPermission: boolean = false; @@ -112,7 +111,6 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { ngOnInit() { this.getVulnerabilities(); this.getScanningPermission(); - this.getProjectScanner(); if (!this.sub) { this.sub = this.eventService.subscribe( HarborEvent.UPDATE_VULNERABILITY_INFO, @@ -203,30 +201,6 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { ); } - getProjectScanner(): void { - this.hasEnabledScanner = false; - this.scanBtnState = ClrLoadingState.LOADING; - this.scanningService.getProjectScanner(this.projectId).subscribe( - response => { - if ( - response && - '{}' !== JSON.stringify(response) && - !response.disabled && - response.health === 'healthy' - ) { - this.scanBtnState = ClrLoadingState.SUCCESS; - this.hasEnabledScanner = true; - } else { - this.scanBtnState = ClrLoadingState.ERROR; - } - this.projectScanner = response; - }, - error => { - this.scanBtnState = ClrLoadingState.ERROR; - } - ); - } - getLevel(v: VulnerabilityItem): number { if (v && v.severity && SEVERITY_LEVEL_MAP[v.severity]) { return SEVERITY_LEVEL_MAP[v.severity]; diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts index 84bf9bf097..dc7ec0a455 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts @@ -18,4 +18,5 @@ export enum ADDITIONS { SUMMARY = 'readme.md', VALUES = 'values.yaml', DEPENDENCIES = 'dependencies', + SBOMS = 'sboms', } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts index 6085c7cd4c..2d01b1f34b 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts @@ -1,12 +1,56 @@ import { inject, TestBed } from '@angular/core/testing'; import { ArtifactListPageService } from './artifact-list-page.service'; import { SharedTestingModule } from '../../../../../shared/shared.module'; +import { + ScanningResultService, + UserPermissionService, +} from 'src/app/shared/services'; +import { of } from 'rxjs'; +import { ClrLoadingState } from '@clr/angular'; describe('ArtifactListPageService', () => { + const FakedScanningResultService = { + getProjectScanner: () => + of({ + access_credential: '', + adapter: 'Trivy', + auth: '', + capabilities: { + support_sbom: true, + support_vulnerability: true, + }, + create_time: '2024-03-06T09:29:43.789Z', + description: 'The Trivy scanner adapter', + disabled: false, + health: 'healthy', + is_default: true, + name: 'Trivy', + skip_certVerify: false, + update_time: '2024-03-06T09:29:43.789Z', + url: 'http://trivy-adapter:8080', + use_internal_addr: true, + uuid: '10c68b62-db9c-11ee-9c72-0242ac130009', + vendor: 'Aqua Security', + version: 'v0.47.0', + }), + }; + const FakedUserPermissionService = { + hasProjectPermissions: () => of([true, true, true, true, true]), + }; beforeEach(() => { TestBed.configureTestingModule({ imports: [SharedTestingModule], - providers: [ArtifactListPageService], + providers: [ + ArtifactListPageService, + { + provide: ScanningResultService, + useValue: FakedScanningResultService, + }, + { + provide: UserPermissionService, + useValue: FakedUserPermissionService, + }, + ], }); }); @@ -16,4 +60,51 @@ describe('ArtifactListPageService', () => { expect(service).toBeTruthy(); } )); + it('Test ArtifactListPageService Permissions validation ', inject( + [ArtifactListPageService], + (service: ArtifactListPageService) => { + service.init(3); + expect(service.hasSbomPermission()).toBeTruthy(); + expect(service.hasAddLabelImagePermission()).toBeTruthy(); + expect(service.hasRetagImagePermission()).toBeTruthy(); + expect(service.hasDeleteImagePermission()).toBeTruthy(); + expect(service.hasScanImagePermission()).toBeTruthy(); + expect(service.hasScannerSupportVulnerability()).toBeTruthy(); + expect(service.hasScannerSupportSBOM()).toBeTruthy(); + } + )); + it('Test ArtifactListPageService updateStates', inject( + [ArtifactListPageService], + (service: ArtifactListPageService) => { + service.init(3); + expect(service.hasEnabledScanner()).toBeTruthy(); + expect(service.getScanBtnState()).toBe(ClrLoadingState.SUCCESS); + expect(service.getSbomBtnState()).toBe(ClrLoadingState.SUCCESS); + service.updateStates( + false, + ClrLoadingState.ERROR, + ClrLoadingState.ERROR + ); + expect(service.hasEnabledScanner()).toBeFalsy(); + expect(service.getScanBtnState()).toBe(ClrLoadingState.ERROR); + expect(service.getSbomBtnState()).toBe(ClrLoadingState.ERROR); + } + )); + it('Test ArtifactListPageService updateCapabilities ', inject( + [ArtifactListPageService], + (service: ArtifactListPageService) => { + service.updateCapabilities({ + support_vulnerability: true, + support_sbom: true, + }); + expect(service.hasScannerSupportVulnerability()).toBeTruthy(); + expect(service.hasScannerSupportSBOM()).toBeTruthy(); + service.updateCapabilities({ + support_vulnerability: false, + support_sbom: false, + }); + expect(service.hasScannerSupportVulnerability()).toBeFalsy(); + expect(service.hasScannerSupportSBOM()).toBeFalsy(); + } + )); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts index 701d4c4532..daf0c0ae5c 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts @@ -6,15 +6,21 @@ import { USERSTATICPERMISSION, } from '../../../../../shared/services'; import { ErrorHandler } from '../../../../../shared/units/error-handler'; +import { Scanner } from '../../../../left-side-nav/interrogation-services/scanner/scanner'; @Injectable() export class ArtifactListPageService { private _scanBtnState: ClrLoadingState; + private _sbomBtnState: ClrLoadingState; private _hasEnabledScanner: boolean = false; + private _hasScannerSupportVulnerability: boolean = false; + private _hasScannerSupportSBOM: boolean = false; private _hasAddLabelImagePermission: boolean = false; private _hasRetagImagePermission: boolean = false; private _hasDeleteImagePermission: boolean = false; private _hasScanImagePermission: boolean = false; + private _hasSbomPermission: boolean = false; + private _scanner: Scanner = undefined; constructor( private scanningService: ScanningResultService, @@ -22,10 +28,18 @@ export class ArtifactListPageService { private errorHandlerService: ErrorHandler ) {} + getProjectScanner(): Scanner { + return this._scanner; + } + getScanBtnState(): ClrLoadingState { return this._scanBtnState; } + getSbomBtnState(): ClrLoadingState { + return this._sbomBtnState; + } + hasEnabledScanner(): boolean { return this._hasEnabledScanner; } @@ -46,30 +60,82 @@ export class ArtifactListPageService { return this._hasScanImagePermission; } + hasSbomPermission(): boolean { + return this._hasSbomPermission; + } + + hasScannerSupportVulnerability(): boolean { + return this._hasScannerSupportVulnerability; + } + + hasScannerSupportSBOM(): boolean { + return this._hasScannerSupportSBOM; + } + init(projectId: number) { this._getProjectScanner(projectId); this._getPermissionRule(projectId); } + updateStates( + enabledScanner: boolean, + scanState?: ClrLoadingState, + sbomState?: ClrLoadingState + ) { + if (scanState) { + this._scanBtnState = scanState; + } + if (sbomState) { + this._sbomBtnState = sbomState; + } + this._hasEnabledScanner = enabledScanner; + } + + updateCapabilities(capabilities?: any) { + if (capabilities) { + if (capabilities?.support_vulnerability !== undefined) { + this._hasScannerSupportVulnerability = + capabilities.support_vulnerability; + } + if (capabilities?.support_sbom !== undefined) { + this._hasScannerSupportSBOM = capabilities.support_sbom; + } + } + } + private _getProjectScanner(projectId: number): void { this._hasEnabledScanner = false; this._scanBtnState = ClrLoadingState.LOADING; + this._sbomBtnState = ClrLoadingState.LOADING; this.scanningService.getProjectScanner(projectId).subscribe( response => { - if ( - response && - '{}' !== JSON.stringify(response) && - !response.disabled && - response.health === 'healthy' - ) { - this._scanBtnState = ClrLoadingState.SUCCESS; - this._hasEnabledScanner = true; - } else { - this._scanBtnState = ClrLoadingState.ERROR; + if (response && '{}' !== JSON.stringify(response)) { + this._scanner = response; + if (!response.disabled && response.health === 'healthy') { + this.updateStates( + true, + ClrLoadingState.SUCCESS, + ClrLoadingState.SUCCESS + ); + if (response?.capabilities) { + this.updateCapabilities(response?.capabilities); + } + } else { + this.updateStates( + false, + ClrLoadingState.ERROR, + ClrLoadingState.ERROR + ); + } } }, error => { - this._scanBtnState = ClrLoadingState.ERROR; + this._scanner = null; + this.updateStates( + false, + ClrLoadingState.ERROR, + ClrLoadingState.ERROR + ); } ); } @@ -94,6 +160,11 @@ export class ArtifactListPageService { action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE .CREATE, }, + { + resource: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.KEY, + action: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.VALUE + .CREATE, + }, ]; this.userPermissionService .hasProjectPermissions(projectId, permissions) @@ -103,6 +174,9 @@ export class ArtifactListPageService { this._hasRetagImagePermission = results[1]; this._hasDeleteImagePermission = results[2]; this._hasScanImagePermission = results[3]; + this._hasSbomPermission = results?.[4] ?? false; + // TODO need to remove the static code + this._hasSbomPermission = true; }, error => this.errorHandlerService.error(error) ); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html index 6258f7a7d2..89b0f6c69b 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html @@ -24,6 +24,7 @@ canScanNow() && selectedRowHasVul() && hasEnabledScanner && + hasScannerSupportVulnerability && hasScanImagePermission ) " @@ -33,15 +34,25 @@ {{ 'VULNERABILITY.SCAN_NOW' | translate }} + + + +
{{ 'REPOSITORY.COPY_DIGEST_ID' | translate }}
+ +
@@ -174,23 +228,29 @@ {{ 'REPOSITORY.VULNERABILITY' | translate }} - + + {{ 'REPOSITORY.SBOM' | translate }} + + + + {{ 'ARTIFACT.ANNOTATION' | translate }} - + {{ 'REPOSITORY.LABELS' | translate }} - + {{ 'REPOSITORY.PUSH_TIME' | translate }} - + {{ 'REPOSITORY.PULL_TIME' | translate }} @@ -389,6 +449,30 @@
+ +
+ + {{ 'ARTIFACT.SBOM_UNSUPPORTED' | translate }} + + + +
+
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss index b90fad6771..23c95498d4 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss @@ -161,6 +161,10 @@ width: 11rem !important; } +.sbom-column { + width: 6rem !important; +} + .annotations-column { width: 5rem !important; } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts index 9eef1097e6..bb45710a17 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts @@ -13,7 +13,7 @@ import { ScanningResultDefaultService, ScanningResultService, } from '../../../../../../../shared/services'; -import { ArtifactFront as Artifact } from '../../../artifact'; +import { ArtifactFront as Artifact, ArtifactFront } from '../../../artifact'; import { ErrorHandler } from '../../../../../../../shared/units/error-handler'; import { OperationService } from '../../../../../../../shared/components/operation/operation.service'; import { ArtifactService as NewArtifactService } from '../../../../../../../../../ng-swagger-gen/services/artifact.service'; @@ -24,10 +24,20 @@ import { ArtifactListPageService } from '../../artifact-list-page.service'; import { ClrLoadingState } from '@clr/angular'; import { Accessory } from 'ng-swagger-gen/models/accessory'; import { ArtifactModule } from '../../../artifact.module'; +import { + SBOM_SCAN_STATUS, + VULNERABILITY_SCAN_STATUS, +} from '../../../../../../../shared/units/utils'; +import { Scanner } from '../../../../../../left-side-nav/interrogation-services/scanner/scanner'; describe('ArtifactListTabComponent', () => { let comp: ArtifactListTabComponent; let fixture: ComponentFixture; + const mockScanner = { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }; const mockActivatedRoute = { snapshot: { params: { @@ -171,6 +181,16 @@ describe('ArtifactListTabComponent', () => { pull_time: '0001-01-01T00:00:00Z', }, ]; + const mockAccessory = { + id: 1, + artifact_id: 2, + subject_artifact_id: 3, + subject_artifact_digest: 'fakeDigest', + subject_artifact_repo: 'test', + size: 120, + digest: 'fakeDigest', + type: 'test', + }; const mockErrorHandler = { error: () => {}, }; @@ -236,9 +256,18 @@ describe('ArtifactListTabComponent', () => { getScanBtnState(): ClrLoadingState { return ClrLoadingState.DEFAULT; }, + getSbomBtnState(): ClrLoadingState { + return ClrLoadingState.DEFAULT; + }, hasEnabledScanner(): boolean { return true; }, + hasSbomPermission(): boolean { + return true; + }, + hasScannerSupportSBOM(): boolean { + return true; + }, hasAddLabelImagePermission(): boolean { return true; }, @@ -251,6 +280,9 @@ describe('ArtifactListTabComponent', () => { hasScanImagePermission(): boolean { return true; }, + getProjectScanner(): Scanner { + return mockScanner; + }, init() {}, }; beforeEach(async () => { @@ -353,6 +385,27 @@ describe('ArtifactListTabComponent', () => { fixture.nativeElement.querySelector('.confirmation-title') ).toBeTruthy(); }); + it('Generate SBOM button should be disabled', async () => { + await fixture.whenStable(); + comp.selectedRow = [mockArtifacts[1]]; + await stepOpenAction(fixture, comp); + const generatedButton = + fixture.nativeElement.querySelector('#generate-sbom-btn'); + fixture.detectChanges(); + await fixture.whenStable(); + expect(generatedButton.disabled).toBeFalsy(); + }); + it('Stop SBOM button should be disabled', async () => { + await fixture.whenStable(); + comp.selectedRow = [mockArtifacts[1]]; + await stepOpenAction(fixture, comp); + const stopButton = + fixture.nativeElement.querySelector('#stop-sbom-btn'); + fixture.detectChanges(); + await fixture.whenStable().then(() => { + expect(stopButton.disabled).toBeTruthy(); + }); + }); it('the length of hide array should equal to the number of column', async () => { comp.loading = false; fixture.detectChanges(); @@ -360,6 +413,93 @@ describe('ArtifactListTabComponent', () => { const cols = fixture.nativeElement.querySelectorAll('.datagrid-column'); expect(cols.length).toEqual(comp.hiddenArray.length); }); + + it('Test isEllipsisActive', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable().then(() => { + expect( + comp.isEllipsisActive(document.createElement('span')) + ).toBeFalsy(); + }); + }); + it('Test deleteAccessory', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.deleteAccessory(mockAccessory); + fixture.detectChanges(); + await fixture.whenStable().then(() => { + expect( + fixture.nativeElement.querySelector('.confirmation-content') + ).toBeTruthy(); + }); + }); + it('Test scanNow', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.selectedRow = mockArtifacts.slice(0, 1); + comp.scanNow(); + expect(comp.onScanArtifactsLength).toBe(1); + }); + it('Test stopNow', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.selectedRow = mockArtifacts.slice(0, 1); + comp.stopNow(); + expect(comp.onStopScanArtifactsLength).toBe(1); + }); + it('Test stopSbom', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.selectedRow = mockArtifacts.slice(0, 1); + comp.stopSbom(); + expect(comp.onStopSbomArtifactsLength).toBe(1); + }); + it('Test tagsString and isRunningState and canStopSbom and canStopScan', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + expect(comp.tagsString([])).toBeNull(); + expect( + comp.isRunningState(VULNERABILITY_SCAN_STATUS.RUNNING) + ).toBeTruthy(); + expect( + comp.isRunningState(VULNERABILITY_SCAN_STATUS.ERROR) + ).toBeFalsy(); + expect(comp.canStopSbom()).toBeFalsy(); + expect(comp.canStopScan()).toBeFalsy(); + }); + it('Test status and handleScanOverview', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + expect(comp.scanStatus(mockArtifacts[0])).toBe( + VULNERABILITY_SCAN_STATUS.ERROR + ); + expect(comp.sbomStatus(null)).toBe(SBOM_SCAN_STATUS.NOT_GENERATED_SBOM); + expect(comp.sbomStatus(mockArtifacts[0])).toBe( + SBOM_SCAN_STATUS.NOT_GENERATED_SBOM + ); + expect(comp.handleScanOverview(mockArtifacts[0])).not.toBeNull(); + }); + it('Test utils', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + expect(comp.selectedRowHasSbom()).toBeFalsy(); + expect(comp.selectedRowHasVul()).toBeFalsy(); + expect(comp.canScanNow()).toBeFalsy(); + expect(comp.hasEnabledSbom()).toBeTruthy(); + expect(comp.canAddLabel()).toBeFalsy(); + }); }); async function stepOpenAction(fixture, comp) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts index 5d4ffad469..48ac4317e5 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts @@ -37,6 +37,7 @@ import { setHiddenArrayToLocalStorage, setPageSizeToLocalStorage, VULNERABILITY_SCAN_STATUS, + SBOM_SCAN_STATUS, } from '../../../../../../../shared/units/utils'; import { ErrorHandler } from '../../../../../../../shared/units/error-handler'; import { ArtifactService } from '../../../artifact.service'; @@ -76,13 +77,14 @@ import { EventService, HarborEvent, } from '../../../../../../../services/event-service/event.service'; -import { AppConfigService } from 'src/app/services/app-config.service'; +import { AppConfigService } from '../../../../../../../services/app-config.service'; import { ArtifactListPageService } from '../../artifact-list-page.service'; import { ACCESSORY_PAGE_SIZE } from './sub-accessories/sub-accessories.component'; import { Accessory } from 'ng-swagger-gen/models/accessory'; import { Tag } from '../../../../../../../../../ng-swagger-gen/models/tag'; import { CopyArtifactComponent } from './copy-artifact/copy-artifact.component'; import { CopyDigestComponent } from './copy-digest/copy-digest.component'; +import { Scanner } from '../../../../../../left-side-nav/interrogation-services/scanner/scanner'; export const AVAILABLE_TIME = '0001-01-01T00:00:00.000Z'; @@ -141,28 +143,61 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { get hasScanImagePermission(): boolean { return this.artifactListPageService.hasScanImagePermission(); } + get hasSbomPermission(): boolean { + return this.artifactListPageService.hasSbomPermission(); + } get hasEnabledScanner(): boolean { return this.artifactListPageService.hasEnabledScanner(); } + get hasScannerSupportVulnerability(): boolean { + return this.artifactListPageService.hasScannerSupportVulnerability(); + } + get hasScannerSupportSBOM(): boolean { + return this.artifactListPageService.hasScannerSupportSBOM(); + } get scanBtnState(): ClrLoadingState { return this.artifactListPageService.getScanBtnState(); } + get generateSbomBtnState(): ClrLoadingState { + return this.artifactListPageService.getSbomBtnState(); + } + onSendingScanCommand: boolean; onSendingStopScanCommand: boolean = false; onStopScanArtifactsLength: number = 0; scanStoppedArtifactLength: number = 0; + + onSendingSbomCommand: boolean; + onSendingStopSbomCommand: boolean = false; + onStopSbomArtifactsLength: number = 0; + sbomStoppedArtifactLength: number = 0; + artifactDigest: string; depth: string; // could Pagination filter filters: string[]; scanFinishedArtifactLength: number = 0; onScanArtifactsLength: number = 0; + sbomFinishedArtifactLength: number = 0; + onSbomArtifactsLength: number = 0; stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; updateArtifactSub: Subscription; hiddenArray: boolean[] = getHiddenArrayFromLocalStorage( PageSizeMapKeys.ARTIFACT_LIST_TAB_COMPONENT, - [false, false, false, false, false, false, true, false, false, false] + [ + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + ] ); deleteAccessorySub: Subscription; copyDigestSub: Subscription; @@ -203,7 +238,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } } ngOnInit() { - this.registryUrl = this.appConfigService.getConfig().registry_url; + const appConfig = this.appConfigService.getConfig(); + this.registryUrl = appConfig.registry_url; this.initRouterData(); if (!this.updateArtifactSub) { this.updateArtifactSub = this.eventService.subscribe( @@ -235,6 +271,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } ); } + if (this.projectId) { + this.artifactListPageService.init(this.projectId); + } } ngOnDestroy() { @@ -250,7 +289,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.copyDigestSub.unsubscribe(); this.copyDigestSub = null; } - this.datagrid['columnsService']?.columns?.forEach((item, index) => { + this.datagrid?.['columnsService']?.columns?.forEach((item, index) => { if (this.depth) { this.hiddenArray[index] = !!item?._value?.hidden; } else { @@ -326,6 +365,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { withImmutableStatus: true, withLabel: true, withScanOverview: true, + withSbomOverview: true, withTag: false, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, withAccessory: false, @@ -350,6 +390,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { withImmutableStatus: true, withLabel: true, withScanOverview: true, + withSbomOverview: true, withTag: false, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, @@ -381,7 +422,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { }); this.getArtifactTagsAsync(this.artifactList); this.getAccessoriesAsync(this.artifactList); - this.checkCosignAsync(this.artifactList); + this.checkCosignAndSbomAsync(this.artifactList); this.getIconsFromBackEnd(); }, error => { @@ -399,6 +440,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { repositoryName: dbEncodeURIComponent(this.repoName), withLabel: true, withScanOverview: true, + withSbomOverview: true, withTag: false, sort: getSortingString(state), XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, @@ -420,7 +462,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.artifactList = res.body; this.getArtifactTagsAsync(this.artifactList); this.getAccessoriesAsync(this.artifactList); - this.checkCosignAsync(this.artifactList); + this.checkCosignAndSbomAsync(this.artifactList); this.getIconsFromBackEnd(); }, error => { @@ -519,6 +561,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { return formatSize(tagSize); } + hasEnabledSbom(): boolean { + return ( + this.hasScannerSupportSBOM && + this.hasEnabledScanner && + this.hasSbomPermission + ); + } + retag() { if (this.selectedRow && this.selectedRow.length && !this.depth) { this.copyArtifactComponent.retag(this.selectedRow[0].digest); @@ -705,15 +755,30 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) { this.router.navigate(relativeRouterLink, { relativeTo: this.activatedRoute, - queryParams: { [UN_LOGGED_PARAM]: YES }, + queryParams: { + [UN_LOGGED_PARAM]: YES, + sbomDigest: artifact.sbomDigest ?? '', + }, }); } else { this.router.navigate(relativeRouterLink, { relativeTo: this.activatedRoute, + queryParams: { sbomDigest: artifact.sbomDigest ?? '' }, }); } } + // Get sbom status + sbomStatus(artifact: Artifact): string { + if (artifact) { + let so = (artifact).sbom_overview; + if (so && so.scan_status) { + return so.scan_status; + } + } + return SBOM_SCAN_STATUS.NOT_GENERATED_SBOM; + } + // Get vulnerability scanning status scanStatus(artifact: Artifact): string { if (artifact) { @@ -745,6 +810,26 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } return false; } + // if has running job, return false + canGenerateSbomNow(): boolean { + if (!this.hasSbomPermission) { + return false; + } + if (this.onSendingSbomCommand) { + return false; + } + if (this.selectedRow && this.selectedRow.length) { + let flag: boolean = true; + this.selectedRow.forEach(item => { + const st: string = this.sbomStatus(item); + if (this.isRunningState(st)) { + flag = false; + } + }); + return flag; + } + return false; + } // Trigger scan scanNow(): void { if (!this.selectedRow.length) { @@ -761,6 +846,22 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { ); }); } + // Generate SBOM + generateSbom(): void { + if (!this.selectedRow.length) { + return; + } + this.sbomFinishedArtifactLength = 0; + this.onSbomArtifactsLength = this.selectedRow.length; + this.onSendingSbomCommand = true; + this.selectedRow.forEach((data: any) => { + let digest = data.digest; + this.eventService.publish( + HarborEvent.START_GENERATE_SBOM, + this.repoName + '/' + digest + ); + }); + } selectedRowHasVul(): boolean { return !!( @@ -771,6 +872,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { ); } + selectedRowHasSbom(): boolean { + return !!(this.selectedRow && this.selectedRow[0]); + } + hasVul(artifact: Artifact): boolean { return !!( artifact && @@ -779,6 +884,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { ); } + hasSbom(artifact: Artifact): boolean { + return !!( + artifact && + artifact.addition_links && + artifact.addition_links[ADDITIONS.SBOMS] + ); + } + submitFinish(e: boolean) { this.scanFinishedArtifactLength += 1; // all selected scan action has started @@ -791,12 +904,38 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.scanStoppedArtifactLength += 1; // all selected scan action has stopped if (this.scanStoppedArtifactLength === this.onStopScanArtifactsLength) { - this.onSendingScanCommand = e; + this.onSendingStopScanCommand = e; } } + + submitSbomFinish(e: boolean) { + this.sbomFinishedArtifactLength += 1; + // all selected scan action has started + if (this.sbomFinishedArtifactLength === this.onSbomArtifactsLength) { + this.onSendingSbomCommand = e; + } + } + + submitSbomStopFinish(e: boolean) { + this.sbomStoppedArtifactLength += 1; + // all selected scan action has stopped + if (this.sbomStoppedArtifactLength === this.onStopSbomArtifactsLength) { + this.onSendingStopSbomCommand = e; + } + } + handleScanOverview(scanOverview: any): any { if (scanOverview) { - return Object.values(scanOverview)[0]; + const keys = Object.keys(scanOverview) ?? []; + return keys.length > 0 ? scanOverview[keys[0]] : null; + } + return null; + } + + handleSbomOverview(sbomOverview: any): any { + if (sbomOverview) { + const keys = Object.keys(sbomOverview) ?? []; + return keys.length > 0 ? sbomOverview[keys[0]] : null; } return null; } @@ -850,12 +989,27 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { if (this.selectedRow && this.selectedRow.length) { for (let i = 0; i < this.selectedRow.length; i++) { if (artifact.digest === this.selectedRow[i].digest) { + if (artifact.sbom_overview) { + this.selectedRow[i].sbom_overview = + artifact.sbom_overview; + } + if (artifact.sbom_overview.sbom_digest) { + this.selectedRow[i].sbomDigest = + artifact.sbom_overview.sbom_digest; + } + if (artifact.accessories !== undefined) { + this.selectedRow[i].accessories = artifact.accessories; + } this.selectedRow.splice(i, 1); break; } } } } + // when finished, remove it from selectedRow + sbomFinished(artifact: Artifact) { + this.scanFinished(artifact); + } getIconsFromBackEnd() { if (this.artifactList && this.artifactList.length) { @@ -929,11 +1083,17 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { }); } } - checkCosignAsync(artifacts: ArtifactFront[]) { + checkCosignAndSbomAsync(artifacts: ArtifactFront[]) { if (artifacts) { if (artifacts.length) { artifacts.forEach(item => { item.signed = CHECKING; + const sbomOverview = item?.sbom_overview; + item.sbomDigest = sbomOverview?.sbom_digest; + let queryTypes = `${AccessoryType.COSIGN} ${AccessoryType.NOTATION}`; + if (!item.sbomDigest) { + queryTypes = `${queryTypes} ${AccessoryType.SBOM}`; + } this.newArtifactService .listAccessories({ projectName: this.projectName, @@ -941,16 +1101,21 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { reference: item.digest, page: 1, pageSize: ACCESSORY_PAGE_SIZE, - q: encodeURIComponent( - `type={${AccessoryType.COSIGN} ${AccessoryType.NOTATION}}` - ), + q: encodeURIComponent(`type={${queryTypes}}`), }) .subscribe({ next: res => { - if (res?.length) { - item.signed = TRUE; - } else { - item.signed = FALSE; + item.signed = res?.filter( + item => item.type !== AccessoryType.SBOM + )?.length + ? TRUE + : FALSE; + if (!item.sbomDigest) { + item.sbomDigest = + res?.filter( + item => + item.type === AccessoryType.SBOM + )?.[0]?.digest ?? undefined; } }, error: err => { @@ -978,6 +1143,23 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } return false; } + // return true if all selected rows are in "running" state + canStopSbom(): boolean { + if (this.onSendingStopSbomCommand) { + return false; + } + if (this.selectedRow && this.selectedRow.length) { + let flag: boolean = true; + this.selectedRow.forEach(item => { + const st: string = this.sbomStatus(item); + if (!this.isRunningState(st)) { + flag = false; + } + }); + return flag; + } + return false; + } isRunningState(state: string): boolean { return ( @@ -1001,6 +1183,22 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { }); } } + + stopSbom() { + if (this.selectedRow && this.selectedRow.length) { + this.sbomStoppedArtifactLength = 0; + this.onStopSbomArtifactsLength = this.selectedRow.length; + this.onSendingStopSbomCommand = true; + this.selectedRow.forEach((data: any) => { + let digest = data.digest; + this.eventService.publish( + HarborEvent.STOP_SBOM_ARTIFACT, + this.repoName + '/' + digest + ); + }); + } + } + tagsString(tags: Tag[]): string { if (tags?.length) { const arr: string[] = []; @@ -1011,6 +1209,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } return null; } + deleteAccessory(a: Accessory) { let titleKey: string, summaryKey: string, diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html index 436363325b..05465eae31 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html @@ -59,6 +59,8 @@ [projectId]="projectId" [repoName]="repositoryName" [digest]="artifactDigest" + [sbomDigest]="sbomDigest" + [tab]="activeTab" [additionLinks]="artifact?.addition_links">
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts index 9a6a0bf2fb..1a7944e83c 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts @@ -30,6 +30,8 @@ describe('ArtifactSummaryComponent', () => { return undefined; }, }; + const mockedSbomDigest = + 'sha256:51a41cec9de9d62ee60e206f5a8a615a028a65653e45539990867417cb486285'; let component: ArtifactSummaryComponent; let fixture: ComponentFixture; const mockActivatedRoute = { @@ -42,6 +44,9 @@ describe('ArtifactSummaryComponent', () => { return of(null); }, }, + queryParams: { + sbomDigest: mockedSbomDigest, + }, parent: { params: { id: 1, @@ -89,6 +94,7 @@ describe('ArtifactSummaryComponent', () => { component = fixture.componentInstance; component.repositoryName = 'demo'; component.artifactDigest = 'sha: acf4234f'; + component.sbomDigest = mockedSbomDigest; fixture.detectChanges(); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts index 7aa95189d1..de2f964440 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts @@ -1,10 +1,7 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { Artifact } from '../../../../../../ng-swagger-gen/models/artifact'; -import { ErrorHandler } from '../../../../shared/units/error-handler'; import { Label } from '../../../../../../ng-swagger-gen/models/label'; -import { ProjectService } from '../../../../shared/services'; import { ActivatedRoute, Router } from '@angular/router'; -import { AppConfigService } from '../../../../services/app-config.service'; import { Project } from '../../project'; import { artifactDefault } from './artifact'; import { SafeUrl } from '@angular/platform-browser'; @@ -24,6 +21,8 @@ import { export class ArtifactSummaryComponent implements OnInit { tagId: string; artifactDigest: string; + sbomDigest?: string; + activeTab?: string; repositoryName: string; projectId: string | number; referArtifactNameArray: string[] = []; @@ -37,10 +36,7 @@ export class ArtifactSummaryComponent implements OnInit { loading: boolean = false; constructor( - private projectService: ProjectService, - private errorHandler: ErrorHandler, private route: ActivatedRoute, - private appConfigService: AppConfigService, private router: Router, private frontEndArtifactService: ArtifactService, private event: EventService @@ -100,6 +96,8 @@ export class ArtifactSummaryComponent implements OnInit { this.repositoryName = this.route.snapshot.params['repo']; this.artifactDigest = this.route.snapshot.params['digest']; this.projectId = this.route.snapshot.parent.params['id']; + this.sbomDigest = this.route.snapshot.queryParams['sbomDigest']; + this.activeTab = this.route.snapshot.queryParams['tab']; if (this.repositoryName && this.artifactDigest) { const resolverData = this.route.snapshot.data; if (resolverData) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact.module.ts b/src/portal/src/app/base/project/repository/artifact/artifact.module.ts index bd4f49961b..0272818b82 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact.module.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact.module.ts @@ -12,9 +12,11 @@ import { SummaryComponent } from './artifact-additions/summary/summary.component import { DependenciesComponent } from './artifact-additions/dependencies/dependencies.component'; import { BuildHistoryComponent } from './artifact-additions/build-history/build-history.component'; import { ArtifactVulnerabilitiesComponent } from './artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component'; +import { ArtifactSbomComponent } from './artifact-additions/artifact-sbom/artifact-sbom.component'; import { ArtifactDefaultService, ArtifactService } from './artifact.service'; import { ArtifactDetailRoutingResolverService } from '../../../../services/routing-resolvers/artifact-detail-routing-resolver.service'; import { ResultBarChartComponent } from './vulnerability-scanning/result-bar-chart.component'; +import { ResultSbomComponent } from './sbom-scanning/sbom-scan.component'; import { ResultTipHistogramComponent } from './vulnerability-scanning/result-tip-histogram/result-tip-histogram.component'; import { HistogramChartComponent } from './vulnerability-scanning/histogram-chart/histogram-chart.component'; import { ArtifactInfoComponent } from './artifact-list-page/artifact-list/artifact-info/artifact-info.component'; @@ -24,6 +26,7 @@ import { CopyArtifactComponent } from './artifact-list-page/artifact-list/artifa import { CopyDigestComponent } from './artifact-list-page/artifact-list/artifact-list-tab/copy-digest/copy-digest.component'; import { ArtifactFilterComponent } from './artifact-list-page/artifact-list/artifact-list-tab/artifact-filter/artifact-filter.component'; import { PullCommandComponent } from './artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component'; +import { SbomTipHistogramComponent } from './sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component'; const routes: Routes = [ { @@ -78,8 +81,11 @@ const routes: Routes = [ SummaryComponent, DependenciesComponent, BuildHistoryComponent, + ArtifactSbomComponent, ArtifactVulnerabilitiesComponent, ResultBarChartComponent, + ResultSbomComponent, + SbomTipHistogramComponent, ResultTipHistogramComponent, HistogramChartComponent, ArtifactInfoComponent, diff --git a/src/portal/src/app/base/project/repository/artifact/artifact.ts b/src/portal/src/app/base/project/repository/artifact/artifact.ts index 9a2c379ae0..aeb65607aa 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact.ts @@ -10,6 +10,7 @@ export interface ArtifactFront extends Artifact { annotationsArray?: Array<{ [key: string]: any }>; tagNumber?: number; signed?: string; + sbomDigest?: string; accessoryNumber?: number; } @@ -75,6 +76,7 @@ export enum AccessoryType { COSIGN = 'signature.cosign', NOTATION = 'signature.notation', NYDUS = 'accelerator.nydus', + SBOM = 'sbom.harbor', } export enum ArtifactType { @@ -166,3 +168,196 @@ export enum ClientNames { CHART = 'Helm', CNAB = 'CNAB', } + +export enum ArtifactSbomType { + SPDX = 'SPDX', +} + +export interface ArtifactSbomPackageItem { + name?: string; + versionInfo?: string; + licenseConcluded?: string; + [key: string]: Object; +} + +export interface ArtifactSbomPackage { + packages: ArtifactSbomPackageItem[]; +} + +export interface ArtifactSbom { + sbomType: ArtifactSbomType; + sbomVersion: string; + sbomName?: string; + sbomDataLicense?: string; + sbomId?: string; + sbomDocumentNamespace?: string; + sbomCreated?: string; + sbomPackage?: ArtifactSbomPackage; + sbomJsonRaw?: Object; +} + +export const ArtifactSbomFieldMapper = { + sbomVersion: 'spdxVersion', + sbomName: 'name', + sbomDataLicense: 'dataLicense', + sbomId: 'SPDXID', + sbomDocumentNamespace: 'documentNamespace', + sbomCreated: 'creationInfo.created', + sbomPackage: { + packages: ['name', 'versionInfo', 'licenseConcluded'], + }, +}; + +/** + * Identify the sbomJson contains the two main properties 'spdxVersion' and 'SPDXID'. + * @param sbomJson SBOM JSON report object. + * @returns true or false + * Return true when the sbomJson object contains the attribues 'spdxVersion' and 'SPDXID'. + * else return false. + */ +export function isSpdxSbom(sbomJson?: Object): boolean { + return Object.keys(sbomJson ?? {}).includes(ArtifactSbomFieldMapper.sbomId); +} + +/** + * Update the value to the data object with the field path. + * @param fieldPath field class path eg {a: {b:'test'}}. field path for b is 'a.b' + * @param data The target object to receive the value. + * @param value The value will be set to the data object. + */ +export function updateObjectWithFieldPath( + fieldPath: string, + data: Object, + value: Object +) { + if (fieldPath && data) { + const fields = fieldPath?.split('.'); + let tempData = data; + fields.forEach((field, index) => { + const properties = Object.getOwnPropertyNames(tempData); + if (field !== '__proto__' && field !== 'constructor') { + if (index === fields.length - 1) { + tempData[field] = value; + } else { + if (!properties.includes(field)) { + tempData[field] = {}; + } + tempData = tempData[field]; + } + } + }); + } +} + +/** + * Get value from data object with field path. + * @param fieldPath field class path eg {a: {b:'test'}}. field path for b is 'a.b' + * @param data The data source target object. + * @returns The value read from data object. + */ +export const getValueFromObjectWithFieldPath = ( + fieldPath: string, + data: Object +) => { + let tempObject = data; + if (fieldPath && data) { + const fields = fieldPath?.split('.'); + fields.forEach(field => { + if (tempObject) { + tempObject = tempObject[field] ?? null; + } + }); + } + return tempObject; +}; + +/** + * Get value from source data object with field path. + * @param fieldPathObject The Object that contains the field paths. + * If we have an Object - {a: {b: 'test', c: [{ d: 2, e: 'v'}]}}. + * The field path for b is 'a.b'. + * The field path for c is {'a.c': ['d', 'e']'}. + * @param sourceData The data source target object. + * @returns the value by field class path. + */ +export function readDataFromArtifactSbomJson( + fieldPathObject: Object, + sourceData: Object +): Object { + let result = null; + if (sourceData) { + switch (typeof fieldPathObject) { + case 'string': + result = getValueFromObjectWithFieldPath( + fieldPathObject, + sourceData + ); + break; + case 'object': + if ( + Array.isArray(fieldPathObject) && + Array.isArray(sourceData) + ) { + result = sourceData.map(source => { + let arrayItem = {}; + fieldPathObject.forEach(field => { + updateObjectWithFieldPath( + field, + arrayItem, + readDataFromArtifactSbomJson(field, source) + ); + }); + return arrayItem; + }); + } else { + const fields = Object.getOwnPropertyNames(fieldPathObject); + result = result ? result : {}; + fields.forEach(field => { + if (sourceData[field]) { + updateObjectWithFieldPath( + field, + result, + readDataFromArtifactSbomJson( + fieldPathObject[field], + sourceData[field] + ) + ); + } + }); + } + break; + default: + break; + } + } + return result; +} + +/** + * Convert SBOM Json report to ArtifactSbom + * @param sbomJson SBOM report in Json format + * @returns ArtifactSbom || null + */ +export function getArtifactSbom(sbomJson?: Object): ArtifactSbom { + if (sbomJson) { + if (isSpdxSbom(sbomJson)) { + const artifactSbom = {}; + artifactSbom.sbomJsonRaw = sbomJson; + artifactSbom.sbomType = ArtifactSbomType.SPDX; + // only retrieve the fields defined in ArtifactSbomFieldMapper + const fields = Object.getOwnPropertyNames(ArtifactSbomFieldMapper); + fields.forEach(field => { + updateObjectWithFieldPath( + field, + artifactSbom, + readDataFromArtifactSbomJson( + ArtifactSbomFieldMapper[field], + sbomJson + ) + ); + }); + return artifactSbom; + } + } + return null; +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-overview.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-overview.ts new file mode 100644 index 0000000000..753a32a206 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-overview.ts @@ -0,0 +1,39 @@ +/* tslint:disable */ + +import { Scanner } from 'ng-swagger-gen/models'; + +/** + * The generate SBOM overview information + */ +export interface SBOMOverview { + /** + * id of the native sbom report + */ + report_id?: string; + + /** + * The start time of the scan process that generating report + */ + start_time?: string; + + /** + * The end time of the scan process that generating report + */ + end_time?: string; + + /** + * The status of the generate SBOM process + */ + scan_status?: string; + + /** + * The digest of the generated SBOM accessory + */ + sbom_digest?: string; + + /** + * The seconds spent for generating the report + */ + duration?: number; + scanner?: Scanner; +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan-component.html b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan-component.html new file mode 100644 index 0000000000..2de038435a --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan-component.html @@ -0,0 +1,39 @@ +
+
+ {{ + 'SBOM.STATE.QUEUED' | translate + }} +
+ +
+
{{ 'SBOM.STATE.SCANNING' | translate }}
+
+
+
+ +
+
+ {{ + 'SBOM.STATE.STOPPED' | translate + }} +
+
+ {{ + 'SBOM.STATE.OTHER_STATUS' | translate + }} +
+
diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts new file mode 100644 index 0000000000..89513730bf --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts @@ -0,0 +1,252 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ResultSbomComponent } from './sbom-scan.component'; +import { + ScanningResultDefaultService, + ScanningResultService, +} from '../../../../../shared/services'; +import { SBOM_SCAN_STATUS } from '../../../../../shared/units/utils'; +import { SharedTestingModule } from '../../../../../shared/shared.module'; +import { SbomTipHistogramComponent } from './sbom-tip-histogram/sbom-tip-histogram.component'; +import { SBOMOverview } from './sbom-overview'; +import { of, timer } from 'rxjs'; +import { ArtifactService, ScanService } from 'ng-swagger-gen/services'; +import { AccessoryType } from '../artifact'; + +describe('ResultSbomComponent (inline template)', () => { + let component: ResultSbomComponent; + let fixture: ComponentFixture; + let mockData: SBOMOverview = { + scan_status: SBOM_SCAN_STATUS.SUCCESS, + end_time: new Date().toUTCString(), + }; + const mockedSbomDigest = + 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3'; + const mockScanner = { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }; + const mockedSbomOverview = { + report_id: '12345', + scan_status: 'Error', + }; + const mockedCloneSbomOverview = { + report_id: '12346', + scan_status: 'Pending', + }; + const FakedScanService = { + scanArtifact: () => of({}), + stopScanArtifact: () => of({}), + }; + const FakedArtifactService = { + getArtifact: () => + of({ + accessories: null, + addition_links: { + build_history: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/build_history', + }, + vulnerabilities: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/vulnerabilities', + }, + }, + digest: 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3', + extra_attrs: { + architecture: 'amd64', + author: '', + config: { + Env: [ + 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + ], + WorkingDir: '/', + }, + created: '2024-01-10T10:05:33.2702206Z', + os: 'linux', + }, + icon: 'sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06', + id: 3, + labels: null, + manifest_media_type: + 'application/vnd.docker.distribution.manifest.v2+json', + media_type: 'application/vnd.docker.container.image.v1+json', + project_id: 3, + pull_time: '2024-04-02T01:50:58.332Z', + push_time: '2024-03-06T09:47:08.163Z', + references: null, + repository_id: 2, + sbom_overview: { + duration: 2, + end_time: '2024-04-02T01:50:59.406Z', + sbom_digest: + 'sha256:8cca43ea666e0e7990c2433e3b185313e6ba303cc7a3124bb767823c79fb74a6', + scan_status: 'Success', + start_time: '2024-04-02T01:50:57.176Z', + }, + size: 3957, + tags: null, + type: 'IMAGE', + }), + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SharedTestingModule], + declarations: [ResultSbomComponent, SbomTipHistogramComponent], + providers: [ + { + provide: ScanningResultService, + useValue: ScanningResultDefaultService, + }, + { + provide: ScanService, + useValue: FakedScanService, + }, + { + provide: ArtifactService, + useValue: FakedArtifactService, + }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ResultSbomComponent); + component = fixture.componentInstance; + component.repoName = 'mockRepo'; + component.inputScanner = mockScanner; + component.artifactDigest = mockedSbomDigest; + component.sbomDigest = mockedSbomDigest; + component.sbomOverview = mockData; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); + it('should show "scan stopped" if status is STOPPED', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.STOPPED; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + let el: HTMLElement = fixture.nativeElement.querySelector('span'); + expect(el).toBeTruthy(); + expect(el.textContent).toEqual('SBOM.STATE.STOPPED'); + }); + }); + + it('should show progress if status is SCANNING', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.RUNNING; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + let el: HTMLElement = + fixture.nativeElement.querySelector('.progress'); + expect(el).toBeTruthy(); + }); + }); + + it('should show QUEUED if status is QUEUED', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.PENDING; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + let el: HTMLElement = + fixture.nativeElement.querySelector('.bar-state'); + expect(el).toBeTruthy(); + let el2: HTMLElement = el.querySelector('span'); + expect(el2).toBeTruthy(); + expect(el2.textContent).toEqual('SBOM.STATE.QUEUED'); + }); + }); + + it('should show summary bar chart if status is COMPLETED', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.SUCCESS; + component.sbomDigest = mockedSbomDigest; + component.accessories = [ + { + type: AccessoryType.SBOM, + digest: mockedSbomDigest, + }, + ]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + let el: HTMLElement = fixture.nativeElement.querySelector('a'); + expect(el).not.toBeNull(); + }); + }); + it('Test ResultSbomComponent getScanner', () => { + fixture.detectChanges(); + component.inputScanner = undefined; + expect(component.getScanner()).toBeUndefined(); + component.inputScanner = mockScanner; + component.sbomOverview = mockedSbomOverview; + expect(component.getScanner()).toBe(mockScanner); + component.projectName = 'test'; + component.repoName = 'ui'; + component.artifactDigest = 'dg'; + expect(component.viewLog()).toBe( + '/api/v2.0/projects/test/repositories/ui/artifacts/dg/scan/12345/log' + ); + component.copyValue(mockedCloneSbomOverview); + expect(component.sbomOverview.report_id).toBe( + mockedCloneSbomOverview.report_id + ); + }); + it('Test ResultSbomComponent status', () => { + component.sbomOverview = mockedSbomOverview; + fixture.detectChanges(); + expect(component.status).toBe(SBOM_SCAN_STATUS.ERROR); + expect(component.completed).toBeFalsy(); + expect(component.queued).toBeFalsy(); + expect(component.generating).toBeFalsy(); + expect(component.stopped).toBeFalsy(); + expect(component.otherStatus).toBeFalsy(); + expect(component.error).toBeTruthy(); + }); + it('Test ResultSbomComponent ngOnDestroy', () => { + component.stateCheckTimer = timer(0, 10000).subscribe(() => {}); + component.ngOnDestroy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.stateCheckTimer).toBeNull(); + expect(component.generateSbomSubscription).toBeNull(); + expect(component.stopSubscription).toBeNull(); + }); + }); + it('Test ResultSbomComponent generateSbom', () => { + fixture.detectChanges(); + component.generateSbom(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onSubmitting).toBeFalse(); + }); + }); + it('Test ResultSbomComponent stopSbom', () => { + fixture.detectChanges(); + component.stopSbom(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onStopping).toBeFalse(); + }); + }); + it('Test ResultSbomComponent getSbomOverview', () => { + fixture.detectChanges(); + component.getSbomOverview(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.sbomOverview.scan_status).toBe( + SBOM_SCAN_STATUS.SUCCESS + ); + }); + }); +}); diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts new file mode 100644 index 0000000000..61c70743fa --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts @@ -0,0 +1,325 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { Subscription, timer } from 'rxjs'; +import { finalize } from 'rxjs/operators'; +import { ErrorHandler } from '../../../../../shared/units/error-handler'; +import { + clone, + CURRENT_BASE_HREF, + dbEncodeURIComponent, + DEFAULT_SUPPORTED_MIME_TYPES, + SBOM_SCAN_STATUS, +} from '../../../../../shared/units/utils'; +import { ArtifactService } from '../../../../../../../ng-swagger-gen/services/artifact.service'; +import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact'; +import { + EventService, + HarborEvent, +} from '../../../../../services/event-service/event.service'; +import { ScanService } from '../../../../../../../ng-swagger-gen/services/scan.service'; +import { Accessory, ScanType } from 'ng-swagger-gen/models'; +import { ScanTypes } from '../../../../../shared/entities/shared.const'; +import { SBOMOverview } from './sbom-overview'; +import { Scanner } from '../../../../left-side-nav/interrogation-services/scanner/scanner'; +const STATE_CHECK_INTERVAL: number = 3000; // 3s +const RETRY_TIMES: number = 3; + +@Component({ + selector: 'hbr-sbom-bar', + templateUrl: './sbom-scan-component.html', + styleUrls: ['./scanning.scss'], +}) +export class ResultSbomComponent implements OnInit, OnDestroy { + @Input() inputScanner: Scanner; + @Input() repoName: string = ''; + @Input() projectName: string = ''; + @Input() projectId: string = ''; + @Input() artifactDigest: string = ''; + @Input() sbomDigest: string = ''; + @Input() sbomOverview: SBOMOverview; + @Input() accessories: Accessory[] = []; + onSubmitting: boolean = false; + onStopping: boolean = false; + retryCounter: number = 0; + stateCheckTimer: Subscription; + generateSbomSubscription: Subscription; + stopSubscription: Subscription; + timerHandler: any; + @Output() + submitFinish: EventEmitter = new EventEmitter(); + // if sending stop scan request is finished, emit to farther component + @Output() + submitStopFinish: EventEmitter = new EventEmitter(); + @Output() + scanFinished: EventEmitter = new EventEmitter(); + + constructor( + private artifactService: ArtifactService, + private scanService: ScanService, + private errorHandler: ErrorHandler, + private eventService: EventService + ) {} + + ngOnInit(): void { + if ( + (this.status === SBOM_SCAN_STATUS.RUNNING || + this.status === SBOM_SCAN_STATUS.PENDING) && + !this.stateCheckTimer + ) { + // Avoid duplicated subscribing + this.stateCheckTimer = timer(0, STATE_CHECK_INTERVAL).subscribe( + () => { + this.getSbomOverview(); + } + ); + } + if (!this.generateSbomSubscription) { + this.generateSbomSubscription = this.eventService.subscribe( + HarborEvent.START_GENERATE_SBOM, + (artifactDigest: string) => { + let myFullTag: string = + this.repoName + '/' + this.artifactDigest; + if (myFullTag === artifactDigest) { + this.generateSbom(); + } + } + ); + } + if (!this.stopSubscription) { + this.stopSubscription = this.eventService.subscribe( + HarborEvent.STOP_SBOM_ARTIFACT, + (artifactDigest: string) => { + let myFullTag: string = + this.repoName + '/' + this.artifactDigest; + if (myFullTag === artifactDigest) { + this.stopSbom(); + } + } + ); + } + } + + ngOnDestroy(): void { + if (this.stateCheckTimer) { + this.stateCheckTimer.unsubscribe(); + this.stateCheckTimer = null; + } + if (this.generateSbomSubscription) { + this.generateSbomSubscription.unsubscribe(); + this.generateSbomSubscription = null; + } + if (this.stopSubscription) { + this.stopSubscription.unsubscribe(); + this.stopSubscription = null; + } + } + + // Get vulnerability scanning status + public get status(): string { + if (this.sbomOverview && this.sbomOverview.scan_status) { + return this.sbomOverview.scan_status; + } + return SBOM_SCAN_STATUS.NOT_GENERATED_SBOM; + } + + public get completed(): boolean { + return this.status === SBOM_SCAN_STATUS.SUCCESS; + } + + public get error(): boolean { + return this.status === SBOM_SCAN_STATUS.ERROR; + } + + public get queued(): boolean { + return this.status === SBOM_SCAN_STATUS.PENDING; + } + + public get generating(): boolean { + return this.status === SBOM_SCAN_STATUS.RUNNING; + } + + public get stopped(): boolean { + return this.status === SBOM_SCAN_STATUS.STOPPED; + } + + public get otherStatus(): boolean { + return !( + this.completed || + this.error || + this.queued || + this.generating || + this.stopped + ); + } + + generateSbom(): void { + if (this.onSubmitting) { + // Avoid duplicated submitting + console.error('duplicated submit'); + return; + } + + if (!this.repoName || !this.artifactDigest) { + console.error('bad repository or tag'); + return; + } + + this.onSubmitting = true; + + this.scanService + .scanArtifact({ + projectName: this.projectName, + reference: this.artifactDigest, + repositoryName: dbEncodeURIComponent(this.repoName), + scanType: { + scan_type: ScanTypes.SBOM, + }, + }) + .pipe(finalize(() => this.submitFinish.emit(false))) + .subscribe( + () => { + this.onSubmitting = false; + // Forcely change status to queued after successful submitting + this.sbomOverview = { + scan_status: SBOM_SCAN_STATUS.PENDING, + }; + // Start check status util the job is done + if (!this.stateCheckTimer) { + // Avoid duplicated subscribing + this.stateCheckTimer = timer( + STATE_CHECK_INTERVAL, + STATE_CHECK_INTERVAL + ).subscribe(() => { + this.getSbomOverview(); + }); + } + }, + error => { + this.onSubmitting = false; + if (error && error.error && error.error.code === 409) { + console.error(error.error.message); + } else { + this.errorHandler.error(error); + } + } + ); + } + + getSbomOverview(): void { + if (!this.repoName || !this.artifactDigest) { + return; + } + this.artifactService + .getArtifact({ + projectName: this.projectName, + repositoryName: dbEncodeURIComponent(this.repoName), + reference: this.artifactDigest, + withSbomOverview: true, + XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, + }) + .subscribe( + (artifact: Artifact) => { + // To keep the same summary reference, use value copy. + if (artifact.sbom_overview) { + this.copyValue(artifact.sbom_overview); + } + if (!this.queued && !this.generating) { + // Scanning should be done + if (this.stateCheckTimer) { + this.stateCheckTimer.unsubscribe(); + this.stateCheckTimer = null; + } + this.scanFinished.emit(artifact); + } + this.eventService.publish( + HarborEvent.UPDATE_SBOM_INFO, + artifact + ); + }, + error => { + this.errorHandler.error(error); + this.retryCounter++; + if (this.retryCounter >= RETRY_TIMES) { + // Stop timer + if (this.stateCheckTimer) { + this.stateCheckTimer.unsubscribe(); + this.stateCheckTimer = null; + } + this.retryCounter = 0; + } + } + ); + } + + copyValue(newVal: SBOMOverview): void { + if (!this.sbomOverview || !newVal || !newVal.scan_status) { + return; + } + this.sbomOverview = clone(newVal); + } + + viewLog(): string { + return `${CURRENT_BASE_HREF}/projects/${ + this.projectName + }/repositories/${dbEncodeURIComponent(this.repoName)}/artifacts/${ + this.artifactDigest + }/scan/${this.sbomOverview.report_id}/log`; + } + + getScanner(): Scanner { + return this.inputScanner; + } + + stopSbom() { + if (this.onStopping) { + // Avoid duplicated stopping command + console.error('duplicated stopping command for SBOM generation'); + return; + } + if (!this.repoName || !this.artifactDigest) { + console.error('bad repository or artifact'); + return; + } + this.onStopping = true; + + this.scanService + .stopScanArtifact({ + projectName: this.projectName, + reference: this.artifactDigest, + repositoryName: dbEncodeURIComponent(this.repoName), + scanType: { + scan_type: ScanTypes.SBOM, + }, + }) + .pipe( + finalize(() => { + this.submitStopFinish.emit(false); + this.onStopping = false; + }) + ) + .subscribe( + () => { + // Start check status util the job is done + if (!this.stateCheckTimer) { + // Avoid duplicated subscribing + this.stateCheckTimer = timer( + STATE_CHECK_INTERVAL, + STATE_CHECK_INTERVAL + ).subscribe(() => { + this.getSbomOverview(); + }); + } + this.errorHandler.info('SBOM.TRIGGER_STOP_SUCCESS'); + }, + error => { + this.errorHandler.error(error); + } + ); + } +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.html b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.html new file mode 100644 index 0000000000..957146acb2 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.html @@ -0,0 +1,86 @@ +
+ +
+ +
+ {{ 'SBOM.NO_SBOM' | translate }} +
+
+ {{ 'SBOM.COMPLETED,' | translate }} +
+
+ +
+
+
+ {{ 'SBOM.PACKAGES' | translate }} +
+
+ + {{ 'SBOM.NO_SBOM' | translate }} + + + {{ 'SBOM.COMPLETED' | translate }} + +
+
+
+ {{ + 'SCANNER.SCANNED_BY' | translate + }} + {{ getScannerInfo() }} +
+
+ {{ + 'SCANNER.DURATION' | translate + }} + {{ duration() }} +
+
+ {{ 'SBOM.CHART.SCANNING_TIME' | translate }} + + {{ completeTimestamp | harborDatetime : 'short' }} +
+
+
+ +
+ +
+ +
+ {{ 'SBOM.CHART.SCANNING_PERCENT' | translate }} + + {{ completePercent }} +
+
+ {{ + 'SBOM.CHART.SCANNING_PERCENT_EXPLAIN' | translate + }} +
+
+
+
diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.scss b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.scss new file mode 100644 index 0000000000..973fa7e1a7 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.scss @@ -0,0 +1,60 @@ +.tip-wrapper { + display: flex; + align-items: center; + color: #fff; + text-align: center; + font-size: 10px; + height: 15px; + line-height: 15px; +} + +.bar-scanning-time { + margin-top: 12px; +} + +.bar-tooltip-font-larger { + span { + font-size: 16px; + vertical-align: middle + } +} + +hr { + border-bottom: 0; + border-color: #aaa; + margin: 6px -10px; +} + +.font-weight-600 { + font-weight: 600; +} + +.margin-left-5 { + margin-left: 5px; +} + +.width-215 { + width: 215px; +} + +.width-150 { + width: 150px; +} + +.level-border>div{ + display: inline-flex; + align-items: center; + justify-items: center; + border-radius: 50%; + height: 26px; + width: 26px; + line-height: 26px; +} + +.tip-block { + position: relative; +} + +.margin-right-5 { + margin-right: 5px; +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.spec.ts new file mode 100644 index 0000000000..232ec3b5e6 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.spec.ts @@ -0,0 +1,103 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ClarityModule } from '@clr/angular'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SbomTipHistogramComponent } from './sbom-tip-histogram.component'; +import { of } from 'rxjs'; +import { Project } from '../../../../../../../app/base/project/project'; +import { Artifact } from 'ng-swagger-gen/models'; + +describe('SbomTipHistogramComponent', () => { + let component: SbomTipHistogramComponent; + let fixture: ComponentFixture; + const mockRouter = { + navigate: () => {}, + }; + const mockedArtifact: Artifact = { + id: 123, + type: 'IMAGE', + }; + const mockedScanner = { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }; + const mockActivatedRoute = { + RouterparamMap: of({ get: key => 'value' }), + snapshot: { + params: { + repo: 'test', + digest: 'ABC', + subscribe: () => { + return of(null); + }, + }, + parent: { + params: { + id: 1, + }, + }, + data: { + artifactResolver: [mockedArtifact, new Project()], + }, + }, + data: of({ + projectResolver: { + ismember: true, + role_name: 'maintainer', + }, + }), + }; + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + ClarityModule, + TranslateModule.forRoot(), + ], + providers: [ + TranslateService, + { provide: Router, useValue: mockRouter }, + { provide: ActivatedRoute, useValue: mockActivatedRoute }, + ], + declarations: [SbomTipHistogramComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SbomTipHistogramComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('Test SbomTipHistogramComponent basic functions', () => { + fixture.whenStable().then(() => { + expect(component).toBeTruthy(); + expect(component.isLimitedSuccess()).toBeFalsy(); + expect(component.showNoSbom()).toBeTruthy(); + expect(component.isThemeLight()).toBeFalsy(); + expect(component.duration()).toBe('0'); + expect(component.completePercent).toBe('0%'); + }); + }); + + it('Test SbomTipHistogramComponent completeTimestamp', () => { + fixture.whenStable().then(() => { + component.sbomSummary.end_time = new Date('2024-04-08 00:01:02'); + expect(component.completeTimestamp).toBe( + component.sbomSummary.end_time + ); + }); + }); + + it('Test SbomTipHistogramComponent getScannerInfo', () => { + fixture.whenStable().then(() => { + expect(component.getScannerInfo()).toBe(''); + component.scanner = mockedScanner; + expect(component.getScannerInfo()).toBe( + `${mockedScanner.name}@${mockedScanner.version}` + ); + }); + }); +}); diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts new file mode 100644 index 0000000000..72a51e3f40 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts @@ -0,0 +1,126 @@ +import { Component, Input } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SbomSummary } from '../../../../../../shared/services'; +import { SBOM_SCAN_STATUS } from '../../../../../../shared/units/utils'; +import { + UN_LOGGED_PARAM, + YES, +} from '../../../../../../account/sign-in/sign-in.service'; +import { HAS_STYLE_MODE, StyleMode } from '../../../../../../services/theme'; +import { ScanTypes } from '../../../../../../shared/entities/shared.const'; +import { Scanner } from '../../../../../left-side-nav/interrogation-services/scanner/scanner'; +import { Accessory } from 'ng-swagger-gen/models'; +import { AccessoryType } from '../../artifact'; + +const MIN = 60; +const MIN_STR = 'min '; +const SEC_STR = 'sec'; +const SUCCESS_PCT: number = 100; + +@Component({ + selector: 'hbr-sbom-tip-histogram', + templateUrl: './sbom-tip-histogram.component.html', + styleUrls: ['./sbom-tip-histogram.component.scss'], +}) +export class SbomTipHistogramComponent { + @Input() scanner: Scanner; + @Input() sbomSummary: SbomSummary = { + scan_status: SBOM_SCAN_STATUS.NOT_GENERATED_SBOM, + }; + @Input() artifactDigest: string = ''; + @Input() sbomDigest: string = ''; + @Input() accessories: Accessory[] = []; + constructor( + private translate: TranslateService, + private activatedRoute: ActivatedRoute, + private router: Router + ) {} + + duration(): string { + if (this.sbomSummary && this.sbomSummary.duration) { + let str = ''; + const min = Math.floor(this.sbomSummary.duration / MIN); + if (min) { + str += min + ' ' + MIN_STR; + } + const sec = this.sbomSummary.duration % MIN; + if (sec) { + str += sec + ' ' + SEC_STR; + } + return str; + } + return '0'; + } + + public getSbomAccessories(): Accessory[] { + return ( + this.accessories.filter( + accessory => accessory.type === AccessoryType.SBOM + ) ?? [] + ); + } + + public get completePercent(): string { + return this.sbomSummary.scan_status === SBOM_SCAN_STATUS.SUCCESS + ? `100%` + : '0%'; + } + + isLimitedSuccess(): boolean { + return ( + this.sbomSummary && this.sbomSummary.complete_percent < SUCCESS_PCT + ); + } + get completeTimestamp(): Date { + return this.sbomSummary && this.sbomSummary.end_time + ? this.sbomSummary.end_time + : new Date(); + } + + showSbomDetailLink(): boolean { + return this.sbomDigest && this.getSbomAccessories.length > 0; + } + + showNoSbom(): boolean { + return !this.sbomDigest && this.getSbomAccessories.length === 0; + } + + isThemeLight() { + return localStorage.getItem(HAS_STYLE_MODE) === StyleMode.LIGHT; + } + + getScannerInfo(): string { + if (this.scanner) { + if (this.scanner.name && this.scanner.version) { + return `${this.scanner.name}@${this.scanner.version}`; + } + if (this.scanner.name && !this.scanner.version) { + return `${this.scanner.name}`; + } + } + return ''; + } + + goIntoArtifactSbomSummaryPage(): void { + const relativeRouterLink: string[] = ['artifacts', this.artifactDigest]; + if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) { + this.router.navigate(relativeRouterLink, { + relativeTo: this.activatedRoute, + queryParams: { + [UN_LOGGED_PARAM]: YES, + sbomDigest: this.sbomDigest ?? '', + tab: ScanTypes.SBOM, + }, + }); + } else { + this.router.navigate(relativeRouterLink, { + relativeTo: this.activatedRoute, + queryParams: { + sbomDigest: this.sbomDigest ?? '', + tab: ScanTypes.SBOM, + }, + }); + } + } +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/scanning.scss b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/scanning.scss new file mode 100644 index 0000000000..dc983f0090 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/scanning.scss @@ -0,0 +1,41 @@ +.bar-wrapper { + width: 210px; +} + +hbr-sbom-bar { + .label,.not-scan { + width: 90%; + } +} + +.bar-state { + .unknow-text { + margin-left: -5px; + } + + .label { + width: 50%; + } +} + +.bar-state-chart { + .loop-height { + height: 2px; + width: 100px; + } +} + +.bar-state-error { + position: relative; +} + +.error-text { + position: relative; + top: 1px; + margin-left: -5px; + cursor: pointer; +} + +.stopped { + border-color: #cccc15; +} diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts index 1325491865..66534a82ba 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts @@ -11,14 +11,43 @@ import { import { VULNERABILITY_SCAN_STATUS } from '../../../../../shared/units/utils'; import { NativeReportSummary } from '../../../../../../../ng-swagger-gen/models/native-report-summary'; import { SharedTestingModule } from '../../../../../shared/shared.module'; +import { of, timer } from 'rxjs'; +import { ArtifactService, ScanService } from 'ng-swagger-gen/services'; describe('ResultBarChartComponent (inline template)', () => { let component: ResultBarChartComponent; let fixture: ComponentFixture; + const mockedSbomDigest = + 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3'; let mockData: NativeReportSummary = { + report_id: '12345', scan_status: VULNERABILITY_SCAN_STATUS.SUCCESS, severity: 'High', end_time: new Date().toUTCString(), + scanner: { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }, + summary: { + total: 124, + fixable: 50, + summary: { + High: 5, + Low: 5, + }, + }, + }; + let mockCloneData: NativeReportSummary = { + report_id: '123456', + scan_status: VULNERABILITY_SCAN_STATUS.SUCCESS, + severity: 'High', + end_time: new Date().toUTCString(), + scanner: { + name: 'Trivy', + vendor: 'vm', + version: 'v1.3', + }, summary: { total: 124, fixable: 50, @@ -29,6 +58,59 @@ describe('ResultBarChartComponent (inline template)', () => { }, }; + const FakedScanService = { + scanArtifact: () => of({}), + stopScanArtifact: () => of({}), + }; + const FakedArtifactService = { + getArtifact: () => + of({ + accessories: null, + addition_links: { + build_history: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/build_history', + }, + vulnerabilities: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/vulnerabilities', + }, + }, + digest: 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3', + extra_attrs: { + architecture: 'amd64', + author: '', + config: { + Env: [ + 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + ], + WorkingDir: '/', + }, + created: '2024-01-10T10:05:33.2702206Z', + os: 'linux', + }, + icon: 'sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06', + id: 3, + labels: null, + manifest_media_type: + 'application/vnd.docker.distribution.manifest.v2+json', + media_type: 'application/vnd.docker.container.image.v1+json', + project_id: 3, + pull_time: '2024-04-02T01:50:58.332Z', + push_time: '2024-03-06T09:47:08.163Z', + references: null, + repository_id: 2, + scan_overview: { + duration: 2, + end_time: '2024-04-02T01:50:59.406Z', + scan_status: 'Success', + start_time: '2024-04-02T01:50:57.176Z', + }, + size: 3957, + tags: null, + type: 'IMAGE', + }), + }; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [SharedTestingModule], @@ -43,6 +125,14 @@ describe('ResultBarChartComponent (inline template)', () => { useValue: ScanningResultDefaultService, }, { provide: JobLogService, useValue: JobLogDefaultService }, + { + provide: ScanService, + useValue: FakedScanService, + }, + { + provide: ArtifactService, + useValue: FakedArtifactService, + }, ], }).compileComponents(); }); @@ -52,6 +142,8 @@ describe('ResultBarChartComponent (inline template)', () => { component = fixture.componentInstance; component.artifactDigest = 'mockTag'; component.summary = mockData; + component.repoName = 'mockRepo'; + component.artifactDigest = mockedSbomDigest; fixture.detectChanges(); }); @@ -109,4 +201,69 @@ describe('ResultBarChartComponent (inline template)', () => { expect(el).not.toBeNull(); }); }); + it('Test ResultBarChartComponent getScanner', () => { + fixture.detectChanges(); + component.summary = mockData; + expect(component.getScanner()).toBe(mockData.scanner); + component.projectName = 'test'; + component.repoName = 'ui'; + component.artifactDigest = 'dg'; + expect(component.viewLog()).toBe( + '/api/v2.0/projects/test/repositories/ui/artifacts/dg/scan/12345/log' + ); + component.copyValue(mockCloneData); + expect(component.summary.report_id).toBe(mockCloneData.report_id); + }); + it('Test ResultBarChartComponent status', () => { + fixture.detectChanges(); + component.summary.scan_status = VULNERABILITY_SCAN_STATUS.SUCCESS; + expect(component.status).toBe(VULNERABILITY_SCAN_STATUS.SUCCESS); + expect(component.completed).toBeTruthy(); + expect(component.queued).toBeFalsy(); + expect(component.scanning).toBeFalsy(); + expect(component.stopped).toBeFalsy(); + expect(component.otherStatus).toBeFalsy(); + expect(component.error).toBeFalsy(); + }); + it('Test ResultBarChartComponent ngOnDestroy', () => { + component.stateCheckTimer = timer(0, 10000).subscribe(() => {}); + component.ngOnDestroy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.stateCheckTimer).toBeNull(); + expect(component.scanSubscription).toBeNull(); + expect(component.stopSubscription).toBeNull(); + }); + }); + it('Test ResultBarChartComponent scanNow', () => { + fixture.detectChanges(); + component.scanNow(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onSubmitting).toBeFalse(); + }); + }); + it('Test ResultBarChartComponent stopScan', () => { + fixture.detectChanges(); + component.stopScan(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onStopping).toBeFalse(); + expect(component.stateCheckTimer).toBeNull(); + }); + }); + it('Test ResultBarChartComponent getSummary', () => { + fixture.detectChanges(); + component.getSummary(); + component.summary.scan_status = VULNERABILITY_SCAN_STATUS.SUCCESS; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.summary.scan_status).toBe( + VULNERABILITY_SCAN_STATUS.SUCCESS + ); + }); + }); }); diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts index 69568d550e..0e14ba1d49 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts @@ -25,6 +25,8 @@ import { HarborEvent, } from '../../../../../services/event-service/event.service'; import { ScanService } from '../../../../../../../ng-swagger-gen/services/scan.service'; +import { ScanType } from 'ng-swagger-gen/models'; +import { ScanTypes } from '../../../../../shared/entities/shared.const'; const STATE_CHECK_INTERVAL: number = 3000; // 3s const RETRY_TIMES: number = 3; @@ -110,7 +112,7 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { this.scanSubscription.unsubscribe(); this.scanSubscription = null; } - if (!this.stopSubscription) { + if (this.stopSubscription) { this.stopSubscription.unsubscribe(); this.stopSubscription = null; } @@ -171,6 +173,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { projectName: this.projectName, reference: this.artifactDigest, repositoryName: dbEncodeURIComponent(this.repoName), + scanType: { + scan_type: ScanTypes.VULNERABILITY, + }, }) .pipe(finalize(() => this.submitFinish.emit(false))) .subscribe( @@ -212,6 +217,7 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { repositoryName: dbEncodeURIComponent(this.repoName), reference: this.artifactDigest, withScanOverview: true, + withAccessory: true, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, }) .subscribe( @@ -286,6 +292,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { projectName: this.projectName, reference: this.artifactDigest, repositoryName: dbEncodeURIComponent(this.repoName), + scanType: { + scan_type: ScanTypes.VULNERABILITY, + }, }) .pipe( finalize(() => { diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss index 5d1f983356..248361e330 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss @@ -8,7 +8,7 @@ } .label { - width: 90%; + width: 50%; } } diff --git a/src/portal/src/app/services/event-service/event.service.ts b/src/portal/src/app/services/event-service/event.service.ts index 88d5e28bd1..31032f52d8 100644 --- a/src/portal/src/app/services/event-service/event.service.ts +++ b/src/portal/src/app/services/event-service/event.service.ts @@ -76,8 +76,11 @@ export enum HarborEvent { SCROLL_TO_POSITION = 'scrollToPosition', REFRESH_PROJECT_INFO = 'refreshProjectInfo', START_SCAN_ARTIFACT = 'startScanArtifact', + START_GENERATE_SBOM = 'startGenerateSbom', STOP_SCAN_ARTIFACT = 'stopScanArtifact', + STOP_SBOM_ARTIFACT = 'stopSbomArtifact', UPDATE_VULNERABILITY_INFO = 'UpdateVulnerabilityInfo', + UPDATE_SBOM_INFO = 'UpdateSbomInfo', REFRESH_EXPORT_JOBS = 'refreshExportJobs', DELETE_ACCESSORY = 'deleteAccessory', COPY_DIGEST = 'copyDigest', diff --git a/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts b/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts index 77701f2a42..c67761e2a1 100644 --- a/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts +++ b/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts @@ -18,7 +18,7 @@ import { ActivatedRouteSnapshot, } from '@angular/router'; import { forkJoin, Observable, of } from 'rxjs'; -import { map, catchError, mergeMap } from 'rxjs/operators'; +import { catchError, mergeMap } from 'rxjs/operators'; import { Artifact } from '../../../../ng-swagger-gen/models/artifact'; import { ArtifactService } from '../../../../ng-swagger-gen/services/artifact.service'; import { Project } from '../../base/project/project'; @@ -51,6 +51,7 @@ export class ArtifactDetailRoutingResolverService { projectName: project.name, withLabel: true, withScanOverview: true, + withSbomOverview: true, withTag: false, withImmutableStatus: true, }), diff --git a/src/portal/src/app/shared/entities/shared.const.ts b/src/portal/src/app/shared/entities/shared.const.ts index be9bdab5f1..573cda40bb 100644 --- a/src/portal/src/app/shared/entities/shared.const.ts +++ b/src/portal/src/app/shared/entities/shared.const.ts @@ -382,3 +382,8 @@ export const stringsForClarity: Partial = { datepickerSelectYearText: 'CLARITY.DATE_PICKER_SELECT_YEAR_TEXT', datepickerSelectedLabel: 'CLARITY.DATE_PICKER_SELECTED_LABEL', }; + +export enum ScanTypes { + SBOM = 'sbom', + VULNERABILITY = 'vulnerability', +} diff --git a/src/portal/src/app/shared/services/endpoint.service.ts b/src/portal/src/app/shared/services/endpoint.service.ts index a22a1d6fdc..0b007bca08 100644 --- a/src/portal/src/app/shared/services/endpoint.service.ts +++ b/src/portal/src/app/shared/services/endpoint.service.ts @@ -29,6 +29,7 @@ export const ADAPTERS_MAP = { dtr: 'DTR', 'tencent-tcr': 'Tencent TCR', 'github-ghcr': 'Github GHCR', + 'volcengine-cr': 'VolcEngine CR', }; /** diff --git a/src/portal/src/app/shared/services/interface.ts b/src/portal/src/app/shared/services/interface.ts index 5d641c99a8..1afbc36af3 100644 --- a/src/portal/src/app/shared/services/interface.ts +++ b/src/portal/src/app/shared/services/interface.ts @@ -211,6 +211,16 @@ export interface VulnerabilitySummary { scanner?: ScannerVo; complete_percent?: number; } +export interface SbomSummary { + report_id?: string; + sbom_digest?: string; + scan_status?: string; + duration?: number; + start_time?: Date; + end_time?: Date; + scanner?: ScannerVo; + complete_percent?: number; +} export interface ScannerVo { name?: string; vendor?: string; diff --git a/src/portal/src/app/shared/services/permission-static.ts b/src/portal/src/app/shared/services/permission-static.ts index 125b15f945..fc15a03031 100644 --- a/src/portal/src/app/shared/services/permission-static.ts +++ b/src/portal/src/app/shared/services/permission-static.ts @@ -105,6 +105,13 @@ export const USERSTATICPERMISSION = { READ: 'read', }, }, + REPOSITORY_TAG_SBOM_JOB: { + KEY: 'sbom', + VALUE: { + CREATE: 'create', + READ: 'read', + }, + }, REPOSITORY_ARTIFACT_LABEL: { KEY: 'artifact-label', VALUE: { diff --git a/src/portal/src/app/shared/services/project.service.ts b/src/portal/src/app/shared/services/project.service.ts index 0a7a7ff8d3..6b8fa2e74e 100644 --- a/src/portal/src/app/shared/services/project.service.ts +++ b/src/portal/src/app/shared/services/project.service.ts @@ -158,6 +158,9 @@ export class ProjectDefaultService extends ProjectService { auto_scan: projectPolicy.ScanImgOnPush ? 'true' : 'false', + auto_sbom_generation: projectPolicy.GenerateSbomOnPush + ? 'true' + : 'false', reuse_sys_cve_allowlist: reuseSysCVEVAllowlist, }, cve_allowlist: projectAllowlist, diff --git a/src/portal/src/app/shared/shared.module.ts b/src/portal/src/app/shared/shared.module.ts index 5409a12e5a..b64995e7aa 100644 --- a/src/portal/src/app/shared/shared.module.ts +++ b/src/portal/src/app/shared/shared.module.ts @@ -127,6 +127,23 @@ ClarityIcons.add({ 21.18,0,0,0,4,21.42,21,21,0,0,0,7.71,33.58a1,1,0,0,0,.81.42h19a1,1,0,0,0, .81-.42A21,21,0,0,0,32,21.42,21.18,21.18,0,0,0,29.1,10.49Z"/> `, + sbom: ` + + + + + + + + + + + + +SBOM + + +`, }); @NgModule({ diff --git a/src/portal/src/app/shared/units/shared.utils.ts b/src/portal/src/app/shared/units/shared.utils.ts index 2b686316f6..f89cc772dc 100644 --- a/src/portal/src/app/shared/units/shared.utils.ts +++ b/src/portal/src/app/shared/units/shared.utils.ts @@ -142,7 +142,16 @@ export const errorHandler = function (error: any): string { } // Not a standard error return Basically not used cover unknown error try { - return JSON.parse(error.error).message; + const jsonError = JSON.parse(error.error); + if (jsonError.errors && jsonError.errors instanceof Array) { + return ( + jsonError.errors?.map(error => error.message) ?? [ + 'UNKNOWN_ERROR', + ] + ).join(','); + } else { + return JSON.parse(error.error).message; + } } catch (err) {} // Not a standard error return Basically not used cover unknown error if (typeof error.error === 'string') { diff --git a/src/portal/src/app/shared/units/utils.ts b/src/portal/src/app/shared/units/utils.ts index ad38954acf..1a7a91df71 100644 --- a/src/portal/src/app/shared/units/utils.ts +++ b/src/portal/src/app/shared/units/utils.ts @@ -252,6 +252,18 @@ export const DEFAULT_PAGE_SIZE: number = 15; */ export const DEFAULT_SUPPORTED_MIME_TYPES = 'application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0'; +/** + * The default supported mime type for SBOM + */ +export const DEFAULT_SBOM_SUPPORTED_MIME_TYPES = + 'application/vnd.security.sbom.report+json; version=1.0'; +/** + * The SBOM supported additional mime types + */ +export const SBOM_SUPPORTED_ADDITIONAL_MIME_TYPES = [ + 'application/spdx+json', + // 'application/vnd.cyclonedx+json', // feature release +]; /** * the property name of vulnerability database updated time @@ -275,6 +287,21 @@ export const VULNERABILITY_SCAN_STATUS = { SUCCESS: 'Success', SCHEDULED: 'Scheduled', }; + +/** + * The state of sbom generation + */ +export const SBOM_SCAN_STATUS = { + // front-end status + NOT_GENERATED_SBOM: 'Not generated SBOM', + // back-end status + PENDING: 'Pending', + RUNNING: 'Running', + ERROR: 'Error', + STOPPED: 'Stopped', + SUCCESS: 'Success', + SCHEDULED: 'Scheduled', +}; /** * The severity of vulnerability scanning */ @@ -468,6 +495,26 @@ export function downloadFile(fileData) { a.remove(); } +/** + * Download the Json Object as a Json file to local. + * @param data Json Object + * @param filename Json filename + */ +export function downloadJson(data, filename) { + const blob = new Blob([JSON.stringify(data)], { + type: 'application/json;charset=utf-8', + }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + document.body.appendChild(a); + a.setAttribute('style', 'display: none'); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); +} + export function getChanges( original: any, afterChange: any @@ -1015,6 +1062,7 @@ export enum PageSizeMapKeys { ARTIFACT_LIST_TAB_COMPONENT = 'ArtifactListTabComponent', ARTIFACT_TAGS_COMPONENT = 'ArtifactTagComponent', ARTIFACT_VUL_COMPONENT = 'ArtifactVulnerabilitiesComponent', + ARTIFACT_SBOM_COMPONENT = 'ArtifactSbomComponent', MEMBER_COMPONENT = 'MemberComponent', LABEL_COMPONENT = 'LabelComponent', P2P_POLICY_COMPONENT = 'P2pPolicyComponent', diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 02be72d265..18fa0c09c0 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -286,7 +286,10 @@ "PREVENT_VULNERABLE_2": "und darüber.", "SCAN": "Scannen auf Schwachstellen", "AUTOSCAN_TOGGLE": "Images automatisch beim Hochladen scannen", - "AUTOSCAN_POLICY": "Scanne Images automatisch, wenn sie in das Projekt hochgeladen werden." + "AUTOSCAN_POLICY": "Scanne Images automatisch, wenn sie in das Projekt hochgeladen werden.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Nutzer als Mitglied hinzufügen", @@ -403,6 +406,7 @@ "REPOSITORY": "Repository", "ARTIFACT": "Artifact", "SCAN": "Scan", + "SBOM": "SBOM", "TAG": "Tag", "ACCESSORY": "Accessory", "ARTIFACT_ADDITION": "Artifact Addition", @@ -699,7 +703,7 @@ "FLATTEN_LEVEL_TIP_3": "'Reduzierung um 3 Ebenen': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandbreite", "BANDWIDTH_ERROR_TIP": "Bitte -1 oder einen Integer-Wert größer als 0 eingeben", - "BANDWIDTH_TOOLTIP": "Legt die maximale Netzwerkbandbreite für jede Ausführung fest. Bitte auf die Anzahl der parallelen Ausführungen achten. Für umbegrenzte Bandbreite, bitte -1 eingeben", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unbegrenzt", "UNREACHABLE_SOURCE_REGISTRY": "Verbindung zur Quell-Registry fehlgeschlagen, bitte sicherstellen, dass die Quell-Registry verfügbar ist, bevor diese Regel angepasst wird: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", @@ -774,6 +778,7 @@ "ARTIFACTS": "Artefakte", "SIZE": "Größe", "VULNERABILITY": "Schwachstellen", + "SBOM": "SBOM", "BUILD_HISTORY": "Build History", "SIGNED": "Signiert", "AUTHOR": "Autor", @@ -800,6 +805,7 @@ "FILTER_BY_LABEL": "Images nach Label filtern", "FILTER_ARTIFACT_BY_LABEL": "Artefakte nach Label filtern", "ADD_LABELS": "Label hinzufügen", + "STOP": "Stop", "RETAG": "Kopieren", "ACTION": "AKTION", "DEPLOY": "Bereitstellen", @@ -1024,6 +1030,42 @@ "IN_PROGRESS": "Suche...", "BACK": "Zurück" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Nicht gescannt", @@ -1068,7 +1110,7 @@ "PLACEHOLDER": "Filter Schwachstellen", "PACKAGE": "Paket", "PACKAGES": "Pakete", - "SCAN_NOW": "Scan", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "SCAN DURCH {{scanner}}", "REPORTED_BY": "GEMELDET VON {{scanner}}", "NO_SCANNER": "KEIN SCANNER", @@ -1104,6 +1146,7 @@ "ALL": "Alle", "PLACEHOLDER": "Keine Artefakte gefunden!", "SCAN_UNSUPPORTED": "Nicht unterstützt", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Zusammenfassung", "DEPENDENCIES": "Dependencies", "VALUES": "Values", @@ -1430,6 +1473,10 @@ "NAME_REQUIRED": "Name ist erforderlich", "NAME_REX": "Der Name muss mindestens 2 Zeichen lang sein. Mit Kleinbuchstaben, Ziffern und ._-. Er muss mit Buchstaben oder Ziffern beginnen.", "DESCRIPTION": "Beschreibung", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpunkt", "ENDPOINT_EXISTS": "URL existiert bereits", "ENDPOINT_REQUIRED": "URL ist erforderlich", @@ -1458,9 +1505,10 @@ "NAME_COLON": "Name:", "VENDOR_COLON": "Hersteller:", "VERSION_COLON": "Version:", - "CAPABILITIES": "Fähigkeiten", + "CAPABILITIES": "Fähigkeiten:", "CONSUMES_MIME_TYPES_COLON": "Benötigt Mime Types:", "PRODUCTS_MIME_TYPES_COLON": "Erzeugt Mime Types:", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "Eigenschaften", "NEW_SCANNER": "NEUER SCANNER", "SET_AS_DEFAULT": "ALS STANDARD SETZEN", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 2fb912febf..05ecdb8e73 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -286,7 +286,10 @@ "PREVENT_VULNERABLE_2": "and above from being deployed.", "SCAN": "Vulnerability scanning", "AUTOSCAN_TOGGLE": "Automatically scan images on push", - "AUTOSCAN_POLICY": "Automatically scan images when they are pushed to the project registry." + "AUTOSCAN_POLICY": "Automatically scan images when they are pushed to the project registry.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Add User Member", @@ -403,6 +406,7 @@ "REPOSITORY": "Repository", "ARTIFACT": "Artifact", "SCAN": "Scan", + "SBOM": "SBOM", "TAG": "Tag", "ACCESSORY": "Accessory", "ARTIFACT_ADDITION": "Artifact Addition", @@ -700,7 +704,7 @@ "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandwidth", "BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0", - "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unlimited", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", @@ -775,6 +779,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "Size", "VULNERABILITY": "Vulnerabilities", + "SBOM": "SBOM", "BUILD_HISTORY": "Build History", "SIGNED": "Signed", "AUTHOR": "Author", @@ -801,6 +806,7 @@ "FILTER_BY_LABEL": "Filter images by label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", "ADD_LABELS": "Add Labels", + "STOP": "Stop", "RETAG": "Copy", "ACTION": "ACTION", "DEPLOY": "DEPLOY", @@ -1025,6 +1031,42 @@ "IN_PROGRESS": "Search...", "BACK": "Back" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM ", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Not Scanned", @@ -1069,7 +1111,7 @@ "PLACEHOLDER": "Filter Vulnerabilities", "PACKAGE": "package", "PACKAGES": "packages", - "SCAN_NOW": "Scan", + "SCAN_NOW": "Scan vulnerability ", "SCAN_BY": "SCAN BY {{scanner}}", "REPORTED_BY": "Reported by {{scanner}}", "NO_SCANNER": "NO SCANNER", @@ -1105,6 +1147,7 @@ "ALL": "All", "PLACEHOLDER": "We couldn't find any artifacts!", "SCAN_UNSUPPORTED": "Unsupported", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Summary", "DEPENDENCIES": "Dependencies", "VALUES": "Values", @@ -1431,6 +1474,10 @@ "NAME_REQUIRED": "Name is required", "NAME_REX": "Name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "EndpointUrl already exists", "ENDPOINT_REQUIRED": "EndpointUrl is required", @@ -1459,9 +1506,10 @@ "NAME_COLON": "Name:", "VENDOR_COLON": "Vendor:", "VERSION_COLON": "Version:", - "CAPABILITIES": "Capabilities", + "CAPABILITIES": "Capabilities:", "CONSUMES_MIME_TYPES_COLON": "Consumes Mime Types:", "PRODUCTS_MIME_TYPES_COLON": "Produces Mime Types:", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "Properties", "NEW_SCANNER": "NEW SCANNER", "SET_AS_DEFAULT": "SET AS DEFAULT", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index dfa6c54490..81a38cfb36 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -287,7 +287,10 @@ "PREVENT_VULNERABLE_2": "y más arriba de ser desplegado.", "SCAN": "Escaneo de vulnerabilidad", "AUTOSCAN_TOGGLE": "Escanee automáticamente las imágenes al instante", - "AUTOSCAN_POLICY": "Escanee automáticamente las imágenes cuando son enviadas al registro del proyecto." + "AUTOSCAN_POLICY": "Escanee automáticamente las imágenes cuando son enviadas al registro del proyecto.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Add User Member", @@ -404,6 +407,7 @@ "REPOSITORY": "Repository", "ARTIFACT": "Artifact", "SCAN": "Scan", + "SBOM": "SBOM", "TAG": "Tag", "ACCESSORY": "Accessory", "ARTIFACT_ADDITION": "Artifact Addition", @@ -700,7 +704,7 @@ "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandwidth", "BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0", - "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unlimited", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", @@ -775,6 +779,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "Size", "VULNERABILITY": "Vulnerabilities", + "SBOM": "SBOM", "BUILD_HISTORY": "Construir Historia", "SIGNED": "Firmada", "AUTHOR": "Autor", @@ -801,6 +806,7 @@ "FILTER_BY_LABEL": "Filter images by label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", "ADD_LABELS": "Add Labels", + "STOP": "Stop", "RETAG": "Copy", "ACTION": "ACTION", "DEPLOY": "DEPLOY", @@ -1023,6 +1029,42 @@ "IN_PROGRESS": "Buscar...", "BACK": "Volver" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Not Scanned", @@ -1067,7 +1109,7 @@ "PLACEHOLDER": "Filter Vulnerabilities", "PACKAGE": "package", "PACKAGES": "packages", - "SCAN_NOW": "Scan", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "SCAN BY {{scanner}}", "REPORTED_BY": "Reported by {{scanner}}", "NO_SCANNER": "NO SCANNER", @@ -1103,6 +1145,7 @@ "ALL": "All", "PLACEHOLDER": "We couldn't find any artifacts!", "SCAN_UNSUPPORTED": "Unsupported", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Summary", "DEPENDENCIES": "Dependencies", "VALUES": "Values", @@ -1427,6 +1470,10 @@ "NAME_REQUIRED": "Name is required", "NAME_REX": "Name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "EndpointUrl already exists", "ENDPOINT_REQUIRED": "EndpointUrl is required", @@ -1455,9 +1502,10 @@ "NAME_COLON": "Name:", "VENDOR_COLON": "Vendor:", "VERSION_COLON": "Version:", - "CAPABILITIES": "Capabilities", + "CAPABILITIES": "Capabilities:", "CONSUMES_MIME_TYPES_COLON": "Consumes Mime Types:", "PRODUCTS_MIME_TYPES_COLON": "Produces Mime Types:", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "Properties", "NEW_SCANNER": "NEW SCANNER", "SET_AS_DEFAULT": "SET AS DEFAULT", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 91b09b8b5f..a8567ac37b 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -5,7 +5,7 @@ "VIC": "vSphere Integrated Containers", "MGMT": "Management", "REG": "Registre", - "HARBOR_SWAGGER": "Harbor Swagger", + "HARBOR_SWAGGER": "Swagger Harbor", "THEME_DARK_TEXT": "SOMBRE", "THEME_LIGHT_TEXT": "CLAIR" }, @@ -28,7 +28,7 @@ "DELETE": "Supprimer", "LOG_IN": "S'identifier", "LOG_IN_OIDC": "Connexion via fournisseur OIDC", - "LOG_IN_OIDC_WITH_PROVIDER_NAME": "LOGIN WITH {{providerName}}", + "LOG_IN_OIDC_WITH_PROVIDER_NAME": "S'IDENTIFIER AVEC {{providerName}}", "SIGN_UP_LINK": "Ouvrir un compte", "SIGN_UP": "S'inscrire", "CONFIRM": "Confirmer", @@ -44,6 +44,8 @@ "NEGATIVE": "Négatif", "COPY": "Copier", "EDIT": "Éditer", + "SWITCH": "Basculer", + "REPLICATE": "Répliquer", "ACTIONS": "Actions", "BROWSE": "Parcourir", "UPLOAD": "Téléverser", @@ -92,6 +94,8 @@ "CRON_REQUIRED": "Le champ est obligatoire et doit être au format cron.", "EMAIL_EXISTING": "L'adresse e-mail existe déjà.", "USER_EXISTING": "Le nom d'utilisateur est déjà utilisé.", + "RULE_USER_EXISTING": "Le nom est déjà utilisé.", + "EMPTY": "Le nom est requis", "NONEMPTY": "Ne peut pas être vide", "REPO_TOOLTIP": "Les utilisateurs ne peuvent effectuer aucune opération sur les images dans ce mode.", "ENDPOINT_FORMAT": "L'endpoint doit commencer par HTTP:// or HTTPS://.", @@ -126,10 +130,12 @@ "ADMIN_RENAME_TIP": "Cliquez sur le bouton pour changer le nom d'utilisateur en \"admin@harbor.local\". Cette opération ne peut pas être annulée.", "RENAME_SUCCESS": "Renommage effectué !", "RENAME_CONFIRM_INFO": "Attention, changer le nom d'utilisateur pour \"admin@harbor.local\" ne peut pas être annulé.", - "CLI_PASSWORD": "CLI secret", - "CLI_PASSWORD_TIP": "The CLI secret can be used as a password, for Docker or Helm client. With the OIDC auth mode enabled, we strongly recommend using robot accounts, as CLI secrets depend on the validity of the ID token and require the user to regularly log in to the UI to refresh the token.", + "CLI_PASSWORD": "Secret CLI", + "CLI_PASSWORD_TIP": "Le secret CLI peut être utilisé comme mot de passe pour le client Docker ou Helm. Lorsque le mode d'authentification est OIDC, nous recommandons fortement d'utiliser des comptes robots, car les secrets CLI dépendent de la validité du jeton ID et nécessitent que l'utilisateur se connecte régulièrement à l'interface utilisateur pour rafraîchir le jeton.", "COPY_SUCCESS": "Copie effectuée", "COPY_ERROR": "Copie échouée", + "ADMIN_CLI_SECRET_BUTTON": "Générer le secret", + "ADMIN_CLI_SECRET_RESET_BUTTON": "Téléversez votre secret", "NEW_SECRET": "Secret", "CONFIRM_SECRET": "Ré-entrez votre Secret", "GENERATE_SUCCESS": "Mise en place du CLI secret effectuée", @@ -242,12 +248,12 @@ "INLINE_HELP_PUBLIC": "Lorsqu'un projet est mis en public, n'importe qui a l'autorisation de lire les dépôts sous ce projet, et l'utilisateur n'a pas besoin d'exécuter \"docker login\" avant de prendre des images de ce projet.", "OF": "sur", "COUNT_QUOTA": "Quota de nombre", - "STORAGE_QUOTA": "Project quota limits", + "STORAGE_QUOTA": "Quota du projet", "COUNT_QUOTA_TIP": "Entrez un entier entre '1' et '100000000', ou '-1' pour un quota illimité", "STORAGE_QUOTA_TIP": "La limite haute du quota de stockage n'accepte que des valeurs entières, au maximum '1024TB'. Entrez '-1' pour un quota illimité", - "QUOTA_UNLIMIT_TIP": "The maximum logical space that can be used by the project. Pour un quota illimité, entrez '-1'.", + "QUOTA_UNLIMIT_TIP": "Espace maximum logique pouvant être utilisé par le projet. Pour un quota illimité, entrez '-1'.", "TYPE": "Type", - "PROXY_CACHE": "Proxy Cache", + "PROXY_CACHE": "Cache proxy", "PROXY_CACHE_TOOLTIP": "Activez cette option pour permettre à ce projet d'agir comme un cache de pull pour un espace de noms particulier dans un registre cible. Harbor ne peut agir en tant que proxy que pour les registres DockerHub et Harbor.", "ENDPOINT": "Endpoint", "PROXY_CACHE_ENDPOINT": "Endpoint du Proxy Cache", @@ -280,15 +286,29 @@ "PREVENT_VULNERABLE_2": "et au-dessus d'être déployées.", "SCAN": "Analyse des vulnérabilités", "AUTOSCAN_TOGGLE": "Analyse automatique des images lors de l'envoi", - "AUTOSCAN_POLICY": "Analyser automatiquement les images lorsqu'elles sont envoyées au projet du registre." + "AUTOSCAN_POLICY": "Analyser automatiquement les images lorsqu'elles sont envoyées au projet du registre.", + "SBOM": "Génération de SBOM", + "AUTOSBOM_TOGGLE": "Générer automatiquement un SBOM au push", + "AUTOSBOM_POLICY": "Générer automatiquement un SBOM lorsque les images sont poussées sur le registre." }, "MEMBER": { "NEW_USER": "Ajouter un nouveau membre", "NEW_MEMBER": "Nouveau membre", "MEMBER": "Membre", "NAME": "Nom", - "EMAIL": "Email", "ROLE": "Rôle", + "SYS_ADMIN": "Administrateur système", + "PROJECT_ADMIN": "Administrateur de projet", + "PROJECT_MAINTAINER": "Mainteneur du projet", + "DEVELOPER": "Développeur", + "GUEST": "Invité", + "LIMITED_GUEST": "Invité avec droits limités", + "DELETE": "Supprimer", + "ITEMS": "entrées", + "ACTIONS": "Actions", + "USER": " Utilisateur", + "USERS": "Utilisateurs", + "EMAIL": "Email", "ADD_USER": "Ajouter un utilisateur", "NEW_USER_INFO": "Ajouter un utilisateur pour être membre de ce projet avec le rôle spécifié", "NEW_GROUP": "Nouveau groupe", @@ -302,20 +322,9 @@ "LDAP_GROUPS": "Groupes", "LDAP_PROPERTY": "Propriété", "ACTION": "Action", - "USER": " Utilisateur", - "USERS": "Utilisateurs", "MEMBER_TYPE": "Type de membre", "GROUP_TYPE": "Groupe", "USER_TYPE": "Utilisateur", - "SYS_ADMIN": "Administrateur système", - "PROJECT_ADMIN": "Administrateur de projet", - "PROJECT_MAINTAINER": "Mainteneur du projet", - "DEVELOPER": "Développeur", - "GUEST": "Invité", - "LIMITED_GUEST": "Invité avec droits limités", - "DELETE": "Supprimer", - "ITEMS": "entrées", - "ACTIONS": "Actions", "USERNAME_IS_REQUIRED": "Nom d'utilisateur requis", "USERNAME_DOES_NOT_EXISTS": "Ce nom d'utilisateur n'existe pas.", "USERNAME_ALREADY_EXISTS": "Ce nom d'utilisateur existe déjà.", @@ -327,8 +336,10 @@ "DELETED_SUCCESS": "Membre supprimé avec succès.", "SWITCHED_SUCCESS": "Rôle du membre changé avec succès.", "OF": "sur", + "SWITCH_TITLE": "Confirmez le changement de membres projet", + "SWITCH_SUMMARY": "Voulez-vous changer les membres projet {{param}}?", "SET_ROLE": "Définir Role", - "REMOVE": "Remove", + "REMOVE": "Retirer", "GROUP_NAME_REQUIRED": "Le nom du groupe est requis", "NON_EXISTENT_GROUP": "Ce groupe n'existe pas", "GROUP_ALREADY_ADDED": "Ce groupe a déjà été ajouté au projet" @@ -339,11 +350,11 @@ "TOKEN": "Jeton", "NEW_ROBOT_ACCOUNT": "Nouveau compte robot", "ENABLED_STATE": "Etat d'activation", + "NUMBER_REQUIRED": "Le champ est requis et doit être un entier autre que 0.", + "DESCRIPTION": "Description", "CREATION": "Date et heure de création", "EXPIRATION": "Date et heure d'expiration", - "NUMBER_REQUIRED": "Le champ est requis et doit être un entier autre que 0.", "TOKEN_EXPIRATION": "Expiration du jeton du compte robot (jours)", - "DESCRIPTION": "Description", "ACTION": "Action", "EDIT": "Éditer", "ITEMS": "entrées", @@ -372,46 +383,47 @@ "NEVER_EXPIRED": "Ne jamais expirer", "NAME_PREFIX": "Préfixe du nom du compte robot", "NAME_PREFIX_REQUIRED": "Le préfixe du nom du compte robot est obligatoire", - "UPDATE": "Update", - "AUDIT_LOG": "Audit Log", - "PREHEAT_INSTANCE": "Preheat Instance", - "PROJECT": "Project", - "REPLICATION_POLICY": "Replication Policy", - "REPLICATION": "Replication", - "REPLICATION_ADAPTER": "Replication Adapter", - "REGISTRY": "Registry", - "SCAN_ALL": "Scan All", - "SYSTEM_VOLUMES": "System Volumes", - "GARBAGE_COLLECTION": "Garbage Collection", - "PURGE_AUDIT": "Purge Audit", + "UPDATE": "Mettre à jour", + "AUDIT_LOG": "Log d'audit", + "PREHEAT_INSTANCE": "Préchauffer l'instance", + "PROJECT": "Projet", + "REPLICATION_POLICY": "Politique de réplication", + "REPLICATION": "Réplication", + "REPLICATION_ADAPTER": "Adaptateur de réplication", + "REGISTRY": "Registre", + "SCAN_ALL": "Scanner tout", + "SYSTEM_VOLUMES": "Volumes système", + "GARBAGE_COLLECTION": "Purge", + "PURGE_AUDIT": "Purger l'audit", "JOBSERVICE_MONITOR": "Job Service Monitor", - "TAG_RETENTION": "Tag Retention", + "TAG_RETENTION": "Rétention des tags", "SCANNER": "Scanner", "LABEL": "Label", - "EXPORT_CVE": "Export CVE", - "SECURITY_HUB": "Security Hub", - "CATALOG": "Catalog", - "METADATA": "Project Metadata", - "REPOSITORY": "Repository", - "ARTIFACT": "Artifact", + "EXPORT_CVE": "Exporter les CVE", + "SECURITY_HUB": "Centre de sécurité", + "CATALOG": "Catalogue", + "METADATA": "Métadonnées du projet", + "REPOSITORY": "Dépôt", + "ARTIFACT": "Artefact", "SCAN": "Scan", + "SBOM": "SBOM", "TAG": "Tag", - "ACCESSORY": "Accessory", - "ARTIFACT_ADDITION": "Artifact Addition", - "ARTIFACT_LABEL": "Artifact Label", - "PREHEAT_POLICY": "Preheat Policy", - "IMMUTABLE_TAG": "Immutable Tag", + "ACCESSORY": "Accessoire", + "ARTIFACT_ADDITION": "Artefact Addition", + "ARTIFACT_LABEL": "Label d'artefact", + "PREHEAT_POLICY": "Politique de préchauffage", + "IMMUTABLE_TAG": "Tag immutable", "LOG": "Log", - "NOTIFICATION_POLICY": "Notification Policy", + "NOTIFICATION_POLICY": "Politique de notification", "QUOTA": "Quota", - "BACK": "Back", - "NEXT": "Next", - "FINISH": "Finish", - "BASIC_INFO": "Basic Information", - "SELECT_PERMISSIONS": "Select Permissions", - "SELECT_SYSTEM_PERMISSIONS": "Select System Permissions", - "SELECT_PROJECT_PERMISSIONS": "Select Project Permissions", - "SYSTEM_PERMISSIONS": "System Permissions" + "BACK": "Retour", + "NEXT": "Suivant", + "FINISH": "Finir", + "BASIC_INFO": "Informations de base", + "SELECT_PERMISSIONS": "Selectionner les permissions", + "SELECT_SYSTEM_PERMISSIONS": "Selectionner les permissions système", + "SELECT_PROJECT_PERMISSIONS": "Selectionner les permissions projet", + "SYSTEM_PERMISSIONS": "Permissions système" }, "WEBHOOK": { "EDIT_BUTTON": "Éditer", @@ -525,7 +537,7 @@ "RESOURCE_TYPE": "Type de ressource" }, "REPLICATION": { - "PUSH_BASED_ONLY": "Only for the push-based replication", + "PUSH_BASED_ONLY": "Uniquement pour la réplication de type push", "YES": "Oui", "SECONDS": "Secondes", "MINUTES": "Minutes", @@ -542,6 +554,7 @@ "OVERRIDE": "Surcharger", "ENABLED_RULE": "Activer la règle", "OVERRIDE_INFO": "Surcharger", + "OPERATION": "Opération", "CURRENT": "courant", "FILTER_PLACEHOLDER": "Filtrer les tâches", "STOP_TITLE": "Confirmer l'arrêt des exécutions", @@ -558,8 +571,6 @@ "SUCCESS": "Succès", "FAILURE": "Échec", "IN_PROGRESS": "En cours", - "STOP_EXECUTIONS": "Arrêter les exécutions", - "ID": "ID", "REPLICATION_RULE": "Règle de réplication", "NEW_REPLICATION_RULE": "Nouvelle règle de réplication", "ENDPOINTS": "Endpoints", @@ -567,8 +578,11 @@ "FILTER_EXECUTIONS_PLACEHOLDER": "Filtrer les exécutions", "DELETION_TITLE": "Confirmer la suppression de la règle", "DELETION_SUMMARY": "Voulez-vous supprimer la règle {{param}} ?", + "REPLICATION_TITLE": "Confirmer la règle de réplication", + "REPLICATION_SUMMARY": "Voulez-vous répliquer la règle {{param}}?", "DELETION_TITLE_FAILURE": "la règle {{param}} n'a pas été supprimée", "DELETION_SUMMARY_FAILURE": "{{param}} ont le statut en attente/en fonctionnement/en train de réessayer", + "REPLICATE_SUMMARY_FAILURE": "ont le statut pending/running", "FILTER_TARGETS_PLACEHOLDER": "Filtrer les endpoints", "DELETION_TITLE_TARGET": "Confirmer la suppression de l'endpoint", "DELETION_SUMMARY_TARGET": "Voulez-vous supprimer l'endpoint {{param}} ?", @@ -580,8 +594,8 @@ "TESTING_CONNECTION": "En train de tester la connexion...", "TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.", "TEST_CONNECTION_FAILURE": "Échec du ping de l'endpoint.", + "ID": "ID", "NAME": "Nom", - "PROJECT": "Projet", "NAME_IS_REQUIRED": "Le nom est obligatoire.", "DESCRIPTION": "Description", "ENABLE": "Activer", @@ -602,8 +616,8 @@ "ACTIVATION": "Activation", "REPLICATION_EXECUTION": "Travaux de réplication", "REPLICATION_EXECUTIONS": "Travaux de réplication", + "STOPJOB": "Stop", "ALL": "Tous", - "END_TIME": "Fin", "PENDING": "En attente", "RUNNING": "En fonctionnement", "ERROR": "Erreur", @@ -617,6 +631,7 @@ "OPERATION": "Opération", "CREATION_TIME": "Heure de départ", "UPDATE_TIME": "Heure de mise à jour", + "END_TIME": "Fin", "LOGS": "Logs", "OF": "sur", "ITEMS": "entrées", @@ -631,7 +646,7 @@ "CANNOT_EDIT": "La règle de réplication ne peut pas être modifiée lorsqu'elle est activée.", "INVALID_DATE": "Date non valide.", "PLACEHOLDER": "Nous n'avons trouvé aucune règle de réplication !", - "JOB_PLACEHOLDER": "Nous n'avons trouvé aucun travail de réplication !", + "JOB_PLACEHOLDER": "Nous n'avons trouvé aucune tâche de réplication !", "NO_ENDPOINT_INFO": "Ajoutez d'abord un endpoint", "NO_LABEL_INFO": "Ajoutez d'abord un label", "NO_PROJECT_INFO": "Ce projet n'existe pas", @@ -688,14 +703,14 @@ "FLATTEN_LEVEL_TIP_3": "'Aplanissement sur 3 niveaux' : 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bande passante", "BANDWIDTH_ERROR_TIP": "Veuillez saisir -1 ou un nombre entier supérieur à 0", - "BANDWIDTH_TOOLTIP": "Définissez la bande passante réseau maximale pour chaque exécution. Veuillez faire attention au nombre d'exécutions simultanées. Pour une bande passante illimitée, veuillez entrer -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Illimitée", "UNREACHABLE_SOURCE_REGISTRY": "Échec de connexion au registre source. Veuillez vérifier que le registre source est disponible avant d'éditer cette règle: {{error}}", - "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", + "CRON_ERROR_TIP": "Le 1er champ de la chaîne cron doit être 0 et le 2ème champ ne peut pas être \"*\"", "COPY_BY_CHUNK": "Copier par morceaux", "COPY_BY_CHUNK_TIP": "Spécifie si le blob doit être copié par morceaux. Transférer par morceaux peut augmenter le nombre de requêtes faites à l'API.", "TRIGGER_STOP_SUCCESS": "Déclenchement avec succès de l'arrêt d'exécution", - "CRON_STR": "Cron String" + "CRON_STR": "Chaîne cron" }, "DESTINATION": { "NEW_ENDPOINT": "Nouveau Endpoint", @@ -712,6 +727,7 @@ "TEST_CONNECTION": "Test de connexion", "TITLE_EDIT": "Éditer l'endpoint", "TITLE_ADD": "Nouveau endpoint de registre", + "EDIT": "Éditer", "DELETE": "Supprimer l'endpoint", "TESTING_CONNECTION": "En train de tester la connexion...", "TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.", @@ -762,6 +778,7 @@ "ARTIFACTS": "Artefacts", "SIZE": "Taille", "VULNERABILITY": "Vulnérabilité", + "SBOM": "SBOM", "BUILD_HISTORY": "Historique de construction", "SIGNED": "Signé", "AUTHOR": "Auteur", @@ -773,12 +790,14 @@ "REPOSITORIES": "Dépôts", "OF": "sur", "ITEMS": "entrées", + "NO_ITEMS": "Aucune entrée", "POP_REPOS": "Dépôts populaires", "DELETED_REPO_SUCCESS": "Dépôt supprimé avec succès.", "DELETED_TAG_SUCCESS": "Tag supprimé avec succès.", "COPY": "Copier", "NOTARY_IS_UNDETERMINED": "Ne peut pas déterminer la signature de ce tag.", "PLACEHOLDER": "Nous n'avons trouvé aucun dépôt !", + "INFO": "Info", "NO_INFO": "Pas de description pour ce dépôt. Vous pouvez l'ajouter à ce dépôt.", "IMAGE": "Images", "LABELS": "Labels", @@ -786,6 +805,7 @@ "FILTER_BY_LABEL": "Filtrer les images par label", "FILTER_ARTIFACT_BY_LABEL": "Filtrer les artefact par label", "ADD_LABELS": "Ajouter des labels", + "STOP": "Stop", "RETAG": "Copier", "ACTION": "Action", "DEPLOY": "Déployer", @@ -830,10 +850,13 @@ "AUTH": "Authentification", "REPLICATION": "Réplication", "LABEL": "Labels", + "REPOSITORY": "Dépôt", "REPO_READ_ONLY": "Dépôt en lecture seule", "WEBHOOK_NOTIFICATION_ENABLED": "Webhooks activés", "SYSTEM": "Réglages Système", "PROJECT_QUOTAS": "Quotas de projet", + "VULNERABILITY": "Vulnérabilité", + "GC": "Purge", "CONFIRM_TITLE": "Confirmer pour annuler", "CONFIRM_SUMMARY": "Certaines modifications n'ont pas été sauvegardées. Voulez-vous les annuler ?", "SAVE_SUCCESS": "La configuration a été sauvegardée avec succès.", @@ -847,6 +870,7 @@ "SELF_REGISTRATION": "Autoriser l'inscription", "AUTH_MODE_DB": "Base de données", "AUTH_MODE_LDAP": "LDAP", + "AUTH_MODE_UAA": "UAA", "AUTH_MODE_HTTP": "Authentification HTTP", "AUTH_MODE_OIDC": "OIDC", "SCOPE_BASE": "Base", @@ -856,12 +880,10 @@ "PRO_CREATION_ADMIN": "Administrateurs seulement", "ROOT_CERT": "Enregistrer le certificat racine", "ROOT_CERT_LINK": "Télécharger", - "GC": "Purge", "REGISTRY_CERTIFICATE": "Certificat du registre", "NO_CHANGE": "Enregistrement abandonné, rien n'a changé", "SKIP_SCANNER_PULL_TIME": "Conserver la date/heure de pull lors d'un scan", "TOOLTIP": { - "REPO_TOOLTIP": "Les utilisateurs ne peuvent effectuer aucune opération sur les images dans ce mode.", "SELF_REGISTRATION_ENABLE": "Activer l'inscription.", "SELF_REGISTRATION_DISABLE": "Désactiver l'inscription.", "VERIFY_REMOTE_CERT": "Détermine si la réplication de l'image doit vérifier le certificat d'un dépôt Harbor distant. Décochez cette case lorsque le registre distant utilise un certificat auto-signé ou non approuvé.", @@ -877,6 +899,8 @@ "PRO_CREATION_RESTRICTION": "L'indicateur pour définir quels utilisateurs ont le droit de créer des projets. Par défaut, tout le monde peut créer un projet. Définissez sur 'Administrateur Seulement' pour que seul un administrateur puisse créer un projet.", "ROOT_CERT_DOWNLOAD": "Télécharger le certificat racine du dépôt.", "SCANNING_POLICY": "Définissez la politique d'analyse des images en fonction des différentes exigences. 'Aucune' : pas de politique active; 'Tous les jours à' : déclenchement de l'analyse à l'heure spécifiée tous les jours.", + "VERIFY_CERT": "Verifier le certificat du serveur LDAP", + "REPO_TOOLTIP": "Les utilisateurs ne peuvent effectuer aucune opération sur les images dans ce mode.", "WEBHOOK_TOOLTIP": "Activez les webhooks pour recevoir des callbacks sur vos endpoint désignés lorsque certaines actions telles que l'image ou le chart est poussé, tiré, supprimé, scanné sont effectuées", "HOURLY_CRON": "Exécuter une fois par heure, au début de l'heure. Équivalent à 0 0 * * * *.", "WEEKLY_CRON": "Exécuter une fois par semaine, à minuit entre Sam. et Dim. Équivalent à 0 0 0 * * 0.", @@ -905,12 +929,18 @@ "GROUP_SCOPE": "Scope des groupes LDAP", "GROUP_SCOPE_INFO": "Scope dans lequel faire la recherche, 'subtree' par défaut." }, + "UAA": { + "ENDPOINT": "Endpoint UAA", + "CLIENT_ID": "ID client UAA", + "CLIENT_SECRET": "Secret client UAA", + "VERIFY_CERT": "Verification certificat UAA" + }, "HTTP_AUTH": { "ENDPOINT": "Endpoint Serveur", "TOKEN_REVIEW": "Endpoint de revue de token", "SKIP_SEARCH": "Passer la recherche", "VERIFY_CERT": "Vérifier le certificat", - "ADMIN_GROUPS": "Admin Groups" + "ADMIN_GROUPS": "Groupes admin" }, "OIDC": { "OIDC_PROVIDER": "Fournisseur OIDC", @@ -999,6 +1029,42 @@ "IN_PROGRESS": "Recherche...", "BACK": "Retour" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Non analysé", @@ -1016,7 +1082,7 @@ "COLUMN_VERSION": "Version Actuelle", "COLUMN_FIXED": "Réparé dans la version", "COLUMN_DESCRIPTION": "Description", - "FOOT_ITEMS": "Items", + "FOOT_ITEMS": "Entrées", "FOOT_OF": "sur", "IN_ALLOW_LIST": "Présent dans la liste blanche CVE", "CVSS3": "CVSS3" @@ -1034,8 +1100,6 @@ "HIGH": "Haut", "MEDIUM": "Moyen", "LOW": "Bas", - "NEGLIGIBLE": "Négligeable", - "UNKNOWN": "Inconnue", "NONE": "Aucune" }, "SINGULAR": "vulnérabilité", @@ -1045,12 +1109,12 @@ "PLACEHOLDER": "Filtrer les vulnérabilités", "PACKAGE": "paquet", "PACKAGES": "paquets", - "SCAN_NOW": "Analyser", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "Scan par {{scanner}}", "REPORTED_BY": "Rapporté par {{scanner}}", "NO_SCANNER": "Aucun scanneur", "TRIGGER_STOP_SUCCESS": "Déclenchement avec succès de l'arrêt d'analyse", - "STOP_NOW": "Arrêter l'analyse" + "STOP_NOW": "Stop Scan" }, "PUSH_IMAGE": { "TITLE": "Commande de push", @@ -1065,7 +1129,7 @@ "TAG_COMMAND": "Taguer une image pour ce projet :", "PUSH_COMMAND": "Push une image dans ce projet :", "COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement les commandes de référence.", - "COPY_PULL_COMMAND": "COPY PULL COMMAND" + "COPY_PULL_COMMAND": "COMMANDE COPY PULL" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filtrer les artefacts", @@ -1081,6 +1145,7 @@ "ALL": "Tous", "PLACEHOLDER": "Nous n'avons trouvé aucun artefact !", "SCAN_UNSUPPORTED": "Non supporté", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Résumé", "DEPENDENCIES": "Dépendances", "VALUES": "Valeurs", @@ -1106,6 +1171,7 @@ "HAS": "a", "SCAN_COMPLETION_TIME": "Analyse terminée", "IMAGE_VULNERABILITIES": "Vulnérabilités de l'image", + "LEVEL_VULNERABILITIES": "Vulnérabilités de niveau", "PLACEHOLDER": "Nous ne trouvons aucun tag !", "COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement.", "FILTER_FOR_TAGS": "Filtrer les tags", @@ -1118,7 +1184,7 @@ "PULL_TIME": "Date/Heure de pull", "PUSH_TIME": "Date/Heure de push", "OF": "sur", - "ITEMS": "items", + "ITEMS": "entrées", "ADD_TAG": "AJOUTER TAG", "REMOVE_TAG": "SUPPRIMER TAG", "NAME_ALREADY_EXISTS": "Ce tag existe déjà dans ce dépôt" @@ -1148,14 +1214,14 @@ "DELETE": "Supprimer", "OF": "sur", "PROJECT_QUOTA_DEFAULT_ARTIFACT": "Nombre par défaut d'artefacts par projet", - "PROJECT_QUOTA_DEFAULT_DISK": "Default quota space per project", + "PROJECT_QUOTA_DEFAULT_DISK": "Quota d'espace par défaut par projet", "EDIT_PROJECT_QUOTAS": "Éditer les quotas projet", "EDIT_DEFAULT_PROJECT_QUOTAS": "Éditer les quotas projet par défaut", "SET_QUOTAS": "Configurer les quotas pour le projet '{{params}}'", "SET_DEFAULT_QUOTAS": "Configurer les quotas projet par défaut lors de la création de nouveaux projets", "COUNT_QUOTA": "Quota de nombre", "COUNT_DEFAULT_QUOTA": "Quota de nombre par défaut", - "STORAGE_QUOTA": "Project quota limits", + "STORAGE_QUOTA": "Limites des quotas de projets", "STORAGE_DEFAULT_QUOTA": "Quota de stockage par défaut", "SAVE_SUCCESS": "Edition de quota effectuée", "UNLIMITED": "Illimité", @@ -1178,6 +1244,7 @@ "ALL": "Tous", "RUNNING": "En cours", "FAILED": "Échoués", + "STOP_EXECUTIONS": "Interrompre l'exécution", "DELETE_PROJECT": "Suppression de projet", "DELETE_REPO": "Suppression de dépôt", "DELETE_TAG": "Suppression de tag", @@ -1187,6 +1254,8 @@ "DELETE_REPLICATION": "Suppression de réplication", "DELETE_MEMBER": "Suppression de membre", "DELETE_GROUP": "Suppression de membre d'un groupe", + "DELETE_CHART_VERSION": "Supprimer la version de chart", + "DELETE_CHART": "Supprimer chart", "SWITCH_ROLE": "Changement de rôle", "ADD_GROUP": "Ajout de membre à un groupe", "ADD_USER": "Ajout de membre utilisateur", @@ -1402,6 +1471,10 @@ "NAME_REQUIRED": "Le nom est requis", "NAME_REX": "Le nom doit comporter au moins 2 caractères avec des minuscules, des chiffres et. _- et doit commencer par des caractères ou des chiffres.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnérabilité", + "SUPPORTED": "Supporté", + "NOT_SUPPORTED": "Non Supporté", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "L'URL de l'endpoint existe déjà", "ENDPOINT_REQUIRED": "L'URL de l'endpoint est requise", @@ -1430,9 +1503,10 @@ "NAME_COLON": "Nom :", "VENDOR_COLON": "Vendeur :", "VERSION_COLON": "Version :", - "CAPABILITIES": "Capacités", + "CAPABILITIES": "Capacités:", "CONSUMES_MIME_TYPES_COLON": "Consomme les types Mime :", "PRODUCTS_MIME_TYPES_COLON": "Produit les types Mime :", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "Propriétés", "NEW_SCANNER": "Nouveau scanneur", "SET_AS_DEFAULT": "PAR DEFAUT", @@ -1606,7 +1680,7 @@ "PREHEAT_EXPLAIN": "Le préchauffage migrera l'image vers le réseau p2p", "CRITERIA_EXPLAIN": "Comme spécifié dans la section 'Sécurité de déploiement' dans l'onglet Configuration", "SKIP_CERT_VERIFY": "Cochez cette case pour ignorer la vérification du certificat lorsque le fournisseur distant utilise un certificat auto-signé ou non approuvé.", - "NAME_TOOLTIP": "Policy name consists of one or more groups of uppercase letter, lowercase letter or number; and groups are separated by a dot, underscore, or hyphen.", + "NAME_TOOLTIP": "Le nom de la politique consiste en un ou plusieurs groupes de lettres (majuscules ou minuscules) ou de chiffres ; les groupes sont séparés par un point, un trait de soulignement ou un trait d'union.", "NEED_HELP": "Veuillez d'abord demander à votre administrateur système d'ajouter un fournisseur" }, "PAGINATION": { @@ -1642,9 +1716,9 @@ "PROJECTS_MODAL_TITLE": "Projets pour le compte robot", "PROJECTS_MODAL_SUMMARY": "Voici les projets couverts par ce compte robot.", "CREATE_ROBOT": "Créer un compte robot Système", - "CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover permissions for the system as well as for specific projects", + "CREATE_ROBOT_SUMMARY": "Créer un compte système Robot qui couvrira les autorisations pour le système ainsi que pour des projets spécifiques", "EDIT_ROBOT": "Éditer un compte robot Système", - "EDIT_ROBOT_SUMMARY": "Edit a system Robot Account that will cover permissions for the system as well as for specific projects", + "EDIT_ROBOT_SUMMARY": "Éditer un compte système Robot qui couvrira les autorisations pour le système ainsi que pour des projets spécifiques", "EXPIRATION_TIME": "Date/Heure d'Expiration", "EXPIRATION_TIME_EXPLAIN": "L'heure d'expiration (en jours, le point de départ est l'heure de création) du jeton du compte robot. Pour ne jamais expirer, entrer \"-1\".", "EXPIRATION_DEFAULT": "jours (défaut)", @@ -1654,7 +1728,7 @@ "COVER_ALL": "Couvrir tous les projets", "COVER_ALL_EXPLAIN": "Cocher pour appliquer à tous les projets existants et futurs", "COVER_ALL_SUMMARY": "\"Tous les projets existants et futurs\" sélectionné.", - "RESET_PERMISSION": "RESET ALL PROJECT PERMISSIONS", + "RESET_PERMISSION": "REINITIALISER TOUTES LES PERMISSIONS PROJET", "PERMISSION_COLUMN": "Permissions", "EXPIRES_AT": "Expire à", "VIEW_SECRET": "Actualiser le secret", @@ -1687,8 +1761,8 @@ "REPOSITORY": "Dépôt", "EXPIRES_IN": "Expire dans", "EXPIRED": "Expiré", - "SELECT_ALL_PROJECT": "SELECT ALL PROJECTS", - "UNSELECT_ALL_PROJECT": "UNSELECT ALL PROJECTS" + "SELECT_ALL_PROJECT": "SELECTIONNER TOUS LES PROJETS", + "UNSELECT_ALL_PROJECT": "DESELECTIONNER TOUS LES PROJETS" }, "ACCESSORY": { "DELETION_TITLE_ACCESSORY": "Confirmer la suppression de l'accessoire", @@ -1739,7 +1813,7 @@ "EXPORT_TITLE": "Export de CVEs", "EXPORT_SUBTITLE": "Définir les conditions d'exportation", "EXPORT_CVE_FILTER_HELP_TEXT": "Entrer plusieurs cveIDs séparés par des virgules", - "CVE_IDS": "CVE IDs", + "CVE_IDS": "IDs CVE", "EXPORT_BUTTON": "Exporter", "JOB_NAME": "Nom de la tâche", "JOB_NAME_REQUIRED": "Le nom de la tâche est requis", @@ -1822,9 +1896,9 @@ "SCHEDULE_RESUME_BTN_INFO": "REPRENDRE — Reprend les files d'attente de tâches à exécuter.", "WORKER_FREE_BTN_INFO": "Arrête les tâches en cours pour libérer le worker", "CRON": "Cron", - "WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ", - "WAITING_TOO_LONG_2": "dashboard.", - "WAITING_TOO_LONG_3": "For more details, please refer to the ", + "WAITING_TOO_LONG_1": "Certaines tâches sont en attente d'exécution depuis plus de 24 heures. Veuillez vérifier le ", + "WAITING_TOO_LONG_2": "tableau de bord.", + "WAITING_TOO_LONG_3": "Pour plus de détails, veuillez consulter le ", "WAITING_TOO_LONG_4": "Wiki." }, "CLARITY": { @@ -1832,8 +1906,8 @@ "CLOSE": "Fermer", "SHOW": "Afficher", "HIDE": "Cacher", - "EXPAND": "Etendre", - "COLLAPSE": "Collapse", + "EXPAND": "Déplier", + "COLLAPSE": "Replier", "MORE": "Plus", "SELECT": "Sélectionner", "SELECT_ALL": "Tout sélectionner", @@ -1854,7 +1928,7 @@ "PREVIOUS_PAGE": "Page précédente", "CURRENT_PAGE": "Page courante", "TOTAL_PAGE": "Pages totales", - "FILTER_ITEMS": "Filtrer les éléments", + "FILTER_ITEMS": "Filtrer les entrées", "MIN_VALUE": "Valeur min", "MAX_VALUE": "Valeur max", "MODAL_CONTENT_START": "Début de la modale", @@ -1890,7 +1964,7 @@ "ENTER_MESSAGE": "Entrer votre message ici" }, "SECURITY_HUB": { - "SECURITY_HUB": "Tableau de bord de sécurité", + "SECURITY_HUB": "Centre de sécurité", "ARTIFACTS": "artefact(s)", "SCANNED": "scannés", "NOT_SCANNED": "non scannés", @@ -1898,7 +1972,7 @@ "TOTAL_AND_FIXABLE": "{{totalNum}} total dont {{fixableNum}} corrigeables", "TOP_5_ARTIFACT": "Top 5 des Artefacts les plus Dangereux", "TOP_5_CVE": "Top 5 des CVEs les plus Dangereuses", - "CVE_ID": "CVE ID", + "CVE_ID": "ID CVE", "VUL": "Vulnérabilités", "CVE": "CVEs", "FILTER_BY": "Filtrer par", diff --git a/src/portal/src/i18n/lang/ko-kr-lang.json b/src/portal/src/i18n/lang/ko-kr-lang.json new file mode 100644 index 0000000000..dd41101e3b --- /dev/null +++ b/src/portal/src/i18n/lang/ko-kr-lang.json @@ -0,0 +1,1984 @@ +{ + "APP_TITLE": { + "VMW_HARBOR": "Harbor", + "HARBOR": "Harbor", + "VIC": "vSphere Integrated Containers", + "MGMT": "관리", + "REG": "레지스트리", + "HARBOR_SWAGGER": "Harbor Swagger", + "THEME_DARK_TEXT": "다크 모드", + "THEME_LIGHT_TEXT": "라이트 모드" + }, + "SIGN_IN": { + "REMEMBER": "사용자 정보 기억하기", + "INVALID_MSG": "유효하지 않은 사용자 이름 또는 비밀번호입니다.", + "FORGOT_PWD": "비밀번호 찾기", + "HEADER_LINK": "로그인", + "CORE_SERVICE_NOT_AVAILABLE": "핵심 서비스를 이용할 수 없습니다.", + "OR": "OR", + "VIA_LOCAL_DB": "로컬 DB를 통해 로그인" + }, + "SIGN_UP": { + "TITLE": "등록하기" + }, + "BUTTON": { + "STOP": "중지", + "CANCEL": "취소", + "OK": "확인", + "DELETE": "삭제", + "LOG_IN": "로그인", + "LOG_IN_OIDC": "OIDC 공급자를 통해 로그인", + "LOG_IN_OIDC_WITH_PROVIDER_NAME": "{{providerName}}을 통해 로그인", + "SIGN_UP_LINK": "계정을 등록합니다.", + "SIGN_UP": "등록", + "CONFIRM": "확인", + "SEND": "전송", + "SAVE": "저장", + "TEST_MAIL": "메일 서버 테스트", + "CLOSE": "닫기", + "TEST_LDAP": "LDAP 서버 테스트", + "TEST_OIDC": "OIDC 서버 테스트", + "MORE_INFO": "추가 정보...", + "YES": "예", + "NO": "아니요", + "NEGATIVE": "아니요", + "COPY": "복사", + "EDIT": "편집", + "SWITCH": "전환", + "REPLICATE": "복제", + "ACTIONS": "동작", + "BROWSE": "검색", + "UPLOAD": "업로드", + "NO_FILE": "파일이 선택되지 않았습니다.", + "ADD": "추가", + "RUN": "실행", + "CONTINUE": "계속", + "ENABLE": "가능", + "DISABLE": "비활성화" + }, + "BATCH": { + "DELETED_SUCCESS": "성공적으로 삭제됐습니다.", + "DELETED_FAILURE": "삭제 실패 또는 부분 실패", + "SWITCH_SUCCESS": "성공적으로 전환했습니다.", + "SWITCH_FAILURE": "전환에 실패했습니다.", + "REPLICATE_SUCCESS": "성공적으로 시작됐습니다.", + "REPLICATE_FAILURE": "시작에 실패했습니다.", + "STOP_SUCCESS": "성공적으로 중지됐습니다.", + "STOP_FAILURE": "작업 중지에 실패했습니다.", + "TIME_OUT": "게이트웨이 타임아웃" + }, + "TOOLTIP": { + "NAME_FILTER": "리소스 이름을 필터링합니다. 모두 일치시키려면 비워두거나 '**'을 입력하세요. 'library/**'는 'library' 아래의 리소스만 일치합니다. 더 많은 패턴에 대해서는 사용자 가이드를 참조하세요.", + "TAG_FILTER": "리소스의 태그/버전 부분을 필터링합니다. 모두 일치시키려면 비워 두거나 '**'를 사용하세요. '1.0*'은 '1.0'으로 시작하는 태그에만 일치합니다. 더 많은 패턴에 대해서는 사용자 가이드를 참조하세요.", + "LABEL_FILTER": "라벨에 따라 리소스를 필터링합니다.", + "RESOURCE_FILTER": "리소스 유형을 필터링합니다.", + "PUSH_BASED": "로컬 'Harbor'의 리소스를 원격 레지스트리로 푸시합니다", + "PULL_BASED": "원격 레지스트리의 리소스를 로컬 'Harbor'로 가져옵니다.", + "DESTINATION_NAMESPACE": "대상 네임스페이스를 지정합니다. 비어 있으면 리소스는 소스와 동일한 네임스페이스에 배치됩니다.", + "OVERRIDE": "동일한 이름의 리소스가 있는 경우 대상의 리소스를 재정의할지 여부를 지정합니다.", + "EMAIL": "이메일은 name@example.com과 같은 유효한 이메일 주소여야 합니다.", + "USER_NAME": "특수 문자를 포함할 수 없으며 최대 길이는 255자입니다.", + "FULL_NAME": "최대 길이는 20자입니다.", + "COMMENT": "코멘트 길이는 30자 미만입니다.", + "CURRENT_PWD": "현재 비밀번호가 필요합니다.", + "PASSWORD": "비밀번호는 8~128자 길이로 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함되어야 합니다.", + "CONFIRM_PWD": "비밀번호가 일치하지 않습니다.", + "SIGN_IN_USERNAME": "사용자 이름을 입력하세요.", + "SIGN_IN_PWD": "비밀번호를 입력하세요", + "SIGN_UP_MAIL": "이메일은 비밀번호를 재설정하는 용도로만 사용됩니다.", + "SIGN_UP_REAL_NAME": "성과 이름", + "ITEM_REQUIRED": "필드를 입력하세요.", + "SCOPE_REQUIRED": "필드는 필수 항목이며 범위 형식이어야 합니다.", + "NUMBER_REQUIRED": "필드는 필수 항목이며 숫자 형식이어야 합니다.", + "PORT_REQUIRED": "필드는 필수 항목이며 유효한 포트 번호여야 합니다.", + "CRON_REQUIRED": "필드는 필수이며 cron 형식이어야 합니다.", + "EMAIL_EXISTING": "이메일 주소가 이미 존재합니다.", + "USER_EXISTING": "이미 사용중인 사용자 이름입니다.", + "RULE_USER_EXISTING": "이미 사용중인 이름입니다", + "EMPTY": "이름을 입력하세요.", + "NONEMPTY": "공란은 허용하지 않습니다", + "REPO_TOOLTIP": "이 모드에서는 사용자가 이미지에 어떤 작업도 수행할 수 없습니다.", + "ENDPOINT_FORMAT": "엔드포인트는 반드시 HTTP:// 또는 HTTPS://로 시작해야합니다.", + "OIDC_ENDPOINT_FORMAT": "엔드포인트는 반드시 HTTPS://로 시작해야합니다.", + "OIDC_NAME": "OIDC 공급자의 이름입니다.", + "OIDC_ENDPOINT": "OIDC 호환 서버의 URL입니다.", + "OIDC_SCOPE": "인증 시 OIDC 서버로 전송되는 범위입니다. “openid”와 “offline_access”를 포함해야 합니다. 'Google'을 사용하는 경우 이 필드에서 'offline_access'를 삭제하세요.", + "OIDC_VERIFYCERT": "OIDC 서버가 자체 서명된 인증서를 통해 호스팅되는 경우 이 상자를 선택 취소하세요.", + "OIDC_AUTOONBOARD": "온보딩 화면을 건너뛰면 사용자가 사용자 이름을 변경할 수 없습니다. 사용자 이름은 ID 토큰에서 제공됩니다.", + "OIDC_USER_CLAIM": "사용자 이름을 검색하는 ID 토큰의 클레임 이름입니다. 지정하지 않으면 기본값은 'name'입니다.", + "NEW_SECRET": "시크릿은 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함된 8자 이상이여야합니다" + }, + "PLACEHOLDER": { + "CURRENT_PWD": "현재 비밀번호를 입력하세요", + "NEW_PWD": "새 비밀번호를 입력하세요", + "CONFIRM_PWD": "새 비밀번호를 확인합니다", + "USER_NAME": "사용자 이름을 입력하세요", + "MAIL": "이메일 주소를 입력하세요", + "FULL_NAME": "성과 이름을 포함한 전체 이름을 입력하세요", + "SIGN_IN_NAME": "사용자 이름", + "SIGN_IN_PWD": "비밀번호" + }, + "PROFILE": { + "TITLE": "사용자 프로필", + "USER_NAME": "사용자 이름", + "EMAIL": "이메일", + "FULL_NAME": "성과 이름", + "COMMENT": "코맨트", + "PASSWORD": "비밀번호", + "SAVE_SUCCESS": "사용자 프로필이 성공적으로 저장됐습니다.", + "ADMIN_RENAME_BUTTON": "사용자 이름 변경", + "ADMIN_RENAME_TIP": "사용자 이름을 \"admin@harbor.local\"로 변경하려면 버튼을 선택하세요. 이 작업은 취소할 수 없습니다.", + "RENAME_SUCCESS": "이름이 성공적으로 변경됐습니다!", + "RENAME_CONFIRM_INFO": "경고, 이름을 admin@harbor.local로 변경한 후에는 취소할 수 없습니다.", + "CLI_PASSWORD": "CLI 시크릿", + "CLI_PASSWORD_TIP": "CLI 시크릿 도커 또는 헬름 클라이언트의 비밀번호로 사용될 수 있습니다. OIDC 인증 모드가 활성화되면 로봇 계정을 사용하는 것이 좋습니다. CLI 비밀번호는 ID 토큰의 유효성에 따라 달라지며 사용자가 정기적으로 UI에 로그인하여 토큰을 새로 고쳐야 하기 때문입니다.", + "COPY_SUCCESS": "복사 성공", + "COPY_ERROR": "복사 실패", + "ADMIN_CLI_SECRET_BUTTON": "시크릿 생성", + "ADMIN_CLI_SECRET_RESET_BUTTON": "시크릿 업로드", + "NEW_SECRET": "시크릿", + "CONFIRM_SECRET": "시크릿 재입력", + "GENERATE_SUCCESS": "Cli 시크릿이 성공적으로 설정됐습니다", + "GENERATE_ERROR": "Cli 시크릿 설정에 실패했습니다", + "CONFIRM_TITLE_CLI_GENERATE": "시크릿을 다시 생성할 수 있습니까?", + "CONFIRM_BODY_CLI_GENERATE": "Cli 시크릿을 재생성하면 이전 Cli 시크릿이 삭제됩니다" + }, + "CHANGE_PWD": { + "TITLE": "비밀번호 변경", + "CURRENT_PWD": "현재 비밀번호", + "NEW_PWD": "새 비밀번호", + "CONFIRM_PWD": "비밀번호 확인", + "SAVE_SUCCESS": "사용자 비밀번호를 성공적으로 변경했습니다.", + "PASS_TIPS": "최소 1개의 대문자, 1개의 소문자, 1개의 숫자가 포함된 8~128자 길이" + }, + "ACCOUNT_SETTINGS": { + "PROFILE": "사용자 프로필", + "CHANGE_PWD": "비밀번호 변경", + "ABOUT": "About", + "LOGOUT": "로그아웃" + }, + "GLOBAL_SEARCH": { + "PLACEHOLDER": "검색 {{param}}...", + "PLACEHOLDER_VIC": "레지스트리 검색..." + }, + "TOP_NAV": { + "DATETIME_RENDERING_DEFAULT": "디폴트" + }, + "SIDE_NAV": { + "DASHBOARD": "데시보드", + "PROJECTS": "프로젝트", + "SYSTEM_MGMT": { + "NAME": "관리", + "USER": "사용자", + "GROUP": "그룹", + "REGISTRY": "레지스트리", + "REPLICATION": "복제", + "CONFIG": "설정", + "VULNERABILITY": "취약점", + "GARBAGE_COLLECTION": "가비지 컬렉션", + "INTERROGATION_SERVICES": "질의 서비스" + }, + "LOGS": "로그", + "TASKS": "테스크", + "API_EXPLORER": "Api 탐색기", + "HARBOR_API_MANAGEMENT": "Harbor API V2.0", + "HELM_API_MANAGEMENT": "Harbor API", + "DISTRIBUTIONS": { + "NAME": "배포", + "INSTANCES": "인스턴스" + } + }, + "USER": { + "ADD_ACTION": "새 사용자", + "ENABLE_ADMIN_ACTION": "관리자로 설정", + "DISABLE_ADMIN_ACTION": "관리자 취소", + "DEL_ACTION": "삭제", + "FILTER_PLACEHOLDER": "유저 필터", + "COLUMN_NAME": "이름", + "COLUMN_ADMIN": "관리자", + "COLUMN_EMAIL": "이메일", + "COLUMN_REG_NAME": "등록 시간", + "IS_ADMIN": "예", + "IS_NOT_ADMIN": "아니요", + "ADD_USER_TITLE": "새 사용자", + "SAVE_SUCCESS": "새 사용자가 성공적으로 생성됐습니다.", + "DELETION_TITLE": "사용자 삭제 확인", + "DELETION_SUMMARY": "사용자{{param}}를 정말 삭제하시겠습니까?", + "DELETE_SUCCESS": "사용자를 성공적으로 삭제했습니다.", + "ITEMS": "아이템", + "OF": "of", + "RESET_OK": "사용자 비밀번호를 성공적으로 초기화했습니다", + "EXISTING_PASSWORD": "새 비밀번호는 이전 비밀번호와 동일할 수 없습니다", + "UNKNOWN": "알 수 없음", + "UNKNOWN_TIP": "값이 \"알 수 없음\"인 경우 ID 공급자 시스템을 통해 사용자가 관리자 상태인지 확인하세요" + }, + "PROJECT": { + "PROJECTS": "프로젝트", + "NAME": "프로젝트 이름", + "ROLE": "역할", + "PUBLIC_OR_PRIVATE": "액세스 레벨", + "REPO_COUNT": "저장소 수", + "CHART_COUNT": "차트 수", + "CREATION_TIME": "생성 시간", + "ACCESS_LEVEL": "액세스 레벨", + "PUBLIC": "공개", + "PRIVATE": "비공개", + "MAKE": "만들기", + "NEW_POLICY": "새 복제 규칙", + "DELETE": "삭제", + "ALL_PROJECTS": "모든 프로젝트", + "PRIVATE_PROJECTS": "공개 프로젝트", + "PUBLIC_PROJECTS": "비공개 프로젝트", + "PROJECT": "프로젝트", + "NEW_PROJECT": "새 프로젝트", + "NAME_TOOLTIP": "프로젝트 이름은 1~255자(영문 소문자, 숫자, ._- 포함)이어야 하며 문자나 숫자로 시작해야 합니다.", + "NAME_IS_REQUIRED": "프로젝트 이름은 필수항목입니다.", + "NAME_ALREADY_EXISTS": "이미 존재하는 프로젝트 이름입니다.", + "NAME_IS_ILLEGAL": "프로젝트 이름이 잘못되었습니다.", + "UNKNOWN_ERROR": "프로젝트를 생성하는 중에 알 수 없는 오류가 발생했습니다.", + "ITEMS": "아이템", + "DELETION_TITLE": "프로젝트 삭제 확인", + "DELETION_SUMMARY": "프로젝트{{param}}를 정말 삭제하시겠습니까?", + "FILTER_PLACEHOLDER": "프로젝트 필터", + "REPLICATION_RULE": "복제 규칙", + "CREATED_SUCCESS": "프로젝트가 성공적으로 생성됐습니다.", + "DELETED_SUCCESS": "프로젝트가 성공적으로 삭제됐습니다.", + "TOGGLED_SUCCESS": "프로젝트가 성공적으로 전환됐습니다.", + "FAILED_TO_DELETE_PROJECT": "프로젝트에 리포지토리 또는 복제 규칙이 포함되어 있거나 헬름 차트를 삭제할 수 없습니다.", + "INLINE_HELP_PUBLIC": "프로젝트가 공개로 설정되면 누구나 이 프로젝트의 저장소에 대한 읽기 권한을 갖게 되며 사용자는 이 프로젝트에서 이미지를 가져오기 전에 \"docker login\"을 실행할 필요가 없습니다.", + "OF": "of", + "COUNT_QUOTA": "할당량 수", + "STORAGE_QUOTA": "프로젝트 할당량 제한", + "COUNT_QUOTA_TIP": "'1'과 '100,000,000' 사이의 정수를 입력하세요. 무제한인 경우 '-1'을 입력하세요.", + "STORAGE_QUOTA_TIP": "저장 장치 할당량의 상한은 '1024TB'로 제한되는 정수 값만 사용합니다. 할당량을 무제한으로 지정하려면 '-1'을 입력하세요.", + "QUOTA_UNLIMIT_TIP": "프로젝트에서 사용할 수 있는 최대 논리적 공간입니다. 할당량을 무제한으로 설정하려면 '-1'을 입력하세요.", + "TYPE": "종류", + "PROXY_CACHE": "프록시 캐시", + "PROXY_CACHE_TOOLTIP": "이 프로젝트가 특정 대상 레지스트리 인스턴스에 대한 풀스루 캐시 역할을 할 수 있도록 하려면 이 옵션을 활성화합니다. Harbor는 DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay, Google GCR, Github GHCR 및 JFrog Artifactory 레지스트리에 대해서만 프록시 역할을 할 수 있습니다.", + "ENDPOINT": "엔드포인트", + "PROXY_CACHE_ENDPOINT": "프록시 캐시 엔드포인트", + "NO_PROJECT": "프로젝트를 찾을 수 없습니다" + }, + "PROJECT_DETAIL": { + "SUMMARY": "요약", + "REPOSITORIES": "저장소", + "REPLICATION": "복제", + "USERS": "맴버", + "LOGS": "로그", + "LABELS": "라벨", + "PROJECTS": "프로젝트", + "CONFIG": "설정", + "HELMCHART": "헬름 차트", + "ROBOT_ACCOUNTS": "로봇 계정", + "WEBHOOKS": "웹훅", + "IMMUTABLE_TAG": "Tag Immutability", + "POLICY": "정책" + }, + "PROJECT_CONFIG": { + "REGISTRY": "프로젝트 레지스트리", + "PUBLIC_TOGGLE": "공개", + "PUBLIC_POLICY": "프로젝트 레지스트리를 공개하면 모든 사람이 모든 저장소에 액세스할 수 있습니다.", + "SECURITY": "배포 보안", + "CONTENT_TRUST_TOGGLE": "콘텐츠 신뢰 활성화", + "CONTENT_TRUST_POLCIY": "확인된 이미지만 배포되도록 허용합니다.", + "PREVENT_VULNERABLE_TOGGLE": "취약한 이미지가 실행되지 않도록 방지합니다.", + "PREVENT_VULNERABLE_1": "Prevent images with vulnerability severity of", + "PREVENT_VULNERABLE_2": "and above from being deployed.", + "SCAN": "취약점 스캔", + "AUTOSCAN_TOGGLE": "푸시 시 이미지 자동 스캔", + "AUTOSCAN_POLICY": "이미지가 프로젝트 레지스트리에 푸시되면 자동으로 이미지를 스캔합니다." + }, + "MEMBER": { + "NEW_USER": "사용자 구성원 추가", + "NEW_MEMBER": "새 구성원", + "MEMBER": "구성원", + "NAME": "이름", + "ROLE": "역할", + "SYS_ADMIN": "시스템 관리자", + "PROJECT_ADMIN": "프로젝트 관리자", + "PROJECT_MAINTAINER": "메인테이너", + "DEVELOPER": "개발자", + "GUEST": "게스트", + "LIMITED_GUEST": "제한된 게스트", + "DELETE": "삭제", + "ITEMS": "아이템", + "ACTIONS": "동작", + "USER": " 사용자", + "USERS": "사용자들", + "EMAIL": "이메일", + "ADD_USER": "사용자 추가", + "NEW_USER_INFO": "역할이 지정된 사용자를 추가하세요.", + "NEW_GROUP": "새 그룹", + "IMPORT_GROUP": "새 그룹 맴버", + "NEW_GROUP_INFO": "기존 사용자 그룹을 추가하거나 LDAP/AD에서 프로젝트 구성원으로 사용자 그룹을 선택합니다.", + "ADD_GROUP_SELECT": "프로젝트 구성원에 기존 사용자 그룹 추가", + "CREATE_GROUP_SELECT": "LDAP에서 프로젝트 구성원으로 그룹 추가", + "LDAP_SEARCH_DN": "LDAP 그룹 DN", + "LDAP_SEARCH_NAME": "이름", + "LDAP_GROUP": "그룹", + "LDAP_GROUPS": "그룹들", + "LDAP_PROPERTY": "속성", + "ACTION": "동작", + "MEMBER_TYPE": "구성원 종류", + "GROUP_TYPE": "그룹", + "USER_TYPE": "사용자", + "USERNAME_IS_REQUIRED": "사용자 이름은 필수항목입니다", + "USERNAME_DOES_NOT_EXISTS": "존재하지 않는 사용자 이름입니다", + "USERNAME_ALREADY_EXISTS": "이미 등록된 사용자 이름입니다", + "UNKNOWN_ERROR": "구성원을 추가하는 중에 알 수 없는 오류가 발생했습니다.", + "FILTER_PLACEHOLDER": "구성원 필터", + "DELETION_TITLE": "프로젝트 구성원 삭제 확인", + "DELETION_SUMMARY": "프로젝트 구성원{{param}}를 정말 삭제하시겠습니까?", + "ADDED_SUCCESS": "구성원를 성공적으로 추가했습니다.", + "DELETED_SUCCESS": "구성원를 성공적으로 삭제했습니다.", + "SWITCHED_SUCCESS": "구성원 역할을 성공적으로 전환했습니다.", + "OF": "of", + "SWITCH_TITLE": "프로젝트 구성원 전환 확인", + "SWITCH_SUMMARY": "프로젝트 구성원 {{param}}을(를) 전환하시겠습니까?", + "SET_ROLE": "역할 설정", + "REMOVE": "제거", + "GROUP_NAME_REQUIRED": "그룹 이름은 필수항목입니다", + "NON_EXISTENT_GROUP": "그룹 이름이 존재하지 않습니다", + "GROUP_ALREADY_ADDED": "이미 프로젝트에 추가된 그룹 이름입니다" + }, + "ROBOT_ACCOUNT": { + "NAME": "이름", + "PERMISSIONS": "권한", + "TOKEN": "시크릿", + "NEW_ROBOT_ACCOUNT": "새 로봇 계정", + "ENABLED_STATE": "활성화된 상태", + "NUMBER_REQUIRED": "필드는 필수이며 0이 아닌 정수여야 합니다.", + "DESCRIPTION": "설명", + "CREATION": "생성 시간", + "EXPIRATION": "만료", + "TOKEN_EXPIRATION": "로봇 토큰 만료(일)", + "ACTION": "동작", + "EDIT": "편집", + "ITEMS": "아이템", + "OF": "of", + "DISABLE_ACCOUNT": "계정 비활성화", + "ENABLE_ACCOUNT": "계정 활성화", + "DELETE": "삭제", + "CREAT_ROBOT_ACCOUNT": "로봇 계정 생성", + "PERMISSIONS_ARTIFACT": "아티팩트", + "PERMISSIONS_HELMCHART": "핼름 차트 (Chart Museum)", + "PUSH": "푸시", + "PULL": "풀(Pull)", + "FILTER_PLACEHOLDER": "로봇 계정 필터", + "ROBOT_NAME": "특수 문자(~#$%)를 포함할 수 없으며 최대 길이는 255자입니다.", + "ACCOUNT_EXISTING": "로봇 계정이 이미 존재합니다.", + "ALERT_TEXT": "이 시크릿을 복사할 수 있는 유일한 기회입니다.다음 기회는 없습니다.", + "CREATED_SUCCESS": "'{{param}}'성공적으로 생성됐습니다.", + "COPY_SUCCESS": "'{{param}}'의 시크릿을 성공적으로 복사했습니다", + "DELETION_TITLE": "로봇 계정 제거 확인", + "DELETION_SUMMARY": "로봇 계정{{param}}을 정말 삭제하시겠습니까?", + "PULL_IS_MUST": "풀(Pull) 권한은 기본적으로 체크되어 있으며 수정할 수 없습니다.", + "EXPORT_TO_FILE": "파일로 내보내기", + "EXPIRES_AT": "~에 만료", + "EXPIRATION_TOOLTIP": "설정하지 않을 경우 시스템 설정에따라 만료 시간이 설정됩니다", + "INVALID_VALUE": "만료 시간 값이 잘못되었습니다", + "NEVER_EXPIRED": "만료되지 않음", + "NAME_PREFIX": "로봇 이름 접두어", + "NAME_PREFIX_REQUIRED": "로봇 이름 접두어는 필수항목입니다", + "UPDATE": "업데이트", + "AUDIT_LOG": "감사 로그", + "PREHEAT_INSTANCE": "인스턴스 예열", + "PROJECT": "프로젝트", + "REPLICATION_POLICY": "복제 정책", + "REPLICATION": "복제", + "REPLICATION_ADAPTER": "복제 어댑터", + "REGISTRY": "레지스트리", + "SCAN_ALL": "모두 스캔", + "SYSTEM_VOLUMES": "시스템 볼륨", + "GARBAGE_COLLECTION": "가비지 컬렉션", + "PURGE_AUDIT": "퍼지 감사", + "JOBSERVICE_MONITOR": "작업 서비스 모니터링", + "TAG_RETENTION": "태그 유지", + "SCANNER": "스캐너", + "LABEL": "라벨", + "EXPORT_CVE": "CVE 내보내기", + "SECURITY_HUB": "보안 허브", + "CATALOG": "카탈로그", + "METADATA": "프로젝트 메타데이터", + "REPOSITORY": "저장소", + "ARTIFACT": "아티팩트", + "SCAN": "스캔", + "SBOM": "SBOM", + "TAG": "태그", + "ACCESSORY": "액세서리", + "ARTIFACT_ADDITION": "아티팩트 추가", + "ARTIFACT_LABEL": "아티팩트 라벨", + "PREHEAT_POLICY": "예열 정책", + "IMMUTABLE_TAG": "Immutable Tag", + "LOG": "로그", + "NOTIFICATION_POLICY": "알림 정책", + "QUOTA": "할당량", + "BACK": "뒤로", + "NEXT": "다음", + "FINISH": "마지막", + "BASIC_INFO": "기본 정보", + "SELECT_PERMISSIONS": "권한 선택", + "SELECT_SYSTEM_PERMISSIONS": "시스템 권한 선택", + "SELECT_PROJECT_PERMISSIONS": "프로젝트 권한 선택", + "SYSTEM_PERMISSIONS": "시스템 권한" + }, + "WEBHOOK": { + "EDIT_BUTTON": "편집", + "ENABLED_BUTTON": "활성", + "DISABLED_BUTTON": "비활성", + "TYPE": "웹훅", + "STATUS": "상태", + "CREATED": "생성 됨", + "ENABLED": "활성화 됨", + "DISABLED": "비활성화 됨", + "OF": "of", + "ITEMS": "아이템", + "LAST_TRIGGERED": "마지막 발생", + "EDIT_WEBHOOK": "웹훅 편집", + "ADD_WEBHOOK": "웹훅 추가", + "CREATE_WEBHOOK": "웹훅 시작하기", + "EDIT_WEBHOOK_DESC": "웹훅 알림을 수신하기 위한 엔드포인트 지정", + "CREATE_WEBHOOK_DESC": "웹훅을 시작하려면 웹훅 서버에 액세스하기 위한 엔드포인트와 자격 증명을 제공하세요.", + "VERIFY_REMOTE_CERT_TOOLTIP": "웹훅이 원격 URL의 인증서를 확인해야 하는지 여부를 결정합니다. 원격 URL이 자체 서명된 인증서 또는 신뢰할 수 없는 인증서를 사용하는 경우 이 상자를 선택 취소합니다.", + "ENDPOINT_URL": "엔드포인트 URL", + "URL_IS_REQUIRED": "엔드포인트 URL은 필수입력항목입니다.", + "AUTH_HEADER": "Auth Header", + "VERIFY_REMOTE_CERT": "원격 인증서 확인", + "TEST_ENDPOINT_BUTTON": "엔드포인트 테스트", + "CANCEL_BUTTON": "취소", + "SAVE_BUTTON": "저장", + "TEST_ENDPOINT_SUCCESS": "연결 테스트가 성공적으로 완료되었습니다.", + "TEST_ENDPOINT_FAILURE": "엔드포인트로 Ping 시도가 실패했습니다.", + "ENABLED_WEBHOOK_TITLE": "웹훅 활성화", + "ENABLED_WEBHOOK_SUMMARY": "웹훅{{name}}을 활성화하시겠습니까??", + "DISABLED_WEBHOOK_TITLE": "웹훅 비활성화", + "DISABLED_WEBHOOK_SUMMARY": "웹훅{{name}}을 비활성화 하시겠습니까?", + "DELETE_WEBHOOK_TITLE": "웹훅 삭제", + "DELETE_WEBHOOK_SUMMARY": "웹훅{{names}}을 삭제하시겠습니까?", + "WEBHOOKS": "웹훅", + "NEW_WEBHOOK": "새 웹훅", + "ENABLE": "활성화", + "DISABLE": "비활성화", + "NAME": "이름", + "TARGET": "엔드포인트 URL", + "EVENT_TYPES": "이벤트 종류", + "DESCRIPTION": "설명", + "NO_WEBHOOK": "웹훅 없음", + "LAST_TRIGGER": "마지막 트리거", + "WEBHOOK_NAME": "웹훅 이름", + "NO_TRIGGER": "트리거 없음", + "NAME_REQUIRED": "이름은 필수입니다", + "NOTIFY_TYPE": "알림 유형", + "EVENT_TYPE": "유형 입력", + "EVENT_TYPE_REQUIRED": "하나 이상의 이벤트 유형이 필요합니다.", + "PAYLOAD_FORMAT": "페이로드 형식", + "CLOUD_EVENT": "클라우드이벤트", + "PAYLOAD_DATA": "페이로드 데이터", + "SLACK_RATE_LIMIT": "Slack 속도 제한에 유의하세요." + + }, + "GROUP": { + "GROUP": "그룹", + "GROUPS": "그룹들", + "IMPORT_LDAP_GROUP": "LDAP Group 불러오기", + "IMPORT_HTTP_GROUP": "새 HTTP Group", + "IMPORT_OIDC_GROUP": "새 OIDC Group", + "ADD": "새 그룹", + "EDIT": "편집", + "DELETE": "삭제", + "NAME": "이름", + "TYPE": "종류", + "DN": "DN", + "GROUP_DN": "Ldap Group DN", + "PROPERTY": "속성", + "REG_TIME": "등록 시간", + "ADD_GROUP_SUCCESS": "그룹 추가 성공", + "EDIT_GROUP_SUCCESS": "그룹 편집 성공", + "LDAP_TYPE": "LDAP", + "HTTP_TYPE": "HTTP", + "OIDC_TYPE": "OIDC", + "OF": "of", + "ITEMS": "아이템", + "NEW_MEMBER": "새 구성원", + "NEW_USER_INFO": "지정된 역할을 가진 이 프로젝트의 구성원이 될 그룹을 추가하세요.", + "ROLE": "역할", + "SYS_ADMIN": "시스템 관리자", + "PROJECT_ADMIN": "프로젝트 관리자", + "PROJECT_MAINTAINER": "메인테이너", + "DEVELOPER": "개발자", + "GUEST": "게스트", + "LIMITED_GUEST": "제한된 게스트", + "DELETION_TITLE": "그룹 구성원 삭제 확인", + "DELETION_SUMMARY": "그룹 구성원(들){{param}}을 정말 삭제하시겠습니까?" + }, + "AUDIT_LOG": { + "USERNAME": "사용자 이름", + "REPOSITORY_NAME": "저장소 이름", + "TAGS": "태그", + "OPERATION": "작업", + "OPERATIONS": "작업들", + "TIMESTAMP": "타임스탬프", + "ALL_OPERATIONS": "모든 작업들", + "PULL": "풀(Pull)", + "PUSH": "푸시", + "CREATE": "생성", + "DELETE": "삭제", + "OTHERS": "기타", + "ADVANCED": "Advanced", + "SIMPLE": "Simple", + "ITEMS": "아이템", + "FILTER_PLACEHOLDER": "로그 필터", + "INVALID_DATE": "잘못된 날짜.", + "OF": "of", + "NOT_FOUND": "로그를 찾을 수 없습니다!", + "RESOURCE": "리소스", + "RESOURCE_TYPE": "리소스 종류" + }, + "REPLICATION": { + "PUSH_BASED_ONLY": "푸시 기반 복제에만 해당됩니다", + "YES": "예", + "SECONDS": "초", + "MINUTES": "분", + "HOURS": "시", + "MONTH": "월", + "DAY_MONTH": "해당 월의 일", + "DAY_WEEK": "해당 주의 일", + "CRON_TITLE": "cron '* * * * * *'에 대한 패턴 설명입니다. cron 문자열은 UTC 시간을 기반으로 합니다.", + "FIELD_NAME": "필드 이름", + "MANDATORY": "Mandatory?", + "ALLOWED_VALUES": "허용되는 값", + "ALLOWED_CHARACTERS": "허용되는 특수 문자", + "TOTAL": "총", + "OVERRIDE": "Override", + "ENABLED_RULE": "규칙 활성화", + "OVERRIDE_INFO": "Override", + "OPERATION": "작업", + "CURRENT": "현재", + "FILTER_PLACEHOLDER": "작업 필터", + "STOP_TITLE": "실행 중지 확인", + "BOTH": "both", + "STOP_SUCCESS": "{{param}}를 성공적으로 실행 중지했습니다", + "STOP_SUMMARY": "{{param}} 실행을 중지하시겠습니까?", + "TASK_ID": "작업 아이디", + "RESOURCE_TYPE": "리소스 유형", + "SOURCE": "소스", + "DESTINATION": "목적지", + "POLICY": "정책", + "DURATION": "지속", + "SUCCESS_RATE": "성공률", + "SUCCESS": "성공", + "FAILURE": "실행", + "IN_PROGRESS": "진행 중", + "REPLICATION_RULE": "복제 규칙", + "NEW_REPLICATION_RULE": "새 복제 규칙", + "ENDPOINTS": "엔드포인트", + "FILTER_POLICIES_PLACEHOLDER": "룰 필터", + "FILTER_EXECUTIONS_PLACEHOLDER": "실행 필터", + "DELETION_TITLE": "복제 규칙 삭제 확인", + "DELETION_SUMMARY": "복제 규칙 {{param}}을(를) 삭제하시겠습니까?", + "REPLICATION_TITLE": "규칙 복제 확인", + "REPLICATION_SUMMARY": "{{param}} 규칙을 복제하시겠습니까?", + "DELETION_TITLE_FAILURE": "규칙 삭제를 삭제하지 못했습니다.", + "DELETION_SUMMARY_FAILURE": "대기 중/실행 중/재시도 중 상태입니다", + "REPLICATE_SUMMARY_FAILURE": "대기 중/실행 중 상태입니다", + "FILTER_TARGETS_PLACEHOLDER": "엔드포인트 필터", + "DELETION_TITLE_TARGET": "엔드포인트 삭제 확인", + "DELETION_SUMMARY_TARGET": "엔드포인트{{param}}를 삭제하시겠습니까?", + "ADD_POLICY": "새 복제 규칙", + "EDIT_POLICY": "편집", + "EDIT_POLICY_TITLE": "복제 규칙 편집", + "DELETE_POLICY": "삭제", + "TEST_CONNECTION": "연결 테스트", + "TESTING_CONNECTION": "연결 테스트 중...", + "TEST_CONNECTION_SUCCESS": "연결이 성공적으로 테스트되었습니다.", + "TEST_CONNECTION_FAILURE": "엔트포인트로 Ping을 보내지 못했습니다.", + "ID": "아이디", + "NAME": "이름", + "NAME_IS_REQUIRED": "이름은 필수항목입니다.", + "DESCRIPTION": "설명", + "ENABLE": "활성화", + "DISABLE": "비활성화", + "REPLICATION_MODE": "복제 모드", + "SRC_REGISTRY": "소스 레지스트리", + "DESTINATION_NAMESPACE": "목적지 레지스트리:네임스페이스", + "DESTINATION_NAME_IS_REQUIRED": "엔드포인트 이름은 필수항목입니다.", + "NEW_DESTINATION": "새 엔드포인트", + "DESTINATION_URL": "엔드포인트 URL", + "DESTINATION_URL_IS_REQUIRED": "엔드포인트 URL은 필수항목입니다.", + "DESTINATION_USERNAME": "사용자 이름", + "DESTINATION_PASSWORD": "패스워드", + "ALL_STATUS": "모든 상태", + "ENABLED": "활성화", + "DISABLED": "비활성화", + "LAST_START_TIME": "마지막 시작 시간", + "ACTIVATION": "활성화", + "REPLICATION_EXECUTION": "실행", + "REPLICATION_EXECUTIONS": "실행", + "STOPJOB": "중지", + "ALL": "전체", + "PENDING": "대기 중", + "RUNNING": "실행 중", + "ERROR": "에러", + "RETRYING": "다시 시도 중", + "STOPPED": "중지 됨", + "FINISHED": "완료 됨", + "CANCELED": "취소 됨", + "SIMPLE": "Simple", + "ADVANCED": "Advanced", + "STATUS": "상태", + "REPLICATION_TRIGGER": "트리거", + "CREATION_TIME": "종료 시간", + "UPDATE_TIME": "갱신 시간", + "END_TIME": "종료 시간", + "LOGS": "로그", + "OF": "of", + "ITEMS": "아이템", + "NO_LOGS": "로그 없음", + "TOGGLE_ENABLE_TITLE": "규칙 활성화", + "TOGGLE_DISABLE_TITLE": "규칙 비활성화", + "CREATED_SUCCESS": "복제 규칙을 성공적으로 생성했습니다.", + "UPDATED_SUCCESS": "복제 규칙을 성공적으로 갱신했습니다.", + "DELETED_SUCCESS": "복제 규칙을 성공적으로 삭제했습니다.", + "DELETED_FAILED": "복제 규칙 삭제를 실패했습니다.", + "TOGGLED_SUCCESS": "복제 규칙 상태를 성공적으로 전환했습니다", + "CANNOT_EDIT": "복제 규칙이 활성화된 동안에는 변경할 수 없습니다.", + "INVALID_DATE": "유효하지 않은 날짜입니다.", + "PLACEHOLDER": "복제 규칙을 찾을 수 없습니다!", + "JOB_PLACEHOLDER": "복제 작업을 찾을 수 없습니다!", + "NO_ENDPOINT_INFO": "엔드포인트를 먼저 추가하세요", + "NO_LABEL_INFO": "라벨을 먼저 추가하세요", + "NO_PROJECT_INFO": "이 프로젝트는 존재하지 않습니다", + "SOURCE_RESOURCE_FILTER": "소스 리소스 필터", + "SCHEDULED": "예정", + "MANUAL": "수동", + "EVENT_BASED": "이벤트 기반", + "DAILY": "매일", + "WEEKLY": "매주", + "SETTING": "옵션", + "TRIGGER": "트리거 조건", + "TARGETS": "대상", + "MODE": "모드", + "TRIGGER_MODE": "트리거 모드", + "SOURCE_PROJECT": "소스 프로젝트", + "REPLICATE": "복제", + "DELETE_REMOTE_IMAGES": "로컬에서 삭제되면 원격 리소스 삭제", + "DELETE_ENABLED": "이 정책을 활성화했습니다", + "NEW": "New", + "NAME_TOOLTIP": "복제 규칙 이름은 소문자, 숫자, ._-가 포함된 2자 이상이어야 하며 문자 또는 숫자로 시작해야 합니다.", + "DESTINATION_NAME_TOOLTIP": "대상 이름은 소문자, 숫자, ._-가 포함된 2자 이상이어야 하며 문자 또는 숫자로 시작해야 합니다.", + "ACKNOWLEDGE": "Acknowledge", + "RULE_DISABLED": "필터에 사용된 라벨이 삭제되었기 때문에 이 규칙이 비활성화되었습니다. \n 규칙을 편집하고 필터를 업데이트하여 활성화하십시오.", + "REPLI_MODE": "복제 모드", + "SOURCE_REGISTRY": "소스 레지스트리", + "SOURCE_NAMESPACES": "소스 네임스페이즈", + "DEST_REGISTRY": "목적지 레지스트리", + "DEST_NAMESPACE": "목적지 네임스페이스", + "NAMESPACE_TOOLTIP": "네임스페이스 이름은 소문자, 숫자 및 ._-/가 포함된 2자 이상이어야 하며 문자 또는 숫자로 시작해야 합니다.", + "TAG": "태그", + "LABEL": "라벨", + "RESOURCE": "리소스", + "ENABLE_TITLE": "규칙 활성화", + "ENABLE_SUMMARY": "규칙{{param}}을 활성화하시겠습니까?", + "DISABLE_TITLE": "규칙 비활성화", + "DISABLE_SUMMARY": "규칙{{param}}을 비활성화하시겠습니까?", + "ENABLE_SUCCESS": "규칙을 성공적으로 활성화했습니다", + "ENABLE_FAILED": "규칙 활성화를 실패했습니다", + "DISABLE_SUCCESS": "규칙을 성공적으로 비활성화 했습니다", + "DISABLE_FAILED": "규칙 비활성화를 실패했습니다", + "DES_REPO_FLATTENING": "목적지 저장소 필터링", + "NAMESPACE": "네임스페이스", + "REPO_FLATTENING": "Flattening", + "NO_FLATTING": "No Flatting", + "FLATTEN_LEVEL_1": "Flatten 1 Level", + "FLATTEN_LEVEL_2": "Flatten 2 Levels", + "FLATTEN_LEVEL_3": "Flatten 3 Levels", + "FLATTEN_ALL": "Flatten All Levels", + "FLATTEN_LEVEL_TIP": "이미지를 복사할 때 중첩된 저장소 구조를 줄입니다. 중첩된 저장소 구조가 'a/b/c/d/img'이고 대상 네임스페이스가 'ns'라고 가정하면 각 항목에 해당하는 결과는 다음과 같습니다.", + "FLATTEN_LEVEL_TIP_ALL": "'Flatten All Levels'(Used prior v2.3): 'a/b/c/d/img' -> 'ns/img'", + "FLATTEN_LEVEL_TIP_NO": "'No Flatting': 'a/b/c/d/img' -> 'ns/a/b/c/d/img", + "FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'", + "FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'", + "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", + "BANDWIDTH": "대역폭", + "BANDWIDTH_ERROR_TIP": "-1 또는 0보다 큰 정수를 입력하세요.", + "BANDWIDTH_TOOLTIP": "각 실행에 대한 최대 네트워크 대역폭을 설정합니다. 동시 실행 횟수에 주의하시기 바랍니다. 무제한 대역폭을 원하시면 -1을 입력하세요.", + "UNLIMITED": "제한 없음", + "UNREACHABLE_SOURCE_REGISTRY": "소스 레지스트리에 연결하지 못했습니다. 이 규칙을 편집하기 전에 소스 레지스트리를 사용할 수 있는지 확인하세요. {{오류}}", + "CRON_ERROR_TIP": "cron 문자열의 첫 번째 필드는 0이어야 하며 두 번째 필드는 \"*\"일 수 없습니다.", + "COPY_BY_CHUNK": "청크로 복사", + "COPY_BY_CHUNK_TIP": "Blob을 청크로 복사할지 여부를 지정합니다. 청크별로 전송하면 API 요청 수가 늘어날 수 있습니다.", + "TRIGGER_STOP_SUCCESS": "실행 중지가 성공적으로 트리거되었습니다.", + "CRON_STR": "Cron 문자열" + }, + "DESTINATION": { + "NEW_ENDPOINT": "새 엔드포인트", + "PROVIDER": "공급자", + "ENDPOINT": "엔드포인트", + "NAME": "이름", + "NAME_IS_REQUIRED": "엔드포인트 이름은 필수입력항목입니다.", + "URL": "엔드포인트 URL", + "URL_IS_REQUIRED": "엔드포인트 URL 이름은 필수입력항목입니다.", + "AUTHENTICATION": "인증", + "ACCESS_ID": "접근 ID", + "ACCESS_SECRET": "접근 시크릿", + "STATUS": "상태", + "TEST_CONNECTION": "테스트 연결", + "TITLE_EDIT": "엔드포인트 편집", + "TITLE_ADD": "새 레지스트리 엔드포인트", + "EDIT": "편집", + "DELETE": "삭제", + "TESTING_CONNECTION": "테스팅 연결...", + "TEST_CONNECTION_SUCCESS": "연결 테스트가 성공적으로 완료되었습니다.", + "TEST_CONNECTION_FAILURE": "엔드포인트로 Ping이 실패했습니다.", + "CONFLICT_NAME": "엔드포인트이름이 이미 존재합니다.", + "INVALID_NAME": "유효하지 않은 엔트포인트 이름.", + "FAILED_TO_GET_TARGET": "엔드포인트를 가져오는데 실패했습니다.", + "CREATION_TIME": "생성 날짜", + "OF": "of", + "ITEMS": "아이템", + "CREATED_SUCCESS": "엔드포인트를 성공적으로 생성했습니다.", + "UPDATED_SUCCESS": "엔드포인트를 성공적으로 업데이트 했습니다.", + "DELETED_SUCCESS": "엔드포인트를 성공적으로 삭제했습니다.", + "DELETED_FAILED": "엔드포인트 삭제를 실패했습니다.", + "CANNOT_EDIT": "복제 규칙이 활성화된 동안에는 엔드포인트를 변경할 수 없습니다..", + "FAILED_TO_DELETE_TARGET_IN_USED": "사용 중인 엔드포인트를 삭제하지 못했습니다.", + "PLACEHOLDER": "엔드포인트를 찾을 수 없습니다!" + }, + "REPOSITORY": { + "COPY_DIGEST_ID": "Digest 복사", + "DELETE": "삭제", + "NAME": "이름", + "TAGS": "테그", + "PLATFORM": "OS/ARCH", + "ARTIFACT_TOOTIP": "이 OCI 인덱스의 아티팩트 목록을 보려면 클릭하세요.", + "ARTIFACTS_COUNT": "아티팩트", + "PULL_COUNT": "풀(Pull) 수", + "PULL_COMMAND": "풀(Pull) 명령어", + "PULL_TIME": "풀(Pull) 시간", + "PUSH_TIME": "푸시 시간", + "IMMUTABLE": "Immutable", + "MY_REPOSITORY": "나의 저장소", + "PUBLIC_REPOSITORY": "공개 저장소", + "DELETION_TITLE_REPO": "저장소 삭제 확인", + "DELETION_TITLE_REPO_SIGNED": "저장소를 삭제할 수 없습니다", + "DELETION_SUMMARY_REPO_SIGNED": "다음과 같은 서명된 이미지가 존재하므로 '{{repoName}}' 저장소를 삭제할 수 없습니다.\n{{signedImages}} \n저장소를 삭제하기 전에 서명된 이미지를 모두 서명 취소해야 합니다!", + "DELETION_SUMMARY_REPO": "{{repoName}} 저장소를 삭제하시겠습니까?", + "DELETION_TITLE_ARTIFACT": "아티팩트 삭제 확인", + "DELETION_SUMMARY_ARTIFACT": "\n{{param}} 아티팩트를 삭제하시겠습니까? 이 아티팩트를 삭제하면 다이제스트에서 참조하는 모든 태그도 삭제됩니다.", + "DELETION_TITLE_TAG": "태그 삭제 확인", + "DELETION_SUMMARY_TAG": "{{param}} 태그를 삭제하시겠습니까?", + "DELETION_TITLE_TAG_DENIED": "서명된 태그를 삭제할 수 없습니다.", + "DELETION_SUMMARY_TAG_DENIED": "태그를 삭제하려면 먼저 공증인에서 태그를 제거해야 합니다.\nDelete from Notary via this command:\n", + "TAGS_NO_DELETE": "읽기 전용 모드에서는 삭제가 금지됩니다.", + "FILTER_FOR_REPOSITORIES": "저장소 필터", + "TAG": "태그", + "ARTIFACT": "아티팩트", + "ARTIFACTS": "아티팩트들", + "SIZE": "크기", + "VULNERABILITY": "취약점", + "SBOM": "SBOM", + "BUILD_HISTORY": "기록 생성", + "SIGNED": "서명됨", + "AUTHOR": "작성자", + "CREATED": "생성 시간", + "DOCKER_VERSION": "도커 버전", + "ARCHITECTURE": "아키텍처", + "OS": "OS", + "SHOW_DETAILS": "자세히 보기", + "REPOSITORIES": "저장소", + "OF": "of", + "ITEMS": "아이템", + "NO_ITEMS": "아이템 없음", + "POP_REPOS": "인기 저장소", + "DELETED_REPO_SUCCESS": "저장소를 성공적으로 삭제했습니다.", + "DELETED_TAG_SUCCESS": "태그를 성공적으로 삭제했습니다.", + "COPY": "복사", + "NOTARY_IS_UNDETERMINED": "이 태그의 서명을 확인할 수 없습니다.", + "PLACEHOLDER": "저장소를 찾을 수 없습니다!", + "INFO": "정보", + "NO_INFO": "이 저장소에 대한 설명이 없습니다. 이 저장소에 추가할 수 있습니다.", + "IMAGE": "이미지", + "LABELS": "라벨", + "ADD_LABEL_TO_IMAGE": "이 이미지에 라벨 추가", + "FILTER_BY_LABEL": "라벨별로 이미지 필터", + "FILTER_ARTIFACT_BY_LABEL": "라벨별로 아티팩트 필터", + "ADD_LABELS": "라벨 추가", + "STOP": "Stop", + "RETAG": "복사", + "ACTION": "동작", + "DEPLOY": "배포", + "ADDITIONAL_INFO": "추가 정보 추가", + "REPO_NAME": "저장소", + "MARKDOWN": "마크다운 스타일링 지원", + "LAST_MODIFIED": "마지막 수정 시간" + }, + "SUMMARY": { + "QUOTAS": "할당량", + "PROJECT_REPOSITORY": "저장소", + "PROJECT_MEMBER": "구성원", + "PROJECT_QUOTAS": "할당량", + "ARTIFACT_COUNT": "아티팩트 수", + "STORAGE_CONSUMPTION": "저장 장치 소모량", + "ADMIN": "관리자(들)", + "MAINTAINER": "메인테이너(들)", + "DEVELOPER": "개발자(들)", + "GUEST": "게스트(들)", + "LIMITED_GUEST": "제한된 게스트(들)", + "SEE_ALL": "모두 보기" + }, + "ALERT": { + "FORM_CHANGE_CONFIRMATION": "일부 수정사항이 아직 저장되지 않았습니다. 취소하시겠습니까?" + }, + "RESET_PWD": { + "TITLE": "비밀번호 재설정", + "CAPTION": "비밀번호를 재설정하기위해 당신의 이메일을 입력하세요", + "EMAIL": "이메일", + "SUCCESS": "비밀번호 재설정 링크가 포함된 메일이 성공적으로 발송되었습니다. 이 대화 상자를 닫고 사서함을 확인할 수 있습니다.", + "CAPTION2": "새 비밀번호를 입력하세요", + "RESET_OK": "비밀번호를 성공적으로 재설정했습니다. 새 비밀번호로 다시 로그인하기위해 'OK'를 클릭하세요." + }, + "RECENT_LOG": { + "SUB_TITLE": "보기", + "SUB_TITLE_SUFIX": "로그" + }, + "CONFIG": { + "SECURITY": "보안", + "HISTORY": "기록", + "TITLE": "설정", + "AUTH": "인증", + "REPLICATION": "복제", + "LABEL": "라벨", + "REPOSITORY": "저장소", + "REPO_READ_ONLY": "읽기 전용 저장소", + "WEBHOOK_NOTIFICATION_ENABLED": "웹훅 활성화", + "SYSTEM": "시스템 셋팅", + "PROJECT_QUOTAS": "프로젝트 할당량", + "VULNERABILITY": "취약점", + "GC": "가비지 컬랙션", + "CONFIRM_TITLE": "취소 확인", + "CONFIRM_SUMMARY": "일부 변경사항이 저장되지 않았습니다. 삭제하시겠습니까?", + "SAVE_SUCCESS": "구성이 성공적으로 저장되었습니다", + "VERIFY_REMOTE_CERT": "원격 인증서 확인", + "TOKEN_EXPIRATION": "토큰 만료(분)", + "SESSION_TIMEOUT": "세션 타임아웃(분)", + "SESSION_TIMEOUT_INFO": "'Harbor'의 세션 타임아웃을 설정합니다. 기본값은 60분입니다.", + "AUTH_MODE": "인증 모드", + "PRIMARY_AUTH_MODE": "기본 인증 모드", + "PRO_CREATION_RESTRICTION": "프로젝트 생성", + "SELF_REGISTRATION": "셀프 레지스트레이션 허용", + "AUTH_MODE_DB": "데이터베이스", + "AUTH_MODE_LDAP": "LDAP", + "AUTH_MODE_UAA": "UAA", + "AUTH_MODE_HTTP": "Http Auth", + "AUTH_MODE_OIDC": "OIDC", + "SCOPE_BASE": "Base", + "SCOPE_ONE_LEVEL": "OneLevel", + "SCOPE_SUBTREE": "하위항목", + "PRO_CREATION_EVERYONE": "모든 사용자", + "PRO_CREATION_ADMIN": "관리자 전용", + "ROOT_CERT": "레지스트리 루트 인증서", + "ROOT_CERT_LINK": "다운로드", + "REGISTRY_CERTIFICATE": "레지스트리 인증서", + "NO_CHANGE": "변경된 사항이 없으므로 저장을 중단합니다.", + "SKIP_SCANNER_PULL_TIME": "스캔시 이미지 \"마지막 풀(Pull) 시간\" 유지", + "TOOLTIP": { + "SELF_REGISTRATION_ENABLE": "가입을 활성화합니다.", + "SELF_REGISTRATION_DISABLE": "가입을 비활성화합니다.", + "VERIFY_REMOTE_CERT": "이미지 복제가 원격 'Harbor' 레지스트리의 인증서를 확인해야 하는지 여부를 결정합니다. 원격 레지스트리가 자체 서명되거나 신뢰할 수 없는 인증서를 사용하는 경우 이 상자를 선택 취소합니다.", + "AUTH_MODE": "기본적으로 인증 모드는 데이터베이스입니다. 즉, 자격 증명은 로컬 데이터베이스에 저장됩니다. LDAP 서버에 대해 사용자 자격 증명을 확인하려면 이를 LDAP로 설정합니다.", + "PRIMARY_AUTH_MODE": "이 인증 모드는 사용자가 로그인하는 기본 방법이 됩니다. 사용자가 ID 공급자 또는 로컬 DB를 통해 로그인하도록 선택하는 로그인 화면은 자동으로 사용자를 이 ID 공급자로 리디렉션합니다. DB를 통한 로그인은 명시적으로 '/account/sign-in' url로 접속 시 가능합니다.", + "LDAP_SEARCH_DN": "LDAP/AD 서버 검색 권한이 있는 사용자의 DN입니다. LDAP/AD 서버가 익명 검색을 지원하지 않는 경우 이 DN 및 ldap_search_pwd를 구성해야 합니다.", + "LDAP_BASE_DN": "LDAP/AD에서 사용자를 조회하는 기본 DN입니다.", + "LDAP_UID": "사용자를 일치시키기 위해 검색에 사용되는 속성입니다. LDAP/AD에 따라 uid, cn, 이메일, sAMAccountName 또는 기타 속성이 될 수 있습니다.", + "LDAP_SCOPE": "사용자를 검색할 범위입니다.", + "TOKEN_EXPIRATION": "토큰 서비스에서 생성된 토큰의 만료 시간(분)입니다. 기본값은 30분입니다.", + "ROBOT_NAME_PREFIX": "각 로봇(Robot) 이름의 접두사 문자열이며 기본값은 'robot$'입니다.", + "ROBOT_TOKEN_EXPIRATION": "로봇 계정 토큰의 만료 시간(일), 기본값은 30일입니다. 분 단위로 환산한 일 수를 표시하고 내림합니다", + "PRO_CREATION_RESTRICTION": "프로젝트 생성 권한이 있는 사용자를 정의하는 플래그입니다. 기본적으로 누구나 프로젝트를 만들 수 있습니다. 관리자만 프로젝트를 생성할 수 있도록 'Admin Only'로 설정하세요.", + "ROOT_CERT_DOWNLOAD": "레지스트리의 루트 인증서를 다운로드합니다.", + "SCANNING_POLICY": "다양한 요구 사항에 따라 이미지 스캔 정책을 설정합니다. '없음': 활성 정책이 없습니다. 'Daily At': 매일 지정된 시간에 검사를 시작합니다.", + "VERIFY_CERT": "LDAP 서버에서 인증서 확인", + "REPO_TOOLTIP": "이 모드에서는 사용자가 이미지에 어떤 작업도 수행할 수 없습니다.", + "WEBHOOK_TOOLTIP": "이미지 또는 차트 푸시, 가져오기, 삭제, 스캔과 같은 특정 작업이 수행될 때 지정된 엔드포인트에서 콜백을 수신하도록 웹훅를 활성화합니다", + "HOURLY_CRON": "매시간 시작되는 시간에 한 번씩 실행합니다. 0 0 * * * *와 같습니다.", + "WEEKLY_CRON": "일주일에 한 번, 토요일/일 사이 자정에 실행됩니다. 0 0 0 * * 0과 같습니다.", + "DAILY_CRON": "하루에 한 번, 자정에 실행하십시오. 0 0 0 * * *와 같습니다.", + "SKIP_SCANNER_PULL_TIME_TOOLTIP": "취약점 스캐너(예: Trivy)는 이미지를 스캔할 때 이미지 \"마지막 풀 시간\"을 업데이트하지 않습니다." + }, + "LDAP": { + "URL": "LDAP URL", + "SEARCH_DN": "LDAP Search DN", + "SEARCH_PWD": "LDAP 검색 비밀번호", + "BASE_DN": "LDAP Base DN", + "FILTER": "LDAP 필터", + "UID": "LDAP UID", + "SCOPE": "LDAP 범위", + "VERIFY_CERT": "LDAP 확인 인증서", + "LDAP_GROUP_BASE_DN": "LDAP Group Base DN", + "LDAP_GROUP_BASE_DN_INFO": "LDAP/AD에서 그룹을 조회할 기본 DN입니다. LDAP 그룹 관련 기능을 활성화해야 하는 경우 이 필드를 비워둘 수 없습니다.", + "LDAP_GROUP_FILTER": "LDAP 그룹 필터", + "LDAP_GROUP_FILTER_INFO": "LDAP/AD 그룹을 검색하기 위한 필터입니다. OpenLDAP의 경우: objectclass=groupOfNames. Active Directory의 경우: objectclass=group. LDAP 그룹 관련 기능이 필요한 경우 이 필드를 비워둘 수 없습니다.", + "LDAP_GROUP_GID": "LDAP Group GID", + "LDAP_GROUP_GID_INFO": "사용자를 일치시키기 위해 검색에 사용되는 속성으로, LDAP/AD에 따라 uid, cn 또는 기타 속성이 될 수 있습니다. Harbor의 그룹 이름은 기본적으로 이 속성으로 지정됩니다. LDAP 그룹 관련 기능을 활성화해야 하는 경우 이 필드를 비워둘 수 없습니다.", + "LDAP_GROUP_ADMIN_DN": "LDAP 그룹 관리자 DN", + "LDAP_GROUP_ADMIN_DN_INFO": "LDAP 그룹 DN을 지정합니다. 이 그룹의 모든 LDAP 사용자는 'Harbor' 관리자 권한을 갖게 됩니다. 원하지 않으시면 비워두세요.", + "LDAP_GROUP_MEMBERSHIP": "LDAP Group 멤버쉽", + "LDAP_GROUP_MEMBERSHIP_INFO": "속성은 LDAP 그룹의 멤버십을 나타내며 기본값은 memberof이며 일부 LDAP 서버에서는 \"ismemberof\"일 수 있습니다. LDAP 그룹 관련 기능을 활성화해야 하는 경우 이 필드를 비워둘 수 없습니다.", + "GROUP_SCOPE": "LDAP 그룹 검색 범위", + "GROUP_SCOPE_INFO": "그룹을 검색할 범위는 기본적으로 Subtree를 선택합니다." + + }, + "UAA": { + "ENDPOINT": "UAA 엔드포인트", + "CLIENT_ID": "UAA 클라이언트 ID", + "CLIENT_SECRET": "UAA 클라이언트 시크릿", + "VERIFY_CERT": "UAA 확인 인증서" + }, + "HTTP_AUTH": { + "ENDPOINT": "서버 엔드포인트", + "TOKEN_REVIEW": "토큰 리뷰 엔드포인트", + "SKIP_SEARCH": "검색 건너뛰기", + "VERIFY_CERT": "인증서 확인", + "ADMIN_GROUPS": "관리 그룹" + }, + "OIDC": { + "OIDC_PROVIDER": "OIDC 공급자 이름", + "OIDC_REDIREC_URL": "OIDC 공급자의 리디렉션 URI가 다음으로 설정되어 있는지 확인하세요.", + "ENDPOINT": "OIDC 엔드포인트", + "CLIENT_ID": "OIDC 클라이언트 ID", + "CLIENTSECRET": "OIDC 클라이언트 시크릿", + "SCOPE": "OIDC 범위", + "OIDC_VERIFYCERT": "인증서 확인", + "OIDC_AUTOONBOARD": "Automatic onboarding", + "USER_CLAIM": "Username Claim", + "OIDC_SETNAME": "OIDC 사용자 이름 설정", + "OIDC_SETNAMECONTENT": "제3자(OIDC)를 통해 인증할 때 처음으로 'Harbor'사용자 이름을 만들어야 합니다. 이는 'Harbor'내에서 프로젝트, 역할 등과 연결되는 데 사용됩니다.", + "OIDC_USERNAME": "사용자 이름", + "GROUP_CLAIM_NAME": "그룹 클레임 이름", + "GROUP_CLAIM_NAME_INFO": "OIDC 공급자에서 구성한 사용자 지정 그룹 클레임의 이름", + "OIDC_ADMIN_GROUP": "OIDC 관리 그룹", + "OIDC_ADMIN_GROUP_INFO": "OIDC 관리자 그룹 이름을 지정합니다. 이 그룹의 모든 OIDC 사용자는 항구 관리자 권한을 갖습니다. 원하지 않으시면 비워두세요.", + "OIDC_GROUP_FILTER": "OIDC 그룹 필터", + "OIDC_GROUP_FILTER_INFO": "제공된 정규식과 일치하는 OIDC 그룹을 필터링합니다. 모든 그룹과 일치하려면 공백으로 유지하세요." + }, + "SCANNING": { + "STOP_SCAN_ALL_SUCCESS": "트리거 정지 스캔이 모두 성공적으로 완료되었습니다!", + "TRIGGER_SCAN_ALL_SUCCESS": "트리거 스캔이 모두 성공적으로 완료되었습니다!", + "TRIGGER_SCAN_ALL_FAIL": "오류로 인해 전체 검사를 실행하지 못했습니다: {{error}}", + "TITLE": "취약점 스캐닝", + "SCAN_ALL": "모두 스캔", + "SCHEDULE_TO_SCAN_ALL": "모두 검사 예약", + "SCAN_NOW": "즉시 스캔", + "SCAN": "스캔", + "NONE_POLICY": "없음", + "DAILY_POLICY": "매일", + "REFRESH_POLICY": "새로고침 시", + "DB_REFRESH_TIME": "데이터베이스 업데이트 날짜", + "DB_NOT_READY": "취약점 데이터베이스가 완전히 준비되지 않았을 수 있습니다!", + "NEXT_SCAN": "이후 사용 가능", + "STATUS": { + "PENDING": "대기 중", + "RUNNING": "실행 중", + "STOPPED": "중지 됨", + "ERROR": "에러", + "SUCCESS": "성공", + "SCHEDULED": "예정 됨" + }, + "MANUAL": "수동", + "SCHEDULED": "예약" + }, + "TEST_MAIL_SUCCESS": "메일 서버 연결이 확인되었습니다.", + "TEST_LDAP_SUCCESS": "LDAP 서버에 대한 연결이 확인되었습니다.", + "TEST_MAIL_FAILED": "에러로 인해 메일 서버를 확인하지 못했습니다: {{param}}.", + "TEST_LDAP_FAILED": "에러로 인해 LDAP 서버를 확인하지 못했습니다: {{param}}.", + "LEAVING_CONFIRMATION_TITLE": "페이지에서 나가시겠습니까", + "LEAVING_CONFIRMATION_SUMMARY": "변경사항이 아직 저장되지 않았습니다. 현재 페이지에서 나가시겠습니까?", + "TEST_OIDC_SUCCESS": "OIDC 서버 연결이 확인되었습니다." + }, + "PAGE_NOT_FOUND": { + "MAIN_TITLE": "페이지를 찾을 수 없습니다", + "SUB_TITLE": "다음의 메인 페이지로 리디렉션 중입니다.", + "UNIT": "초..." + }, + "ABOUT": { + "VERSION": "버전", + "BUILD": "빌드", + "COPYRIGHT": "프로젝트 'Harbor'는 콘텐츠를 저장, 서명 및 검색하는 신뢰할 수 있는 오픈 소스 클라우드 네이티브 레지스트리 프로젝트입니다. 'Harbor'는 보안, ID, 관리 등 사용자에게 일반적으로 필요한 기능을 추가하여 오픈 소스 Docker 배포를 확장합니다. 'Harbor'는 사용자 관리, 액세스 제어, 활동 모니터링, 인스턴스 간 복제와 같은 고급 기능을 지원합니다. 레지스트리를 빌드 및 실행 환경에 더 가깝게 두면 이미지 전송 효율성도 향상될 수 있습니다.", + "COPYRIGHT_SUFIX": ".", + "TRADEMARK": "VMware는 미국 및 기타 국가에서 VMware, Inc.의 등록 상표 또는 상표입니다. 여기에 언급된 기타 모든 상표 및 이름은 해당 회사의 상표일 수 있습니다.", + "END_USER_LICENSE": "최종 사용자 라이센스 계약", + "OPEN_SOURCE_LICENSE": "오픈소스/서드 파티 라이선스" + }, + "START_PAGE": { + "GETTING_START": "", + "GETTING_START_TITLE": "시작하기" + }, + "TOP_REPO": "인기 저장소", + "STATISTICS": { + "PRO_ITEM": "프로젝트", + "REPO_ITEM": "저장소", + "INDEX_PRIVATE": "비공개", + "INDEX_PUB": "공개", + "INDEX_TOTAL": "합계", + "STORAGE": "저장 장치", + "LIMIT": "제한", + "STORAGE_USED": "저장 장치 사용량" + }, + "SEARCH": { + "IN_PROGRESS": "검색 중...", + "BACK": "뒤로" + }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, + "VULNERABILITY": { + "STATE": { + "OTHER_STATUS": "스캔되지 않음", + "QUEUED": "큐에 등록됨", + "ERROR": "로그 보기", + "SCANNING": "스캔 중", + "STOPPED": "스캔 중지 됨" + }, + "GRID": { + "PLACEHOLDER": "스캐닝 결과를 찾을 수 없습니다!", + "COLUMN_ID": "취약점", + "COLUMN_SEVERITY": "심각도", + "COLUMN_PACKAGE": "패키지", + "COLUMN_PACKAGES": "패키지들", + "COLUMN_VERSION": "현재 버전", + "COLUMN_FIXED": "수정된 버전", + "COLUMN_DESCRIPTION": "설명", + "FOOT_ITEMS": "아이템", + "FOOT_OF": "of", + "IN_ALLOW_LIST": "CVE 허용 목록에 나열됨", + "CVSS3": "CVSS3" + }, + "CHART": { + "SCANNING_TIME": "스캔 완료 시간:", + "SCANNING_PERCENT": "스캔 완료 퍼센테이지:", + "SCANNING_PERCENT_EXPLAIN": "스캔 완료 비율은 성공적으로 스캔한 이미지 수 / 이미지 인덱스 내에서 참조된 총 이미지 수로 계산됩니다.", + "TOOLTIPS_TITLE": "{{totalPackages}} {{package}}의 {{totalVulnerability}}가 {{vulnerability}}을 알고있습니다.", + "TOOLTIPS_TITLE_SINGULAR": "{{package}}의 {{totalVulnerability}} 가 {{vulnerability}}를 알고있습니다.", + "TOOLTIPS_TITLE_ZERO": "인식 가능한 취약점이 감지되지 않았습니다." + }, + "SEVERITY": { + "CRITICAL": "심각", + "HIGH": "높은", + "MEDIUM": "중간", + "LOW": "낮음", + "NONE": "없음" + }, + "SINGULAR": "취약점", + "OVERALL_SEVERITY": "취약점 심각도:", + "NO_VULNERABILITY": "취약점 없음", + "PLURAL": "취약점", + "PLACEHOLDER": "취약점 필터", + "PACKAGE": "패키지", + "PACKAGES": "패키지들", + "SCAN_NOW": "Scan vulnerability", + "SCAN_BY": "{{scanner}로 스캔", + "REPORTED_BY": "{{scanner}}로 보고 됨", + "NO_SCANNER": "스캐너 없음", + "TRIGGER_STOP_SUCCESS": "트리거 중지 스캔이 성공적으로 수행되었습니다", + "STOP_NOW": "Stop Scan" + }, + "PUSH_IMAGE": { + "TITLE": "푸시 명령어", + "DOCKER": "도커", + "PODMAN": "Podman", + "HELM": "헬름", + "CNAB": "CNAB", + "TAG_COMMAND_CHART": "이 프로젝트에 대한 차트 패키징:", + "PUSH_COMMAND_CHART": "이 프로젝트에 차트 푸시:", + "PUSH_COMMAND_CNAB": "CNAB를 이 프로젝트에 푸시:", + "TOOLTIP": "이 프로젝트에 아티팩트를 푸시하기 위한 명령 참조입니다.", + "TAG_COMMAND": "이 프로젝트의 이미지에 태그를 지정:", + "PUSH_COMMAND": "이 프로젝트에 이미지 푸시:", + "COPY_ERROR": "복사에 실패했습니다. 명령 참조를 수동으로 복사해 보세요.", + "COPY_PULL_COMMAND": "풀(PULL) 명령어 복사" + }, + "ARTIFACT": { + "FILTER_FOR_ARTIFACTS": "아티팩트 필터", + "ADDITIONS": "추가사항", + "ANNOTATION": "주석", + "OVERVIEW": "개요", + "IMAGE": "이미지", + "CHART": "차트", + "CNAB": "CNAB", + "WASM": "WASM", + "TAGGED": "태그 됨", + "UNTAGGED": "태그가 지정되지 않음", + "ALL": "모두", + "PLACEHOLDER": "아티팩트를 찾을 수 없음!", + "SCAN_UNSUPPORTED": "지원되지 않음", + "SBOM_UNSUPPORTED": "Unsupported", + "SUMMARY": "요약", + "DEPENDENCIES": "종속성", + "VALUES": "값", + "NAME": "이름", + "REPO": "저장소", + "OF": "of", + "VERSION": "버전", + "NO_README": "이 차트에서는 추가 정보 파일이 제공되지 않습니다.", + "ITEMS": "아이템", + "SHOW_KV": "키-값 쌍", + "SHOW_YAML": "YAML 파일" + }, + "TAG": { + "CREATION_TIME_PREFIX": "생성 날짜", + "CREATOR_PREFIX": "by", + "ANONYMITY": "익명", + "IMAGE_DETAILS": "이미지 상세", + "DOCKER_VERSION": "도커 버전", + "ARCHITECTURE": "아키텍쳐", + "OS": "OS", + "OS_VERSION": "OS 버전", + "HAVE": "have", + "HAS": "has", + "SCAN_COMPLETION_TIME": "스캔 완료", + "IMAGE_VULNERABILITIES": "이미지 취약점", + "LEVEL_VULNERABILITIES": "레벨 취약젖ㅁ", + "PLACEHOLDER": "태그를 찾을 수 없습니다!", + "COPY_ERROR": "복사 실패, 수동으로 복사하세요.", + "FILTER_FOR_TAGS": "필터 태그", + "AUTHOR": "작성자", + "LABELS": "라벨", + "CREATION": "생성 날짜", + "COMMAND": "명령어", + "UPLOADTIME": "업로드 시간", + "NAME": "이름", + "PULL_TIME": "풀(Pull) 시간", + "PUSH_TIME": "푸시 시간", + "OF": "of", + "ITEMS": "아이템", + "ADD_TAG": "태그 추가", + "REMOVE_TAG": "태그 제거", + "NAME_ALREADY_EXISTS": "저장소에 태그가 이미 존재합니다" + }, + "LABEL": { + "LABEL": "라벨", + "DESCRIPTION": "설명", + "CREATION_TIME": "생성 시간", + "NEW_LABEL": "새 라벨", + "EDIT": "편집", + "DELETE": "삭제", + "LABEL_NAME": "라벨 이름", + "COLOR": "색상", + "FILTER_LABEL_PLACEHOLDER": "라벨 필터", + "NO_LABELS": "라벨 없음", + "DELETION_TITLE_TARGET": "라벨 삭제 확인", + "DELETION_SUMMARY_TARGET": "{{param}}를 삭제하시겠습니까?", + "PLACEHOLDER": "라벨을 찾을 수 없습니다!", + "NAME_ALREADY_EXISTS": "라벨 이름이 이미 존재합니다." + }, + "QUOTA": { + "PROJECT": "프로젝트", + "OWNER": "소유자", + "COUNT": "개수", + "STORAGE": "스토리지", + "EDIT": "편집", + "DELETE": "삭제", + "OF": "of", + "PROJECT_QUOTA_DEFAULT_ARTIFACT": "프로젝트당 기본 아티팩트 수", + "PROJECT_QUOTA_DEFAULT_DISK": "프로젝트당 기본 할당량 공간", + "EDIT_PROJECT_QUOTAS": "프로젝트 할당량 편집", + "EDIT_DEFAULT_PROJECT_QUOTAS": "기본 프로젝트 할당량 편집", + "SET_QUOTAS": "{{params}}' 프로젝트에 대한 프로젝트 할당량을 설정하세요", + "SET_DEFAULT_QUOTAS": "새 프로젝트 생성 시 기본 프로젝트 할당량 설정", + "COUNT_QUOTA": "아티팩트 개수", + "COUNT_DEFAULT_QUOTA": "기본 아티팩트 개수", + "STORAGE_QUOTA": "프로젝트 할당량 제한", + "STORAGE_DEFAULT_QUOTA": "기본 저장 공간 할당량", + "SAVE_SUCCESS": "할당량 편집 성공", + "UNLIMITED": "제한 없음", + "INVALID_INPUT": "유효하지 않은 입력", + "PLACEHOLDER": "프로젝트를 찾을 수 없습니다", + "FILTER_PLACEHOLDER": "이름(정확히 일치)으로 검색", + "QUOTA_USED": "사용된 할당량" + }, + "WEEKLY": { + "MONDAY": "월요일", + "TUESDAY": "화요일", + "WEDNESDAY": "수요일", + "THURSDAY": "목요일", + "FRIDAY": "금요일", + "SATURDAY": "토요일", + "SUNDAY": "일요일" + }, + "OPERATION": { + "LOCAL_EVENT": "로컬 이벤트", + "ALL": "모두", + "RUNNING": "실행 중", + "FAILED": "실패", + "STOP_EXECUTIONS": "실행 중지", + "DELETE_PROJECT": "프로젝트 삭제", + "DELETE_REPO": "저장소 삭제", + "DELETE_TAG": "태그 삭제", + "DELETE_USER": "사용자 삭제", + "DELETE_ROBOT": "로봇 삭제", + "DELETE_REGISTRY": "레지스트리 삭제", + "DELETE_REPLICATION": "복제 삭제", + "DELETE_MEMBER": "사용자 구성원 삭제", + "DELETE_GROUP": "그룹 구성원 삭제", + "DELETE_CHART_VERSION": "차트 버전 삭제", + "DELETE_CHART": "차트 삭제", + "SWITCH_ROLE": "역할 변경", + "ADD_GROUP": "그룹 맴버 추가", + "ADD_USER": "유저 맴버 추가", + "DELETE_LABEL": "라벨 삭제", + "REPLICATION": "복제", + "DAY_AGO": " 일 전", + "HOUR_AGO": " 시간 전", + "MINUTE_AGO": " 분 전", + "SECOND_AGO": "1분 미만", + "EVENT_LOG": "이벤트 로그" + }, + "UNKNOWN_ERROR": "알 수 없는 에러가 발생했습니다. 다시 시도해 주세요.", + "UNAUTHORIZED_ERROR": "세션이 유효하지 않거나 만료됐습니다. 로그인 후 다시 시도하세요.", + "REPO_READ_ONLY": "'Harbor'는 읽기 전용 모드로 설정되어 있으며 읽기 전용 모드에서는 저장소 삭제, 아티팩트 태그 그리고 이미지 푸시가 비활성화됩니다", + "FORBIDDEN_ERROR": "작업을 수행하기위한 권한이 없습니다.", + "GENERAL_ERROR": "서비스 호출 수행 중 오류가 발생했습니다: {{param}}.", + "BAD_REQUEST_ERROR": "잘못된 요청으로인해 작업을 수행할 수 없습니다.", + "NOT_FOUND_ERROR": "오브젝트가 존재하지 않기 때문에 요청을 완료할 수 없습니다.", + "CONFLICT_ERROR": "귀하의 제출 내용이 충돌하기 때문에 귀하의 작업을 수행할 수 없습니다.", + "PRECONDITION_FAILED": "전제 조건 요류로 인해 작업을 수행할 수 없습니다.", + "SERVER_ERROR": "내부 서버 오류가 발생하여 귀하의 작업을 수행할 수 없습니다.", + "INCONRRECT_OLD_PWD": "이전 비밀번호가 올바르지 않습니다.", + "UNKNOWN": "n/a", + "STATUS": "상태", + "START_TIME": "시작 시간", + "CREATION_TIME": "생성 시간", + "UPDATE_TIME": "갱신 시간", + "LOGS": "로그", + "PENDING": "대기 중", + "FINISHED": "완료", + "STOPPED": "중지", + "RUNNING": "실행 중", + "ERROR": "에러", + "SCHEDULE": { + "NONE": "없음", + "DAILY": "매일", + "WEEKLY": "매주", + "HOURLY": "매시", + "CUSTOM": "사용자 정의", + "MANUAL": "수동", + "SCHEDULE": "예약", + "CRON": "cron", + "ON": "on", + "AT": "at", + "NOSCHEDULE": "예약내역을 가져오던 중 에러가 발생했습니다" + + }, + "GC": { + "CURRENT_SCHEDULE": "가비지 컬렉션 예약", + "GC_NOW": "가비지 컬렉션 즉시 실행", + "JOB_HISTORY": "가비지 컬렉션 기록", + "JOB_ID": "작업 ID", + "TRIGGER_TYPE": "발생 유형", + "LATEST_JOBS": "마지막 {{param}} 작업", + "MSG_SUCCESS": "가비지 컬렉션 성공", + "MSG_SCHEDULE_SET": "가비지 컬렉션 일정이 추가됐습니다", + "MSG_SCHEDULE_RESET": "가비지 컬렉션 일정이 초기화됐습니다", + "PARAMETERS": "파라미터", + "DELETE_UNTAGGED": "태그가 지정되지 않은 아티팩트에 대한 가비지 수집 허용", + "EXPLAIN": "가비지 컬렉션은 레지스트리 성능에 영향을 미칠 수 있는 커퓨팅 집약적 작업입니다.", + "EXPLAIN_TIME_WINDOW": "지난 2시간(기본 기간) 동안 업로드된 아티팩트는 가비지 컬렉션에서 제외됩니다.", + "DRY_RUN_SUCCESS": "모의 테스트가 성공적으로 실행됐습니다", + "DELETE_DETAIL": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted, {{size}} space freed up", + "DELETE_DETAIL_DRY_RUN": "{{blob}} blob(s) and {{manifest}} manifest(s) could be deleted, {{size}} space could be freed up", + "WORKERS_TOOLTIP": "가비지 컬렉션 작업을 병렬로 실행할 수 있는 작업자 수를 설정합니다. 기본값은 1입니다." + }, + "RETAG": { + "MSG_SUCCESS": "아티팩트를 성공적으로 복사했습니다.", + "TIP_REPO": " 저장소 이름은 경로 구성 요소로 구분됩니다. 저장소 이름의 구성 요소는 하나 이상의 소문자, 영숫자 문자여야 하며 선택적으로 마침표, 대시 또는 밑줄로 구분됩니다. 보다 엄격하게는 정규식 [a-z0-9]+(?:[._-][a-z0-9]+)*와 일치해야 합니다. 저장소 이름에 두 개 이상의 경로 구성 요소가 있는 경우 다음과 같아야 합니다. 슬래시('/')로 구분됩니다. 슬래시를 포함한 저장소 이름의 전체 길이는 256자 미만이어야 합니다.", + "TIP_TAG": "태그는 저장소의 Docker 이미지에 적용되는 라벨입니다. 태그는 저장소의 다양한 이미지를 서로 구별하는 방법입니다. Regex와 일치해야 합니다: (`[\\w][\\w.-]{0,127}`)" + }, + "CVE_ALLOWLIST": { + "DEPLOYMENT_SECURITY": "배포 보안", + "CVE_ALLOWLIST": "CVE 허용 목록", + "SYS_ALLOWLIST_EXPLAIN": "시스템 허용 목록을 사용하면 이미지의 취약점을 계산할 때 이 목록의 취약점을 무시할 수 있습니다.", + "ADD_SYS": "시스템 허용 목록에 CVE 아이디 추가", + "WARNING_SYS": "시스템 CVE 허용 목록이 만료되었습니다. 만료 날짜를 연장하여 허용 목록을 활성화할 수 있습니다.", + "WARNING_PRO": "프로젝트 CVE 허용 목록이 만료되었습니다. 만료 날짜를 연장하여 허용 목록을 활성화할 수 있습니다.", + "ADD": "추가", + "ENTER": "CVE 아이디(들) 입력", + "HELP": "구분 기호: 쉼표 또는 개행 문자", + "NONE": "없음", + "EXPIRES_AT": "~에 만료", + "NEVER_EXPIRES": "만료되지 않음", + "PRO_ALLOWLIST_EXPLAIN": "프로젝트 허용 목록을 사용하면 이 프로젝트에서 이미지를 푸시하고 가져올 때 이 목록의 취약점을 무시할 수 있습니다.", + "PRO_OR_SYS": "시스템 수준에서 구성된 기본 허용 목록을 사용하거나 '프로젝트 허용 목록'을 클릭하여 새 허용 목록을 만들 수 있습니다.", + "MERGE_INTO": "시스템 허용 목록도 추가하려면 '시스템에서 복사'를 클릭하기 전에 개별 CVE ID를 추가하세요.", + "SYS_ALLOWLIST": "시스템 허용 항목", + "PRO_ALLOWLIST": "프로젝트 허용 항목", + "ADD_SYSTEM": "시스템에서 복사" + }, + "TAG_RETENTION": { + "TAG_RETENTION": "태그 보관", + "RETENTION_RULES": "보관 규칙", + "RULE_NAME_1": " 지난 {{number}}일 동안의 아티팩트", + "RULE_NAME_2": " 가장 최근의 활성 {{number}} 아티팩트", + "RULE_NAME_3": " 가장 최근에 푸시된 {{number}} 아티팩트", + "RULE_NAME_4": " 가장 최근에 가져온 {{number}} 아티팩트", + "RULE_NAME_5": " always", + "ADD_RULE": "규칙 추가", + "ADD_RULE_HELP_1": "규칙을 추가하려면 ADD RULE 버튼을 클릭하세요.", + "ADD_RULE_HELP_2": "태그 보관 정책은 하루에 한 번 실행됩니다.", + "RETENTION_RUNS": "보관 실행", + "RUN_NOW": "즉시 실행", + "WHAT_IF_RUN": "모의 테스트", + "ABORT": "ABORT", + "SERIAL": "아이디", + "STATUS": "상태", + "DRY_RUN": "모의 테스트", + "START_TIME": "시작 시간", + "DURATION": "지속 기간", + "DETAILS": "상세", + "REPOSITORY": "저장소", + "EDIT": "편집", + "DISABLE": "비활성화", + "ENABLE": "활성화", + "DELETE": "삭제", + "ADD_TITLE": "태그 보관 규칙 추가", + "ADD_SUBTITLE": "이 프로젝트에 대한 태그 보관 규칙을 지정하세요. 모든 태그 보관 규칙은 독립적으로 계산되며 각 규칙은 선택한 저장소 목록에 적용될 수 있습니다.", + "BY_WHAT": "아티팩트 수 또는 일수별", + "RULE_TEMPLATE_1": "지난 #일 동안의 아티팩트", + "RULE_TEMPLATE_2": "가장 최근의 활성 # 아티팩트", + "RULE_TEMPLATE_3": "가장 최근에 푸시된 아티팩트 #개", + "RULE_TEMPLATE_4": "가장 최근에 가져온 아티팩트 #개", + "RULE_TEMPLATE_5": " 항상", + "ACTION_RETAIN": " 보관", + "UNIT_DAY": "날", + "UNIT_COUNT": "개수", + "NUMBER": "번호", + "IN_REPOSITORIES": "For the repositories", + "REP_SEPARATOR": "쉼표로 구분된 저장소, 저장소* 또는 **를 여러 개 입력하세요.", + "TAGS": "태그", + "UNTAGGED": " 태그되지 않음", + "INCLUDE_UNTAGGED": " 태그가 지정되지 않은 아티팩트", + "MATCHES_TAGS": "태그와 일치", + "MATCHES_EXCEPT_TAGS": "태그를 제외하고 일치", + "TAG_SEPARATOR": "여러 개의 쉼표로 구분된 태그(태그* 또는 **)를 입력하세요. 선택적으로 위의 확인란을 선택하여 '포함' 또는 '제외' 선택기를 적용할 때 태그가 지정되지 않은 모든 아티팩트를 포함합니다.", + "LABELS": "라벨", + "MATCHES_LABELS": "라벨과 일치", + "MATCHES_EXCEPT_LABELS": "라벨을 제외한 일치", + "REP_LABELS": "여러 개의 쉼표로 구분된 라벨을 입력하세요.", + "RETENTION_RUN": "보관 실행", + "RETENTION_RUN_EXPLAIN": "보존 정책을 실행하면 이 프로젝트의 아티팩트에 부정적인 영향을 미칠 수 있으며 영향을 받은 아티팩트 태그가 삭제됩니다. 취소를 누르고 DRY RUN을 사용하여 이 정책의 효과를 시뮬레이션합니다. 그렇지 않으면 RUN을 눌러 계속 진행하세요.", + "RETENTION_RUN_ABORTED": "보존 실행이 중단되었습니다.", + "RETENTION_RUN_ABORTED_EXPLAIN": "이 보존 실행이 중단되었습니다. 이미 삭제된 아티팩트는 되돌릴 수 없습니다. 다른 실행을 시작하여 아티팩트를 계속 삭제할 수 있습니다. 실행을 시뮬레이션하려면 DRY RUN을 사용할 수 있습니다..", + "LOADING": "로딩...", + "NO_EXECUTION": "실행중인 내용을 찾을 수 없습니다!", + "NO_HISTORY": "기록을 찾을 수 없습니다!!", + "DELETION": "삭제", + "EDIT_TITLE": "태그 보관 규칙 편집", + "LOG": "로그", + "EXCLUDES": "제외", + "MATCHES": "일치", + "REPO": " 저장소", + "EXC": " excluding ", + "MAT": " matching ", + "AND": " and", + "WITH": " with ", + "WITHOUT": " without ", + "LOWER_LABELS": " labels", + "WITH_CONDITION": " with", + "LOWER_TAGS": " tags", + "TRIGGER": "Schedule", + "RETAINED": "Retained", + "TOTAL": "전체", + "NONE": " 없음", + "RULE_NAME_6": " 지난 {{number}}일 동안 가져온 아티팩트", + "RULE_NAME_7": " 지난 {{number}}일 동안 푸시된 아티팩트", + "RULE_TEMPLATE_6": " 지난 #일 이내에 가져온 아티팩트", + "RULE_TEMPLATE_7": " 지난 #일 이내에 푸시된 아티팩트", + "SCHEDULE": "예정", + "SCHEDULE_WARNING": "보존 정책을 실행하면 Harbor 프로젝트에서 아티팩트를 삭제하는 되돌릴 수 없는 효과가 발생합니다. 예약하기 전에 모든 정책을 다시 한번 확인하세요.", + "EXISTING_RULE": "기존 규칙", + "ILLEGAL_RULE": "불법적인 규칙", + "INVALID_RULE": "잘못된 규칙", + "COUNT_LARGE": "\"COUNT\" 매개변수가 너무 큽니다.", + "DAYS_LARGE": "\"DAYS\" 매개변수가 너무 큽니다.", + "EXECUTION_TYPE": "실행 유형", + "ACTION": "동작", + "YES": "예", + "NO": "아니요" + }, + "IMMUTABLE_TAG": { + "IMMUTABLE_RULES": "불변성 규칙", + "ADD_RULE": "규칙 추가", + "ADD_RULE_HELP_1": "규칙을 추가하려면 규칙 추가 버튼을 누르세요.", + "EDIT": "편집", + "DISABLE": "비활성화", + "ENABLE": "활성화", + "DELETE": "삭제", + "ADD_TITLE": "태그 불변성 규칙 추가", + "ADD_SUBTITLE": "이 프로젝트에 대한 태그 불변성 규칙을 지정합니다. 참고: 모든 태그 불변성 규칙은 먼저 독립적으로 계산된 다음 통합되어 불변성 태그의 최종 세트를 캡처합니다.", + "IN_REPOSITORIES": "For the repositories", + "REP_SEPARATOR": "쉼표로 구분된 저장소, 저장소* 또는 **를 여러 개 입력하세요.", + "TAGS": "태그", + "TAG_SEPARATOR": "여러 개의 쉼표로 구분된 태그(태그* 또는 **)를 입력하세요.", + "EDIT_TITLE": "태그 불변성 규칙 편집", + "EXC": " excluding ", + "MAT": " matching ", + "AND": " and", + "WITH": " with ", + "WITHOUT": " without ", + "LOWER_LABELS": " labels", + "LOWER_TAGS": " tags", + "NONE": "없음", + "EXISTING_RULE": "Existing rule", + "ACTION": "ACTION" + }, + "SCANNER": { + "DELETION_SUMMARY": "스캐너 {{param}}을 삭제하시겠습니까?", + "SKIP_CERT_VERIFY": "원격 레지스트리가 자체 서명되거나 신뢰할 수 없는 인증서를 사용할 때 인증서 확인을 건너뛰려면 이 상자를 선택하십시오.", + "NAME": "이름", + "NAME_EXISTS": "이미 존재하는 이름입니다", + "NAME_REQUIRED": "이름은 필수 항목입니다", + "NAME_REX": "이름은 2자 이상이어야 하며 소문자, 숫자, ._-가 포함되어야 하며 문자나 숫자로 시작해야 합니다.", + "DESCRIPTION": "설명", + "ENDPOINT": "엔드포인트", + "ENDPOINT_EXISTS": "엔드포인트 URL이 이미 존재합니다", + "ENDPOINT_REQUIRED": "엔드포인트 URL은 필수 항목입니다", + "ILLEGAL_ENDPOINT": "엔드포인트 Url 이 잘못됐습니다", + "AUTH": "권한 부여", + "NONE": "없음", + "BASIC": "기본", + "BEARER": "Bearer", + "API_KEY": "APIKey", + "USERNAME": "사용자 이름", + "USERNAME_REQUIRED": "사용자 이름은 필수 항목입니다", + "PASSWORD": "비밀번호", + "PASSWORD_REQUIRED": "비밀번호는 필수 항목입니다", + "TOKEN": "토큰", + "TOKEN_REQUIRED": "토큰은 필수 항목입니다", + "API_KEY_REQUIRED": "APIKey 는 필수 항목입니다", + "SKIP": "인증서 확인 건너뛰기", + "ADD_SCANNER": "스캐너 추가", + "EDIT_SCANNER": "스캐너 편집", + "TEST_CONNECTION": "TEST CONNECTION", + "ADD_SUCCESS": "성공적으로 추가됐습니다", + "TEST_PASS": "테스트 통과", + "TEST_FAILED": "Ping: registration {{name}}:{{url}} is unreachable", + "UPDATE_SUCCESS": "성공적으로 업데이트됏습니다", + "SCANNER_COLON": "스캐너:", + "NAME_COLON": "이름:", + "VENDOR_COLON": "Vendor:", + "VERSION_COLON": "버전:", + "CAPABILITIES": "기능:", + "CONSUMES_MIME_TYPES_COLON": "Mime 유형 사용:", + "PRODUCTS_MIME_TYPES_COLON": "Mime 유형 제공:", + "CAPABILITIES_TYPE": "Type:", + "PROPERTIES": "속성", + "NEW_SCANNER": "새 스캐너", + "SET_AS_DEFAULT": "기본으로 설정", + "HEALTH": "상태", + "DISABLED": "비활성화", + "NO_SCANNER": "스캐너를 찾을 수 없습니다", + "DEFAULT": "기본", + "HEALTHY": "안정적인", + "UNHEALTHY": "위험한", + "SCANNERS": "스캐너들", + "SCANNER": "스캐너", + "EDIT": "편집", + "NOT_AVAILABLE": "사용 불가", + "ADAPTER": "Adapter", + "VENDOR": "Vendor", + "VERSION": "버전", + "SELECT_SCANNER": "스캐너 선택", + "ENABLED": "활성화 됨", + "ENABLE": "활성화", + "DISABLE": "비활성화", + "DELETE_SUCCESS": "성공적으로 살제했습니다", + "TOTAL": "전체", + "FIXABLE": "수정가능", + "DURATION": "지속 기간:", + "OPTIONS": "옵션", + "USE_INNER": "내부 레지스트리 주소 사용", + "USE_INNER_TIP": "옵션을 선택하면 스캐너는 관련 콘텐츠에 액세스하기 위해 내부 레지스트리 주소를 사용해야 합니다.", + "VULNERABILITY_SEVERITY": "취약점 심각도:", + "CONFIRM_DELETION": "스캐너 삭제 확인", + "NO_PROJECT_SCANNER": "스캐너 없음", + "SET_UNHEALTHY_SCANNER": "스캐너 선택:{{name}} 이 위험한 상태입니다", + "SCANNED_BY": "스캔한 사람:", + "IMAGE_SCANNERS": "이미지 스캐너", + "VIEW_DOC": "문서 보기", + "ALL_SCANNERS": "모든 스캐너", + "HELP_INFO_1": "기본 스캐너가 설치되었습니다. 다른 스캐너를 설치하려면 다음을 참조하십시오", + "HELP_INFO_2": "문서.", + "NO_DEFAULT_SCANNER": "기본 스캐너 없음" + }, + "DISTRIBUTION": { + "FILTER_INSTANCE_PLACEHOLDER": "인스턴스 필터", + "FILTER_HISTORIES_PLACEHOLDER": "필터 기록", + "ADD_ACTION": "새 인스턴스", + "PREHEAT_ACTION": "예열", + "EDIT_ACTION": "편집", + "ENABLE_ACTION": "활성화", + "DISABLE_ACTION": "비활성화", + "DELETE_ACTION": "삭제", + "NOT_FOUND": "인스턴스를 찾을 수 없습니다!", + "NAME": "이름", + "ENDPOINT": "엔드포인트", + "STATUS": "상태", + "ENABLED": "활성화", + "SETUP_TIMESTAMP": "타임스탬프 설정", + "PROVIDER": "제공자", + "DELETION_TITLE": "인스턴스 삭제 확인", + "DELETION_SUMMARY": "인스턴스(들) {{param}}를 삭제하시겠습니까?", + "ENABLE_TITLE": "인스턴스(들) 활성화", + "ENABLE_SUMMARY": "인스턴스(들) {{param}}를 활성화하시겠습니까?", + "DISABLE_TITLE": "인스턴스(들) 비활성화", + "DISABLE_SUMMARY": "인스턴스(들){{param}}를 비활성화 하시겠습니까?", + "IMAGE": "이미지", + "START_TIME": "시작 시간", + "FINISH_TIME": "종료 시간", + "INSTANCE": "인스턴스", + "HISTORIES": "기록", + "CREATE_SUCCESS": "인스턴스를 성공적으로 생성했습니다", + "CREATE_FAILED": "인스턴스 생성을 실패했습니다", + "DELETED_SUCCESS": "인스턴스(들)를 성공적으로 삭제했습니다", + "DELETED_FAILED": "인스턴스(들)삭제에 실패했습니다", + "ENABLE_SUCCESS": "인스턴스(들)를 성공적으로 활성화했습니다", + "ENABLE_FAILED": "인스턴스(들) 활성화 실패", + "DISABLE_SUCCESS": "인스턴스(들)를 성공적으로 비활성화 했습니다", + "DISABLE_FAILED": "인스턴스(들) 비활성화를 실패했습니다", + "UPDATE_SUCCESS": "인스턴스(들)를 성공적으로 업데이트했습니다", + "UPDATE_FAILED": "인스턴스(들) 업데이트에 실패했습니다", + "REQUEST_PREHEAT_SUCCESS": "예열을 성공적으로 요청했습니다", + "REQUEST_PREHEAT_FAILED": "예열 요청을 실패했습니다", + "DESCRIPTION": "설명", + "AUTH_MODE": "인증 모드", + "USERNAME": "사용자 이름", + "PASSWORD": "비밀번호", + "TOKEN": "토큰", + "SETUP_NEW_INSTANCE": "새 인스턴스 설치", + "EDIT_INSTANCE": "인스턴스 편집", + "SETUP": { + "NAME_PLACEHOLDER": "인스턴스 이름 입력", + "DESCRIPTION_PLACEHOLDER": "인스턴스 설명 입력", + "ENDPOINT_PLACEHOLDER": "인스턴스 엔드포인트 입력", + "USERNAME_PLACEHOLDER": "사용자 이름 입력", + "PASSWORD_PLACEHOLDER": "비밀번호 입력", + "TOKEN_PLACEHOLDER": "토큰 입력" + }, + "MAINTAINER": "메인테이너(들)", + "SOURCE": "Source", + "VERSION": "버전", + "SET_AS_DEFAULT": "기본으로 설정", + "DELETE_INSTANCE": "인스턴스 삭제", + "ENABLE_INSTANCE": "인스턴스 활성화", + "DISABLE_INSTANCE": "인스턴스 비활성화", + "SET_DEFAULT_SUCCESS": "기본값으로 설정됐습니다", + "SET_DEFAULT_FAILED": "기본값 설정에 실패했습니다", + "UPDATE_INSTANCE": "인스턴스 업데이트", + "CREATE_INSTANCE": "인스턴스 생성" + }, + "P2P_PROVIDER": { + "P2P_PROVIDER": "P2P 예열", + "POLICIES": "정책", + "NEW_POLICY": "새 정책", + "NAME": "이름", + "ENABLED": "활성화", + "PROVIDER": "공급자", + "FILTERS": "필터", + "TRIGGER": "트리거", + "CREATED": "생성 시간", + "DESCRIPTION": "설명", + "NO_POLICY": "정책 없음", + "ENABLED_POLICY_SUMMARY": "정책{{name}}을 활성화하시겠습니까?", + "DISABLED_POLICY_SUMMARY": "정책{{name}}을 비활성화하시겠습니까?", + "ENABLED_POLICY_TITLE": "정책 활성화", + "DISABLED_POLICY_TITLE": "정책 비활성화", + "DELETE_POLICY_SUMMARY": "{{names}}정책을 삭제하시겠습니까?", + "EDIT_POLICY": "P2P 공급자 정책 편집", + "ADD_POLICY": "P2P 공급자 정책 성생", + "NAME_REQUIRED": "이름은 필수 항목입니다", + "PROVIDER_REQUIRED": "공급자는 필수 항목입니다", + "ADDED_SUCCESS": "정책을 성공적으로 추가했습니다", + "UPDATED_SUCCESS": "정책을 성공적으로 업데이트했습니다", + "EXECUTE": "실행", + "EXECUTE_SUCCESSFULLY": "성공적으로 실행됐습니다", + "EXECUTE_TITLE": "정책 실행 확인", + "EXECUTE_SUMMARY": "정책{{param}}을 실행하시겠습니까?", + "STOP_TITLE": "정책 중지 확인", + "STOP_SUMMARY": "정책{{param}}을 중지하시겠습니까?", + "STOP_SUCCESSFULLY": "정책을 성공적으로 중지했습니다", + "STATUS_MSG": "상태 메시지", + "JOB_PLACEHOLDER": "실행된 내용을 찾을 수 없습니다", + "PROVIDER_TYPE": "Vendor", + "ID": "실행 ID", + "NO_PROVIDER": "제공자를 먼저 추가해주세요", + "ARTIFACT": "아티팩트", + "DIGEST": "Digest", + "TYPE": "유형", + "TASKS": "작업", + "TASKS_PLACEHOLDER": "작업을 찾을 수 없습니다", + "SEVERITY_WARNING": "여기의 취약점 설정은 여기의 설정을 재정의하는 관련 프로젝트 구성과 충돌합니다.", + "REPOS": "저장소", + "TAGS": "태그", + "CRITERIA": "기준", + "ONLY_SIGNED": "서명된 이미지만", + "PREHEAT_ON_PUSH": "푸시 시 예열", + "START_TEXT": "No vulnerability severity of", + "EDN_TEXT": "and above", + "LABELS": "라벨", + "SCHEDULE": "예약", + "TEST_FAILED": "테스트 실패", + "MANUAL": "수동", + "SCHEDULED": "예약", + "EVENT_BASED": "이벤트 기반", + "EVENT_BASED_EXPLAIN_LINE1": "다음 이벤트가 발생할 때마다 정책이 평가됩니다:", + "EVENT_BASED_EXPLAIN_LINE2": "아티팩트 푸시됐습니다", + "EVENT_BASED_EXPLAIN_LINE3": "아티팩트 라벨이 지정됐습니다", + "EVENT_BASED_EXPLAIN_LINE4": "아티팩트 스캔됐습니다", + "REPO_REQUIRED": "저장소는 필수 항목입니다", + "TAG_REQUIRED": "태그는 필수 항목입니다", + "DELETE_SUCCESSFULLY": "정책이 성공적으로 삭제됐습니다", + "UPDATED_SUCCESSFULLY": "정책이 성공적으로 업데이트됐습니다", + "EXECUTIONS": "실행", + "TAG_SEPARATOR": "쉼표로 구분된 태그를 여러 개 입력하세요(태그* 또는 **).", + "CONTENT_WARNING": "여기의 콘텐츠 신뢰 설정은 여기의 설정을 재정의하는 관련 프로젝트 구성과 충돌합니다.", + "PREHEAT_EXPLAIN": "예열은 이미지를 p2p 네트워크로 마이그레이션합니다.", + "CRITERIA_EXPLAIN": "구성 탭 아래 '배포 보안' 섹션에 지정된 대로입니다", + "SKIP_CERT_VERIFY": "원격 공급자가 자체 서명되거나 신뢰할 수 없는 인증서를 사용할 때 인증서 확인을 건너뛰려면 이 상자를 선택합니다.", + "NAME_TOOLTIP": "정책 이름은 하나 이상의 대문자, 소문자 또는 숫자 그룹으로 구성됩니다. 그룹은 점, 밑줄 또는 하이픈으로 구분됩니다.", + "NEED_HELP": "먼저 시스템 관리자에게 공급자를 추가해 달라고 요청하세요." + }, + "PAGINATION": { + "PAGE_SIZE": "페이지 크기" + }, + "SYSTEM_ROBOT": { + "READ": "읽기", + "CREATE": "생성", + "ARTIFACT": "아티팩트", + "ADD_ROBOT": "로봇 추가", + "UPDATE_ROBOT": "로봇 갱신", + "UPDATE_ROBOT_SUCCESSFULLY": "로봇이 성공적으로 갱신됐습니다", + "PLACEHOLDER": "새 시크릿 입력", + "SECRET": "시크릿은 8~128자 길이로 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함되어야 합니다.", + "REFRESH_SECRET": "시크릿 새로고침", + "REFRESH_SECRET_SUCCESS": "시크릿이 성공적으로 새로고쳐졌습니다", + "DELETE_ROBOT": "로봇 삭제", + "DELETE_ROBOT_SUCCESS": "로봇(들)이 성공적으로 삭제됐습니다", + "ENABLE_TITLE": "로봇 활성화", + "ENABLE_SUMMARY": "로봇{{param}}을 활성화하시겠습니까?", + "DISABLE_TITLE": "로봇 비활성화", + "DISABLE_SUMMARY": "로봇{{param}}을 비활성화 하시겠습니까?", + "ENABLE_ROBOT_SUCCESSFULLY": "로봇을 성공적으로 활성화했습니다", + "DISABLE_ROBOT_SUCCESSFULLY": "로봇을 성공적으로 비활성화했습니다", + "ROBOT_ACCOUNT": "로봇 계정", + "PROJECTS": "프로젝트", + "ALL_PROJECTS": "모든 프로젝트", + "PERMISSIONS": "권한", + "REFRESH_SECRET_SUMMARY": "로봇 계정 비밀을 자동으로 새로 고치거나 선택적으로 세부 정보를 열어 새 비밀을 수동으로 지정하세요.", + "TOKEN": "시크릿", + "NEW_TOKEN": "새 시크릿", + "REFRESH": "새로고침", + "PROJECTS_MODAL_TITLE": "로봇 계정을 위한 프로젝트", + "PROJECTS_MODAL_SUMMARY": "이 로봇 계정이 다루는 프로젝트가 있습니다.", + "CREATE_ROBOT": "시스템 로봇 계정 생성", + "CREATE_ROBOT_SUMMARY": "시스템 및 특정 프로젝트에 대한 권한을 포함하는 시스템 로봇 계정을 생성합니다", + "EDIT_ROBOT": "시스템 로봇 계정 편집", + "EDIT_ROBOT_SUMMARY": "시스템 및 특정 프로젝트에 대한 권한을 포함하는 시스템 로봇 계정을 편집합니다", + "EXPIRATION_TIME": "만료 시간", + "EXPIRATION_TIME_EXPLAIN": "로봇 계정의 토큰 만료 시간(일 단위, 시작 시점은 생성 시간)입니다. 만료되지 않으려면 \"-1\"을 입력하세요.", + "EXPIRATION_DEFAULT": "일(기본값)", + "EXPIRATION_DAYS": "# 일 지정", + "EXPIRATION_NEVER": "만료 안됨", + "EXPIRATION_REQUIRED": "유효한 만료 시간은 필수 항목입니다", + "COVER_ALL": "모든 프로젝트를 커버", + "COVER_ALL_EXPLAIN": "기존 및 향후 모든 프로젝트에 적용하려면 선택하세요.", + "COVER_ALL_SUMMARY": "현재 및 미래의 모든 프로젝트가 선택되었습니다.", + "RESET_PERMISSION": "모든 프로젝트 권한 재설정", + "PERMISSION_COLUMN": "권한", + "EXPIRES_AT": "만료 시간", + "VIEW_SECRET": "시크릿 새로고침", + "LEGACY": "Legacy", + "CREATE_PROJECT_ROBOT": "로봇 계정 생성", + "CREATE_PROJECT_ROBOT_SUMMARY": "이 프로젝트를 위한 로봇 계정 생성", + "EDIT_PROJECT_ROBOT": "로봇 계정 편집", + "EDIT_PROJECT_ROBOT_SUMMARY": "이 프로젝트를 위한 로봇 계정 편집", + "NOT_FOUND": "로봇을 찾을 수 없습니다!", + "SELECT_ALL": "모두 선택", + "UNSELECT_ALL": "모두 선택하지 않음", + "ROBOT_ACCOUNT_NAV": "로봇 계정", + "COVERED_PROJECTS": "프로젝트", + "CONFIRM_SECRET": "시크릿 확인", + "SECRET_AGAIN": "시크릿을 다시 입력하세요", + "INCONSISTENT": "두 시크릿이 일치하지 않습니다", + "NAME_TOOLTIP": "로봇 이름은 1~255자(영문 소문자, 숫자, ._- 포함)여야 하며 문자나 숫자로 시작해야 합니다.", + "ENABLE_NEW_SECRET": "새을 수동으로 지정하려면 이 옵션을 활성화하세요", + "DELETE": "삭제", + "ARTIFACT_LABEL": "아티팩트 라벨", + "SCAN": "스캔", + "SCANNER_PULL": "스캐너 풀(PUll)", + "SEARCH_BY_NAME": "이름으로 검색(접두사 없이)", + "FINAL_PROJECT_NAME_TIP": "최종 프로젝트 로봇 이름은 접두사, 프로젝트 이름, 더하기 기호 및 현재 입력 값으로 구성됩니다.", + "FINAL_SYSTEM_NAME_TIP": "최종 시스템 로봇 이름은 접두어와 현재 입력 값으로 구성됩니다.", + "PUSH_AND_PULL": "푸시", + "PUSH_PERMISSION_TOOLTIP": "푸시 권한은 단독으로 작동할 수 없으며, 풀 권한과 함께 작동해야 합니다.", + "STOP": "중지", + "LIST": "리스트", + "REPOSITORY": "저장소", + "EXPIRES_IN": "만료 시간", + "EXPIRED": "만료 됨", + "SELECT_ALL_PROJECT": "모든 프로젝트 선택", + "UNSELECT_ALL_PROJECT": "모든 프로젝트 선택 취소" + }, + "ACCESSORY": { + "DELETION_TITLE_ACCESSORY": "액세서리 삭제 확인", + "DELETION_SUMMARY_ACCESSORY": "아티팩트의 액세서리{{param}}를 삭제하시겠습니까?", + "DELETION_SUMMARY_ONE_ACCESSORY": "액세서리{{param}}를 삭제하시겠습니까?", + "DELETE_ACCESSORY": "액세서리 삭제", + "DELETED_SUCCESS": "액세서리를 성공적으로 삭제했습니다", + "DELETED_FAILED": "액세서리 삭제를 실패했습니닼", + "CO_SIGNED": "Cosign에 의해 서명됨", + "NOTARY_SIGNED": "Notary에 의해 서명됨", + "ACCESSORY": "액세서리", + "ACCESSORIES": "액세서리들", + "SUBJECT_ARTIFACT": "서브젝트 아티팩트", + "CO_SIGN": "Cosign", + "NOTARY": "Notation", + "PLACEHOLDER": "액세서리를 찾을 수 없습니다!" + }, + "CLEARANCES": { + "CLEARANCES": "정리", + "AUDIT_LOG": "로그 로테이션", + "LAST_COMPLETED": "마지막 성공", + "NEXT_SCHEDULED_TIME": "다음 예약 시간", + "SCHEDULE_TO_PURGE": "제거 예약", + "KEEP_IN": "기록을 보관하세요", + "KEEP_IN_TOOLTIP": "이 간격으로 기록을 보관하세요", + "KEEP_IN_ERROR": "이 항목의 값은 정수여야 하며 시간 값은 0보다 크고 10000일보다 작아야 합니다.", + "DAYS": "일", + "HOURS": "시간", + "INCLUDED_OPERATIONS": "포함된 작업", + "INCLUDED_OPERATION_TOOLTIP": "선택한 작업에 대한 감사 로그 삭제", + "INCLUDED_OPERATION_ERROR": "작업을 하나 이상 선택하세요.", + "PURGE_NOW": "지금 제거", + "PURGE_NOW_SUCCESS": "제거가 성공적으로 발생됐습니다", + "PURGE_SCHEDULE_RESET": "제거 예약이 초기화됐습니다", + "PURGE_HISTORY": "제거 기록", + "FORWARD_ENDPOINT": "감사 로그를 Syslog 엔트포인트로 전달", + "FORWARD_ENDPOINT_TOOLTIP": "감사 로그를 syslog 엔드포인트로 전달합니다(예: harbor-log:10514)", + "SKIP_DATABASE": "감사 로그 데이터베이스 건너뛰기", + "SKIP_DATABASE_TOOLTIP": "데이터베이스의 감사 로그 로그로 건너뛰기, 감사 로그 전달 엔드포인트가 구성된 경우에만 사용 가능", + "STOP_GC_SUCCESS": "가비지 컬렉션 작업 중지를 성공적으로 발생시켰습니다", + "STOP_PURGE_SUCCESS": "제거 중지를 성공적으로 발생시켰습니다", + "NO_GC_RECORDS": "가비지 컬렉션 기록을 찾을 수 없습니다!", + "NO_PURGE_RECORDS": "제거 기록을 찾을 수 없습니다!" + }, + "CVE_EXPORT": { + "EXPORT_SOME_PROJECTS": "CVE 내보내기", + "ALL_PROJECTS": "모든 프로젝트", + "EXPORT_TITLE": "CVE 내보내기", + "EXPORT_SUBTITLE": "내보내기 조건 설정", + "EXPORT_CVE_FILTER_HELP_TEXT": "여러 개의 쉼표로 구분된 cveId를 입력하세요", + "CVE_IDS": "CVE IDs", + "EXPORT_BUTTON": "내보내기", + "JOB_NAME": "작업 이름", + "JOB_NAME_REQUIRED": "작업 이름은 필수 항목입니다", + "JOB_NAME_EXISTING": "작업 이름이 이미 존재합니다", + "TRIGGER_EXPORT_SUCCESS": "CVE 내보내기가 성공적으로 발생됐습니다!" + }, + "JOB_SERVICE_DASHBOARD": { + "SCHEDULE_PAUSED": "예약(일시 중지)", + "SCHEDULE_BEEN_PAUSED": "{{param}} 가 일시 중지 됐습니다", + "PENDING_JOBS": "대기열에 대기중인 작업", + "OTHERS": "기타", + "STOP_ALL": "모두 중지", + "CONFIRM_STOP_ALL": "모두 중지 확인", + "CONFIRM_STOP_ALL_CONTENT": "대기열의 모든 작업을 중지하시겠습니까?", + "STOP_ALL_SUCCESS": "대기열의 모든 작업을 성공적으로 중지했습니다", + "STOP_BTN": "중지", + "PAUSE_BTN": "일시 중지", + "RESUME_BTN": "재개", + "JOB_TYPE": "작업 유형", + "PENDING_COUNT": "대기 수", + "LATENCY": "지연시간", + "PAUSED": "일시 중지", + "NO_JOB_QUEUE": "대기열을 찾을 수 없습니다", + "CONFIRM_STOPPING_JOBS": "작업 중지 확인", + "CONFIRM_STOPPING_JOBS_CONTENT": "{{param}} 작업을 중지하시겠습니까?", + "CONFIRM_PAUSING_JOBS": "작업 일시 중지 확인", + "CONFIRM_PAUSING_JOBS_CONTENT": "{{param}} 작업을 일시 중지 하시겠습니까?", + "CONFIRM_RESUMING_JOBS": "작업 재개 확인", + "CONFIRM_RESUMING_JOBS_CONTENT": "{{param}} 작업을 재개하시겠습니까?", + "STOP_SUCCESS": "작업을 성공적으로 중지했습니다", + "PAUSE_SUCCESS": "작업을 성공적으로 일시 중지 했습니다", + "RESUME_SUCCESS": "작업을 성공적으로 재개했습니다", + "SCHEDULES": "예약", + "RUNNING_STATUS": "실행 중", + "RESUME_ALL_BTN_TEXT": "모두 재개", + "PAUSE_ALL_BTN_TEXT": "모두 일시 중지", + "CONFIRM_PAUSING_ALL": "모든 작업 일시 중지 확인", + "CONFIRM_PAUSING_ALL_CONTENT": "예정된 모든 작업을 일시 중지 하시겠습니까?", + "CONFIRM_RESUMING_ALL": "모든 작업 재개 확인", + "CONFIRM_RESUMING_ALL_CONTENT": "예정된 모든 작업을 재개하시겠습니까?", + "PAUSE_ALL_SUCCESS": "예정된 모든 작업이 성공적으로 일시 중지 됐습니다", + "RESUME_ALL_SUCCESS": "예정된 모든 작업이 성공적으로 재개됐습니다", + "VENDOR_TYPE": "공급자 분류", + "VENDOR_ID": "공급자 ID", + "NO_SCHEDULE": "에약 내역을 찾을 수 없습니다", + "WORKERS": "작업자", + "FREE_ALL": "모두 해제", + "CONFIRM_FREE_ALL": "모두 해제 확인", + "CONFIRM_FREE_ALL_CONTENT": "모든 작업자를 해제하시겠습니까?", + "CONFIRM_FREE_WORKERS": "작업자 해제 확인", + "CONFIRM_FREE_WORKERS_CONTENT": "작업자{{param}}를 해제하시겠습니까?", + "FREE_WORKER_SUCCESS": "작업자를 성공적으로 해제했습니다", + "FREE_ALL_SUCCESS": "모든 작업자를 성공적으로 해제했습니다", + "WORKER_POOL": "작업자 풀(Pool)", + "WORKER_POOL_ID": "작업자 풀(Pool) ID", + "PID": "Pid", + "START_AT": "시작시간", + "HEARTBEAT_AT": "상태 확인시간", + "CONCURRENCY": "동시성", + "NO_WORKER_POOL": "작업자 풀(pool)을 찾을 수 없습니다", + "FREE": "해제", + "WORKER_ID": "작업자 아이디", + "JOB_ID": "작업 아이디", + "CHECK_IN_AT": "체크인 시간", + "NO_WORKER": "작업자(Worker)를 찾을 수 없습니다", + "JOB_QUEUE": "작업 대기열", + "JOB_SERVICE_DASHBOARD": "작업 서비스 대시보드", + "OPERATION_STOP_ALL_QUEUES": "모든 대기열 중지", + "OPERATION_STOP_SPECIFIED_QUEUES": "특정 대기열 중지", + "OPERATION_PAUSE_SPECIFIED_QUEUES": "특정 대기열 일시중지", + "OPERATION_RESUME_SPECIFIED_QUEUES": "특정 대기열 재시작", + "OPERATION_PAUSE_SCHEDULE": "모든 예약 일시정지", + "OPERATION_RESUME_SCHEDULE": "모든 예약 재개", + "OPERATION_FREE_ALL": "모든 작업자 해제", + "OPERATION_FREE_SPECIFIED_WORKERS": "특정 작업자 해제", + "QUEUE_STOP_BTN_INFO": "중지 — 선택된 대기열의 모든 작업을 중지하고 제거합니다.", + "QUEUE_PAUSE_BTN_INFO": "일시 중지 — 이 유형의 작업 대기열에서 작업 실행을 일시 중지합니다. 대기열이 일시 중지되면 작업이 대기열에 추가될 수 있습니다.", + "QUEUE_RESUME_BTN_INFO": "재개 — 이 유형의 작업 대기열에서 작업 실행을 재개합니다.", + "SCHEDULE_PAUSE_BTN_INFO": "일시 중지 — 실행을 위해 모든 일정을 일시 중지합니다.", + "SCHEDULE_RESUME_BTN_INFO": "재개 — 실행할 모든 일정을 재개합니다.", + "WORKER_FREE_BTN_INFO": "현재 실행 중인 작업을 중지하여 작업자를 해제합니다.", + "CRON": "반복 작업", + "WAITING_TOO_LONG_1": "특정 작업의 실행이 24시간 이상 보류되었습니다. 작업 서비스를 확인해주세요", + "WAITING_TOO_LONG_2": "데쉬보드.", + "WAITING_TOO_LONG_3": "자세한 내용은 다음을 확인해주세요", + "WAITING_TOO_LONG_4": "Wiki." + }, + "CLARITY": { + "OPEN": "열기", + "CLOSE": "보기", + "SHOW": "닫기", + "HIDE": "숨기기", + "EXPAND": "확장하기", + "COLLAPSE": "접기", + "MORE": "더 보기", + "SELECT": "선택", + "SELECT_ALL": "모두 선택", + "PREVIOUS": "이전", + "NEXT": "다음", + "CURRENT": "현재로 이동", + "INFO": "정보", + "SUCCESS": "성공", + "WARNING": "경고", + "DANGER": "에러", + "ROW_ACTION": "사용 가능한 동작", + "PICK_COLUMNS": "열(Columns) 관리", + "SHOW_COLUMNS": "열(Columns) 보기", + "SORT_COLUMNS": "열(Column) 정렬", + "FIRST_PAGE": "첫 페이지", + "LAST_PAGE": "마지막 페이지", + "NEXT_PAGE": "다음 페이지", + "PREVIOUS_PAGE": "이전 페이지", + "CURRENT_PAGE": "현재 페이지", + "TOTAL_PAGE": "전체 페이지", + "FILTER_ITEMS": "항목 필터링", + "MIN_VALUE": "최소 값", + "MAX_VALUE": "최대 값", + "MODAL_CONTENT_START": "모달 콘텐츠의 시작", + "MODAL_CONTENT_END": "모달 콘텐츠 끝", + "SHOW_COLUMNS_MENU_DESCRIPTION": "열(Columns) 보기 혹은 가리기", + "ALL_COLUMNS_SELECTED": "모든 열(Columns) 선택", + "SIGNPOST_TOGGLE": "이정표 토글", + "SIGNPOST_CLOSE": "이정표 닫기", + "LOADING": "로딩", + "DATE_PICKER_DIALOG_LABEL": "날짜 선택", + "DATE_PICKER_TOGGLE_CHOOSE_DATE_LABEL": "날짜 선택", + "DATE_PICKER_TOGGLE_CHANGE_DATE_LABEL": "날짜 변경, {SELECTED_DATE}", + "DATE_PICKER_PREVIOUS_MONTH": "이전 달", + "DATE_PICKER_CURRENT_MONTH": "이번 달", + "DATE_PICKER_NEXT_MONTH": "다음 달", + "DATE_PICKER_PREVIOUS_DECADE": "작년", + "DATE_PICKER_NEXT_DECADE": "내년", + "DATE_PICKER_CURRENT_DECADE": "올해", + "DATE_PICKER_SELECT_MONTH_TEXT": "월을 선택하세요. 현재 월은 {CALENDAR_MONTH}입니다", + "DATE_PICKER_SELECT_YEAR_TEXT": "년을 선택해서요. 올해는 {CALENDAR_YEAR}입니다", + "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - 선택 됨" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "베너 메시지", + "MESSAGE_TYPE": "메시지 유형", + "CLOSABLE": "닫을 수 있음", + "FROM": "From", + "TO": "To", + "SUCCESS": "성공", + "INFO": "정보", + "WARNING": "경고", + "DANGER": "위험", + "ENTER_MESSAGE": "여기에 미시지를 임력하세요" + }, + "SECURITY_HUB": { + "SECURITY_HUB": "보안 허브", + "ARTIFACTS": "아티팩트(들)", + "SCANNED": "스캔 됨", + "NOT_SCANNED": "스캔 안됨", + "TOTAL_VUL": "전체 취약점", + "TOTAL_AND_FIXABLE": "{{totalNum}} total with {{fixableNum}} fixable", + "TOP_5_ARTIFACT": "가장 위험한 아티팩트 상위 5개", + "TOP_5_CVE": "가장 위험한 CVE 상위 5개", + "CVE_ID": "CVE ID", + "VUL": "취약점", + "CVE": "CVEs", + "FILTER_BY": "Filter by", + "OPTION_ALL": "전체", + "OPTION_PROJECT_ID_NAME": "프로젝트 이름", + "SEARCH": "검색", + "REPO_NAME": "저장소 이름", + "TOOLTIP": "CVSS3을 제외한 모든 필터는 정확한 일치만 지원합니다.", + "NO_VUL": "취약점을 찾을 수 없습니다.", + "INVALID_VALUE": "CVSS3 점수의 범위는 0에서 10 사이여야 합니다.", + "PAGE_TITLE_TOOLTIP": "포괄적인 아티팩트 수는 아티팩트 액세서리를 포함한 개별 아티팩트의 누적 합계와 이미지 인덱스 및 CNAB 아티팩트와 관련된 하위 아티팩트로 구성됩니다." + } +} diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 686ac647ad..b9b7b5ad04 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -284,7 +284,10 @@ "PREVENT_VULNERABLE_2": "e acima de serem utilizadas.", "SCAN": "Análise de vulnerabilidades", "AUTOSCAN_TOGGLE": "Verificar imagens automaticamente", - "AUTOSCAN_POLICY": "Imagens serão analisadas automaticamente quando enviadas ao repositório do projeto." + "AUTOSCAN_POLICY": "Imagens serão analisadas automaticamente quando enviadas ao repositório do projeto.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Adicionar um usuário", @@ -401,6 +404,7 @@ "REPOSITORY": "Repository", "ARTIFACT": "Artifact", "SCAN": "Scan", + "SBOM": "SBOM", "TAG": "Tag", "ACCESSORY": "Accessory", "ARTIFACT_ADDITION": "Artifact Addition", @@ -698,7 +702,7 @@ "FLATTEN_LEVEL_TIP_3": "'Nivelar 3 posições': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Banda", "BANDWIDTH_ERROR_TIP": "Informe um número inteiro maior que 0 (zero) ou -1", - "BANDWIDTH_TOOLTIP": "Informe o limite de banda para cada execução. Tome cuidado e observe a relação com o número de execuções simultâneas. Para remover qualquer limite, informe -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Ilimitado", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", @@ -773,6 +777,7 @@ "ARTIFACTS": "Artefatos", "SIZE": "Tamanho", "VULNERABILITY": "Vulnerabilidade", + "SBOM": "SBOM", "SIGNED": "Assinada", "AUTHOR": "Autor", "CREATED": "Data de criação", @@ -799,6 +804,7 @@ "FILTER_BY_LABEL": "Filtrar imagens por marcadores", "FILTER_ARTIFACT_BY_LABEL": "Filtrar por marcador", "ADD_LABELS": "Adicionar Marcadores", + "STOP": "Stop", "RETAG": "Copiar", "ACTION": "AÇÃO", "DEPLOY": "IMPLANTAR", @@ -1021,6 +1027,42 @@ "IN_PROGRESS": "Buscando...", "BACK": "Voltar" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Não analisado", @@ -1065,12 +1107,12 @@ "PLACEHOLDER": "Filtrar", "PACKAGE": "pacote", "PACKAGES": "pacotes", - "SCAN_NOW": "Examinar", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "EXAMINAR COM {{scanner}}", "REPORTED_BY": "Encontrado com {{scanner}}", "NO_SCANNER": "NENHUM", "TRIGGER_STOP_SUCCESS": "Exame foi interrompido", - "STOP_NOW": "Interromper" + "STOP_NOW": "Stop Scan" }, "PUSH_IMAGE": { "TITLE": "Comando Push", @@ -1101,6 +1143,7 @@ "ALL": "Todos", "PLACEHOLDER": "Nenhum artefato encontrado!", "SCAN_UNSUPPORTED": "Não pode ser examinada", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Resumo", "DEPENDENCIES": "Dependências", "VALUES": "Valores", @@ -1427,6 +1470,10 @@ "NAME_REQUIRED": "Nome é obrigatório", "NAME_REX": "O nome precisa ter no mínimo 2 caracteres. Pode conter apenas letras minúsculas, numeros, ou símbolos ._- e deve começar por uma letra ou número.", "DESCRIPTION": "Descrição", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endereço", "ENDPOINT_EXISTS": "Endereço já usado por outro examinador", "ENDPOINT_REQUIRED": "Endereço é obrigatório", @@ -1455,9 +1502,10 @@ "NAME_COLON": "Nome:", "VENDOR_COLON": "Fornecedor:", "VERSION_COLON": "Versão:", - "CAPABILITIES": "Habilidades", + "CAPABILITIES": "Habilidades:", "CONSUMES_MIME_TYPES_COLON": "Consome estes Mime Types:", "PRODUCTS_MIME_TYPES_COLON": "Produz estes Mime Types:", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "Propriedades", "NEW_SCANNER": "NOVO EXAMINADOR", "SET_AS_DEFAULT": "DEFINIR COMO PADRÃO", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index dafe49651d..a23f555b13 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -286,7 +286,10 @@ "PREVENT_VULNERABLE_2": "ve yukarıda yüklenilmekte.", "SCAN": "Güvenlik açığı taraması", "AUTOSCAN_TOGGLE": "İmajları yüklerken anında tarayın", - "AUTOSCAN_POLICY": "İmajlar proje kayıt defterine yüklenildiğinde otomatik olarak tarayın." + "AUTOSCAN_POLICY": "İmajlar proje kayıt defterine yüklenildiğinde otomatik olarak tarayın.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Kullanıcı Üyesi Ekle", @@ -403,6 +406,7 @@ "REPOSITORY": "Repository", "ARTIFACT": "Artifact", "SCAN": "Scan", + "SBOM": "SBOM", "TAG": "Tag", "ACCESSORY": "Accessory", "ARTIFACT_ADDITION": "Artifact Addition", @@ -699,7 +703,7 @@ "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandwidth", "BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0", - "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unlimited", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", @@ -774,6 +778,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "Boyut", "VULNERABILITY": "Güvenlik Açığı", + "SBOM": "SBOM", "BUILD_HISTORY": "Geçmişi Oluştur", "SIGNED": "İmzalanmış", "AUTHOR": "Yazar", @@ -800,6 +805,7 @@ "FILTER_BY_LABEL": "İmajları etikete göre filtrele", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", "ADD_LABELS": "Etiketler Ekle", + "STOP": "Stop", "RETAG": "Copy", "ACTION": "AKSİYON", "DEPLOY": "YÜKLE", @@ -1024,6 +1030,42 @@ "IN_PROGRESS": "Ara...", "BACK": "Geri" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Taranmadı", @@ -1068,7 +1110,7 @@ "PLACEHOLDER": "Güvenlik Açıklarını Filtrele", "PACKAGE": "paket", "PACKAGES": "paketler", - "SCAN_NOW": "Tara", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "SCAN BY {{scanner}}", "REPORTED_BY": "Reported by {{scanner}}", "NO_SCANNER": "NO SCANNER", @@ -1104,6 +1146,7 @@ "ALL": "All", "PLACEHOLDER": "We couldn't find any artifacts!", "SCAN_UNSUPPORTED": "Unsupported", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Özet", "DEPENDENCIES": "Bağımlılıklar", "VALUES": "Değerler", @@ -1430,6 +1473,10 @@ "NAME_REQUIRED": "Name is required", "NAME_REX": "Name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "EndpointUrl already exists", "ENDPOINT_REQUIRED": "EndpointUrl is required", @@ -1458,9 +1505,10 @@ "NAME_COLON": "Name:", "VENDOR_COLON": "Vendor:", "VERSION_COLON": "Version:", - "CAPABILITIES": "Capabilities", + "CAPABILITIES": "Capabilities:", "CONSUMES_MIME_TYPES_COLON": "Consumes Mime Types:", "PRODUCTS_MIME_TYPES_COLON": "Produces Mime Types:", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "Properties", "NEW_SCANNER": "NEW SCANNER", "SET_AS_DEFAULT": "SET AS DEFAULT", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 206e4e7268..3b4b1e0674 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -285,7 +285,10 @@ "PREVENT_VULNERABLE_2": "以上的镜像运行。", "SCAN": "漏洞扫描", "AUTOSCAN_TOGGLE": "自动扫描镜像", - "AUTOSCAN_POLICY": "当镜像上传后,自动进行扫描" + "AUTOSCAN_POLICY": "当镜像上传后,自动进行扫描", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "添加用户成员", @@ -402,6 +405,7 @@ "REPOSITORY": "仓库", "ARTIFACT": "Artifact", "SCAN": "扫描", + "SBOM": "SBOM", "TAG": "Tag", "ACCESSORY": "附件", "ARTIFACT_ADDITION": "Artifact 额外信息", @@ -698,7 +702,7 @@ "FLATTEN_LEVEL_TIP_3": "'替换3级': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "带宽", "BANDWIDTH_ERROR_TIP": "请输入-1或者大于0的整数", - "BANDWIDTH_TOOLTIP": "设置执行该条同步规则时的最大网络带宽。实际总带宽需要考虑并发执行的情况。如无需限制,请输入-1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "无限制", "UNREACHABLE_SOURCE_REGISTRY": "连接源仓库失败,在编辑此规则前请先确保源仓库可用: {{error}}", "CRON_ERROR_TIP": "Cron 字符串的第一项必须为0且第二项不能为\"*\"", @@ -773,6 +777,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "大小", "VULNERABILITY": "漏洞", + "SBOM": "SBOM", "BUILD_HISTORY": "构建历史", "SIGNED": "已签名", "AUTHOR": "作者", @@ -797,6 +802,7 @@ "LABELS": "标签", "ADD_LABEL_TO_IMAGE": "添加标签到此镜像", "ADD_LABELS": "添加标签", + "STOP": "Stop", "RETAG": "拷贝", "FILTER_BY_LABEL": "过滤标签", "FILTER_ARTIFACT_BY_LABEL": "通过标签过滤Artifact", @@ -1022,6 +1028,42 @@ "IN_PROGRESS": "搜索中...", "BACK": "返回" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "未扫描", @@ -1066,7 +1108,7 @@ "PLACEHOLDER": "过滤漏洞", "PACKAGE": "组件", "PACKAGES": "组件", - "SCAN_NOW": "扫描", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "使用 {{scanner}} 进行扫描", "REPORTED_BY": "结果由 {{scanner}} 提供", "NO_SCANNER": "无扫描器", @@ -1102,6 +1144,7 @@ "ALL": "所有", "PLACEHOLDER": "未发现任何 artifacts!", "SCAN_UNSUPPORTED": "不支持扫描", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "概要", "DEPENDENCIES": "依赖", "VALUES": "取值", @@ -1426,6 +1469,10 @@ "NAME_REQUIRED": "名称为必填项", "NAME_REX": "名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。", "DESCRIPTION": "描述", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "地址", "ENDPOINT_EXISTS": "地址已存在", "ENDPOINT_REQUIRED": "地址为必填项", @@ -1454,9 +1501,10 @@ "NAME_COLON": "Name:", "VENDOR_COLON": "Vendor:", "VERSION_COLON": "Version:", - "CAPABILITIES": "Capabilities", + "CAPABILITIES": "Capabilities:", "CONSUMES_MIME_TYPES_COLON": "Consumes Mime Types:", "PRODUCTS_MIME_TYPES_COLON": "Produces Mime Types:", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "Properties", "NEW_SCANNER": "新建扫描器", "SET_AS_DEFAULT": "设为默认", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 281c2c94ba..76a3920669 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -285,7 +285,10 @@ "PREVENT_VULNERABLE_2": "或更高危險級別的映像檔部署。", "SCAN": "弱點掃描", "AUTOSCAN_TOGGLE": "推送時自動掃描映像檔", - "AUTOSCAN_POLICY": "當映像檔推送到專案儲存庫時自動掃描。" + "AUTOSCAN_POLICY": "當映像檔推送到專案儲存庫時自動掃描。", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "新增使用者成員", @@ -402,6 +405,7 @@ "REPOSITORY": "Repository", "ARTIFACT": "Artifact", "SCAN": "Scan", + "SBOM": "SBOM", "TAG": "Tag", "ACCESSORY": "Accessory", "ARTIFACT_ADDITION": "Artifact Addition", @@ -698,7 +702,7 @@ "FLATTEN_LEVEL_TIP_3": "'平整化 3 等級': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "頻寬", "BANDWIDTH_ERROR_TIP": "請輸入 -1 或大於 0 的整數", - "BANDWIDTH_TOOLTIP": "設定每個執行的最大網路頻寬。請注意並發執行的數量。若要無限制頻寬,請輸入 -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "無限制", "UNREACHABLE_SOURCE_REGISTRY": "無法連線到來源 Registry,請在編輯此規則之前確保來源 Registry 可用: {{error}}", "CRON_ERROR_TIP": "cron 字串的第一個欄位必須是 0,第二個欄位不能是 \"*\"", @@ -773,6 +777,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "大小", "VULNERABILITY": "弱點", + "SBOM": "SBOM", "BUILD_HISTORY": "建置歷史", "SIGNED": "已簽署", "AUTHOR": "作者", @@ -797,6 +802,7 @@ "LABELS": "標籤", "ADD_LABEL_TO_IMAGE": "新增標籤到此映像檔", "ADD_LABELS": "新增標籤", + "STOP": "Stop", "RETAG": "複製", "FILTER_BY_LABEL": "篩選標籤", "FILTER_ARTIFACT_BY_LABEL": "透過標籤篩選 Artifact", @@ -1021,6 +1027,42 @@ "IN_PROGRESS": "搜尋中...", "BACK": "返回" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "COMPLETED": "Completed", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Generate SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop Generate SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" +}, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "未掃描", @@ -1101,6 +1143,7 @@ "ALL": "全部", "PLACEHOLDER": "未發現任何 artifacts!", "SCAN_UNSUPPORTED": "不支援掃描", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "摘要", "DEPENDENCIES": "相依性", "VALUES": "值", @@ -1425,6 +1468,10 @@ "NAME_REQUIRED": "名稱必填", "NAME_REX": "名稱必須由小寫字母、數字和 ._- 組成,至少2個字元,並且必須以字母或數字開頭。", "DESCRIPTION": "描述", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "端點", "ENDPOINT_EXISTS": "端點 URL 已存在", "ENDPOINT_REQUIRED": "端點 URL 必填", @@ -1453,9 +1500,10 @@ "NAME_COLON": "名稱:", "VENDOR_COLON": "供應商:", "VERSION_COLON": "版本:", - "CAPABILITIES": "功能", + "CAPABILITIES": "功能:", "CONSUMES_MIME_TYPES_COLON": "可處理的 MIME 類型:", "PRODUCTS_MIME_TYPES_COLON": "產出的 MIME 類型:", + "CAPABILITIES_TYPE": "Type:", "PROPERTIES": "屬性", "NEW_SCANNER": "新增掃描器", "SET_AS_DEFAULT": "設為預設", diff --git a/src/server/middleware/cosign/cosign.go b/src/server/middleware/cosign/cosign.go index 13021cb101..53fcdc7edd 100644 --- a/src/server/middleware/cosign/cosign.go +++ b/src/server/middleware/cosign/cosign.go @@ -65,6 +65,38 @@ var ( } ] } +*/ +// cosign adopt oci-spec 1.1 will have request and manifest like below +// It will skip this middleware since not using cosignRe for subject artifact reference +// use Subject Middleware indtead +/* +PUT /v2/library/goharbor/harbor-db/manifests/sha256:aabea2bdd5a6fb79c13837b88c7b158f4aa57a621194ee21959d0b520eda412f +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.dev.cosign.artifact.sig.v1+json", + "size": 233, + "digest": "sha256:c025e9532dbc880534be96dbbb86a6bf63a272faced7f07bb8b4ceb45ca938d1" + }, + "layers": [ + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 257, + "digest": "sha256:38d07d81bf1d026da6420295113115d999ad6da90073b5e67147f978626423e6", + "annotations": { + "dev.cosignproject.cosign/signature": "MEUCIDOQc6I4MSd4/s8Bc8S7LXHCOnm4MGimpQdeCInLzM0VAiEAhWWYxmwEmYrFJ8xYNE3ow7PS4zeGe1R4RUbXRIawKJ4=", + "dev.sigstore.cosign/bundle": "{\"SignedEntryTimestamp\":\"MEUCIC5DSFQx3nZhPFquF4NAdfetjqLR6qAa9i04cEtAg7VjAiEAzG2DUxqH+MdFSPih/EL/Vvsn3L1xCJUlOmRZeUYZaG0=\",\"Payload\":{\"body\":\"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzOGQwN2Q4MWJmMWQwMjZkYTY0MjAyOTUxMTMxMTVkOTk5YWQ2ZGE5MDA3M2I1ZTY3MTQ3Zjk3ODYyNjQyM2U2In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRE9RYzZJNE1TZDQvczhCYzhTN0xYSENPbm00TUdpbXBRZGVDSW5Mek0wVkFpRUFoV1dZeG13RW1ZckZKOHhZTkUzb3c3UFM0emVHZTFSNFJVYlhSSWF3S0o0PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGWVVoSk1DOTZiWEpIYW1VNE9FeFVTM0ZDU2tvNWJXZDNhWEprWkFwaVJrZGpNQzlRYWtWUUwxbFJNelJwZFZweWJGVnRhMGx3ZDBocFdVTmxSV3M0YWpoWE5rSnBaV3BxTHk5WmVVRnZZaXN5VTFCTGRqUkJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=\",\"integratedTime\":1712651102,\"logIndex\":84313668,\"logID\":\"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\"}}" + } + } + ], + "subject": { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 2621, + "digest": "sha256:e50f88df1b11f94627e35bed9f34214392363508a2b07146d0a94516da97e4c0" + } +} + */ func SignatureMiddleware() func(http.Handler) http.Handler { return middleware.AfterResponse(func(w http.ResponseWriter, r *http.Request, statusCode int) error { diff --git a/src/server/middleware/repoproxy/proxy.go b/src/server/middleware/repoproxy/proxy.go index 5fb44a6817..ddb2017843 100644 --- a/src/server/middleware/repoproxy/proxy.go +++ b/src/server/middleware/repoproxy/proxy.go @@ -28,6 +28,7 @@ import ( "github.com/goharbor/harbor/src/controller/proxy" "github.com/goharbor/harbor/src/controller/registry" "github.com/goharbor/harbor/src/lib" + "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" httpLib "github.com/goharbor/harbor/src/lib/http" "github.com/goharbor/harbor/src/lib/log" @@ -259,16 +260,21 @@ func setHeaders(w http.ResponseWriter, size int64, mediaType string, dig string) } // isProxySession check if current security context is proxy session -func isProxySession(ctx context.Context) bool { +func isProxySession(ctx context.Context, projectName string) bool { sc, ok := security.FromContext(ctx) if !ok { log.Error("Failed to get security context") return false } - if sc.GetUsername() == proxycachesecret.ProxyCacheService { + username := sc.GetUsername() + if username == proxycachesecret.ProxyCacheService { return true } - return false + // it should include the auto generate SBOM session, so that it could generate SBOM accessory in proxy cache project + robotPrefix := config.RobotPrefix(ctx) + scannerPrefix := config.ScannerRobotPrefix(ctx) + prefix := fmt.Sprintf("%s%s+%s", robotPrefix, projectName, scannerPrefix) + return strings.HasPrefix(username, prefix) } // DisableBlobAndManifestUploadMiddleware disable push artifact to a proxy project with a non-proxy session @@ -281,7 +287,7 @@ func DisableBlobAndManifestUploadMiddleware() func(http.Handler) http.Handler { httpLib.SendError(w, err) return } - if p.IsProxy() && !isProxySession(ctx) { + if p.IsProxy() && !isProxySession(ctx, art.ProjectName) { httpLib.SendError(w, errors.DeniedError( errors.Errorf("can not push artifact to a proxy project: %v", p.Name))) diff --git a/src/server/middleware/repoproxy/proxy_test.go b/src/server/middleware/repoproxy/proxy_test.go index 4b7ee3d757..c47f20db04 100644 --- a/src/server/middleware/repoproxy/proxy_test.go +++ b/src/server/middleware/repoproxy/proxy_test.go @@ -18,7 +18,9 @@ import ( "context" "testing" + "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/security" + "github.com/goharbor/harbor/src/common/security/local" "github.com/goharbor/harbor/src/common/security/proxycachesecret" securitySecret "github.com/goharbor/harbor/src/common/security/secret" ) @@ -29,6 +31,19 @@ func TestIsProxySession(t *testing.T) { sc2 := proxycachesecret.NewSecurityContext("library/hello-world") proxyCtx := security.NewContext(context.Background(), sc2) + + user := &models.User{ + Username: "robot$library+scanner-8ec3b47a-fd29-11ee-9681-0242c0a87009", + } + userSc := local.NewSecurityContext(user) + scannerCtx := security.NewContext(context.Background(), userSc) + + otherRobot := &models.User{ + Username: "robot$library+test-8ec3b47a-fd29-11ee-9681-0242c0a87009", + } + userSc2 := local.NewSecurityContext(otherRobot) + nonScannerCtx := security.NewContext(context.Background(), userSc2) + cases := []struct { name string in context.Context @@ -44,15 +59,24 @@ func TestIsProxySession(t *testing.T) { in: proxyCtx, want: true, }, + { + name: `robot account`, + in: scannerCtx, + want: true, + }, + { + name: `non scanner robot`, + in: nonScannerCtx, + want: false, + }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - got := isProxySession(tt.in) + got := isProxySession(tt.in, "library") if got != tt.want { t.Errorf(`(%v) = %v; want "%v"`, tt.in, got, tt.want) } - }) } } diff --git a/src/server/middleware/security/v2_token.go b/src/server/middleware/security/v2_token.go index a4b4a30692..3c13afcc31 100644 --- a/src/server/middleware/security/v2_token.go +++ b/src/server/middleware/security/v2_token.go @@ -15,12 +15,14 @@ package security import ( - "fmt" "net/http" "strings" + "github.com/golang-jwt/jwt/v5" + registry_token "github.com/docker/distribution/registry/auth/token" + "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security/v2token" svc_token "github.com/goharbor/harbor/src/core/service/token" @@ -34,16 +36,6 @@ type v2TokenClaims struct { Access []*registry_token.ResourceActions `json:"access"` } -func (vtc *v2TokenClaims) Valid() error { - if err := vtc.Claims.Valid(); err != nil { - return err - } - if !vtc.VerifyAudience(svc_token.Registry, true) { - return fmt.Errorf("invalid token audience: %s", vtc.Audience) - } - return nil -} - type v2Token struct{} func (vt *v2Token) Generate(req *http.Request) security.Context { @@ -67,7 +59,8 @@ func (vt *v2Token) Generate(req *http.Request) security.Context { logger.Warningf("failed to decode bearer token: %v", err) return nil } - if err := t.Claims.Valid(); err != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway), jwt.WithAudience(svc_token.Registry)) + if err := v.Validate(t.Claims); err != nil { logger.Warningf("failed to decode bearer token: %v", err) return nil } diff --git a/src/server/middleware/subject/subject.go b/src/server/middleware/subject/subject.go index 0719b5c34d..7995703e2f 100644 --- a/src/server/middleware/subject/subject.go +++ b/src/server/middleware/subject/subject.go @@ -39,8 +39,14 @@ var ( // the media type of notation signature layer mediaTypeNotationLayer = "application/vnd.cncf.notary.signature" + // cosign media type in config layer, which would support in oci-spec1.1 + mediaTypeCosignConfig = "application/vnd.dev.cosign.artifact.sig.v1+json" + // annotation of nydus image layerAnnotationNydusBootstrap = "containerd.io/snapshot/nydus-bootstrap" + + // media type of harbor sbom + mediaTypeHarborSBOM = "application/vnd.goharbor.harbor.sbom.v1" ) /* @@ -106,6 +112,15 @@ func Middleware() func(http.Handler) http.Handler { return err } + /* + when an images is pushed, it could be + 1. single image (do nothing) + 2. an accesory image + 3. a subject image + 4. both as an accessory and a subject image + and a subject image or accessory image could be pushed in either order + */ + if mf.Subject != nil { subjectArt, err := artifact.Ctl.GetByReference(ctx, info.Repository, mf.Subject.Digest.String(), nil) if err != nil { @@ -113,7 +128,7 @@ func Middleware() func(http.Handler) http.Handler { logger.Errorf("failed to get subject artifact: %s, error: %v", mf.Subject.Digest, err) return err } - log.Debug("the subject of the signature doesn't exist.") + log.Debug("the subject artifact doesn't exist.") } art, err := artifact.Ctl.GetByReference(ctx, info.Repository, info.Reference, nil) if err != nil { @@ -128,13 +143,22 @@ func Middleware() func(http.Handler) http.Handler { Digest: art.Digest, } accData.Type = model.TypeSubject - switch mf.Config.MediaType { + // since oci-spec 1.1, image type may from artifactType if presents, otherwise would be Config.MediaType + fromType := mf.Config.MediaType + if mf.ArtifactType != "" { + fromType = mf.ArtifactType + } + switch fromType { case ocispec.MediaTypeImageConfig, schema2.MediaTypeImageConfig: if isNydusImage(mf) { accData.Type = model.TypeNydusAccelerator } case mediaTypeNotationLayer: accData.Type = model.TypeNotationSignature + case mediaTypeCosignConfig: + accData.Type = model.TypeCosignSignature + case mediaTypeHarborSBOM: + accData.Type = model.TypeHarborSBOM } if subjectArt != nil { accData.SubArtifactID = subjectArt.ID @@ -152,18 +176,18 @@ func Middleware() func(http.Handler) http.Handler { // when subject artifact is pushed after accessory artifact, current subject artifact do not exist. // so we use reference manifest subject digest instead of subjectArt.Digest w.Header().Set("OCI-Subject", mf.Subject.Digest.String()) - } else { + } + + // check if images is a Subject artifact + digest := digest.FromBytes(body) + accs, err := accessory.Mgr.List(ctx, q.New(q.KeyWords{"SubjectArtifactDigest": digest, "SubjectArtifactRepo": info.Repository})) + if err != nil { + logger.Errorf("failed to list accessory artifact: %s, error: %v", digest, err) + return err + } + if len(accs) > 0 { // In certain cases, the OCI client may push the subject artifact and accessory in either order. // Therefore, it is necessary to handle situations where the client pushes the accessory ahead of the subject artifact. - digest := digest.FromBytes(body) - accs, err := accessory.Mgr.List(ctx, q.New(q.KeyWords{"SubjectArtifactDigest": digest, "SubjectArtifactRepo": info.Repository})) - if err != nil { - logger.Errorf("failed to list accessory artifact: %s, error: %v", digest, err) - return err - } - if len(accs) <= 0 { - return nil - } art, err := artifact.Ctl.GetByReference(ctx, info.Repository, digest.String(), nil) if err != nil { logger.Errorf("failed to list artifact: %s, error: %v", digest, err) diff --git a/src/server/middleware/subject/subject_test.go b/src/server/middleware/subject/subject_test.go index 5dc11c3333..323b0c78bb 100644 --- a/src/server/middleware/subject/subject_test.go +++ b/src/server/middleware/subject/subject_test.go @@ -44,19 +44,19 @@ func (suite *MiddlewareTestSuite) prepare(name, digset string, withoutSub ...boo "schemaVersion":2, "mediaType":"application/vnd.oci.image.manifest.v1+json", "config":{ - "mediaType":"application/vnd.example.sbom", + "mediaType":"application/vnd.example.main", "size":2, "digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }, "layers":[ { - "mediaType":"application/vnd.example.sbom.text", + "mediaType":"application/vnd.example.main.text", "size":37, "digest":"sha256:45592a729ef6884ea3297e9510d79104f27aeef5f4919b3a921e3abb7f469709" } ], "annotations":{ - "org.example.sbom.format":"text" + "org.example.main.format":"text" }, "subject":{ "mediaType":"application/vnd.oci.image.manifest.v1+json", @@ -70,19 +70,19 @@ func (suite *MiddlewareTestSuite) prepare(name, digset string, withoutSub ...boo "schemaVersion":2, "mediaType":"application/vnd.oci.image.manifest.v1+json", "config":{ - "mediaType":"application/vnd.example.sbom", + "mediaType":"application/vnd.example.main", "size":2, "digest":"%s" }, "layers":[ { - "mediaType":"application/vnd.example.sbom.text", + "mediaType":"application/vnd.example.main.text", "size":37, "digest":"sha256:45592a729ef6884ea3297e9510d79104f27aeef5f4919b3a921e3abb7f469709" } ], "annotations":{ - "org.example.sbom.format":"text" + "org.example.main.format":"text" }}`, digset) } diff --git a/src/server/registry/manifest.go b/src/server/registry/manifest.go index 4693c3a7e6..6cf02486a8 100644 --- a/src/server/registry/manifest.go +++ b/src/server/registry/manifest.go @@ -145,6 +145,8 @@ func deleteManifest(w http.ResponseWriter, req *http.Request) { // add parse digest here is to return ErrDigestInvalidFormat before GetByReference throws an NOT_FOUND(404) // Do not add the logic into GetByReference as it's a shared method for PUT/GET/DELETE/Internal call, // and NOT_FOUND satisfy PUT/GET/Internal call. + // According to https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#deleting-tags + // If tag deletion is disabled, the registry MUST respond with either a 400 Bad Request or a 405 Method Not Allowed if _, err := digest.Parse(reference); err != nil { lib_http.SendError(w, errors.Wrapf(err, "unsupported digest %s", reference).WithCode(errors.UNSUPPORTED)) return diff --git a/src/server/registry/referrers.go b/src/server/registry/referrers.go index 3213889dfa..00504dfd4c 100644 --- a/src/server/registry/referrers.go +++ b/src/server/registry/referrers.go @@ -22,11 +22,16 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/goharbor/harbor/src/lib/cache" + "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" lib_http "github.com/goharbor/harbor/src/lib/http" + "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/accessory" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/cached/manifest/redis" + "github.com/goharbor/harbor/src/pkg/registry" "github.com/goharbor/harbor/src/server/router" "github.com/goharbor/harbor/src/server/v2.0/handler" ) @@ -38,12 +43,16 @@ func newReferrersHandler() http.Handler { return &referrersHandler{ artifactManager: artifact.NewManager(), accessoryManager: accessory.NewManager(), + registryClient: registry.Cli, + maniCacheManager: redis.NewManager(), } } type referrersHandler struct { artifactManager artifact.Manager accessoryManager accessory.Manager + registryClient registry.Client + maniCacheManager redis.CachedManager } func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -63,20 +72,8 @@ func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - // Get the artifact by reference - art, err := r.artifactManager.GetByDigest(ctx, repository, reference) - if err != nil { - if errors.IsNotFoundErr(err) { - // If artifact not found, return empty json - newListReferrersOK().WithPayload(nil).WriteResponse(w) - return - } - lib_http.SendError(w, err) - return - } - // Query accessories with matching subject artifact digest - query := q.New(q.KeyWords{"SubjectArtifactDigest": art.Digest, "SubjectArtifactRepo": art.RepositoryName}) + query := q.New(q.KeyWords{"SubjectArtifactDigest": reference, "SubjectArtifactRepo": repository}) total, err := r.accessoryManager.Count(ctx, query) if err != nil { lib_http.SendError(w, err) @@ -87,29 +84,67 @@ func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { lib_http.SendError(w, err) return } - // Build index manifest from accessories mfs := make([]ocispec.Descriptor, 0) for _, acc := range accs { - accArt, err := r.artifactManager.GetByDigest(ctx, repository, acc.GetData().Digest) + accArtDigest := acc.GetData().Digest + accArt, err := r.artifactManager.GetByDigest(ctx, repository, accArtDigest) if err != nil { lib_http.SendError(w, err) return } - mf := ocispec.Descriptor{ + // whether get manifest from cache + fromCache := false + // whether need write manifest to cache + writeCache := false + var maniContent []byte + + // pull manifest, will try to pull from cache first + // and write to cache when pull manifest from registry at first time + if config.CacheEnabled() { + maniContent, err = r.maniCacheManager.Get(req.Context(), accArtDigest) + if err == nil { + fromCache = true + } else { + log.Debugf("failed to get manifest %s from cache, will fallback to registry, error: %v", accArtDigest, err) + if errors.As(err, &cache.ErrNotFound) { + writeCache = true + } + } + } + if !fromCache { + mani, _, err := r.registryClient.PullManifest(accArt.RepositoryName, accArtDigest) + if err != nil { + lib_http.SendError(w, err) + return + } + _, maniContent, err = mani.Payload() + if err != nil { + lib_http.SendError(w, err) + return + } + // write manifest to cache when first time pulling + if writeCache { + err = r.maniCacheManager.Save(req.Context(), accArtDigest, maniContent) + if err != nil { + log.Warningf("failed to save accArt manifest %s to cache, error: %v", accArtDigest, err) + } + } + } + desc := ocispec.Descriptor{ MediaType: accArt.ManifestMediaType, - Size: accArt.Size, + Size: int64(len(maniContent)), Digest: digest.Digest(accArt.Digest), Annotations: accArt.Annotations, - ArtifactType: accArt.MediaType, + ArtifactType: accArt.ArtifactType, } - // filter by the artifactType since the artifactType is actually the config media type of the artifact. + // filter use accArt.ArtifactType as artifactType if at != "" { - if accArt.MediaType == at { - mfs = append(mfs, mf) + if accArt.ArtifactType == at { + mfs = append(mfs, desc) } } else { - mfs = append(mfs, mf) + mfs = append(mfs, desc) } } diff --git a/src/server/registry/referrers_test.go b/src/server/registry/referrers_test.go index d9c960a013..5eab49ee79 100644 --- a/src/server/registry/referrers_test.go +++ b/src/server/registry/referrers_test.go @@ -10,15 +10,43 @@ import ( beegocontext "github.com/beego/beego/v2/server/web/context" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" - "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/cache" + "github.com/goharbor/harbor/src/lib/config" accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model" basemodel "github.com/goharbor/harbor/src/pkg/accessory/model/base" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/distribution" "github.com/goharbor/harbor/src/server/router" "github.com/goharbor/harbor/src/testing/mock" accessorytesting "github.com/goharbor/harbor/src/testing/pkg/accessory" arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact" + testmanifest "github.com/goharbor/harbor/src/testing/pkg/cached/manifest/redis" + regtesting "github.com/goharbor/harbor/src/testing/pkg/registry" +) + +var ( + OCIManifest = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.example.sbom", + "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", + "size": 123 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "name": "test-image" + } + }` ) func TestReferrersHandlerOK(t *testing.T) { @@ -37,9 +65,10 @@ func TestReferrersHandlerOK(t *testing.T) { artifactMock.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything). Return(&artifact.Artifact{ - Digest: digestVal, + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", MediaType: "application/vnd.example.sbom", + ArtifactType: "application/vnd.example.sbom", Size: 1000, Annotations: map[string]string{ "name": "test-image", @@ -57,13 +86,23 @@ func TestReferrersHandlerOK(t *testing.T) { SubArtifactDigest: digestVal, SubArtifactRepo: "goharbor", Type: accessorymodel.TypeCosignSignature, + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", }, }, }, nil) + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifest)) + if err != nil { + t.Fatal(err) + } + regCliMock := ®testing.Client{} + config.DefaultMgr().Set(context.TODO(), "cache_enabled", false) + mock.OnAnything(regCliMock, "PullManifest").Return(manifest, "", nil) + handler := &referrersHandler{ artifactManager: artifactMock, accessoryManager: accessoryMock, + registryClient: regCliMock, } handler.ServeHTTP(rec, req) @@ -73,10 +112,89 @@ func TestReferrersHandlerOK(t *testing.T) { t.Errorf("Expected status code %d, but got %d", http.StatusOK, rec.Code) } index := &ocispec.Index{} - json.Unmarshal([]byte(rec.Body.String()), index) + json.Unmarshal(rec.Body.Bytes(), index) if index.Manifests[0].ArtifactType != "application/vnd.example.sbom" { t.Errorf("Expected response body %s, but got %s", "application/vnd.example.sbom", rec.Body.String()) } + _, content, _ := manifest.Payload() + assert.Equal(t, int64(len(content)), index.Manifests[0].Size) +} + +func TestReferrersHandlerSavetoCache(t *testing.T) { + rec := httptest.NewRecorder() + digestVal := "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b" + req, err := http.NewRequest("GET", "/v2/test/repository/referrers/sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b", nil) + if err != nil { + t.Fatal(err) + } + input := &beegocontext.BeegoInput{} + input.SetParam(":reference", digestVal) + *req = *(req.WithContext(context.WithValue(req.Context(), router.ContextKeyInput{}, input))) + + artifactMock := &arttesting.Manager{} + accessoryMock := &accessorytesting.Manager{} + + artifactMock.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything). + Return(&artifact.Artifact{ + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", + ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", + MediaType: "application/vnd.example.sbom", + ArtifactType: "application/vnd.example.sbom", + Size: 1000, + Annotations: map[string]string{ + "name": "test-image", + }, + }, nil) + + accessoryMock.On("Count", mock.Anything, mock.Anything). + Return(int64(1), nil) + accessoryMock.On("List", mock.Anything, mock.Anything). + Return([]accessorymodel.Accessory{ + &basemodel.Default{ + Data: accessorymodel.AccessoryData{ + ID: 1, + ArtifactID: 2, + SubArtifactDigest: digestVal, + SubArtifactRepo: "goharbor", + Type: accessorymodel.TypeCosignSignature, + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", + }, + }, + }, nil) + + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifest)) + if err != nil { + t.Fatal(err) + } + + // cache_enabled pull from cahce + config.DefaultMgr().Set(context.TODO(), "cache_enabled", true) + cacheManagerMock := &testmanifest.CachedManager{} + mock.OnAnything(cacheManagerMock, "Get").Return(nil, fmt.Errorf("unable to do stuff: %w", cache.ErrNotFound)) + regCliMock := ®testing.Client{} + mock.OnAnything(regCliMock, "PullManifest").Return(manifest, "", nil) + mock.OnAnything(cacheManagerMock, "Save").Return(nil) + + handler := &referrersHandler{ + artifactManager: artifactMock, + accessoryManager: accessoryMock, + registryClient: regCliMock, + maniCacheManager: cacheManagerMock, + } + + handler.ServeHTTP(rec, req) + + // check that the response has the expected status code (200 OK) + if rec.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, rec.Code) + } + index := &ocispec.Index{} + json.Unmarshal(rec.Body.Bytes(), index) + if index.Manifests[0].ArtifactType != "application/vnd.example.sbom" { + t.Errorf("Expected response body %s, but got %s", "application/vnd.example.sbom", rec.Body.String()) + } + _, content, _ := manifest.Payload() + assert.Equal(t, int64(len(content)), index.Manifests[0].Size) } func TestReferrersHandlerEmpty(t *testing.T) { @@ -93,8 +211,10 @@ func TestReferrersHandlerEmpty(t *testing.T) { artifactMock := &arttesting.Manager{} accessoryMock := &accessorytesting.Manager{} - artifactMock.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything). - Return(nil, errors.NotFoundError(nil)) + accessoryMock.On("Count", mock.Anything, mock.Anything). + Return(int64(0), nil) + accessoryMock.On("List", mock.Anything, mock.Anything). + Return([]accessorymodel.Accessory{}, nil) handler := &referrersHandler{ artifactManager: artifactMock, @@ -109,7 +229,6 @@ func TestReferrersHandlerEmpty(t *testing.T) { } index := &ocispec.Index{} json.Unmarshal([]byte(rec.Body.String()), index) - fmt.Println(index) if index.SchemaVersion != 0 && len(index.Manifests) != -0 { t.Errorf("Expected empty response body, but got %s", rec.Body.String()) } diff --git a/src/server/v2.0/handler/artifact.go b/src/server/v2.0/handler/artifact.go index 84d78cc5db..dfc457047d 100644 --- a/src/server/v2.0/handler/artifact.go +++ b/src/server/v2.0/handler/artifact.go @@ -107,8 +107,8 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr if err != nil { return a.SendError(ctx, err) } - - assembler := assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)) + overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview))) + assembler := assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities)) var artifacts []*models.Artifact for _, art := range arts { artifact := &model.Artifact{} @@ -138,8 +138,9 @@ func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtif } art := &model.Artifact{} art.Artifact = *artifact + overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview))) - err = assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx) + err = assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx) if err != nil { log.Warningf("failed to assemble vulnerabilities with artifact, error: %v", err) } diff --git a/src/server/v2.0/handler/assembler/report.go b/src/server/v2.0/handler/assembler/report.go new file mode 100644 index 0000000000..7a97ec9926 --- /dev/null +++ b/src/server/v2.0/handler/assembler/report.go @@ -0,0 +1,139 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package assembler + +import ( + "context" + + "github.com/goharbor/harbor/src/controller/scan" + "github.com/goharbor/harbor/src/lib" + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/lib/q" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model" + "github.com/goharbor/harbor/src/pkg/task" + "github.com/goharbor/harbor/src/server/v2.0/handler/model" +) + +const ( + vulnerabilitiesAddition = "vulnerabilities" + sbomAddition = "sbom" +) + +// NewScanReportAssembler returns vul assembler +func NewScanReportAssembler(option *model.OverviewOptions, mimeTypes []string) *ScanReportAssembler { + return &ScanReportAssembler{ + overviewOption: option, + scanChecker: scan.NewChecker(), + scanCtl: scan.DefaultController, + executionMgr: task.ExecMgr, + mimeTypes: mimeTypes, + } +} + +// ScanReportAssembler vul assembler +type ScanReportAssembler struct { + scanChecker scan.Checker + scanCtl scan.Controller + executionMgr task.ExecutionManager + + artifacts []*model.Artifact + mimeTypes []string + overviewOption *model.OverviewOptions +} + +// WithArtifacts set artifacts for the assembler +func (assembler *ScanReportAssembler) WithArtifacts(artifacts ...*model.Artifact) *ScanReportAssembler { + assembler.artifacts = artifacts + + return assembler +} + +// Assemble assemble vul for the artifacts +func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error { + version := lib.GetAPIVersion(ctx) + + for _, artifact := range assembler.artifacts { + isScannable, err := assembler.scanChecker.IsScannable(ctx, &artifact.Artifact) + if err != nil { + log.Errorf("check the scannable status of %s@%s failed, error: %v", artifact.RepositoryName, artifact.Digest, err) + continue + } + + if !isScannable { + continue + } + + artifact.SetAdditionLink(vulnerabilitiesAddition, version) + + if assembler.overviewOption.WithVuln { + for _, mimeType := range assembler.mimeTypes { + overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType}) + if err != nil { + log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, mimeType, err) + } else if len(overview) > 0 { + artifact.ScanOverview = overview + break + } + } + } + + // set sbom additional link if it is supported, use the empty digest + artifact.SetSBOMAdditionLink("", version) + if assembler.overviewOption.WithSBOM { + overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeSBOMReport}) + if err != nil { + log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, v1.MimeTypeSBOMReport, err) + } + if len(overview) == 0 { + log.Warningf("overview is empty, retrieve sbom status from execution") + query := q.New(q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest, "extra_attrs.enabled_capabilities.type": "sbom"}) + // sort by ID desc to get the latest execution + query.Sorts = []*q.Sort{q.NewSort("ID", true)} + execs, err := assembler.executionMgr.List(ctx, query) + if err != nil { + log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, v1.MimeTypeSBOMReport, err) + continue + } + // if no execs, means this artifact is not scanned, leave the sbom_overview empty + if len(execs) == 0 { + continue + } + artifact.SBOMOverView = map[string]interface{}{ + sbomModel.ScanStatus: execs[0].Status, + sbomModel.StartTime: execs[0].StartTime, + sbomModel.EndTime: execs[0].EndTime, + sbomModel.Duration: int64(execs[0].EndTime.Sub(execs[0].StartTime).Seconds()), + } + continue + } + + artifact.SBOMOverView = map[string]interface{}{ + sbomModel.StartTime: overview[sbomModel.StartTime], + sbomModel.EndTime: overview[sbomModel.EndTime], + sbomModel.ScanStatus: overview[sbomModel.ScanStatus], + sbomModel.SBOMDigest: overview[sbomModel.SBOMDigest], + sbomModel.Duration: overview[sbomModel.Duration], + sbomModel.ReportID: overview[sbomModel.ReportID], + sbomModel.Scanner: overview[sbomModel.Scanner], + } + if sbomDgst, ok := overview[sbomModel.SBOMDigest].(string); ok { + // set additional link for sbom digest + artifact.SetSBOMAdditionLink(sbomDgst, version) + } + } + } + return nil +} diff --git a/src/server/v2.0/handler/assembler/report_test.go b/src/server/v2.0/handler/assembler/report_test.go new file mode 100644 index 0000000000..003632068a --- /dev/null +++ b/src/server/v2.0/handler/assembler/report_test.go @@ -0,0 +1,142 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package assembler + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + + v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + "github.com/goharbor/harbor/src/pkg/task" + "github.com/goharbor/harbor/src/server/v2.0/handler/model" + "github.com/goharbor/harbor/src/testing/controller/scan" + "github.com/goharbor/harbor/src/testing/mock" + mockTask "github.com/goharbor/harbor/src/testing/pkg/task" +) + +type VulAssemblerTestSuite struct { + suite.Suite +} + +func (suite *VulAssemblerTestSuite) TestScannable() { + checker := &scan.Checker{} + scanCtl := &scan.Controller{} + + assembler := ScanReportAssembler{ + scanChecker: checker, + scanCtl: scanCtl, + overviewOption: model.NewOverviewOptions(model.WithVuln(true)), + mimeTypes: []string{"mimeType"}, + } + + mock.OnAnything(checker, "IsScannable").Return(true, nil) + + summary := map[string]interface{}{"key": "value"} + mock.OnAnything(scanCtl, "GetSummary").Return(summary, nil) + + var artifact model.Artifact + + suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO())) + suite.Len(artifact.AdditionLinks, 2) + suite.Equal(artifact.ScanOverview, summary) +} + +func (suite *VulAssemblerTestSuite) TestNotScannable() { + checker := &scan.Checker{} + scanCtl := &scan.Controller{} + + assembler := ScanReportAssembler{ + scanChecker: checker, + scanCtl: scanCtl, + overviewOption: model.NewOverviewOptions(model.WithVuln(true)), + } + + mock.OnAnything(checker, "IsScannable").Return(false, nil) + + summary := map[string]interface{}{"key": "value"} + mock.OnAnything(scanCtl, "GetSummary").Return(summary, nil) + + var art model.Artifact + + suite.Nil(assembler.WithArtifacts(&art).Assemble(context.TODO())) + suite.Len(art.AdditionLinks, 0) + scanCtl.AssertNotCalled(suite.T(), "GetSummary") +} + +func (suite *VulAssemblerTestSuite) TestAssembleSBOMOverview() { + checker := &scan.Checker{} + scanCtl := &scan.Controller{} + + assembler := ScanReportAssembler{ + scanChecker: checker, + scanCtl: scanCtl, + overviewOption: model.NewOverviewOptions(model.WithSBOM(true)), + mimeTypes: []string{v1sq.MimeTypeSBOMReport}, + } + + mock.OnAnything(checker, "IsScannable").Return(true, nil) + overview := map[string]interface{}{ + "sbom_digest": "sha256:123456", + "scan_status": "Success", + } + mock.OnAnything(scanCtl, "GetSummary").Return(overview, nil) + + var artifact model.Artifact + err := assembler.WithArtifacts(&artifact).Assemble(context.TODO()) + suite.Nil(err) + suite.Equal(artifact.SBOMOverView["sbom_digest"], "sha256:123456") + suite.Equal(artifact.SBOMOverView["scan_status"], "Success") + +} + +func (suite *VulAssemblerTestSuite) TestAssembleSBOMOverviewImageIndex() { + checker := &scan.Checker{} + scanCtl := &scan.Controller{} + exeMgr := &mockTask.ExecutionManager{} + + assembler := ScanReportAssembler{ + scanChecker: checker, + scanCtl: scanCtl, + executionMgr: exeMgr, + overviewOption: model.NewOverviewOptions(model.WithSBOM(true)), + mimeTypes: []string{v1sq.MimeTypeSBOMReport}, + } + + mock.OnAnything(checker, "IsScannable").Return(true, nil) + overview := map[string]interface{}{} + mock.OnAnything(scanCtl, "GetSummary").Return(overview, nil) + execs := []*task.Execution{ + {ID: 1, Status: "Error"}, + {ID: 2, Status: "Success"}, + } + mock.OnAnything(exeMgr, "List").Return(execs, nil).Once() + + var artifact model.Artifact + err := assembler.WithArtifacts(&artifact).Assemble(context.TODO()) + suite.Nil(err) + suite.Equal(artifact.SBOMOverView["scan_status"], "Error") + + mock.OnAnything(exeMgr, "List").Return(nil, nil).Once() + var artifact2 model.Artifact + err2 := assembler.WithArtifacts(&artifact2).Assemble(context.TODO()) + suite.Nil(err2) + suite.Nil(artifact2.SBOMOverView, "sbom overview should be nil") +} + +func TestVulAssemblerTestSuite(t *testing.T) { + suite.Run(t, &VulAssemblerTestSuite{}) +} diff --git a/src/server/v2.0/handler/assembler/vul.go b/src/server/v2.0/handler/assembler/vul.go deleted file mode 100644 index 055baab351..0000000000 --- a/src/server/v2.0/handler/assembler/vul.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright Project Harbor Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package assembler - -import ( - "context" - - "github.com/goharbor/harbor/src/controller/scan" - "github.com/goharbor/harbor/src/lib" - "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/server/v2.0/handler/model" -) - -const ( - vulnerabilitiesAddition = "vulnerabilities" -) - -// NewVulAssembler returns vul assembler -func NewVulAssembler(withScanOverview bool, mimeTypes []string) *VulAssembler { - return &VulAssembler{ - scanChecker: scan.NewChecker(), - scanCtl: scan.DefaultController, - - withScanOverview: withScanOverview, - mimeTypes: mimeTypes, - } -} - -// VulAssembler vul assembler -type VulAssembler struct { - scanChecker scan.Checker - scanCtl scan.Controller - - artifacts []*model.Artifact - withScanOverview bool - mimeTypes []string -} - -// WithArtifacts set artifacts for the assembler -func (assembler *VulAssembler) WithArtifacts(artifacts ...*model.Artifact) *VulAssembler { - assembler.artifacts = artifacts - - return assembler -} - -// Assemble assemble vul for the artifacts -func (assembler *VulAssembler) Assemble(ctx context.Context) error { - version := lib.GetAPIVersion(ctx) - - for _, artifact := range assembler.artifacts { - isScannable, err := assembler.scanChecker.IsScannable(ctx, &artifact.Artifact) - if err != nil { - log.Errorf("check the scannable status of %s@%s failed, error: %v", artifact.RepositoryName, artifact.Digest, err) - continue - } - - if !isScannable { - continue - } - - artifact.SetAdditionLink(vulnerabilitiesAddition, version) - - if assembler.withScanOverview { - for _, mimeType := range assembler.mimeTypes { - overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType}) - if err != nil { - log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, mimeType, err) - } else if len(overview) > 0 { - artifact.ScanOverview = overview - break - } - } - } - } - - return nil -} diff --git a/src/server/v2.0/handler/assembler/vul_test.go b/src/server/v2.0/handler/assembler/vul_test.go deleted file mode 100644 index 202720afa3..0000000000 --- a/src/server/v2.0/handler/assembler/vul_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright Project Harbor Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package assembler - -import ( - "context" - "testing" - - "github.com/stretchr/testify/suite" - - "github.com/goharbor/harbor/src/server/v2.0/handler/model" - "github.com/goharbor/harbor/src/testing/controller/scan" - "github.com/goharbor/harbor/src/testing/mock" -) - -type VulAssemblerTestSuite struct { - suite.Suite -} - -func (suite *VulAssemblerTestSuite) TestScannable() { - checker := &scan.Checker{} - scanCtl := &scan.Controller{} - - assembler := VulAssembler{ - scanChecker: checker, - scanCtl: scanCtl, - withScanOverview: true, - mimeTypes: []string{"mimeType"}, - } - - mock.OnAnything(checker, "IsScannable").Return(true, nil) - - summary := map[string]interface{}{"key": "value"} - mock.OnAnything(scanCtl, "GetSummary").Return(summary, nil) - - var artifact model.Artifact - - suite.Nil(assembler.WithArtifacts(&artifact).Assemble(context.TODO())) - suite.Len(artifact.AdditionLinks, 1) - suite.Equal(artifact.ScanOverview, summary) -} - -func (suite *VulAssemblerTestSuite) TestNotScannable() { - checker := &scan.Checker{} - scanCtl := &scan.Controller{} - - assembler := VulAssembler{ - scanChecker: checker, - scanCtl: scanCtl, - withScanOverview: true, - } - - mock.OnAnything(checker, "IsScannable").Return(false, nil) - - summary := map[string]interface{}{"key": "value"} - mock.OnAnything(scanCtl, "GetSummary").Return(summary, nil) - - var art model.Artifact - - suite.Nil(assembler.WithArtifacts(&art).Assemble(context.TODO())) - suite.Len(art.AdditionLinks, 0) - scanCtl.AssertNotCalled(suite.T(), "GetSummary") -} - -func TestVulAssemblerTestSuite(t *testing.T) { - suite.Run(t, &VulAssemblerTestSuite{}) -} diff --git a/src/server/v2.0/handler/model/artifact.go b/src/server/v2.0/handler/model/artifact.go index 3627d9cede..f931c0abf2 100644 --- a/src/server/v2.0/handler/model/artifact.go +++ b/src/server/v2.0/handler/model/artifact.go @@ -28,7 +28,9 @@ import ( // Artifact model type Artifact struct { artifact.Artifact + // TODO: rename to VulOverview ScanOverview map[string]interface{} `json:"scan_overview"` + SBOMOverView map[string]interface{} `json:"sbom_overview"` } // ToSwagger converts the artifact to the swagger model @@ -84,6 +86,18 @@ func (a *Artifact) ToSwagger() *models.Artifact { art.ScanOverview[key] = summary } } + if len(a.SBOMOverView) > 0 { + js, err := json.Marshal(a.SBOMOverView) + if err != nil { + log.Warningf("convert sbom summary failed, error: %v", err) + } + sbomOverview := &models.SBOMOverview{} + err = json.Unmarshal(js, sbomOverview) + if err != nil { + log.Warningf("failed to get sbom summary: error: %v", err) + } + art.SbomOverview = sbomOverview + } return art } diff --git a/src/server/v2.0/handler/model/option.go b/src/server/v2.0/handler/model/option.go new file mode 100644 index 0000000000..06aab99434 --- /dev/null +++ b/src/server/v2.0/handler/model/option.go @@ -0,0 +1,47 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +// OverviewOptions define the option to query overview info +type OverviewOptions struct { + WithVuln bool + WithSBOM bool +} + +// Option define the func to build options +type Option func(*OverviewOptions) + +// NewOverviewOptions create a new OverviewOptions +func NewOverviewOptions(options ...Option) *OverviewOptions { + opts := &OverviewOptions{} + for _, f := range options { + f(opts) + } + return opts +} + +// WithVuln set the option to query vulnerability info +func WithVuln(enable bool) Option { + return func(o *OverviewOptions) { + o.WithVuln = enable + } +} + +// WithSBOM set the option to query SBOM info +func WithSBOM(enable bool) Option { + return func(o *OverviewOptions) { + o.WithSBOM = enable + } +} diff --git a/src/server/v2.0/handler/model/option_test.go b/src/server/v2.0/handler/model/option_test.go new file mode 100644 index 0000000000..1b1bf2f37c --- /dev/null +++ b/src/server/v2.0/handler/model/option_test.go @@ -0,0 +1,33 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOverviewOptions(t *testing.T) { + // Test NewOverviewOptions with WithVuln and WithSBOM + opts := NewOverviewOptions(WithVuln(true), WithSBOM(true)) + assert.True(t, opts.WithVuln) + assert.True(t, opts.WithSBOM) + + // Test NewOverviewOptions with WithVuln and WithSBOM set to false + opts = NewOverviewOptions(WithVuln(false), WithSBOM(false)) + assert.False(t, opts.WithVuln) + assert.False(t, opts.WithSBOM) +} diff --git a/src/server/v2.0/handler/model/scanner.go b/src/server/v2.0/handler/model/scanner.go index 34d7e6b925..a2fd45ce9f 100644 --- a/src/server/v2.0/handler/model/scanner.go +++ b/src/server/v2.0/handler/model/scanner.go @@ -52,6 +52,7 @@ func (s *ScannerRegistration) ToSwagger(_ context.Context) *models.ScannerRegist Vendor: s.Vendor, Version: s.Version, Health: s.Health, + Capabilities: s.Capabilities, } } @@ -74,6 +75,7 @@ func (s *ScannerMetadata) ToSwagger(_ context.Context) *models.ScannerAdapterMet var capabilities []*models.ScannerCapability for _, c := range s.Capabilities { capabilities = append(capabilities, &models.ScannerCapability{ + Type: c.Type, ConsumesMimeTypes: c.ConsumesMimeTypes, ProducesMimeTypes: c.ProducesMimeTypes, }) diff --git a/src/server/v2.0/handler/model/webhook_policy.go b/src/server/v2.0/handler/model/webhook_policy.go index 0443df220c..a0cded5905 100644 --- a/src/server/v2.0/handler/model/webhook_policy.go +++ b/src/server/v2.0/handler/model/webhook_policy.go @@ -57,7 +57,7 @@ func (n *WebhookPolicy) ToTargets() []*models.WebhookTargetObject { return results } -// NewNotifiactionPolicy ... +// NewWebhookPolicy ... func NewWebhookPolicy(p *model.Policy) *WebhookPolicy { return &WebhookPolicy{ Policy: p, diff --git a/src/server/v2.0/handler/permissions.go b/src/server/v2.0/handler/permissions.go index d90ad608b1..0189abf237 100644 --- a/src/server/v2.0/handler/permissions.go +++ b/src/server/v2.0/handler/permissions.go @@ -21,6 +21,7 @@ import ( "github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/common/security" + "github.com/goharbor/harbor/src/common/security/local" "github.com/goharbor/harbor/src/controller/member" "github.com/goharbor/harbor/src/controller/user" "github.com/goharbor/harbor/src/lib/errors" @@ -57,15 +58,14 @@ func (p *permissionsAPI) GetPermissions(ctx context.Context, _ permissions.GetPe if secCtx.IsSysAdmin() { isSystemAdmin = true } else { - user, err := p.uc.GetByName(ctx, secCtx.GetUsername()) - if err != nil { - return p.SendError(ctx, err) + if sc, ok := secCtx.(*local.SecurityContext); ok { + user := sc.User() + var err error + isProjectAdmin, err = p.mc.IsProjectAdmin(ctx, *user) + if err != nil { + return p.SendError(ctx, err) + } } - is, err := p.mc.IsProjectAdmin(ctx, user.UserID) - if err != nil { - return p.SendError(ctx, err) - } - isProjectAdmin = is } if !isSystemAdmin && !isProjectAdmin { return p.SendError(ctx, errors.ForbiddenError(errors.New("only admins(system and project) can access permissions"))) diff --git a/src/server/v2.0/handler/project.go b/src/server/v2.0/handler/project.go index 692a26d6f6..29286f8ec4 100644 --- a/src/server/v2.0/handler/project.go +++ b/src/server/v2.0/handler/project.go @@ -590,12 +590,16 @@ func (a *projectAPI) GetScannerOfProject(ctx context.Context, params operation.G return a.SendError(ctx, err) } - scanner, err := a.scannerCtl.GetRegistrationByProject(ctx, p.ProjectID) + s, err := a.scannerCtl.GetRegistrationByProject(ctx, p.ProjectID) if err != nil { return a.SendError(ctx, err) } - - return operation.NewGetScannerOfProjectOK().WithPayload(model.NewScannerRegistration(scanner).ToSwagger(ctx)) + if s != nil { + if err := a.scannerCtl.RetrieveCap(ctx, s); err != nil { + log.Warningf(scanner.RetrieveCapFailMsg, err) + } + } + return operation.NewGetScannerOfProjectOK().WithPayload(model.NewScannerRegistration(s).ToSwagger(ctx)) } func (a *projectAPI) ListScannerCandidatesOfProject(ctx context.Context, params operation.ListScannerCandidatesOfProjectParams) middleware.Responder { diff --git a/src/server/v2.0/handler/project_metadata.go b/src/server/v2.0/handler/project_metadata.go index 8a2ba6cb76..a5bde2e453 100644 --- a/src/server/v2.0/handler/project_metadata.go +++ b/src/server/v2.0/handler/project_metadata.go @@ -143,7 +143,7 @@ func (p *projectMetadataAPI) validate(metas map[string]string) (map[string]strin switch key { case proModels.ProMetaPublic, proModels.ProMetaEnableContentTrust, proModels.ProMetaEnableContentTrustCosign, - proModels.ProMetaPreventVul, proModels.ProMetaAutoScan, proModels.ProMetaReuseSysCVEAllowlist: + proModels.ProMetaAutoSBOMGen, proModels.ProMetaPreventVul, proModels.ProMetaAutoScan, proModels.ProMetaReuseSysCVEAllowlist: v, err := strconv.ParseBool(value) if err != nil { return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value) diff --git a/src/server/v2.0/handler/project_test.go b/src/server/v2.0/handler/project_test.go index 21ba79a0a2..eca7f09a3a 100644 --- a/src/server/v2.0/handler/project_test.go +++ b/src/server/v2.0/handler/project_test.go @@ -22,6 +22,7 @@ import ( "github.com/goharbor/harbor/src/pkg/project/models" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/server/v2.0/restapi" projecttesting "github.com/goharbor/harbor/src/testing/controller/project" scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner" @@ -36,6 +37,7 @@ type ProjectTestSuite struct { scannerCtl *scannertesting.Controller project *models.Project reg *scanner.Registration + metadata *v1.ScannerAdapterMetadata } func (suite *ProjectTestSuite) SetupSuite() { @@ -59,7 +61,12 @@ func (suite *ProjectTestSuite) SetupSuite() { scannerCtl: suite.scannerCtl, }, } - + suite.metadata = &v1.ScannerAdapterMetadata{ + Capabilities: []*v1.ScannerCapability{ + {Type: "vulnerability", ProducesMimeTypes: []string{v1.MimeTypeScanResponse}}, + {Type: "sbom", ProducesMimeTypes: []string{v1.MimeTypeSBOMReport}}, + }, + } suite.Suite.SetupSuite() } @@ -81,7 +88,8 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { // scanner not found mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(nil, nil).Once() - + mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() + mock.OnAnything(suite.scannerCtl, "RetrieveCap").Return(nil).Once() res, err := suite.Get("/projects/1/scanner") suite.NoError(err) suite.Equal(200, res.StatusCode) @@ -90,7 +98,8 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { { mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once() - + mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() + mock.OnAnything(suite.scannerCtl, "RetrieveCap").Return(nil).Once() var scanner scanner.Registration res, err := suite.GetJSON("/projects/1/scanner", &scanner) suite.NoError(err) @@ -101,7 +110,9 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { { mock.OnAnything(projectCtlMock, "GetByName").Return(suite.project, nil).Once() mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() + mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once() + mock.OnAnything(suite.scannerCtl, "RetrieveCap").Return(nil).Once() var scanner scanner.Registration res, err := suite.GetJSON("/projects/library/scanner", &scanner) diff --git a/src/server/v2.0/handler/scan.go b/src/server/v2.0/handler/scan.go index 8691246e55..a22eaaaede 100644 --- a/src/server/v2.0/handler/scan.go +++ b/src/server/v2.0/handler/scan.go @@ -25,6 +25,7 @@ import ( "github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/pkg/distribution" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/scan" ) @@ -50,7 +51,15 @@ func (s *scanAPI) Prepare(ctx context.Context, _ string, params interface{}) mid } func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopScanArtifactParams) middleware.Responder { - if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionStop, rbac.ResourceScan); err != nil { + scanType := v1.ScanTypeVulnerability + if params.ScanType != nil && validScanType(params.ScanType.ScanType) { + scanType = params.ScanType.ScanType + } + res := rbac.ResourceScan + if scanType == v1.ScanTypeSbom { + res = rbac.ResourceSBOM + } + if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionStop, res); err != nil { return s.SendError(ctx, err) } @@ -60,7 +69,7 @@ func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopSca return s.SendError(ctx, err) } - if err := s.scanCtl.Stop(ctx, curArtifact); err != nil { + if err := s.scanCtl.Stop(ctx, curArtifact, params.ScanType.ScanType); err != nil { return s.SendError(ctx, err) } @@ -68,21 +77,28 @@ func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopSca } func (s *scanAPI) ScanArtifact(ctx context.Context, params operation.ScanArtifactParams) middleware.Responder { - if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionCreate, rbac.ResourceScan); err != nil { + scanType := v1.ScanTypeVulnerability + options := []scan.Option{} + if !distribution.IsDigest(params.Reference) { + options = append(options, scan.WithTag(params.Reference)) + } + if params.ScanType != nil && validScanType(params.ScanType.ScanType) { + scanType = params.ScanType.ScanType + options = append(options, scan.WithScanType(scanType)) + } + res := rbac.ResourceScan + if scanType == v1.ScanTypeSbom { + res = rbac.ResourceSBOM + } + if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionCreate, res); err != nil { return s.SendError(ctx, err) } - repository := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName) artifact, err := s.artCtl.GetByReference(ctx, repository, params.Reference, nil) if err != nil { return s.SendError(ctx, err) } - options := []scan.Option{} - if !distribution.IsDigest(params.Reference) { - options = append(options, scan.WithTag(params.Reference)) - } - if err := s.scanCtl.Scan(ctx, artifact, options...); err != nil { return s.SendError(ctx, err) } @@ -112,3 +128,7 @@ func (s *scanAPI) GetReportLog(ctx context.Context, params operation.GetReportLo return operation.NewGetReportLogOK().WithPayload(string(bytes)) } + +func validScanType(scanType string) bool { + return scanType == "sbom" || scanType == "vulnerability" +} diff --git a/src/server/v2.0/handler/scan_test.go b/src/server/v2.0/handler/scan_test.go index f073b83e95..e0977b80c4 100644 --- a/src/server/v2.0/handler/scan_test.go +++ b/src/server/v2.0/handler/scan_test.go @@ -16,6 +16,7 @@ package handler import ( "fmt" + "github.com/goharbor/harbor/src/server/v2.0/models" "testing" "github.com/stretchr/testify/suite" @@ -67,12 +68,12 @@ func (suite *ScanTestSuite) TestStopScan() { suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times) url := "/projects/library/repositories/nginx/artifacts/sha256:e4f0474a75c510f40b37b6b7dc2516241ffa8bde5a442bde3d372c9519c84d90/scan/stop" - + body := models.ScanType{ScanType: "sbom"} { // failed to get artifact by reference mock.OnAnything(suite.artifactCtl, "GetByReference").Return(&artifact.Artifact{}, fmt.Errorf("failed to get artifact by reference")).Once() - res, err := suite.Post(url, nil) + res, err := suite.PostJSON(url, body) suite.NoError(err) suite.Equal(500, res.StatusCode) } @@ -82,7 +83,7 @@ func (suite *ScanTestSuite) TestStopScan() { mock.OnAnything(suite.artifactCtl, "GetByReference").Return(nil, nil).Once() mock.OnAnything(suite.scanCtl, "Stop").Return(fmt.Errorf("nil artifact to stop scan")).Once() - res, err := suite.Post(url, nil) + res, err := suite.PostJSON(url, body) suite.NoError(err) suite.Equal(500, res.StatusCode) } @@ -92,7 +93,7 @@ func (suite *ScanTestSuite) TestStopScan() { mock.OnAnything(suite.artifactCtl, "GetByReference").Return(&artifact.Artifact{}, nil).Once() mock.OnAnything(suite.scanCtl, "Stop").Return(nil).Once() - res, err := suite.Post(url, nil) + res, err := suite.PostJSON(url, body) suite.NoError(err) suite.Equal(202, res.StatusCode) } diff --git a/src/testing/common/security/context.go b/src/testing/common/security/context.go index 59bc1dfe05..9f6c80f6bc 100644 --- a/src/testing/common/security/context.go +++ b/src/testing/common/security/context.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package security @@ -19,6 +19,10 @@ type Context struct { func (_m *Context) Can(ctx context.Context, action types.Action, resource types.Resource) bool { ret := _m.Called(ctx, action, resource) + if len(ret) == 0 { + panic("no return value specified for Can") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, types.Action, types.Resource) bool); ok { r0 = rf(ctx, action, resource) @@ -33,6 +37,10 @@ func (_m *Context) Can(ctx context.Context, action types.Action, resource types. func (_m *Context) GetUsername() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetUsername") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -47,6 +55,10 @@ func (_m *Context) GetUsername() string { func (_m *Context) IsAuthenticated() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsAuthenticated") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -61,6 +73,10 @@ func (_m *Context) IsAuthenticated() bool { func (_m *Context) IsSolutionUser() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsSolutionUser") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -75,6 +91,10 @@ func (_m *Context) IsSolutionUser() bool { func (_m *Context) IsSysAdmin() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsSysAdmin") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -89,6 +109,10 @@ func (_m *Context) IsSysAdmin() bool { func (_m *Context) Name() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Name") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/src/testing/controller/artifact/controller.go b/src/testing/controller/artifact/controller.go index 96e23c6eaf..8fe07f2978 100644 --- a/src/testing/controller/artifact/controller.go +++ b/src/testing/controller/artifact/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package artifact @@ -25,6 +25,10 @@ type Controller struct { func (_m *Controller) AddLabel(ctx context.Context, artifactID int64, labelID int64) error { ret := _m.Called(ctx, artifactID, labelID) + if len(ret) == 0 { + panic("no return value specified for AddLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, artifactID, labelID) @@ -39,6 +43,10 @@ func (_m *Controller) AddLabel(ctx context.Context, artifactID int64, labelID in func (_m *Controller) Copy(ctx context.Context, srcRepo string, reference string, dstRepo string) (int64, error) { ret := _m.Called(ctx, srcRepo, reference, dstRepo) + if len(ret) == 0 { + panic("no return value specified for Copy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (int64, error)); ok { @@ -63,6 +71,10 @@ func (_m *Controller) Copy(ctx context.Context, srcRepo string, reference string func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -87,6 +99,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -101,6 +117,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Ensure(ctx context.Context, repository string, digest string, option *artifact.ArtOption) (bool, int64, error) { ret := _m.Called(ctx, repository, digest, option) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 bool var r1 int64 var r2 error @@ -132,6 +152,10 @@ func (_m *Controller) Ensure(ctx context.Context, repository string, digest stri func (_m *Controller) Get(ctx context.Context, id int64, option *artifact.Option) (*artifact.Artifact, error) { ret := _m.Called(ctx, id, option) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *artifact.Option) (*artifact.Artifact, error)); ok { @@ -158,6 +182,10 @@ func (_m *Controller) Get(ctx context.Context, id int64, option *artifact.Option func (_m *Controller) GetAddition(ctx context.Context, artifactID int64, additionType string) (*processor.Addition, error) { ret := _m.Called(ctx, artifactID, additionType) + if len(ret) == 0 { + panic("no return value specified for GetAddition") + } + var r0 *processor.Addition var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*processor.Addition, error)); ok { @@ -184,6 +212,10 @@ func (_m *Controller) GetAddition(ctx context.Context, artifactID int64, additio func (_m *Controller) GetByReference(ctx context.Context, repository string, reference string, option *artifact.Option) (*artifact.Artifact, error) { ret := _m.Called(ctx, repository, reference, option) + if len(ret) == 0 { + panic("no return value specified for GetByReference") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, *artifact.Option) (*artifact.Artifact, error)); ok { @@ -206,10 +238,42 @@ func (_m *Controller) GetByReference(ctx context.Context, repository string, ref return r0, r1 } +// HasUnscannableLayer provides a mock function with given fields: ctx, dgst +func (_m *Controller) HasUnscannableLayer(ctx context.Context, dgst string) (bool, error) { + ret := _m.Called(ctx, dgst) + + if len(ret) == 0 { + panic("no return value specified for HasUnscannableLayer") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { + return rf(ctx, dgst) + } + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, dgst) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, dgst) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // List provides a mock function with given fields: ctx, query, option func (_m *Controller) List(ctx context.Context, query *q.Query, option *artifact.Option) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, query, option) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, *artifact.Option) ([]*artifact.Artifact, error)); ok { @@ -236,6 +300,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, option *artifact func (_m *Controller) RemoveLabel(ctx context.Context, artifactID int64, labelID int64) error { ret := _m.Called(ctx, artifactID, labelID) + if len(ret) == 0 { + panic("no return value specified for RemoveLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, artifactID, labelID) @@ -250,6 +318,10 @@ func (_m *Controller) RemoveLabel(ctx context.Context, artifactID int64, labelID func (_m *Controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, _a3 time.Time) error { ret := _m.Called(ctx, artifactID, tagID, _a3) + if len(ret) == 0 { + panic("no return value specified for UpdatePullTime") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64, time.Time) error); ok { r0 = rf(ctx, artifactID, tagID, _a3) @@ -264,6 +336,10 @@ func (_m *Controller) UpdatePullTime(ctx context.Context, artifactID int64, tagI func (_m *Controller) Walk(ctx context.Context, root *artifact.Artifact, walkFn func(*artifact.Artifact) error, option *artifact.Option) error { ret := _m.Called(ctx, root, walkFn, option) + if len(ret) == 0 { + panic("no return value specified for Walk") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, func(*artifact.Artifact) error, *artifact.Option) error); ok { r0 = rf(ctx, root, walkFn, option) diff --git a/src/testing/controller/blob/controller.go b/src/testing/controller/blob/controller.go index fa212af25d..3081f5e255 100644 --- a/src/testing/controller/blob/controller.go +++ b/src/testing/controller/blob/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package blob @@ -25,6 +25,10 @@ type Controller struct { func (_m *Controller) AssociateWithArtifact(ctx context.Context, blobDigests []string, artifactDigest string) error { ret := _m.Called(ctx, blobDigests, artifactDigest) + if len(ret) == 0 { + panic("no return value specified for AssociateWithArtifact") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string) error); ok { r0 = rf(ctx, blobDigests, artifactDigest) @@ -39,6 +43,10 @@ func (_m *Controller) AssociateWithArtifact(ctx context.Context, blobDigests []s func (_m *Controller) AssociateWithProjectByDigest(ctx context.Context, blobDigest string, projectID int64) error { ret := _m.Called(ctx, blobDigest, projectID) + if len(ret) == 0 { + panic("no return value specified for AssociateWithProjectByDigest") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, blobDigest, projectID) @@ -53,6 +61,10 @@ func (_m *Controller) AssociateWithProjectByDigest(ctx context.Context, blobDige func (_m *Controller) AssociateWithProjectByID(ctx context.Context, blobID int64, projectID int64) error { ret := _m.Called(ctx, blobID, projectID) + if len(ret) == 0 { + panic("no return value specified for AssociateWithProjectByID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, blobID, projectID) @@ -67,6 +79,10 @@ func (_m *Controller) AssociateWithProjectByID(ctx context.Context, blobID int64 func (_m *Controller) CalculateTotalSize(ctx context.Context, excludeForeign bool) (int64, error) { ret := _m.Called(ctx, excludeForeign) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool) (int64, error)); ok { @@ -91,6 +107,10 @@ func (_m *Controller) CalculateTotalSize(ctx context.Context, excludeForeign boo func (_m *Controller) CalculateTotalSizeByProject(ctx context.Context, projectID int64, excludeForeign bool) (int64, error) { ret := _m.Called(ctx, projectID, excludeForeign) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSizeByProject") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) (int64, error)); ok { @@ -115,6 +135,10 @@ func (_m *Controller) CalculateTotalSizeByProject(ctx context.Context, projectID func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -129,6 +153,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Ensure(ctx context.Context, digest string, contentType string, size int64) (int64, error) { ret := _m.Called(ctx, digest, contentType, size) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (int64, error)); ok { @@ -160,6 +188,10 @@ func (_m *Controller) Exist(ctx context.Context, digest string, options ...blob. _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Exist") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, ...blob.Option) (bool, error)); ok { @@ -184,6 +216,10 @@ func (_m *Controller) Exist(ctx context.Context, digest string, options ...blob. func (_m *Controller) Fail(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Fail") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) @@ -198,6 +234,10 @@ func (_m *Controller) Fail(ctx context.Context, _a1 *models.Blob) error { func (_m *Controller) FindMissingAssociationsForProject(ctx context.Context, projectID int64, blobs []*models.Blob) ([]*models.Blob, error) { ret := _m.Called(ctx, projectID, blobs) + if len(ret) == 0 { + panic("no return value specified for FindMissingAssociationsForProject") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Blob) ([]*models.Blob, error)); ok { @@ -231,6 +271,10 @@ func (_m *Controller) Get(ctx context.Context, digest string, options ...blob.Op _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, ...blob.Option) (*models.Blob, error)); ok { @@ -257,6 +301,10 @@ func (_m *Controller) Get(ctx context.Context, digest string, options ...blob.Op func (_m *Controller) GetAcceptedBlobSize(ctx context.Context, sessionID string) (int64, error) { ret := _m.Called(ctx, sessionID) + if len(ret) == 0 { + panic("no return value specified for GetAcceptedBlobSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (int64, error)); ok { @@ -281,6 +329,10 @@ func (_m *Controller) GetAcceptedBlobSize(ctx context.Context, sessionID string) func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.Blob, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Blob, error)); ok { @@ -307,6 +359,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.Blob, func (_m *Controller) SetAcceptedBlobSize(ctx context.Context, sessionID string, size int64) error { ret := _m.Called(ctx, sessionID, size) + if len(ret) == 0 { + panic("no return value specified for SetAcceptedBlobSize") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, sessionID, size) @@ -321,6 +377,10 @@ func (_m *Controller) SetAcceptedBlobSize(ctx context.Context, sessionID string, func (_m *Controller) Sync(ctx context.Context, references []distribution.Descriptor) error { ret := _m.Called(ctx, references) + if len(ret) == 0 { + panic("no return value specified for Sync") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []distribution.Descriptor) error); ok { r0 = rf(ctx, references) @@ -335,6 +395,10 @@ func (_m *Controller) Sync(ctx context.Context, references []distribution.Descri func (_m *Controller) Touch(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Touch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) @@ -349,6 +413,10 @@ func (_m *Controller) Touch(ctx context.Context, _a1 *models.Blob) error { func (_m *Controller) Update(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/controller/config/controller.go b/src/testing/controller/config/controller.go index 8fd145413a..c82468518e 100644 --- a/src/testing/controller/config/controller.go +++ b/src/testing/controller/config/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package config @@ -18,6 +18,10 @@ type Controller struct { func (_m *Controller) AllConfigs(ctx context.Context) (map[string]interface{}, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for AllConfigs") + } + var r0 map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]interface{}, error)); ok { @@ -44,6 +48,10 @@ func (_m *Controller) AllConfigs(ctx context.Context) (map[string]interface{}, e func (_m *Controller) ConvertForGet(ctx context.Context, cfg map[string]interface{}, internal bool) (map[string]*models.Value, error) { ret := _m.Called(ctx, cfg, internal) + if len(ret) == 0 { + panic("no return value specified for ConvertForGet") + } + var r0 map[string]*models.Value var r1 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}, bool) (map[string]*models.Value, error)); ok { @@ -70,6 +78,10 @@ func (_m *Controller) ConvertForGet(ctx context.Context, cfg map[string]interfac func (_m *Controller) OverwriteConfig(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for OverwriteConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -84,6 +96,10 @@ func (_m *Controller) OverwriteConfig(ctx context.Context) error { func (_m *Controller) UpdateUserConfigs(ctx context.Context, conf map[string]interface{}) error { ret := _m.Called(ctx, conf) + if len(ret) == 0 { + panic("no return value specified for UpdateUserConfigs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) error); ok { r0 = rf(ctx, conf) @@ -98,6 +114,10 @@ func (_m *Controller) UpdateUserConfigs(ctx context.Context, conf map[string]int func (_m *Controller) UserConfigs(ctx context.Context) (map[string]*models.Value, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for UserConfigs") + } + var r0 map[string]*models.Value var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]*models.Value, error)); ok { diff --git a/src/testing/controller/jobservice/scheduler_controller.go b/src/testing/controller/jobservice/scheduler_controller.go index 806214fd27..4f5a09328f 100644 --- a/src/testing/controller/jobservice/scheduler_controller.go +++ b/src/testing/controller/jobservice/scheduler_controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobservice @@ -21,6 +21,10 @@ type SchedulerController struct { func (_m *SchedulerController) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *SchedulerController) Count(ctx context.Context, query *q.Query) (int64 func (_m *SchedulerController) Create(ctx context.Context, vendorType string, cronType string, cron string, callbackFuncName string, policy interface{}, extrasParam map[string]interface{}) (int64, error) { ret := _m.Called(ctx, vendorType, cronType, cron, callbackFuncName, policy, extrasParam) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, interface{}, map[string]interface{}) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *SchedulerController) Create(ctx context.Context, vendorType string, cr func (_m *SchedulerController) Delete(ctx context.Context, vendorType string) error { ret := _m.Called(ctx, vendorType) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, vendorType) @@ -83,6 +95,10 @@ func (_m *SchedulerController) Delete(ctx context.Context, vendorType string) er func (_m *SchedulerController) Get(ctx context.Context, vendorType string) (*scheduler.Schedule, error) { ret := _m.Called(ctx, vendorType) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*scheduler.Schedule, error)); ok { @@ -109,6 +125,10 @@ func (_m *SchedulerController) Get(ctx context.Context, vendorType string) (*sch func (_m *SchedulerController) List(ctx context.Context, query *q.Query) ([]*scheduler.Schedule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scheduler.Schedule, error)); ok { @@ -135,6 +155,10 @@ func (_m *SchedulerController) List(ctx context.Context, query *q.Query) ([]*sch func (_m *SchedulerController) Paused(ctx context.Context) (bool, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Paused") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { diff --git a/src/testing/controller/project/controller.go b/src/testing/controller/project/controller.go index 69f3563e66..bcb9331b2b 100644 --- a/src/testing/controller/project/controller.go +++ b/src/testing/controller/project/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package project @@ -25,6 +25,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -49,6 +53,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Create(ctx context.Context, _a1 *models.Project) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.Project) (int64, error)); ok { @@ -73,6 +81,10 @@ func (_m *Controller) Create(ctx context.Context, _a1 *models.Project) (int64, e func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -87,6 +99,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Exists(ctx context.Context, projectIDOrName interface{}) (bool, error) { ret := _m.Called(ctx, projectIDOrName) + if len(ret) == 0 { + panic("no return value specified for Exists") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, interface{}) (bool, error)); ok { @@ -118,6 +134,10 @@ func (_m *Controller) Get(ctx context.Context, projectIDOrName interface{}, opti _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, interface{}, ...project.Option) (*models.Project, error)); ok { @@ -151,6 +171,10 @@ func (_m *Controller) GetByName(ctx context.Context, projectName string, options _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, ...project.Option) (*models.Project, error)); ok { @@ -184,6 +208,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...proje _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...project.Option) ([]*models.Project, error)); ok { @@ -210,6 +238,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...proje func (_m *Controller) ListRoles(ctx context.Context, projectID int64, u *commonmodels.User) ([]int, error) { ret := _m.Called(ctx, projectID, u) + if len(ret) == 0 { + panic("no return value specified for ListRoles") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *commonmodels.User) ([]int, error)); ok { @@ -236,6 +268,10 @@ func (_m *Controller) ListRoles(ctx context.Context, projectID int64, u *commonm func (_m *Controller) Update(ctx context.Context, _a1 *models.Project) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Project) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/controller/proxy/remote_interface.go b/src/testing/controller/proxy/remote_interface.go index eb93612805..48b52a094a 100644 --- a/src/testing/controller/proxy/remote_interface.go +++ b/src/testing/controller/proxy/remote_interface.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package proxy @@ -19,6 +19,10 @@ type RemoteInterface struct { func (_m *RemoteInterface) BlobReader(repo string, dig string) (int64, io.ReadCloser, error) { ret := _m.Called(repo, dig) + if len(ret) == 0 { + panic("no return value specified for BlobReader") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -52,6 +56,10 @@ func (_m *RemoteInterface) BlobReader(repo string, dig string) (int64, io.ReadCl func (_m *RemoteInterface) ListTags(repo string) ([]string, error) { ret := _m.Called(repo) + if len(ret) == 0 { + panic("no return value specified for ListTags") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { @@ -78,6 +86,10 @@ func (_m *RemoteInterface) ListTags(repo string) ([]string, error) { func (_m *RemoteInterface) Manifest(repo string, ref string) (distribution.Manifest, string, error) { ret := _m.Called(repo, ref) + if len(ret) == 0 { + panic("no return value specified for Manifest") + } + var r0 distribution.Manifest var r1 string var r2 error @@ -111,6 +123,10 @@ func (_m *RemoteInterface) Manifest(repo string, ref string) (distribution.Manif func (_m *RemoteInterface) ManifestExist(repo string, ref string) (bool, *distribution.Descriptor, error) { ret := _m.Called(repo, ref) + if len(ret) == 0 { + panic("no return value specified for ManifestExist") + } + var r0 bool var r1 *distribution.Descriptor var r2 error diff --git a/src/testing/controller/purge/controller.go b/src/testing/controller/purge/controller.go index 3771e28e8e..7c3e27fedb 100644 --- a/src/testing/controller/purge/controller.go +++ b/src/testing/controller/purge/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package purge @@ -18,6 +18,10 @@ type Controller struct { func (_m *Controller) Start(ctx context.Context, policy purge.JobPolicy, trigger string) (int64, error) { ret := _m.Called(ctx, policy, trigger) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, purge.JobPolicy, string) (int64, error)); ok { @@ -42,6 +46,10 @@ func (_m *Controller) Start(ctx context.Context, policy purge.JobPolicy, trigger func (_m *Controller) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) diff --git a/src/testing/controller/quota/controller.go b/src/testing/controller/quota/controller.go index 23b10a091c..7053fb4d74 100644 --- a/src/testing/controller/quota/controller.go +++ b/src/testing/controller/quota/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package quota @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -55,6 +59,10 @@ func (_m *Controller) Create(ctx context.Context, reference string, referenceID _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, types.ResourceList, ...types.ResourceList) (int64, error)); ok { @@ -79,6 +87,10 @@ func (_m *Controller) Create(ctx context.Context, reference string, referenceID func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -100,6 +112,10 @@ func (_m *Controller) Get(ctx context.Context, id int64, options ...quota.Option _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...quota.Option) (*models.Quota, error)); ok { @@ -133,6 +149,10 @@ func (_m *Controller) GetByRef(ctx context.Context, reference string, referenceI _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetByRef") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, ...quota.Option) (*models.Quota, error)); ok { @@ -159,6 +179,10 @@ func (_m *Controller) GetByRef(ctx context.Context, reference string, referenceI func (_m *Controller) IsEnabled(ctx context.Context, reference string, referenceID string) (bool, error) { ret := _m.Called(ctx, reference, referenceID) + if len(ret) == 0 { + panic("no return value specified for IsEnabled") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { @@ -190,6 +214,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...quota _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...quota.Option) ([]*models.Quota, error)); ok { @@ -223,6 +251,10 @@ func (_m *Controller) Refresh(ctx context.Context, reference string, referenceID _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Refresh") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, ...quota.Option) error); ok { r0 = rf(ctx, reference, referenceID, options...) @@ -237,6 +269,10 @@ func (_m *Controller) Refresh(ctx context.Context, reference string, referenceID func (_m *Controller) Request(ctx context.Context, reference string, referenceID string, resources types.ResourceList, f func() error) error { ret := _m.Called(ctx, reference, referenceID, resources, f) + if len(ret) == 0 { + panic("no return value specified for Request") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, types.ResourceList, func() error) error); ok { r0 = rf(ctx, reference, referenceID, resources, f) @@ -251,6 +287,10 @@ func (_m *Controller) Request(ctx context.Context, reference string, referenceID func (_m *Controller) Update(ctx context.Context, _a1 *models.Quota) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Quota) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/controller/replication/controller.go b/src/testing/controller/replication/controller.go index 76138da960..02a2afbb59 100644 --- a/src/testing/controller/replication/controller.go +++ b/src/testing/controller/replication/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package replication @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for CreatePolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (i func (_m *Controller) DeletePolicy(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeletePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -62,6 +70,10 @@ func (_m *Controller) DeletePolicy(ctx context.Context, id int64) error { func (_m *Controller) ExecutionCount(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ExecutionCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -86,6 +98,10 @@ func (_m *Controller) ExecutionCount(ctx context.Context, query *q.Query) (int64 func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*replication.Execution, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetExecution") + } + var r0 *replication.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*replication.Execution, error)); ok { @@ -112,6 +128,10 @@ func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*rep func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetPolicy") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -138,6 +158,10 @@ func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, e func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*replication.Task, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTask") + } + var r0 *replication.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*replication.Task, error)); ok { @@ -164,6 +188,10 @@ func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*replication.T func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTaskLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -190,6 +218,10 @@ func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, err func (_m *Controller) ListExecutions(ctx context.Context, query *q.Query) ([]*replication.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListExecutions") + } + var r0 []*replication.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*replication.Execution, error)); ok { @@ -216,6 +248,10 @@ func (_m *Controller) ListExecutions(ctx context.Context, query *q.Query) ([]*re func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPolicies") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -242,6 +278,10 @@ func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*mode func (_m *Controller) ListTasks(ctx context.Context, query *q.Query) ([]*replication.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListTasks") + } + var r0 []*replication.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*replication.Task, error)); ok { @@ -268,6 +308,10 @@ func (_m *Controller) ListTasks(ctx context.Context, query *q.Query) ([]*replica func (_m *Controller) PolicyCount(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for PolicyCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -292,6 +336,10 @@ func (_m *Controller) PolicyCount(ctx context.Context, query *q.Query) (int64, e func (_m *Controller) Start(ctx context.Context, policy *model.Policy, resource *regmodel.Resource, trigger string) (int64, error) { ret := _m.Called(ctx, policy, resource, trigger) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, *regmodel.Resource, string) (int64, error)); ok { @@ -316,6 +364,10 @@ func (_m *Controller) Start(ctx context.Context, policy *model.Policy, resource func (_m *Controller) Stop(ctx context.Context, executionID int64) error { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, executionID) @@ -330,6 +382,10 @@ func (_m *Controller) Stop(ctx context.Context, executionID int64) error { func (_m *Controller) TaskCount(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for TaskCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -361,6 +417,10 @@ func (_m *Controller) UpdatePolicy(ctx context.Context, policy *model.Policy, pr _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdatePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, ...string) error); ok { r0 = rf(ctx, policy, props...) diff --git a/src/testing/controller/repository/controller.go b/src/testing/controller/repository/controller.go index 92896a00c3..bdc1097705 100644 --- a/src/testing/controller/repository/controller.go +++ b/src/testing/controller/repository/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package repository @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) AddPullCount(ctx context.Context, id int64, count uint64) error { ret := _m.Called(ctx, id, count) + if len(ret) == 0 { + panic("no return value specified for AddPullCount") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, uint64) error); ok { r0 = rf(ctx, id, count) @@ -34,6 +38,10 @@ func (_m *Controller) AddPullCount(ctx context.Context, id int64, count uint64) func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -58,6 +66,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -72,6 +84,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Ensure(ctx context.Context, name string) (bool, int64, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 bool var r1 int64 var r2 error @@ -103,6 +119,10 @@ func (_m *Controller) Ensure(ctx context.Context, name string) (bool, int64, err func (_m *Controller) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.RepoRecord, error)); ok { @@ -129,6 +149,10 @@ func (_m *Controller) Get(ctx context.Context, id int64) (*model.RepoRecord, err func (_m *Controller) GetByName(ctx context.Context, name string) (*model.RepoRecord, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.RepoRecord, error)); ok { @@ -155,6 +179,10 @@ func (_m *Controller) GetByName(ctx context.Context, name string) (*model.RepoRe func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RepoRecord, error)); ok { @@ -188,6 +216,10 @@ func (_m *Controller) Update(ctx context.Context, _a1 *model.RepoRecord, propert _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord, ...string) error); ok { r0 = rf(ctx, _a1, properties...) diff --git a/src/testing/controller/retention/controller.go b/src/testing/controller/retention/controller.go index d27ce8d0a3..8eb3c98895 100644 --- a/src/testing/controller/retention/controller.go +++ b/src/testing/controller/retention/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package retention @@ -22,6 +22,10 @@ type Controller struct { func (_m *Controller) CreateRetention(ctx context.Context, p *policy.Metadata) (int64, error) { ret := _m.Called(ctx, p) + if len(ret) == 0 { + panic("no return value specified for CreateRetention") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *policy.Metadata) (int64, error)); ok { @@ -46,6 +50,10 @@ func (_m *Controller) CreateRetention(ctx context.Context, p *policy.Metadata) ( func (_m *Controller) DeleteRetention(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteRetention") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -60,6 +68,10 @@ func (_m *Controller) DeleteRetention(ctx context.Context, id int64) error { func (_m *Controller) DeleteRetentionByProject(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteRetentionByProject") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -74,6 +86,10 @@ func (_m *Controller) DeleteRetentionByProject(ctx context.Context, projectID in func (_m *Controller) GetRetention(ctx context.Context, id int64) (*policy.Metadata, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetRetention") + } + var r0 *policy.Metadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*policy.Metadata, error)); ok { @@ -100,6 +116,10 @@ func (_m *Controller) GetRetention(ctx context.Context, id int64) (*policy.Metad func (_m *Controller) GetRetentionExec(ctx context.Context, eid int64) (*pkgretention.Execution, error) { ret := _m.Called(ctx, eid) + if len(ret) == 0 { + panic("no return value specified for GetRetentionExec") + } + var r0 *pkgretention.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgretention.Execution, error)); ok { @@ -126,6 +146,10 @@ func (_m *Controller) GetRetentionExec(ctx context.Context, eid int64) (*pkgrete func (_m *Controller) GetRetentionExecTask(ctx context.Context, taskID int64) (*pkgretention.Task, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetRetentionExecTask") + } + var r0 *pkgretention.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgretention.Task, error)); ok { @@ -152,6 +176,10 @@ func (_m *Controller) GetRetentionExecTask(ctx context.Context, taskID int64) (* func (_m *Controller) GetRetentionExecTaskLog(ctx context.Context, taskID int64) ([]byte, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetRetentionExecTaskLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -178,6 +206,10 @@ func (_m *Controller) GetRetentionExecTaskLog(ctx context.Context, taskID int64) func (_m *Controller) GetTotalOfRetentionExecTasks(ctx context.Context, executionID int64) (int64, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfRetentionExecTasks") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { @@ -202,6 +234,10 @@ func (_m *Controller) GetTotalOfRetentionExecTasks(ctx context.Context, executio func (_m *Controller) GetTotalOfRetentionExecs(ctx context.Context, policyID int64) (int64, error) { ret := _m.Called(ctx, policyID) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfRetentionExecs") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { @@ -226,6 +262,10 @@ func (_m *Controller) GetTotalOfRetentionExecs(ctx context.Context, policyID int func (_m *Controller) ListRetentionExecTasks(ctx context.Context, executionID int64, query *q.Query) ([]*pkgretention.Task, error) { ret := _m.Called(ctx, executionID, query) + if len(ret) == 0 { + panic("no return value specified for ListRetentionExecTasks") + } + var r0 []*pkgretention.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*pkgretention.Task, error)); ok { @@ -252,6 +292,10 @@ func (_m *Controller) ListRetentionExecTasks(ctx context.Context, executionID in func (_m *Controller) ListRetentionExecs(ctx context.Context, policyID int64, query *q.Query) ([]*pkgretention.Execution, error) { ret := _m.Called(ctx, policyID, query) + if len(ret) == 0 { + panic("no return value specified for ListRetentionExecs") + } + var r0 []*pkgretention.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*pkgretention.Execution, error)); ok { @@ -278,6 +322,10 @@ func (_m *Controller) ListRetentionExecs(ctx context.Context, policyID int64, qu func (_m *Controller) OperateRetentionExec(ctx context.Context, eid int64, action string) error { ret := _m.Called(ctx, eid, action) + if len(ret) == 0 { + panic("no return value specified for OperateRetentionExec") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, eid, action) @@ -292,6 +340,10 @@ func (_m *Controller) OperateRetentionExec(ctx context.Context, eid int64, actio func (_m *Controller) TriggerRetentionExec(ctx context.Context, policyID int64, trigger string, dryRun bool) (int64, error) { ret := _m.Called(ctx, policyID, trigger, dryRun) + if len(ret) == 0 { + panic("no return value specified for TriggerRetentionExec") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string, bool) (int64, error)); ok { @@ -316,6 +368,10 @@ func (_m *Controller) TriggerRetentionExec(ctx context.Context, policyID int64, func (_m *Controller) UpdateRetention(ctx context.Context, p *policy.Metadata) error { ret := _m.Called(ctx, p) + if len(ret) == 0 { + panic("no return value specified for UpdateRetention") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *policy.Metadata) error); ok { r0 = rf(ctx, p) diff --git a/src/testing/controller/robot/controller.go b/src/testing/controller/robot/controller.go index ab54afe598..03c6af4838 100644 --- a/src/testing/controller/robot/controller.go +++ b/src/testing/controller/robot/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package robot @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Create(ctx context.Context, r *robot.Robot) (int64, string, error) { ret := _m.Called(ctx, r) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 string var r2 error @@ -75,6 +83,10 @@ func (_m *Controller) Create(ctx context.Context, r *robot.Robot) (int64, string func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -89,6 +101,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Get(ctx context.Context, id int64, option *robot.Option) (*robot.Robot, error) { ret := _m.Called(ctx, id, option) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *robot.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *robot.Option) (*robot.Robot, error)); ok { @@ -115,6 +131,10 @@ func (_m *Controller) Get(ctx context.Context, id int64, option *robot.Option) ( func (_m *Controller) List(ctx context.Context, query *q.Query, option *robot.Option) ([]*robot.Robot, error) { ret := _m.Called(ctx, query, option) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*robot.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, *robot.Option) ([]*robot.Robot, error)); ok { @@ -141,6 +161,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, option *robot.Op func (_m *Controller) Update(ctx context.Context, r *robot.Robot, option *robot.Option) error { ret := _m.Called(ctx, r, option) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *robot.Robot, *robot.Option) error); ok { r0 = rf(ctx, r, option) diff --git a/src/testing/controller/scan/checker.go b/src/testing/controller/scan/checker.go index 71f1601858..9c2e4b6743 100644 --- a/src/testing/controller/scan/checker.go +++ b/src/testing/controller/scan/checker.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scan @@ -19,6 +19,10 @@ type Checker struct { func (_m *Checker) IsScannable(ctx context.Context, _a1 *artifact.Artifact) (bool, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for IsScannable") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) (bool, error)); ok { diff --git a/src/testing/controller/scan/controller.go b/src/testing/controller/scan/controller.go index 2efdd80919..5ec8091a49 100644 --- a/src/testing/controller/scan/controller.go +++ b/src/testing/controller/scan/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scan @@ -32,6 +32,10 @@ func (_m *Controller) DeleteReports(ctx context.Context, digests ...string) erro _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for DeleteReports") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { r0 = rf(ctx, digests...) @@ -46,6 +50,10 @@ func (_m *Controller) DeleteReports(ctx context.Context, digests ...string) erro func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) ([]*daoscan.Report, error) { ret := _m.Called(ctx, _a1, mimeTypes) + if len(ret) == 0 { + panic("no return value specified for GetReport") + } + var r0 []*daoscan.Report var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) ([]*daoscan.Report, error)); ok { @@ -72,6 +80,10 @@ func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mim func (_m *Controller) GetScanLog(ctx context.Context, art *artifact.Artifact, uuid string) ([]byte, error) { ret := _m.Called(ctx, art, uuid) + if len(ret) == 0 { + panic("no return value specified for GetScanLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string) ([]byte, error)); ok { @@ -98,6 +110,10 @@ func (_m *Controller) GetScanLog(ctx context.Context, art *artifact.Artifact, uu func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) { ret := _m.Called(ctx, _a1, mimeTypes) + if len(ret) == 0 { + panic("no return value specified for GetSummary") + } + var r0 map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) (map[string]interface{}, error)); ok { @@ -124,6 +140,10 @@ func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mi func (_m *Controller) GetVulnerable(ctx context.Context, _a1 *artifact.Artifact, allowlist models.CVESet, allowlistIsExpired bool) (*scan.Vulnerable, error) { ret := _m.Called(ctx, _a1, allowlist, allowlistIsExpired) + if len(ret) == 0 { + panic("no return value specified for GetVulnerable") + } + var r0 *scan.Vulnerable var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, models.CVESet, bool) (*scan.Vulnerable, error)); ok { @@ -157,6 +177,10 @@ func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Scan") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, ...scan.Option) error); ok { r0 = rf(ctx, _a1, options...) @@ -171,6 +195,10 @@ func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) (int64, error) { ret := _m.Called(ctx, trigger, async) + if len(ret) == 0 { + panic("no return value specified for ScanAll") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, bool) (int64, error)); ok { @@ -191,13 +219,17 @@ func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) ( return r0, r1 } -// Stop provides a mock function with given fields: ctx, _a1 -func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact) error { - ret := _m.Called(ctx, _a1) +// Stop provides a mock function with given fields: ctx, _a1, capType +func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact, capType string) error { + ret := _m.Called(ctx, _a1, capType) + + if len(ret) == 0 { + panic("no return value specified for Stop") + } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) error); ok { - r0 = rf(ctx, _a1) + if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string) error); ok { + r0 = rf(ctx, _a1, capType) } else { r0 = ret.Error(0) } @@ -209,6 +241,10 @@ func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact) error { func (_m *Controller) StopScanAll(ctx context.Context, executionID int64, async bool) error { ret := _m.Called(ctx, executionID, async) + if len(ret) == 0 { + panic("no return value specified for StopScanAll") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { r0 = rf(ctx, executionID, async) diff --git a/src/testing/controller/scandataexport/controller.go b/src/testing/controller/scandataexport/controller.go index 08b93c74be..b75de2fc86 100644 --- a/src/testing/controller/scandataexport/controller.go +++ b/src/testing/controller/scandataexport/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scandataexport @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) DeleteExecution(ctx context.Context, executionID int64) error { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for DeleteExecution") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, executionID) @@ -34,6 +38,10 @@ func (_m *Controller) DeleteExecution(ctx context.Context, executionID int64) er func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*export.Execution, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetExecution") + } + var r0 *export.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*export.Execution, error)); ok { @@ -60,6 +68,10 @@ func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*exp func (_m *Controller) GetTask(ctx context.Context, executionID int64) (*task.Task, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetTask") + } + var r0 *task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Task, error)); ok { @@ -86,6 +98,10 @@ func (_m *Controller) GetTask(ctx context.Context, executionID int64) (*task.Tas func (_m *Controller) ListExecutions(ctx context.Context, userName string) ([]*export.Execution, error) { ret := _m.Called(ctx, userName) + if len(ret) == 0 { + panic("no return value specified for ListExecutions") + } + var r0 []*export.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*export.Execution, error)); ok { @@ -112,6 +128,10 @@ func (_m *Controller) ListExecutions(ctx context.Context, userName string) ([]*e func (_m *Controller) Start(ctx context.Context, criteria export.Request) (int64, error) { ret := _m.Called(ctx, criteria) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, export.Request) (int64, error)); ok { diff --git a/src/testing/controller/scanner/controller.go b/src/testing/controller/scanner/controller.go index b07a82f5df..38d66f9b9c 100644 --- a/src/testing/controller/scanner/controller.go +++ b/src/testing/controller/scanner/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scanner @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) CreateRegistration(ctx context.Context, registration *scanner.Registration) (string, error) { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for CreateRegistration") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) (string, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) CreateRegistration(ctx context.Context, registration *scan func (_m *Controller) DeleteRegistration(ctx context.Context, registrationUUID string) (*scanner.Registration, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for DeleteRegistration") + } + var r0 *scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*scanner.Registration, error)); ok { @@ -74,6 +82,10 @@ func (_m *Controller) DeleteRegistration(ctx context.Context, registrationUUID s func (_m *Controller) GetMetadata(ctx context.Context, registrationUUID string) (*v1.ScannerAdapterMetadata, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for GetMetadata") + } + var r0 *v1.ScannerAdapterMetadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*v1.ScannerAdapterMetadata, error)); ok { @@ -100,6 +112,10 @@ func (_m *Controller) GetMetadata(ctx context.Context, registrationUUID string) func (_m *Controller) GetRegistration(ctx context.Context, registrationUUID string) (*scanner.Registration, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for GetRegistration") + } + var r0 *scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*scanner.Registration, error)); ok { @@ -133,6 +149,10 @@ func (_m *Controller) GetRegistrationByProject(ctx context.Context, projectID in _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetRegistrationByProject") + } + var r0 *scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...controllerscanner.Option) (*scanner.Registration, error)); ok { @@ -159,6 +179,10 @@ func (_m *Controller) GetRegistrationByProject(ctx context.Context, projectID in func (_m *Controller) GetTotalOfRegistrations(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfRegistrations") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -183,6 +207,10 @@ func (_m *Controller) GetTotalOfRegistrations(ctx context.Context, query *q.Quer func (_m *Controller) ListRegistrations(ctx context.Context, query *q.Query) ([]*scanner.Registration, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListRegistrations") + } + var r0 []*scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scanner.Registration, error)); ok { @@ -209,6 +237,10 @@ func (_m *Controller) ListRegistrations(ctx context.Context, query *q.Query) ([] func (_m *Controller) Ping(ctx context.Context, registration *scanner.Registration) (*v1.ScannerAdapterMetadata, error) { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 *v1.ScannerAdapterMetadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) (*v1.ScannerAdapterMetadata, error)); ok { @@ -235,6 +267,10 @@ func (_m *Controller) Ping(ctx context.Context, registration *scanner.Registrati func (_m *Controller) RegistrationExists(ctx context.Context, registrationUUID string) bool { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for RegistrationExists") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { r0 = rf(ctx, registrationUUID) @@ -245,10 +281,32 @@ func (_m *Controller) RegistrationExists(ctx context.Context, registrationUUID s return r0 } +// RetrieveCap provides a mock function with given fields: ctx, r +func (_m *Controller) RetrieveCap(ctx context.Context, r *scanner.Registration) error { + ret := _m.Called(ctx, r) + + if len(ret) == 0 { + panic("no return value specified for RetrieveCap") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) error); ok { + r0 = rf(ctx, r) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // SetDefaultRegistration provides a mock function with given fields: ctx, registrationUUID func (_m *Controller) SetDefaultRegistration(ctx context.Context, registrationUUID string) error { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for SetDefaultRegistration") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, registrationUUID) @@ -263,6 +321,10 @@ func (_m *Controller) SetDefaultRegistration(ctx context.Context, registrationUU func (_m *Controller) SetRegistrationByProject(ctx context.Context, projectID int64, scannerID string) error { ret := _m.Called(ctx, projectID, scannerID) + if len(ret) == 0 { + panic("no return value specified for SetRegistrationByProject") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, projectID, scannerID) @@ -277,6 +339,10 @@ func (_m *Controller) SetRegistrationByProject(ctx context.Context, projectID in func (_m *Controller) UpdateRegistration(ctx context.Context, registration *scanner.Registration) error { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for UpdateRegistration") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) error); ok { r0 = rf(ctx, registration) diff --git a/src/testing/controller/securityhub/controller.go b/src/testing/controller/securityhub/controller.go index 55449b37e5..5382fb2b1a 100644 --- a/src/testing/controller/securityhub/controller.go +++ b/src/testing/controller/securityhub/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package securityhub @@ -22,6 +22,10 @@ type Controller struct { func (_m *Controller) CountVuls(ctx context.Context, scannerUUID string, projectID int64, tuneCount bool, query *q.Query) (int64, error) { ret := _m.Called(ctx, scannerUUID, projectID, tuneCount, query) + if len(ret) == 0 { + panic("no return value specified for CountVuls") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, bool, *q.Query) (int64, error)); ok { @@ -46,6 +50,10 @@ func (_m *Controller) CountVuls(ctx context.Context, scannerUUID string, project func (_m *Controller) ListVuls(ctx context.Context, scannerUUID string, projectID int64, withTag bool, query *q.Query) ([]*model.VulnerabilityItem, error) { ret := _m.Called(ctx, scannerUUID, projectID, withTag, query) + if len(ret) == 0 { + panic("no return value specified for ListVuls") + } + var r0 []*model.VulnerabilityItem var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, bool, *q.Query) ([]*model.VulnerabilityItem, error)); ok { @@ -79,6 +87,10 @@ func (_m *Controller) SecuritySummary(ctx context.Context, projectID int64, opti _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for SecuritySummary") + } + var r0 *model.Summary var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...securityhub.Option) (*model.Summary, error)); ok { diff --git a/src/testing/controller/systemartifact/controller.go b/src/testing/controller/systemartifact/controller.go index 73c4824b44..52354277ad 100644 --- a/src/testing/controller/systemartifact/controller.go +++ b/src/testing/controller/systemartifact/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package systemartifact @@ -17,6 +17,10 @@ type Controller struct { func (_m *Controller) Start(ctx context.Context, async bool, trigger string) error { ret := _m.Called(ctx, async, trigger) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, bool, string) error); ok { r0 = rf(ctx, async, trigger) diff --git a/src/testing/controller/task/controller.go b/src/testing/controller/task/controller.go index 256b9774aa..4e542513de 100644 --- a/src/testing/controller/task/controller.go +++ b/src/testing/controller/task/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Get(ctx context.Context, id int64) (*pkgtask.Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *pkgtask.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgtask.Task, error)); ok { @@ -70,6 +78,10 @@ func (_m *Controller) Get(ctx context.Context, id int64) (*pkgtask.Task, error) func (_m *Controller) GetLog(ctx context.Context, id int64) ([]byte, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -96,6 +108,10 @@ func (_m *Controller) GetLog(ctx context.Context, id int64) ([]byte, error) { func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*pkgtask.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*pkgtask.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*pkgtask.Task, error)); ok { @@ -122,6 +138,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*pkgtask.Task func (_m *Controller) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) diff --git a/src/testing/controller/task/execution_controller.go b/src/testing/controller/task/execution_controller.go index 22580b6f3a..5501b14de7 100644 --- a/src/testing/controller/task/execution_controller.go +++ b/src/testing/controller/task/execution_controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type ExecutionController struct { func (_m *ExecutionController) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *ExecutionController) Count(ctx context.Context, query *q.Query) (int64 func (_m *ExecutionController) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -58,6 +66,10 @@ func (_m *ExecutionController) Delete(ctx context.Context, id int64) error { func (_m *ExecutionController) Get(ctx context.Context, id int64) (*pkgtask.Execution, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *pkgtask.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgtask.Execution, error)); ok { @@ -84,6 +96,10 @@ func (_m *ExecutionController) Get(ctx context.Context, id int64) (*pkgtask.Exec func (_m *ExecutionController) List(ctx context.Context, query *q.Query) ([]*pkgtask.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*pkgtask.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*pkgtask.Execution, error)); ok { @@ -110,6 +126,10 @@ func (_m *ExecutionController) List(ctx context.Context, query *q.Query) ([]*pkg func (_m *ExecutionController) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) diff --git a/src/testing/controller/user/controller.go b/src/testing/controller/user/controller.go index 17f7ea5ef6..5976018d4b 100644 --- a/src/testing/controller/user/controller.go +++ b/src/testing/controller/user/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package user @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Create(ctx context.Context, u *models.User) (int, error) { ret := _m.Called(ctx, u) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.User) (int, error)); ok { @@ -72,6 +80,10 @@ func (_m *Controller) Create(ctx context.Context, u *models.User) (int, error) { func (_m *Controller) Delete(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -86,6 +98,10 @@ func (_m *Controller) Delete(ctx context.Context, id int) error { func (_m *Controller) Get(ctx context.Context, id int, opt *user.Option) (*models.User, error) { ret := _m.Called(ctx, id, opt) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, int, *user.Option) (*models.User, error)); ok { @@ -112,6 +128,10 @@ func (_m *Controller) Get(ctx context.Context, id int, opt *user.Option) (*model func (_m *Controller) GetByName(ctx context.Context, username string) (*models.User, error) { ret := _m.Called(ctx, username) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.User, error)); ok { @@ -138,6 +158,10 @@ func (_m *Controller) GetByName(ctx context.Context, username string) (*models.U func (_m *Controller) GetBySubIss(ctx context.Context, sub string, iss string) (*models.User, error) { ret := _m.Called(ctx, sub, iss) + if len(ret) == 0 { + panic("no return value specified for GetBySubIss") + } + var r0 *models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.User, error)); ok { @@ -171,6 +195,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...userm _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...usermodels.Option) ([]*models.User, error)); ok { @@ -197,6 +225,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...userm func (_m *Controller) OnboardOIDCUser(ctx context.Context, u *models.User) error { ret := _m.Called(ctx, u) + if len(ret) == 0 { + panic("no return value specified for OnboardOIDCUser") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.User) error); ok { r0 = rf(ctx, u) @@ -211,6 +243,10 @@ func (_m *Controller) OnboardOIDCUser(ctx context.Context, u *models.User) error func (_m *Controller) SetCliSecret(ctx context.Context, id int, secret string) error { ret := _m.Called(ctx, id, secret) + if len(ret) == 0 { + panic("no return value specified for SetCliSecret") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, secret) @@ -225,6 +261,10 @@ func (_m *Controller) SetCliSecret(ctx context.Context, id int, secret string) e func (_m *Controller) SetSysAdmin(ctx context.Context, id int, adminFlag bool) error { ret := _m.Called(ctx, id, adminFlag) + if len(ret) == 0 { + panic("no return value specified for SetSysAdmin") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, bool) error); ok { r0 = rf(ctx, id, adminFlag) @@ -246,6 +286,10 @@ func (_m *Controller) UpdateOIDCMeta(ctx context.Context, ou *models.OIDCUser, c _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdateOIDCMeta") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser, ...string) error); ok { r0 = rf(ctx, ou, cols...) @@ -260,6 +304,10 @@ func (_m *Controller) UpdateOIDCMeta(ctx context.Context, ou *models.OIDCUser, c func (_m *Controller) UpdatePassword(ctx context.Context, id int, password string) error { ret := _m.Called(ctx, id, password) + if len(ret) == 0 { + panic("no return value specified for UpdatePassword") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, password) @@ -281,6 +329,10 @@ func (_m *Controller) UpdateProfile(ctx context.Context, u *models.User, cols .. _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdateProfile") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.User, ...string) error); ok { r0 = rf(ctx, u, cols...) @@ -295,6 +347,10 @@ func (_m *Controller) UpdateProfile(ctx context.Context, u *models.User, cols .. func (_m *Controller) VerifyPassword(ctx context.Context, usernameOrEmail string, password string) (bool, error) { ret := _m.Called(ctx, usernameOrEmail, password) + if len(ret) == 0 { + panic("no return value specified for VerifyPassword") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { diff --git a/src/testing/controller/webhook/controller.go b/src/testing/controller/webhook/controller.go index a956dc7f18..c50d448501 100644 --- a/src/testing/controller/webhook/controller.go +++ b/src/testing/controller/webhook/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package webhook @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) CountExecutions(ctx context.Context, policyID int64, query *q.Query) (int64, error) { ret := _m.Called(ctx, policyID, query) + if len(ret) == 0 { + panic("no return value specified for CountExecutions") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) (int64, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) CountExecutions(ctx context.Context, policyID int64, query func (_m *Controller) CountPolicies(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for CountPolicies") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -72,6 +80,10 @@ func (_m *Controller) CountPolicies(ctx context.Context, query *q.Query) (int64, func (_m *Controller) CountTasks(ctx context.Context, execID int64, query *q.Query) (int64, error) { ret := _m.Called(ctx, execID, query) + if len(ret) == 0 { + panic("no return value specified for CountTasks") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) (int64, error)); ok { @@ -96,6 +108,10 @@ func (_m *Controller) CountTasks(ctx context.Context, execID int64, query *q.Que func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for CreatePolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -120,6 +136,10 @@ func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (i func (_m *Controller) DeletePolicy(ctx context.Context, policyID int64) error { ret := _m.Called(ctx, policyID) + if len(ret) == 0 { + panic("no return value specified for DeletePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, policyID) @@ -134,6 +154,10 @@ func (_m *Controller) DeletePolicy(ctx context.Context, policyID int64) error { func (_m *Controller) GetLastTriggerTime(ctx context.Context, eventType string, policyID int64) (time.Time, error) { ret := _m.Called(ctx, eventType, policyID) + if len(ret) == 0 { + panic("no return value specified for GetLastTriggerTime") + } + var r0 time.Time var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) (time.Time, error)); ok { @@ -158,6 +182,10 @@ func (_m *Controller) GetLastTriggerTime(ctx context.Context, eventType string, func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetPolicy") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -184,6 +212,10 @@ func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, e func (_m *Controller) GetRelatedPolices(ctx context.Context, projectID int64, eventType string) ([]*model.Policy, error) { ret := _m.Called(ctx, projectID, eventType) + if len(ret) == 0 { + panic("no return value specified for GetRelatedPolices") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) ([]*model.Policy, error)); ok { @@ -210,6 +242,10 @@ func (_m *Controller) GetRelatedPolices(ctx context.Context, projectID int64, ev func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*task.Task, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTask") + } + var r0 *task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Task, error)); ok { @@ -236,6 +272,10 @@ func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*task.Task, er func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTaskLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -262,6 +302,10 @@ func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, err func (_m *Controller) ListExecutions(ctx context.Context, policyID int64, query *q.Query) ([]*task.Execution, error) { ret := _m.Called(ctx, policyID, query) + if len(ret) == 0 { + panic("no return value specified for ListExecutions") + } + var r0 []*task.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*task.Execution, error)); ok { @@ -288,6 +332,10 @@ func (_m *Controller) ListExecutions(ctx context.Context, policyID int64, query func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPolicies") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -314,6 +362,10 @@ func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*mode func (_m *Controller) ListTasks(ctx context.Context, execID int64, query *q.Query) ([]*task.Task, error) { ret := _m.Called(ctx, execID, query) + if len(ret) == 0 { + panic("no return value specified for ListTasks") + } + var r0 []*task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*task.Task, error)); ok { @@ -340,6 +392,10 @@ func (_m *Controller) ListTasks(ctx context.Context, execID int64, query *q.Quer func (_m *Controller) UpdatePolicy(ctx context.Context, policy *model.Policy) error { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for UpdatePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok { r0 = rf(ctx, policy) diff --git a/src/testing/lib/cache/cache.go b/src/testing/lib/cache/cache.go index e28a1040ad..e4e719d9b9 100644 --- a/src/testing/lib/cache/cache.go +++ b/src/testing/lib/cache/cache.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cache @@ -21,6 +21,10 @@ type Cache struct { func (_m *Cache) Contains(ctx context.Context, key string) bool { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Contains") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { r0 = rf(ctx, key) @@ -35,6 +39,10 @@ func (_m *Cache) Contains(ctx context.Context, key string) bool { func (_m *Cache) Delete(ctx context.Context, key string) error { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, key) @@ -49,6 +57,10 @@ func (_m *Cache) Delete(ctx context.Context, key string) error { func (_m *Cache) Fetch(ctx context.Context, key string, value interface{}) error { ret := _m.Called(ctx, key, value) + if len(ret) == 0 { + panic("no return value specified for Fetch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) error); ok { r0 = rf(ctx, key, value) @@ -63,6 +75,10 @@ func (_m *Cache) Fetch(ctx context.Context, key string, value interface{}) error func (_m *Cache) Ping(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -84,6 +100,10 @@ func (_m *Cache) Save(ctx context.Context, key string, value interface{}, expira _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}, ...time.Duration) error); ok { r0 = rf(ctx, key, value, expiration...) @@ -98,6 +118,10 @@ func (_m *Cache) Save(ctx context.Context, key string, value interface{}, expira func (_m *Cache) Scan(ctx context.Context, match string) (cache.Iterator, error) { ret := _m.Called(ctx, match) + if len(ret) == 0 { + panic("no return value specified for Scan") + } + var r0 cache.Iterator var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (cache.Iterator, error)); ok { diff --git a/src/testing/lib/cache/iterator.go b/src/testing/lib/cache/iterator.go index ec7dc6f088..7a80fb382a 100644 --- a/src/testing/lib/cache/iterator.go +++ b/src/testing/lib/cache/iterator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cache @@ -17,6 +17,10 @@ type Iterator struct { func (_m *Iterator) Next(ctx context.Context) bool { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Next") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context) bool); ok { r0 = rf(ctx) @@ -31,6 +35,10 @@ func (_m *Iterator) Next(ctx context.Context) bool { func (_m *Iterator) Val() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Val") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/src/testing/lib/config/manager.go b/src/testing/lib/config/manager.go index f040869640..ff6456b5dd 100644 --- a/src/testing/lib/config/manager.go +++ b/src/testing/lib/config/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package config @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Get(ctx context.Context, key string) *metadata.ConfigureValue { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *metadata.ConfigureValue if rf, ok := ret.Get(0).(func(context.Context, string) *metadata.ConfigureValue); ok { r0 = rf(ctx, key) @@ -36,6 +40,10 @@ func (_m *Manager) Get(ctx context.Context, key string) *metadata.ConfigureValue func (_m *Manager) GetAll(ctx context.Context) map[string]interface{} { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetAll") + } + var r0 map[string]interface{} if rf, ok := ret.Get(0).(func(context.Context) map[string]interface{}); ok { r0 = rf(ctx) @@ -52,6 +60,10 @@ func (_m *Manager) GetAll(ctx context.Context) map[string]interface{} { func (_m *Manager) GetDatabaseCfg() *models.Database { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetDatabaseCfg") + } + var r0 *models.Database if rf, ok := ret.Get(0).(func() *models.Database); ok { r0 = rf() @@ -68,6 +80,10 @@ func (_m *Manager) GetDatabaseCfg() *models.Database { func (_m *Manager) GetUserCfgs(ctx context.Context) map[string]interface{} { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetUserCfgs") + } + var r0 map[string]interface{} if rf, ok := ret.Get(0).(func(context.Context) map[string]interface{}); ok { r0 = rf(ctx) @@ -84,6 +100,10 @@ func (_m *Manager) GetUserCfgs(ctx context.Context) map[string]interface{} { func (_m *Manager) Load(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Load") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -98,6 +118,10 @@ func (_m *Manager) Load(ctx context.Context) error { func (_m *Manager) Save(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -117,6 +141,10 @@ func (_m *Manager) Set(ctx context.Context, key string, value interface{}) { func (_m *Manager) UpdateConfig(ctx context.Context, cfgs map[string]interface{}) error { ret := _m.Called(ctx, cfgs) + if len(ret) == 0 { + panic("no return value specified for UpdateConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) error); ok { r0 = rf(ctx, cfgs) @@ -131,6 +159,10 @@ func (_m *Manager) UpdateConfig(ctx context.Context, cfgs map[string]interface{} func (_m *Manager) ValidateCfg(ctx context.Context, cfgs map[string]interface{}) error { ret := _m.Called(ctx, cfgs) + if len(ret) == 0 { + panic("no return value specified for ValidateCfg") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) error); ok { r0 = rf(ctx, cfgs) diff --git a/src/testing/lib/orm/creator.go b/src/testing/lib/orm/creator.go index 7844ecaf2c..d10e5f918b 100644 --- a/src/testing/lib/orm/creator.go +++ b/src/testing/lib/orm/creator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package orm @@ -16,6 +16,10 @@ type Creator struct { func (_m *Creator) Create() orm.Ormer { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 orm.Ormer if rf, ok := ret.Get(0).(func() orm.Ormer); ok { r0 = rf() diff --git a/src/testing/pkg/accessory/dao/dao.go b/src/testing/pkg/accessory/dao/dao.go index 886ee4f14e..d4165cf9b3 100644 --- a/src/testing/pkg/accessory/dao/dao.go +++ b/src/testing/pkg/accessory/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -20,6 +20,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, accessory *dao.Accessory) (int64, error) { ret := _m.Called(ctx, accessory) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Accessory) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *DAO) Create(ctx context.Context, accessory *dao.Accessory) (int64, err func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) DeleteAccessories(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for DeleteAccessories") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -106,6 +122,10 @@ func (_m *DAO) DeleteAccessories(ctx context.Context, query *q.Query) (int64, er func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Accessory, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Accessory, error)); ok { @@ -132,6 +152,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Accessory, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*dao.Accessory, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Accessory, error)); ok { @@ -158,6 +182,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*dao.Accessory, erro func (_m *DAO) Update(ctx context.Context, accessory *dao.Accessory) error { ret := _m.Called(ctx, accessory) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Accessory) error); ok { r0 = rf(ctx, accessory) diff --git a/src/testing/pkg/accessory/manager.go b/src/testing/pkg/accessory/manager.go index 9a3312a32a..1245311d87 100644 --- a/src/testing/pkg/accessory/manager.go +++ b/src/testing/pkg/accessory/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package accessory @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 model.AccessoryData) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, model.AccessoryData) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 model.AccessoryData) (int64, func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) DeleteAccessories(ctx context.Context, _a1 *q.Query) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for DeleteAccessories") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) error); ok { r0 = rf(ctx, _a1) @@ -96,6 +112,10 @@ func (_m *Manager) DeleteAccessories(ctx context.Context, _a1 *q.Query) error { func (_m *Manager) Ensure(ctx context.Context, subArtDigest string, subArtRepo string, subArtID int64, artifactID int64, size int64, digest string, accType string) error { ret := _m.Called(ctx, subArtDigest, subArtRepo, subArtID, artifactID, size, digest, accType) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, int64, int64, string, string) error); ok { r0 = rf(ctx, subArtDigest, subArtRepo, subArtID, artifactID, size, digest, accType) @@ -110,6 +130,10 @@ func (_m *Manager) Ensure(ctx context.Context, subArtDigest string, subArtRepo s func (_m *Manager) Get(ctx context.Context, id int64) (model.Accessory, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 model.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (model.Accessory, error)); ok { @@ -136,6 +160,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (model.Accessory, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]model.Accessory, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []model.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]model.Accessory, error)); ok { @@ -162,6 +190,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]model.Accessory, func (_m *Manager) Update(ctx context.Context, _a1 model.AccessoryData) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, model.AccessoryData) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/accessory/model/accessory.go b/src/testing/pkg/accessory/model/accessory.go index 04d4dad65f..a33371896e 100644 --- a/src/testing/pkg/accessory/model/accessory.go +++ b/src/testing/pkg/accessory/model/accessory.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package model @@ -16,6 +16,10 @@ type Accessory struct { func (_m *Accessory) Display() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Display") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -30,6 +34,10 @@ func (_m *Accessory) Display() bool { func (_m *Accessory) GetData() model.AccessoryData { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetData") + } + var r0 model.AccessoryData if rf, ok := ret.Get(0).(func() model.AccessoryData); ok { r0 = rf() @@ -44,6 +52,10 @@ func (_m *Accessory) GetData() model.AccessoryData { func (_m *Accessory) IsHard() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsHard") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -58,6 +70,10 @@ func (_m *Accessory) IsHard() bool { func (_m *Accessory) IsSoft() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsSoft") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -72,6 +88,10 @@ func (_m *Accessory) IsSoft() bool { func (_m *Accessory) Kind() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Kind") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/src/testing/pkg/allowlist/dao/dao.go b/src/testing/pkg/allowlist/dao/dao.go index d818c5e57c..a8ebfec107 100644 --- a/src/testing/pkg/allowlist/dao/dao.go +++ b/src/testing/pkg/allowlist/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -19,6 +19,10 @@ type DAO struct { func (_m *DAO) QueryByProjectID(ctx context.Context, pid int64) (*models.CVEAllowlist, error) { ret := _m.Called(ctx, pid) + if len(ret) == 0 { + panic("no return value specified for QueryByProjectID") + } + var r0 *models.CVEAllowlist var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*models.CVEAllowlist, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) QueryByProjectID(ctx context.Context, pid int64) (*models.CVEAllo func (_m *DAO) Set(ctx context.Context, l models.CVEAllowlist) (int64, error) { ret := _m.Called(ctx, l) + if len(ret) == 0 { + panic("no return value specified for Set") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.CVEAllowlist) (int64, error)); ok { diff --git a/src/testing/pkg/allowlist/manager.go b/src/testing/pkg/allowlist/manager.go index a3c7dc30e7..ecb1b41153 100644 --- a/src/testing/pkg/allowlist/manager.go +++ b/src/testing/pkg/allowlist/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package robot @@ -18,6 +18,10 @@ type Manager struct { func (_m *Manager) CreateEmpty(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for CreateEmpty") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -32,6 +36,10 @@ func (_m *Manager) CreateEmpty(ctx context.Context, projectID int64) error { func (_m *Manager) Get(ctx context.Context, projectID int64) (*models.CVEAllowlist, error) { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.CVEAllowlist var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*models.CVEAllowlist, error)); ok { @@ -58,6 +66,10 @@ func (_m *Manager) Get(ctx context.Context, projectID int64) (*models.CVEAllowli func (_m *Manager) GetSys(ctx context.Context) (*models.CVEAllowlist, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetSys") + } + var r0 *models.CVEAllowlist var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*models.CVEAllowlist, error)); ok { @@ -84,6 +96,10 @@ func (_m *Manager) GetSys(ctx context.Context) (*models.CVEAllowlist, error) { func (_m *Manager) Set(ctx context.Context, projectID int64, list models.CVEAllowlist) error { ret := _m.Called(ctx, projectID, list) + if len(ret) == 0 { + panic("no return value specified for Set") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, models.CVEAllowlist) error); ok { r0 = rf(ctx, projectID, list) @@ -98,6 +114,10 @@ func (_m *Manager) Set(ctx context.Context, projectID int64, list models.CVEAllo func (_m *Manager) SetSys(ctx context.Context, list models.CVEAllowlist) error { ret := _m.Called(ctx, list) + if len(ret) == 0 { + panic("no return value specified for SetSys") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.CVEAllowlist) error); ok { r0 = rf(ctx, list) diff --git a/src/testing/pkg/artifact/manager.go b/src/testing/pkg/artifact/manager.go index fb246aebd8..bf19a0b605 100644 --- a/src/testing/pkg/artifact/manager.go +++ b/src/testing/pkg/artifact/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package artifact @@ -23,6 +23,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -47,6 +51,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *artifact.Artifact) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) (int64, error)); ok { @@ -71,6 +79,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *artifact.Artifact) (int64, e func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -85,6 +97,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) DeleteReference(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteReference") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -99,6 +115,10 @@ func (_m *Manager) DeleteReference(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*artifact.Artifact, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*artifact.Artifact, error)); ok { @@ -125,6 +145,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*artifact.Artifact, error func (_m *Manager) GetByDigest(ctx context.Context, repository string, digest string) (*artifact.Artifact, error) { ret := _m.Called(ctx, repository, digest) + if len(ret) == 0 { + panic("no return value specified for GetByDigest") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*artifact.Artifact, error)); ok { @@ -151,6 +175,10 @@ func (_m *Manager) GetByDigest(ctx context.Context, repository string, digest st func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*artifact.Artifact, error)); ok { @@ -177,6 +205,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*artifact.Artifa func (_m *Manager) ListReferences(ctx context.Context, query *q.Query) ([]*artifact.Reference, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListReferences") + } + var r0 []*artifact.Reference var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*artifact.Reference, error)); ok { @@ -210,6 +242,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *artifact.Artifact, props ... _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, ...string) error); ok { r0 = rf(ctx, _a1, props...) @@ -224,6 +260,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *artifact.Artifact, props ... func (_m *Manager) UpdatePullTime(ctx context.Context, id int64, pullTime time.Time) error { ret := _m.Called(ctx, id, pullTime) + if len(ret) == 0 { + panic("no return value specified for UpdatePullTime") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, time.Time) error); ok { r0 = rf(ctx, id, pullTime) diff --git a/src/testing/pkg/audit/dao/dao.go b/src/testing/pkg/audit/dao/dao.go index 97665fdaaa..74dd03aaa6 100644 --- a/src/testing/pkg/audit/dao/dao.go +++ b/src/testing/pkg/audit/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, access *model.AuditLog) (int64, error) { ret := _m.Called(ctx, access) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.AuditLog) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, access *model.AuditLog) (int64, error func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.AuditLog, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.AuditLog, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.AuditLog, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.AuditLog, error)); ok { @@ -135,6 +155,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, err func (_m *DAO) Purge(ctx context.Context, retentionHour int, includeOperations []string, dryRun bool) (int64, error) { ret := _m.Called(ctx, retentionHour, includeOperations, dryRun) + if len(ret) == 0 { + panic("no return value specified for Purge") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int, []string, bool) (int64, error)); ok { @@ -159,6 +183,10 @@ func (_m *DAO) Purge(ctx context.Context, retentionHour int, includeOperations [ func (_m *DAO) UpdateUsername(ctx context.Context, username string, usernameReplace string) error { ret := _m.Called(ctx, username, usernameReplace) + if len(ret) == 0 { + panic("no return value specified for UpdateUsername") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, username, usernameReplace) diff --git a/src/testing/pkg/audit/manager.go b/src/testing/pkg/audit/manager.go index 1efcd06dfa..887996c0ba 100644 --- a/src/testing/pkg/audit/manager.go +++ b/src/testing/pkg/audit/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package audit @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.AuditLog) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.AuditLog) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.AuditLog) (int64, erro func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.AuditLog, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.AuditLog, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.AuditLog, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.AuditLog, error)); ok { @@ -134,6 +154,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, func (_m *Manager) Purge(ctx context.Context, retentionHour int, includeOperations []string, dryRun bool) (int64, error) { ret := _m.Called(ctx, retentionHour, includeOperations, dryRun) + if len(ret) == 0 { + panic("no return value specified for Purge") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int, []string, bool) (int64, error)); ok { @@ -158,6 +182,10 @@ func (_m *Manager) Purge(ctx context.Context, retentionHour int, includeOperatio func (_m *Manager) UpdateUsername(ctx context.Context, username string, replaceWith string) error { ret := _m.Called(ctx, username, replaceWith) + if len(ret) == 0 { + panic("no return value specified for UpdateUsername") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, username, replaceWith) diff --git a/src/testing/pkg/blob/manager.go b/src/testing/pkg/blob/manager.go index 3edba7fbb6..8308e4aaf6 100644 --- a/src/testing/pkg/blob/manager.go +++ b/src/testing/pkg/blob/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package blob @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) AssociateWithArtifact(ctx context.Context, blobDigest string, artifactDigest string) (int64, error) { ret := _m.Called(ctx, blobDigest, artifactDigest) + if len(ret) == 0 { + panic("no return value specified for AssociateWithArtifact") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) AssociateWithArtifact(ctx context.Context, blobDigest string, func (_m *Manager) AssociateWithProject(ctx context.Context, blobID int64, projectID int64) (int64, error) { ret := _m.Called(ctx, blobID, projectID) + if len(ret) == 0 { + panic("no return value specified for AssociateWithProject") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) AssociateWithProject(ctx context.Context, blobID int64, proje func (_m *Manager) CalculateTotalSize(ctx context.Context, excludeForeignLayer bool) (int64, error) { ret := _m.Called(ctx, excludeForeignLayer) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool) (int64, error)); ok { @@ -92,6 +104,10 @@ func (_m *Manager) CalculateTotalSize(ctx context.Context, excludeForeignLayer b func (_m *Manager) CalculateTotalSizeByProject(ctx context.Context, projectID int64, excludeForeignLayer bool) (int64, error) { ret := _m.Called(ctx, projectID, excludeForeignLayer) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSizeByProject") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) (int64, error)); ok { @@ -116,6 +132,10 @@ func (_m *Manager) CalculateTotalSizeByProject(ctx context.Context, projectID in func (_m *Manager) CleanupAssociationsForArtifact(ctx context.Context, artifactDigest string) error { ret := _m.Called(ctx, artifactDigest) + if len(ret) == 0 { + panic("no return value specified for CleanupAssociationsForArtifact") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, artifactDigest) @@ -130,6 +150,10 @@ func (_m *Manager) CleanupAssociationsForArtifact(ctx context.Context, artifactD func (_m *Manager) CleanupAssociationsForProject(ctx context.Context, projectID int64, blobs []*models.Blob) error { ret := _m.Called(ctx, projectID, blobs) + if len(ret) == 0 { + panic("no return value specified for CleanupAssociationsForProject") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Blob) error); ok { r0 = rf(ctx, projectID, blobs) @@ -144,6 +168,10 @@ func (_m *Manager) CleanupAssociationsForProject(ctx context.Context, projectID func (_m *Manager) Create(ctx context.Context, digest string, contentType string, size int64) (int64, error) { ret := _m.Called(ctx, digest, contentType, size) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (int64, error)); ok { @@ -168,6 +196,10 @@ func (_m *Manager) Create(ctx context.Context, digest string, contentType string func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -182,6 +214,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) FindBlobsShouldUnassociatedWithProject(ctx context.Context, projectID int64, blobs []*models.Blob) ([]*models.Blob, error) { ret := _m.Called(ctx, projectID, blobs) + if len(ret) == 0 { + panic("no return value specified for FindBlobsShouldUnassociatedWithProject") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Blob) ([]*models.Blob, error)); ok { @@ -208,6 +244,10 @@ func (_m *Manager) FindBlobsShouldUnassociatedWithProject(ctx context.Context, p func (_m *Manager) Get(ctx context.Context, digest string) (*models.Blob, error) { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Blob, error)); ok { @@ -234,6 +274,10 @@ func (_m *Manager) Get(ctx context.Context, digest string) (*models.Blob, error) func (_m *Manager) GetByArt(ctx context.Context, digest string) ([]*models.Blob, error) { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for GetByArt") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*models.Blob, error)); ok { @@ -260,6 +304,10 @@ func (_m *Manager) GetByArt(ctx context.Context, digest string) ([]*models.Blob, func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Blob, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Blob, error)); ok { @@ -286,6 +334,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Blob, er func (_m *Manager) Update(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) @@ -300,6 +352,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *models.Blob) error { func (_m *Manager) UpdateBlobStatus(ctx context.Context, _a1 *models.Blob) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for UpdateBlobStatus") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) (int64, error)); ok { @@ -324,6 +380,10 @@ func (_m *Manager) UpdateBlobStatus(ctx context.Context, _a1 *models.Blob) (int6 func (_m *Manager) UselessBlobs(ctx context.Context, timeWindowHours int64) ([]*models.Blob, error) { ret := _m.Called(ctx, timeWindowHours) + if len(ret) == 0 { + panic("no return value specified for UselessBlobs") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*models.Blob, error)); ok { diff --git a/src/testing/pkg/cached/manifest/redis/cached_manager.go b/src/testing/pkg/cached/manifest/redis/cached_manager.go index 6ae99bb961..033e96aca9 100644 --- a/src/testing/pkg/cached/manifest/redis/cached_manager.go +++ b/src/testing/pkg/cached/manifest/redis/cached_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package redis @@ -19,6 +19,10 @@ type CachedManager struct { func (_m *CachedManager) CacheClient(ctx context.Context) cache.Cache { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for CacheClient") + } + var r0 cache.Cache if rf, ok := ret.Get(0).(func(context.Context) cache.Cache); ok { r0 = rf(ctx) @@ -35,6 +39,10 @@ func (_m *CachedManager) CacheClient(ctx context.Context) cache.Cache { func (_m *CachedManager) CountCache(ctx context.Context) (int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for CountCache") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok { @@ -59,6 +67,10 @@ func (_m *CachedManager) CountCache(ctx context.Context) (int64, error) { func (_m *CachedManager) Delete(ctx context.Context, digest string) error { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, digest) @@ -73,6 +85,10 @@ func (_m *CachedManager) Delete(ctx context.Context, digest string) error { func (_m *CachedManager) DeleteCache(ctx context.Context, key string) error { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for DeleteCache") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, key) @@ -87,6 +103,10 @@ func (_m *CachedManager) DeleteCache(ctx context.Context, key string) error { func (_m *CachedManager) FlushAll(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for FlushAll") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -101,6 +121,10 @@ func (_m *CachedManager) FlushAll(ctx context.Context) error { func (_m *CachedManager) Get(ctx context.Context, digest string) ([]byte, error) { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { @@ -127,6 +151,10 @@ func (_m *CachedManager) Get(ctx context.Context, digest string) ([]byte, error) func (_m *CachedManager) ResourceType(ctx context.Context) string { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for ResourceType") + } + var r0 string if rf, ok := ret.Get(0).(func(context.Context) string); ok { r0 = rf(ctx) @@ -141,6 +169,10 @@ func (_m *CachedManager) ResourceType(ctx context.Context) string { func (_m *CachedManager) Save(ctx context.Context, digest string, manifest []byte) error { ret := _m.Called(ctx, digest, manifest) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, []byte) error); ok { r0 = rf(ctx, digest, manifest) diff --git a/src/testing/pkg/immutable/dao/dao.go b/src/testing/pkg/immutable/dao/dao.go index a3e841e6f3..8ad5aac0fe 100644 --- a/src/testing/pkg/immutable/dao/dao.go +++ b/src/testing/pkg/immutable/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) CreateImmutableRule(ctx context.Context, ir *model.ImmutableRule) (int64, error) { ret := _m.Called(ctx, ir) + if len(ret) == 0 { + panic("no return value specified for CreateImmutableRule") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.ImmutableRule) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) CreateImmutableRule(ctx context.Context, ir *model.ImmutableRule) func (_m *DAO) DeleteImmutableRule(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteImmutableRule") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) DeleteImmutableRule(ctx context.Context, id int64) error { func (_m *DAO) GetImmutableRule(ctx context.Context, id int64) (*model.ImmutableRule, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetImmutableRule") + } + var r0 *model.ImmutableRule var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.ImmutableRule, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) GetImmutableRule(ctx context.Context, id int64) (*model.Immutable func (_m *DAO) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.ImmutableRule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListImmutableRules") + } + var r0 []*model.ImmutableRule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.ImmutableRule, error)); ok { @@ -135,6 +155,10 @@ func (_m *DAO) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model func (_m *DAO) ToggleImmutableRule(ctx context.Context, id int64, status bool) error { ret := _m.Called(ctx, id, status) + if len(ret) == 0 { + panic("no return value specified for ToggleImmutableRule") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { r0 = rf(ctx, id, status) @@ -149,6 +173,10 @@ func (_m *DAO) ToggleImmutableRule(ctx context.Context, id int64, status bool) e func (_m *DAO) UpdateImmutableRule(ctx context.Context, projectID int64, ir *model.ImmutableRule) error { ret := _m.Called(ctx, projectID, ir) + if len(ret) == 0 { + panic("no return value specified for UpdateImmutableRule") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, *model.ImmutableRule) error); ok { r0 = rf(ctx, projectID, ir) diff --git a/src/testing/pkg/joblog/dao/dao.go b/src/testing/pkg/joblog/dao/dao.go index 37fd4b9b0e..353d2c1685 100644 --- a/src/testing/pkg/joblog/dao/dao.go +++ b/src/testing/pkg/joblog/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) { ret := _m.Called(ctx, jobLog) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.JobLog) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) func (_m *DAO) DeleteBefore(ctx context.Context, t time.Time) (int64, error) { ret := _m.Called(ctx, t) + if len(ret) == 0 { + panic("no return value specified for DeleteBefore") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, time.Time) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) DeleteBefore(ctx context.Context, t time.Time) (int64, error) { func (_m *DAO) Get(ctx context.Context, uuid string) (*models.JobLog, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.JobLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.JobLog, error)); ok { diff --git a/src/testing/pkg/joblog/manager.go b/src/testing/pkg/joblog/manager.go index 103fb07349..d00e413c9e 100644 --- a/src/testing/pkg/joblog/manager.go +++ b/src/testing/pkg/joblog/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package joblog @@ -21,6 +21,10 @@ type Manager struct { func (_m *Manager) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) { ret := _m.Called(ctx, jobLog) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.JobLog) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *Manager) Create(ctx context.Context, jobLog *models.JobLog) (int64, er func (_m *Manager) DeleteBefore(ctx context.Context, t time.Time) (int64, error) { ret := _m.Called(ctx, t) + if len(ret) == 0 { + panic("no return value specified for DeleteBefore") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, time.Time) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *Manager) DeleteBefore(ctx context.Context, t time.Time) (int64, error) func (_m *Manager) Get(ctx context.Context, uuid string) (*models.JobLog, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.JobLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.JobLog, error)); ok { diff --git a/src/testing/pkg/jobmonitor/job_service_monitor_client.go b/src/testing/pkg/jobmonitor/job_service_monitor_client.go index 569357d4bf..d528776fc8 100644 --- a/src/testing/pkg/jobmonitor/job_service_monitor_client.go +++ b/src/testing/pkg/jobmonitor/job_service_monitor_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -16,6 +16,10 @@ type JobServiceMonitorClient struct { func (_m *JobServiceMonitorClient) Queues() ([]*work.Queue, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Queues") + } + var r0 []*work.Queue var r1 error if rf, ok := ret.Get(0).(func() ([]*work.Queue, error)); ok { @@ -42,6 +46,10 @@ func (_m *JobServiceMonitorClient) Queues() ([]*work.Queue, error) { func (_m *JobServiceMonitorClient) WorkerObservations() ([]*work.WorkerObservation, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for WorkerObservations") + } + var r0 []*work.WorkerObservation var r1 error if rf, ok := ret.Get(0).(func() ([]*work.WorkerObservation, error)); ok { @@ -68,6 +76,10 @@ func (_m *JobServiceMonitorClient) WorkerObservations() ([]*work.WorkerObservati func (_m *JobServiceMonitorClient) WorkerPoolHeartbeats() ([]*work.WorkerPoolHeartbeat, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for WorkerPoolHeartbeats") + } + var r0 []*work.WorkerPoolHeartbeat var r1 error if rf, ok := ret.Get(0).(func() ([]*work.WorkerPoolHeartbeat, error)); ok { diff --git a/src/testing/pkg/jobmonitor/pool_manager.go b/src/testing/pkg/jobmonitor/pool_manager.go index 7b41ac6d86..51dc4fbf9a 100644 --- a/src/testing/pkg/jobmonitor/pool_manager.go +++ b/src/testing/pkg/jobmonitor/pool_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -18,6 +18,10 @@ type PoolManager struct { func (_m *PoolManager) List(ctx context.Context, monitorClient jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.WorkerPool, error) { ret := _m.Called(ctx, monitorClient) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*jobmonitor.WorkerPool var r1 error if rf, ok := ret.Get(0).(func(context.Context, jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.WorkerPool, error)); ok { diff --git a/src/testing/pkg/jobmonitor/queue_manager.go b/src/testing/pkg/jobmonitor/queue_manager.go index de7862bae0..bce10ef38e 100644 --- a/src/testing/pkg/jobmonitor/queue_manager.go +++ b/src/testing/pkg/jobmonitor/queue_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -18,6 +18,10 @@ type QueueManager struct { func (_m *QueueManager) List(ctx context.Context, monitClient jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.Queue, error) { ret := _m.Called(ctx, monitClient) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*jobmonitor.Queue var r1 error if rf, ok := ret.Get(0).(func(context.Context, jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.Queue, error)); ok { diff --git a/src/testing/pkg/jobmonitor/redis_client.go b/src/testing/pkg/jobmonitor/redis_client.go index bf1c5c4e45..5a7f91837c 100644 --- a/src/testing/pkg/jobmonitor/redis_client.go +++ b/src/testing/pkg/jobmonitor/redis_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -17,6 +17,10 @@ type RedisClient struct { func (_m *RedisClient) AllJobTypes(ctx context.Context) ([]string, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for AllJobTypes") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { @@ -43,6 +47,10 @@ func (_m *RedisClient) AllJobTypes(ctx context.Context) ([]string, error) { func (_m *RedisClient) PauseJob(ctx context.Context, jobName string) error { ret := _m.Called(ctx, jobName) + if len(ret) == 0 { + panic("no return value specified for PauseJob") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, jobName) @@ -57,6 +65,10 @@ func (_m *RedisClient) PauseJob(ctx context.Context, jobName string) error { func (_m *RedisClient) StopPendingJobs(ctx context.Context, jobType string) ([]string, error) { ret := _m.Called(ctx, jobType) + if len(ret) == 0 { + panic("no return value specified for StopPendingJobs") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { @@ -83,6 +95,10 @@ func (_m *RedisClient) StopPendingJobs(ctx context.Context, jobType string) ([]s func (_m *RedisClient) UnpauseJob(ctx context.Context, jobName string) error { ret := _m.Called(ctx, jobName) + if len(ret) == 0 { + panic("no return value specified for UnpauseJob") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, jobName) diff --git a/src/testing/pkg/jobmonitor/worker_manager.go b/src/testing/pkg/jobmonitor/worker_manager.go index 35563d24b6..d6f136f2ae 100644 --- a/src/testing/pkg/jobmonitor/worker_manager.go +++ b/src/testing/pkg/jobmonitor/worker_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -18,6 +18,10 @@ type WorkerManager struct { func (_m *WorkerManager) List(ctx context.Context, monitClient jobmonitor.JobServiceMonitorClient, poolID string) ([]*jobmonitor.Worker, error) { ret := _m.Called(ctx, monitClient, poolID) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*jobmonitor.Worker var r1 error if rf, ok := ret.Get(0).(func(context.Context, jobmonitor.JobServiceMonitorClient, string) ([]*jobmonitor.Worker, error)); ok { diff --git a/src/testing/pkg/label/dao/dao.go b/src/testing/pkg/label/dao/dao.go index 71b64de51c..63cb4525b9 100644 --- a/src/testing/pkg/label/dao/dao.go +++ b/src/testing/pkg/label/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, label *model.Label) (int64, error) { ret := _m.Called(ctx, label) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, label *model.Label) (int64, error) { func (_m *DAO) CreateReference(ctx context.Context, reference *model.Reference) (int64, error) { ret := _m.Called(ctx, reference) + if len(ret) == 0 { + panic("no return value specified for CreateReference") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Reference) (int64, error)); ok { @@ -93,6 +105,10 @@ func (_m *DAO) CreateReference(ctx context.Context, reference *model.Reference) func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -107,6 +123,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) DeleteReference(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteReference") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -121,6 +141,10 @@ func (_m *DAO) DeleteReference(ctx context.Context, id int64) error { func (_m *DAO) DeleteReferences(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for DeleteReferences") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -145,6 +169,10 @@ func (_m *DAO) DeleteReferences(ctx context.Context, query *q.Query) (int64, err func (_m *DAO) Get(ctx context.Context, id int64) (*model.Label, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Label, error)); ok { @@ -171,6 +199,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Label, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Label, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Label, error)); ok { @@ -197,6 +229,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Label, error) func (_m *DAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) { ret := _m.Called(ctx, artifactID) + if len(ret) == 0 { + panic("no return value specified for ListByArtifact") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*model.Label, error)); ok { @@ -223,6 +259,10 @@ func (_m *DAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.L func (_m *DAO) Update(ctx context.Context, label *model.Label) error { ret := _m.Called(ctx, label) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) error); ok { r0 = rf(ctx, label) diff --git a/src/testing/pkg/label/manager.go b/src/testing/pkg/label/manager.go index 9fbd4727d8..73c7229446 100644 --- a/src/testing/pkg/label/manager.go +++ b/src/testing/pkg/label/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package label @@ -21,6 +21,10 @@ type Manager struct { func (_m *Manager) AddTo(ctx context.Context, labelID int64, artifactID int64) error { ret := _m.Called(ctx, labelID, artifactID) + if len(ret) == 0 { + panic("no return value specified for AddTo") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, labelID, artifactID) @@ -35,6 +39,10 @@ func (_m *Manager) AddTo(ctx context.Context, labelID int64, artifactID int64) e func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -59,6 +67,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.Label) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) (int64, error)); ok { @@ -83,6 +95,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.Label) (int64, error) func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -97,6 +113,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Label, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Label, error)); ok { @@ -123,6 +143,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Label, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Label, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Label, error)); ok { @@ -149,6 +173,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Label, er func (_m *Manager) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) { ret := _m.Called(ctx, artifactID) + if len(ret) == 0 { + panic("no return value specified for ListByArtifact") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*model.Label, error)); ok { @@ -175,6 +203,10 @@ func (_m *Manager) ListByArtifact(ctx context.Context, artifactID int64) ([]*mod func (_m *Manager) RemoveAllFrom(ctx context.Context, artifactID int64) error { ret := _m.Called(ctx, artifactID) + if len(ret) == 0 { + panic("no return value specified for RemoveAllFrom") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, artifactID) @@ -189,6 +221,10 @@ func (_m *Manager) RemoveAllFrom(ctx context.Context, artifactID int64) error { func (_m *Manager) RemoveFrom(ctx context.Context, labelID int64, artifactID int64) error { ret := _m.Called(ctx, labelID, artifactID) + if len(ret) == 0 { + panic("no return value specified for RemoveFrom") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, labelID, artifactID) @@ -203,6 +239,10 @@ func (_m *Manager) RemoveFrom(ctx context.Context, labelID int64, artifactID int func (_m *Manager) RemoveFromAllArtifacts(ctx context.Context, labelID int64) error { ret := _m.Called(ctx, labelID) + if len(ret) == 0 { + panic("no return value specified for RemoveFromAllArtifacts") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, labelID) @@ -217,6 +257,10 @@ func (_m *Manager) RemoveFromAllArtifacts(ctx context.Context, labelID int64) er func (_m *Manager) Update(ctx context.Context, _a1 *model.Label) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/ldap/manager.go b/src/testing/pkg/ldap/manager.go index 4303c79bfb..de682ae5d1 100644 --- a/src/testing/pkg/ldap/manager.go +++ b/src/testing/pkg/ldap/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package ldap @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) ImportUser(ctx context.Context, sess *ldap.Session, ldapImportUsers []string) ([]model.FailedImportUser, error) { ret := _m.Called(ctx, sess, ldapImportUsers) + if len(ret) == 0 { + panic("no return value specified for ImportUser") + } + var r0 []model.FailedImportUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, *ldap.Session, []string) ([]model.FailedImportUser, error)); ok { @@ -48,6 +52,10 @@ func (_m *Manager) ImportUser(ctx context.Context, sess *ldap.Session, ldapImpor func (_m *Manager) Ping(ctx context.Context, cfg models.LdapConf) (bool, error) { ret := _m.Called(ctx, cfg) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.LdapConf) (bool, error)); ok { @@ -72,6 +80,10 @@ func (_m *Manager) Ping(ctx context.Context, cfg models.LdapConf) (bool, error) func (_m *Manager) SearchGroup(ctx context.Context, sess *ldap.Session, groupName string, groupDN string) ([]model.Group, error) { ret := _m.Called(ctx, sess, groupName, groupDN) + if len(ret) == 0 { + panic("no return value specified for SearchGroup") + } + var r0 []model.Group var r1 error if rf, ok := ret.Get(0).(func(context.Context, *ldap.Session, string, string) ([]model.Group, error)); ok { @@ -98,6 +110,10 @@ func (_m *Manager) SearchGroup(ctx context.Context, sess *ldap.Session, groupNam func (_m *Manager) SearchUser(ctx context.Context, sess *ldap.Session, username string) ([]model.User, error) { ret := _m.Called(ctx, sess, username) + if len(ret) == 0 { + panic("no return value specified for SearchUser") + } + var r0 []model.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, *ldap.Session, string) ([]model.User, error)); ok { diff --git a/src/testing/pkg/member/fake_member_manager.go b/src/testing/pkg/member/fake_member_manager.go index af75d17464..1517a3784c 100644 --- a/src/testing/pkg/member/fake_member_manager.go +++ b/src/testing/pkg/member/fake_member_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package member @@ -21,6 +21,10 @@ type Manager struct { func (_m *Manager) AddProjectMember(ctx context.Context, _a1 models.Member) (int, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for AddProjectMember") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.Member) (int, error)); ok { @@ -45,6 +49,10 @@ func (_m *Manager) AddProjectMember(ctx context.Context, _a1 models.Member) (int func (_m *Manager) Delete(ctx context.Context, projectID int64, memberID int) error { ret := _m.Called(ctx, projectID, memberID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int) error); ok { r0 = rf(ctx, projectID, memberID) @@ -59,6 +67,10 @@ func (_m *Manager) Delete(ctx context.Context, projectID int64, memberID int) er func (_m *Manager) DeleteMemberByProjectID(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteMemberByProjectID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -73,6 +85,10 @@ func (_m *Manager) DeleteMemberByProjectID(ctx context.Context, projectID int64) func (_m *Manager) DeleteMemberByUserID(ctx context.Context, uid int) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for DeleteMemberByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, uid) @@ -87,6 +103,10 @@ func (_m *Manager) DeleteMemberByUserID(ctx context.Context, uid int) error { func (_m *Manager) Get(ctx context.Context, projectID int64, memberID int) (*models.Member, error) { ret := _m.Called(ctx, projectID, memberID) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Member var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int) (*models.Member, error)); ok { @@ -120,6 +140,10 @@ func (_m *Manager) GetTotalOfProjectMembers(ctx context.Context, projectID int64 _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfProjectMembers") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query, ...int) (int, error)); ok { @@ -144,6 +168,10 @@ func (_m *Manager) GetTotalOfProjectMembers(ctx context.Context, projectID int64 func (_m *Manager) List(ctx context.Context, queryMember models.Member, query *q.Query) ([]*models.Member, error) { ret := _m.Called(ctx, queryMember, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Member var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.Member, *q.Query) ([]*models.Member, error)); ok { @@ -170,6 +198,10 @@ func (_m *Manager) List(ctx context.Context, queryMember models.Member, query *q func (_m *Manager) ListRoles(ctx context.Context, user *models.User, projectID int64) ([]int, error) { ret := _m.Called(ctx, user, projectID) + if len(ret) == 0 { + panic("no return value specified for ListRoles") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.User, int64) ([]int, error)); ok { @@ -196,6 +228,10 @@ func (_m *Manager) ListRoles(ctx context.Context, user *models.User, projectID i func (_m *Manager) SearchMemberByName(ctx context.Context, projectID int64, entityName string) ([]*models.Member, error) { ret := _m.Called(ctx, projectID, entityName) + if len(ret) == 0 { + panic("no return value specified for SearchMemberByName") + } + var r0 []*models.Member var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) ([]*models.Member, error)); ok { @@ -222,6 +258,10 @@ func (_m *Manager) SearchMemberByName(ctx context.Context, projectID int64, enti func (_m *Manager) UpdateRole(ctx context.Context, projectID int64, pmID int, role int) error { ret := _m.Called(ctx, projectID, pmID, role) + if len(ret) == 0 { + panic("no return value specified for UpdateRole") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int, int) error); ok { r0 = rf(ctx, projectID, pmID, role) diff --git a/src/testing/pkg/notification/policy/dao/dao.go b/src/testing/pkg/notification/policy/dao/dao.go index 55d9e9ba45..83181b5a40 100644 --- a/src/testing/pkg/notification/policy/dao/dao.go +++ b/src/testing/pkg/notification/policy/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, n *model.Policy) (int64, error) { ret := _m.Called(ctx, n) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, n *model.Policy) (int64, error) { func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -135,6 +155,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Policy, error func (_m *DAO) Update(ctx context.Context, n *model.Policy) error { ret := _m.Called(ctx, n) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok { r0 = rf(ctx, n) diff --git a/src/testing/pkg/notification/policy/manager.go b/src/testing/pkg/notification/policy/manager.go index 716d91b448..b68aaecb5f 100644 --- a/src/testing/pkg/notification/policy/manager.go +++ b/src/testing/pkg/notification/policy/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package policy @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.Policy) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.Policy) (int64, error) func (_m *Manager) Delete(ctx context.Context, policyID int64) error { ret := _m.Called(ctx, policyID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, policyID) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, policyID int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *Manager) GetRelatedPolices(ctx context.Context, projectID int64, eventType string) ([]*model.Policy, error) { ret := _m.Called(ctx, projectID, eventType) + if len(ret) == 0 { + panic("no return value specified for GetRelatedPolices") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) ([]*model.Policy, error)); ok { @@ -134,6 +154,10 @@ func (_m *Manager) GetRelatedPolices(ctx context.Context, projectID int64, event func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -160,6 +184,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, e func (_m *Manager) Update(ctx context.Context, _a1 *model.Policy) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/oidc/dao/meta_dao.go b/src/testing/pkg/oidc/dao/meta_dao.go index d66a416e0f..c773133ef2 100644 --- a/src/testing/pkg/oidc/dao/meta_dao.go +++ b/src/testing/pkg/oidc/dao/meta_dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type MetaDAO struct { func (_m *MetaDAO) Create(ctx context.Context, oidcUser *models.OIDCUser) (int, error) { ret := _m.Called(ctx, oidcUser) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser) (int, error)); ok { @@ -45,6 +49,10 @@ func (_m *MetaDAO) Create(ctx context.Context, oidcUser *models.OIDCUser) (int, func (_m *MetaDAO) DeleteByUserID(ctx context.Context, uid int) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for DeleteByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, uid) @@ -59,6 +67,10 @@ func (_m *MetaDAO) DeleteByUserID(ctx context.Context, uid int) error { func (_m *MetaDAO) GetByUsername(ctx context.Context, username string) (*models.OIDCUser, error) { ret := _m.Called(ctx, username) + if len(ret) == 0 { + panic("no return value specified for GetByUsername") + } + var r0 *models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.OIDCUser, error)); ok { @@ -85,6 +97,10 @@ func (_m *MetaDAO) GetByUsername(ctx context.Context, username string) (*models. func (_m *MetaDAO) List(ctx context.Context, query *q.Query) ([]*models.OIDCUser, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.OIDCUser, error)); ok { @@ -118,6 +134,10 @@ func (_m *MetaDAO) Update(ctx context.Context, oidcUser *models.OIDCUser, props _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser, ...string) error); ok { r0 = rf(ctx, oidcUser, props...) diff --git a/src/testing/pkg/oidc/meta_manager.go b/src/testing/pkg/oidc/meta_manager.go index 676a336239..c9a688a2e8 100644 --- a/src/testing/pkg/oidc/meta_manager.go +++ b/src/testing/pkg/oidc/meta_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package oidc @@ -18,6 +18,10 @@ type MetaManager struct { func (_m *MetaManager) Create(ctx context.Context, oidcUser *models.OIDCUser) (int, error) { ret := _m.Called(ctx, oidcUser) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser) (int, error)); ok { @@ -42,6 +46,10 @@ func (_m *MetaManager) Create(ctx context.Context, oidcUser *models.OIDCUser) (i func (_m *MetaManager) DeleteByUserID(ctx context.Context, uid int) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for DeleteByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, uid) @@ -56,6 +64,10 @@ func (_m *MetaManager) DeleteByUserID(ctx context.Context, uid int) error { func (_m *MetaManager) GetBySubIss(ctx context.Context, sub string, iss string) (*models.OIDCUser, error) { ret := _m.Called(ctx, sub, iss) + if len(ret) == 0 { + panic("no return value specified for GetBySubIss") + } + var r0 *models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.OIDCUser, error)); ok { @@ -82,6 +94,10 @@ func (_m *MetaManager) GetBySubIss(ctx context.Context, sub string, iss string) func (_m *MetaManager) GetByUserID(ctx context.Context, uid int) (*models.OIDCUser, error) { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for GetByUserID") + } + var r0 *models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, int) (*models.OIDCUser, error)); ok { @@ -108,6 +124,10 @@ func (_m *MetaManager) GetByUserID(ctx context.Context, uid int) (*models.OIDCUs func (_m *MetaManager) SetCliSecretByUserID(ctx context.Context, uid int, secret string) error { ret := _m.Called(ctx, uid, secret) + if len(ret) == 0 { + panic("no return value specified for SetCliSecretByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, uid, secret) @@ -129,6 +149,10 @@ func (_m *MetaManager) Update(ctx context.Context, oidcUser *models.OIDCUser, co _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser, ...string) error); ok { r0 = rf(ctx, oidcUser, cols...) diff --git a/src/testing/pkg/project/manager.go b/src/testing/pkg/project/manager.go index c29bb1c9fb..10b1c8e086 100644 --- a/src/testing/pkg/project/manager.go +++ b/src/testing/pkg/project/manager.go @@ -1,13 +1,16 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package project import ( context "context" - models "github.com/goharbor/harbor/src/pkg/project/models" + commonmodels "github.com/goharbor/harbor/src/common/models" + mock "github.com/stretchr/testify/mock" + models "github.com/goharbor/harbor/src/pkg/project/models" + q "github.com/goharbor/harbor/src/lib/q" ) @@ -20,6 +23,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +51,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *models.Project) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.Project) (int64, error)); ok { @@ -68,6 +79,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *models.Project) (int64, erro func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +97,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, idOrName interface{}) (*models.Project, error) { ret := _m.Called(ctx, idOrName) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, interface{}) (*models.Project, error)); ok { @@ -108,6 +127,10 @@ func (_m *Manager) Get(ctx context.Context, idOrName interface{}) (*models.Proje func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Project, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Project, error)); ok { @@ -130,25 +153,29 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Project, return r0, r1 } -// ListAdminRolesOfUser provides a mock function with given fields: ctx, userID -func (_m *Manager) ListAdminRolesOfUser(ctx context.Context, userID int) ([]models.Member, error) { - ret := _m.Called(ctx, userID) +// ListAdminRolesOfUser provides a mock function with given fields: ctx, user +func (_m *Manager) ListAdminRolesOfUser(ctx context.Context, user commonmodels.User) ([]models.Member, error) { + ret := _m.Called(ctx, user) + + if len(ret) == 0 { + panic("no return value specified for ListAdminRolesOfUser") + } var r0 []models.Member var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int) ([]models.Member, error)); ok { - return rf(ctx, userID) + if rf, ok := ret.Get(0).(func(context.Context, commonmodels.User) ([]models.Member, error)); ok { + return rf(ctx, user) } - if rf, ok := ret.Get(0).(func(context.Context, int) []models.Member); ok { - r0 = rf(ctx, userID) + if rf, ok := ret.Get(0).(func(context.Context, commonmodels.User) []models.Member); ok { + r0 = rf(ctx, user) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]models.Member) } } - if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { - r1 = rf(ctx, userID) + if rf, ok := ret.Get(1).(func(context.Context, commonmodels.User) error); ok { + r1 = rf(ctx, user) } else { r1 = ret.Error(1) } @@ -167,6 +194,10 @@ func (_m *Manager) ListRoles(ctx context.Context, projectID int64, userID int, g _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for ListRoles") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int, ...int) ([]int, error)); ok { diff --git a/src/testing/pkg/project/metadata/manager.go b/src/testing/pkg/project/metadata/manager.go index 3a6e071c07..7135b3be23 100644 --- a/src/testing/pkg/project/metadata/manager.go +++ b/src/testing/pkg/project/metadata/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package metadata @@ -19,6 +19,10 @@ type Manager struct { func (_m *Manager) Add(ctx context.Context, projectID int64, meta map[string]string) error { ret := _m.Called(ctx, projectID, meta) + if len(ret) == 0 { + panic("no return value specified for Add") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]string) error); ok { r0 = rf(ctx, projectID, meta) @@ -40,6 +44,10 @@ func (_m *Manager) Delete(ctx context.Context, projectID int64, meta ...string) _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...string) error); ok { r0 = rf(ctx, projectID, meta...) @@ -61,6 +69,10 @@ func (_m *Manager) Get(ctx context.Context, projectID int64, meta ...string) (ma _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 map[string]string var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...string) (map[string]string, error)); ok { @@ -87,6 +99,10 @@ func (_m *Manager) Get(ctx context.Context, projectID int64, meta ...string) (ma func (_m *Manager) List(ctx context.Context, name string, value string) ([]*models.ProjectMetadata, error) { ret := _m.Called(ctx, name, value) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.ProjectMetadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]*models.ProjectMetadata, error)); ok { @@ -113,6 +129,10 @@ func (_m *Manager) List(ctx context.Context, name string, value string) ([]*mode func (_m *Manager) Update(ctx context.Context, projectID int64, meta map[string]string) error { ret := _m.Called(ctx, projectID, meta) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]string) error); ok { r0 = rf(ctx, projectID, meta) diff --git a/src/testing/pkg/queuestatus/manager.go b/src/testing/pkg/queuestatus/manager.go index 836631dfeb..924bc4ca85 100644 --- a/src/testing/pkg/queuestatus/manager.go +++ b/src/testing/pkg/queuestatus/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package queuestatus @@ -18,6 +18,10 @@ type Manager struct { func (_m *Manager) AllJobTypeStatus(ctx context.Context) (map[string]bool, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for AllJobTypeStatus") + } + var r0 map[string]bool var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]bool, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) AllJobTypeStatus(ctx context.Context) (map[string]bool, error func (_m *Manager) CreateOrUpdate(ctx context.Context, status *model.JobQueueStatus) (int64, error) { ret := _m.Called(ctx, status) + if len(ret) == 0 { + panic("no return value specified for CreateOrUpdate") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.JobQueueStatus) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) CreateOrUpdate(ctx context.Context, status *model.JobQueueSta func (_m *Manager) Get(ctx context.Context, jobType string) (*model.JobQueueStatus, error) { ret := _m.Called(ctx, jobType) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.JobQueueStatus var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.JobQueueStatus, error)); ok { @@ -94,6 +106,10 @@ func (_m *Manager) Get(ctx context.Context, jobType string) (*model.JobQueueStat func (_m *Manager) List(ctx context.Context) ([]*model.JobQueueStatus, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.JobQueueStatus var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.JobQueueStatus, error)); ok { @@ -120,6 +136,10 @@ func (_m *Manager) List(ctx context.Context) ([]*model.JobQueueStatus, error) { func (_m *Manager) UpdateStatus(ctx context.Context, jobType string, paused bool) error { ret := _m.Called(ctx, jobType, paused) + if len(ret) == 0 { + panic("no return value specified for UpdateStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, bool) error); ok { r0 = rf(ctx, jobType, paused) diff --git a/src/testing/pkg/quota/driver/driver.go b/src/testing/pkg/quota/driver/driver.go index 377caf2b00..2ef8b7c37a 100644 --- a/src/testing/pkg/quota/driver/driver.go +++ b/src/testing/pkg/quota/driver/driver.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package driver @@ -20,6 +20,10 @@ type Driver struct { func (_m *Driver) CalculateUsage(ctx context.Context, key string) (types.ResourceList, error) { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for CalculateUsage") + } + var r0 types.ResourceList var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (types.ResourceList, error)); ok { @@ -46,6 +50,10 @@ func (_m *Driver) CalculateUsage(ctx context.Context, key string) (types.Resourc func (_m *Driver) Enabled(ctx context.Context, key string) (bool, error) { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Enabled") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { @@ -70,6 +78,10 @@ func (_m *Driver) Enabled(ctx context.Context, key string) (bool, error) { func (_m *Driver) HardLimits(ctx context.Context) types.ResourceList { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for HardLimits") + } + var r0 types.ResourceList if rf, ok := ret.Get(0).(func(context.Context) types.ResourceList); ok { r0 = rf(ctx) @@ -86,6 +98,10 @@ func (_m *Driver) HardLimits(ctx context.Context) types.ResourceList { func (_m *Driver) Load(ctx context.Context, key string) (driver.RefObject, error) { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Load") + } + var r0 driver.RefObject var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (driver.RefObject, error)); ok { @@ -112,6 +128,10 @@ func (_m *Driver) Load(ctx context.Context, key string) (driver.RefObject, error func (_m *Driver) Validate(hardLimits types.ResourceList) error { ret := _m.Called(hardLimits) + if len(ret) == 0 { + panic("no return value specified for Validate") + } + var r0 error if rf, ok := ret.Get(0).(func(types.ResourceList) error); ok { r0 = rf(hardLimits) diff --git a/src/testing/pkg/quota/manager.go b/src/testing/pkg/quota/manager.go index b5feaaec6c..4d570f3ed4 100644 --- a/src/testing/pkg/quota/manager.go +++ b/src/testing/pkg/quota/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package quota @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -53,6 +57,10 @@ func (_m *Manager) Create(ctx context.Context, reference string, referenceID str _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, types.ResourceList, ...types.ResourceList) (int64, error)); ok { @@ -77,6 +85,10 @@ func (_m *Manager) Create(ctx context.Context, reference string, referenceID str func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -91,6 +103,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*models.Quota, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*models.Quota, error)); ok { @@ -117,6 +133,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*models.Quota, error) { func (_m *Manager) GetByRef(ctx context.Context, reference string, referenceID string) (*models.Quota, error) { ret := _m.Called(ctx, reference, referenceID) + if len(ret) == 0 { + panic("no return value specified for GetByRef") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.Quota, error)); ok { @@ -143,6 +163,10 @@ func (_m *Manager) GetByRef(ctx context.Context, reference string, referenceID s func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Quota, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Quota, error)); ok { @@ -169,6 +193,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Quota, e func (_m *Manager) Update(ctx context.Context, _a1 *models.Quota) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Quota) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/rbac/dao/dao.go b/src/testing/pkg/rbac/dao/dao.go index 7f2138499a..91a2c351ce 100644 --- a/src/testing/pkg/rbac/dao/dao.go +++ b/src/testing/pkg/rbac/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) CreatePermission(ctx context.Context, rp *model.RolePermission) (int64, error) { ret := _m.Called(ctx, rp) + if len(ret) == 0 { + panic("no return value specified for CreatePermission") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RolePermission) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) CreatePermission(ctx context.Context, rp *model.RolePermission) ( func (_m *DAO) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPolicy) (int64, error) { ret := _m.Called(ctx, pp) + if len(ret) == 0 { + panic("no return value specified for CreateRbacPolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.PermissionPolicy) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPolicy) func (_m *DAO) DeletePermission(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeletePermission") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) DeletePermission(ctx context.Context, id int64) error { func (_m *DAO) DeletePermissionsByRole(ctx context.Context, roleType string, roleID int64) error { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for DeletePermissionsByRole") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, roleType, roleID) @@ -97,6 +113,10 @@ func (_m *DAO) DeletePermissionsByRole(ctx context.Context, roleType string, rol func (_m *DAO) DeleteRbacPolicy(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteRbacPolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -111,6 +131,10 @@ func (_m *DAO) DeleteRbacPolicy(ctx context.Context, id int64) error { func (_m *DAO) GetPermissionsByRole(ctx context.Context, roleType string, roleID int64) ([]*model.UniversalRolePermission, error) { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for GetPermissionsByRole") + } + var r0 []*model.UniversalRolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]*model.UniversalRolePermission, error)); ok { @@ -137,6 +161,10 @@ func (_m *DAO) GetPermissionsByRole(ctx context.Context, roleType string, roleID func (_m *DAO) ListPermissions(ctx context.Context, query *q.Query) ([]*model.RolePermission, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPermissions") + } + var r0 []*model.RolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RolePermission, error)); ok { @@ -163,6 +191,10 @@ func (_m *DAO) ListPermissions(ctx context.Context, query *q.Query) ([]*model.Ro func (_m *DAO) ListRbacPolicies(ctx context.Context, query *q.Query) ([]*model.PermissionPolicy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListRbacPolicies") + } + var r0 []*model.PermissionPolicy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.PermissionPolicy, error)); ok { diff --git a/src/testing/pkg/rbac/manager.go b/src/testing/pkg/rbac/manager.go index 3de52e1d4c..0cc460753d 100644 --- a/src/testing/pkg/rbac/manager.go +++ b/src/testing/pkg/rbac/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package rbac @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) CreatePermission(ctx context.Context, rp *model.RolePermission) (int64, error) { ret := _m.Called(ctx, rp) + if len(ret) == 0 { + panic("no return value specified for CreatePermission") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RolePermission) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) CreatePermission(ctx context.Context, rp *model.RolePermissio func (_m *Manager) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPolicy) (int64, error) { ret := _m.Called(ctx, pp) + if len(ret) == 0 { + panic("no return value specified for CreateRbacPolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.PermissionPolicy) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPol func (_m *Manager) DeletePermission(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeletePermission") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) DeletePermission(ctx context.Context, id int64) error { func (_m *Manager) DeletePermissionsByRole(ctx context.Context, roleType string, roleID int64) error { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for DeletePermissionsByRole") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, roleType, roleID) @@ -96,6 +112,10 @@ func (_m *Manager) DeletePermissionsByRole(ctx context.Context, roleType string, func (_m *Manager) DeleteRbacPolicy(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteRbacPolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -110,6 +130,10 @@ func (_m *Manager) DeleteRbacPolicy(ctx context.Context, id int64) error { func (_m *Manager) GetPermissionsByRole(ctx context.Context, roleType string, roleID int64) ([]*model.UniversalRolePermission, error) { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for GetPermissionsByRole") + } + var r0 []*model.UniversalRolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]*model.UniversalRolePermission, error)); ok { @@ -136,6 +160,10 @@ func (_m *Manager) GetPermissionsByRole(ctx context.Context, roleType string, ro func (_m *Manager) ListPermissions(ctx context.Context, query *q.Query) ([]*model.RolePermission, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPermissions") + } + var r0 []*model.RolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RolePermission, error)); ok { @@ -162,6 +190,10 @@ func (_m *Manager) ListPermissions(ctx context.Context, query *q.Query) ([]*mode func (_m *Manager) ListRbacPolicies(ctx context.Context, query *q.Query) ([]*model.PermissionPolicy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListRbacPolicies") + } + var r0 []*model.PermissionPolicy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.PermissionPolicy, error)); ok { diff --git a/src/testing/pkg/reg/adapter/adapter.go b/src/testing/pkg/reg/adapter/adapter.go index 6f8ad39b50..a10a0f2ff1 100644 --- a/src/testing/pkg/reg/adapter/adapter.go +++ b/src/testing/pkg/reg/adapter/adapter.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package adapter @@ -16,6 +16,10 @@ type Adapter struct { func (_m *Adapter) HealthCheck() (string, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for HealthCheck") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func() (string, error)); ok { @@ -40,6 +44,10 @@ func (_m *Adapter) HealthCheck() (string, error) { func (_m *Adapter) Info() (*model.RegistryInfo, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Info") + } + var r0 *model.RegistryInfo var r1 error if rf, ok := ret.Get(0).(func() (*model.RegistryInfo, error)); ok { @@ -66,6 +74,10 @@ func (_m *Adapter) Info() (*model.RegistryInfo, error) { func (_m *Adapter) PrepareForPush(_a0 []*model.Resource) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for PrepareForPush") + } + var r0 error if rf, ok := ret.Get(0).(func([]*model.Resource) error); ok { r0 = rf(_a0) diff --git a/src/testing/pkg/reg/dao/dao.go b/src/testing/pkg/reg/dao/dao.go index 0f11dd37fb..23ae0ecc8a 100644 --- a/src/testing/pkg/reg/dao/dao.go +++ b/src/testing/pkg/reg/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -20,6 +20,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, registry *dao.Registry) (int64, error) { ret := _m.Called(ctx, registry) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Registry) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *DAO) Create(ctx context.Context, registry *dao.Registry) (int64, error func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Registry, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Registry, error)); ok { @@ -108,6 +124,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Registry, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*dao.Registry, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Registry, error)); ok { @@ -141,6 +161,10 @@ func (_m *DAO) Update(ctx context.Context, registry *dao.Registry, props ...stri _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Registry, ...string) error); ok { r0 = rf(ctx, registry, props...) diff --git a/src/testing/pkg/reg/manager.go b/src/testing/pkg/reg/manager.go index 291af39276..c25e542d11 100644 --- a/src/testing/pkg/reg/manager.go +++ b/src/testing/pkg/reg/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package manager @@ -23,6 +23,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -47,6 +51,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, registry *model.Registry) (int64, error) { ret := _m.Called(ctx, registry) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Registry) (int64, error)); ok { @@ -71,6 +79,10 @@ func (_m *Manager) Create(ctx context.Context, registry *model.Registry) (int64, func (_m *Manager) CreateAdapter(ctx context.Context, registry *model.Registry) (adapter.Adapter, error) { ret := _m.Called(ctx, registry) + if len(ret) == 0 { + panic("no return value specified for CreateAdapter") + } + var r0 adapter.Adapter var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Registry) (adapter.Adapter, error)); ok { @@ -97,6 +109,10 @@ func (_m *Manager) CreateAdapter(ctx context.Context, registry *model.Registry) func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -111,6 +127,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Registry, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Registry, error)); ok { @@ -137,6 +157,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Registry, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Registry, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Registry, error)); ok { @@ -163,6 +187,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Registry, func (_m *Manager) ListRegistryProviderInfos(ctx context.Context) (map[string]*model.AdapterPattern, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for ListRegistryProviderInfos") + } + var r0 map[string]*model.AdapterPattern var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]*model.AdapterPattern, error)); ok { @@ -189,6 +217,10 @@ func (_m *Manager) ListRegistryProviderInfos(ctx context.Context) (map[string]*m func (_m *Manager) ListRegistryProviderTypes(ctx context.Context) ([]string, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for ListRegistryProviderTypes") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { @@ -222,6 +254,10 @@ func (_m *Manager) Update(ctx context.Context, registry *model.Registry, props . _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Registry, ...string) error); ok { r0 = rf(ctx, registry, props...) diff --git a/src/testing/pkg/registry/fake_registry_client.go b/src/testing/pkg/registry/fake_registry_client.go index adb9c3446a..72a6decf23 100644 --- a/src/testing/pkg/registry/fake_registry_client.go +++ b/src/testing/pkg/registry/fake_registry_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package registry @@ -21,6 +21,10 @@ type Client struct { func (_m *Client) BlobExist(repository string, digest string) (bool, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for BlobExist") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(string, string) (bool, error)); ok { @@ -45,6 +49,10 @@ func (_m *Client) BlobExist(repository string, digest string) (bool, error) { func (_m *Client) Catalog() ([]string, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Catalog") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func() ([]string, error)); ok { @@ -71,6 +79,10 @@ func (_m *Client) Catalog() ([]string, error) { func (_m *Client) Copy(srcRepository string, srcReference string, dstRepository string, dstReference string, override bool) error { ret := _m.Called(srcRepository, srcReference, dstRepository, dstReference, override) + if len(ret) == 0 { + panic("no return value specified for Copy") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string, string, bool) error); ok { r0 = rf(srcRepository, srcReference, dstRepository, dstReference, override) @@ -85,6 +97,10 @@ func (_m *Client) Copy(srcRepository string, srcReference string, dstRepository func (_m *Client) DeleteBlob(repository string, digest string) error { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for DeleteBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, digest) @@ -99,6 +115,10 @@ func (_m *Client) DeleteBlob(repository string, digest string) error { func (_m *Client) DeleteManifest(repository string, reference string) error { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for DeleteManifest") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, reference) @@ -113,6 +133,10 @@ func (_m *Client) DeleteManifest(repository string, reference string) error { func (_m *Client) Do(req *http.Request) (*http.Response, error) { ret := _m.Called(req) + if len(ret) == 0 { + panic("no return value specified for Do") + } + var r0 *http.Response var r1 error if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok { @@ -139,6 +163,10 @@ func (_m *Client) Do(req *http.Request) (*http.Response, error) { func (_m *Client) ListTags(repository string) ([]string, error) { ret := _m.Called(repository) + if len(ret) == 0 { + panic("no return value specified for ListTags") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { @@ -165,6 +193,10 @@ func (_m *Client) ListTags(repository string) ([]string, error) { func (_m *Client) ManifestExist(repository string, reference string) (bool, *distribution.Descriptor, error) { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for ManifestExist") + } + var r0 bool var r1 *distribution.Descriptor var r2 error @@ -198,6 +230,10 @@ func (_m *Client) ManifestExist(repository string, reference string) (bool, *dis func (_m *Client) MountBlob(srcRepository string, digest string, dstRepository string) error { ret := _m.Called(srcRepository, digest, dstRepository) + if len(ret) == 0 { + panic("no return value specified for MountBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string) error); ok { r0 = rf(srcRepository, digest, dstRepository) @@ -212,6 +248,10 @@ func (_m *Client) MountBlob(srcRepository string, digest string, dstRepository s func (_m *Client) Ping() error { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() @@ -226,6 +266,10 @@ func (_m *Client) Ping() error { func (_m *Client) PullBlob(repository string, digest string) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for PullBlob") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -259,6 +303,10 @@ func (_m *Client) PullBlob(repository string, digest string) (int64, io.ReadClos func (_m *Client) PullBlobChunk(repository string, digest string, blobSize int64, start int64, end int64) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest, blobSize, start, end) + if len(ret) == 0 { + panic("no return value specified for PullBlobChunk") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -299,6 +347,10 @@ func (_m *Client) PullManifest(repository string, reference string, acceptedMedi _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for PullManifest") + } + var r0 distribution.Manifest var r1 string var r2 error @@ -332,6 +384,10 @@ func (_m *Client) PullManifest(repository string, reference string, acceptedMedi func (_m *Client) PushBlob(repository string, digest string, size int64, blob io.Reader) error { ret := _m.Called(repository, digest, size, blob) + if len(ret) == 0 { + panic("no return value specified for PushBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, int64, io.Reader) error); ok { r0 = rf(repository, digest, size, blob) @@ -346,6 +402,10 @@ func (_m *Client) PushBlob(repository string, digest string, size int64, blob io func (_m *Client) PushBlobChunk(repository string, digest string, blobSize int64, chunk io.Reader, start int64, end int64, location string) (string, int64, error) { ret := _m.Called(repository, digest, blobSize, chunk, start, end, location) + if len(ret) == 0 { + panic("no return value specified for PushBlobChunk") + } + var r0 string var r1 int64 var r2 error @@ -377,6 +437,10 @@ func (_m *Client) PushBlobChunk(repository string, digest string, blobSize int64 func (_m *Client) PushManifest(repository string, reference string, mediaType string, payload []byte) (string, error) { ret := _m.Called(repository, reference, mediaType, payload) + if len(ret) == 0 { + panic("no return value specified for PushManifest") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(string, string, string, []byte) (string, error)); ok { diff --git a/src/testing/pkg/replication/dao/dao.go b/src/testing/pkg/replication/dao/dao.go index 0a0ed964b3..29c9a2be49 100644 --- a/src/testing/pkg/replication/dao/dao.go +++ b/src/testing/pkg/replication/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, policy *model.Policy) (int64, error) func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -142,6 +162,10 @@ func (_m *DAO) Update(ctx context.Context, policy *model.Policy, props ...string _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, ...string) error); ok { r0 = rf(ctx, policy, props...) diff --git a/src/testing/pkg/replication/manager.go b/src/testing/pkg/replication/manager.go index 9289c4f8c4..652331740e 100644 --- a/src/testing/pkg/replication/manager.go +++ b/src/testing/pkg/replication/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package manager @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, policy *model.Policy) (int64, err func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -141,6 +161,10 @@ func (_m *Manager) Update(ctx context.Context, policy *model.Policy, props ...st _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, ...string) error); ok { r0 = rf(ctx, policy, props...) diff --git a/src/testing/pkg/repository/dao/dao.go b/src/testing/pkg/repository/dao/dao.go index 7e5563eedc..59094a22c6 100644 --- a/src/testing/pkg/repository/dao/dao.go +++ b/src/testing/pkg/repository/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) AddPullCount(ctx context.Context, id int64, count uint64) error { ret := _m.Called(ctx, id, count) + if len(ret) == 0 { + panic("no return value specified for AddPullCount") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, uint64) error); ok { r0 = rf(ctx, id, count) @@ -35,6 +39,10 @@ func (_m *DAO) AddPullCount(ctx context.Context, id int64, count uint64) error { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -59,6 +67,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, repository *model.RepoRecord) (int64, error) { ret := _m.Called(ctx, repository) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord) (int64, error)); ok { @@ -83,6 +95,10 @@ func (_m *DAO) Create(ctx context.Context, repository *model.RepoRecord) (int64, func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -97,6 +113,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.RepoRecord, error)); ok { @@ -123,6 +143,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RepoRecord, error)); ok { @@ -149,6 +173,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, e func (_m *DAO) NonEmptyRepos(ctx context.Context) ([]*model.RepoRecord, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for NonEmptyRepos") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.RepoRecord, error)); ok { @@ -182,6 +210,10 @@ func (_m *DAO) Update(ctx context.Context, repository *model.RepoRecord, props . _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord, ...string) error); ok { r0 = rf(ctx, repository, props...) diff --git a/src/testing/pkg/repository/manager.go b/src/testing/pkg/repository/manager.go index f0de300a59..db6df7bde6 100644 --- a/src/testing/pkg/repository/manager.go +++ b/src/testing/pkg/repository/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package repository @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) AddPullCount(ctx context.Context, id int64, count uint64) error { ret := _m.Called(ctx, id, count) + if len(ret) == 0 { + panic("no return value specified for AddPullCount") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, uint64) error); ok { r0 = rf(ctx, id, count) @@ -34,6 +38,10 @@ func (_m *Manager) AddPullCount(ctx context.Context, id int64, count uint64) err func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -58,6 +66,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.RepoRecord) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord) (int64, error)); ok { @@ -82,6 +94,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.RepoRecord) (int64, er func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -96,6 +112,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.RepoRecord, error)); ok { @@ -122,6 +142,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.RepoRecord, error) func (_m *Manager) GetByName(ctx context.Context, name string) (*model.RepoRecord, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.RepoRecord, error)); ok { @@ -148,6 +172,10 @@ func (_m *Manager) GetByName(ctx context.Context, name string) (*model.RepoRecor func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RepoRecord, error)); ok { @@ -174,6 +202,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.RepoRecor func (_m *Manager) NonEmptyRepos(ctx context.Context) ([]*model.RepoRecord, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for NonEmptyRepos") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.RepoRecord, error)); ok { @@ -207,6 +239,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *model.RepoRecord, props ...s _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord, ...string) error); ok { r0 = rf(ctx, _a1, props...) diff --git a/src/testing/pkg/robot/dao/dao.go b/src/testing/pkg/robot/dao/dao.go index d3255265f2..f1160128fe 100644 --- a/src/testing/pkg/robot/dao/dao.go +++ b/src/testing/pkg/robot/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, r *model.Robot) (int64, error) { ret := _m.Called(ctx, r) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, r *model.Robot) (int64, error) { func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) DeleteByProjectID(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteByProjectID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -97,6 +113,10 @@ func (_m *DAO) DeleteByProjectID(ctx context.Context, projectID int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.Robot, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Robot, error)); ok { @@ -123,6 +143,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Robot, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Robot, error)); ok { @@ -156,6 +180,10 @@ func (_m *DAO) Update(ctx context.Context, r *model.Robot, props ...string) erro _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot, ...string) error); ok { r0 = rf(ctx, r, props...) diff --git a/src/testing/pkg/robot/manager.go b/src/testing/pkg/robot/manager.go index 56be7000c9..12101d09ed 100644 --- a/src/testing/pkg/robot/manager.go +++ b/src/testing/pkg/robot/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package robot @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, m *model.Robot) (int64, error) { ret := _m.Called(ctx, m) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, m *model.Robot) (int64, error) { func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) DeleteByProjectID(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteByProjectID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -96,6 +112,10 @@ func (_m *Manager) DeleteByProjectID(ctx context.Context, projectID int64) error func (_m *Manager) Get(ctx context.Context, id int64) (*model.Robot, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Robot, error)); ok { @@ -122,6 +142,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Robot, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Robot, error)); ok { @@ -155,6 +179,10 @@ func (_m *Manager) Update(ctx context.Context, m *model.Robot, props ...string) _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot, ...string) error); ok { r0 = rf(ctx, m, props...) diff --git a/src/testing/pkg/scan/export/artifact_digest_calculator.go b/src/testing/pkg/scan/export/artifact_digest_calculator.go index 0a3436672e..150c11cfd6 100644 --- a/src/testing/pkg/scan/export/artifact_digest_calculator.go +++ b/src/testing/pkg/scan/export/artifact_digest_calculator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package export @@ -17,6 +17,10 @@ type ArtifactDigestCalculator struct { func (_m *ArtifactDigestCalculator) Calculate(fileName string) (digest.Digest, error) { ret := _m.Called(fileName) + if len(ret) == 0 { + panic("no return value specified for Calculate") + } + var r0 digest.Digest var r1 error if rf, ok := ret.Get(0).(func(string) (digest.Digest, error)); ok { diff --git a/src/testing/pkg/scan/export/filter_processor.go b/src/testing/pkg/scan/export/filter_processor.go index fac0dbfffc..4087eba62b 100644 --- a/src/testing/pkg/scan/export/filter_processor.go +++ b/src/testing/pkg/scan/export/filter_processor.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package export @@ -19,6 +19,10 @@ type FilterProcessor struct { func (_m *FilterProcessor) ProcessLabelFilter(ctx context.Context, labelIDs []int64, arts []*artifact.Artifact) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, labelIDs, arts) + if len(ret) == 0 { + panic("no return value specified for ProcessLabelFilter") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, []int64, []*artifact.Artifact) ([]*artifact.Artifact, error)); ok { @@ -45,6 +49,10 @@ func (_m *FilterProcessor) ProcessLabelFilter(ctx context.Context, labelIDs []in func (_m *FilterProcessor) ProcessRepositoryFilter(ctx context.Context, filter string, projectIds []int64) ([]int64, error) { ret := _m.Called(ctx, filter, projectIds) + if len(ret) == 0 { + panic("no return value specified for ProcessRepositoryFilter") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []int64) ([]int64, error)); ok { @@ -71,6 +79,10 @@ func (_m *FilterProcessor) ProcessRepositoryFilter(ctx context.Context, filter s func (_m *FilterProcessor) ProcessTagFilter(ctx context.Context, filter string, repositoryIds []int64) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, filter, repositoryIds) + if len(ret) == 0 { + panic("no return value specified for ProcessTagFilter") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []int64) ([]*artifact.Artifact, error)); ok { diff --git a/src/testing/pkg/scan/export/manager.go b/src/testing/pkg/scan/export/manager.go index 3261f9b502..158ed47adb 100644 --- a/src/testing/pkg/scan/export/manager.go +++ b/src/testing/pkg/scan/export/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package export @@ -18,6 +18,10 @@ type Manager struct { func (_m *Manager) Fetch(ctx context.Context, params export.Params) ([]export.Data, error) { ret := _m.Called(ctx, params) + if len(ret) == 0 { + panic("no return value specified for Fetch") + } + var r0 []export.Data var r1 error if rf, ok := ret.Get(0).(func(context.Context, export.Params) ([]export.Data, error)); ok { diff --git a/src/testing/pkg/scan/report/manager.go b/src/testing/pkg/scan/report/manager.go index 7689335062..046ac253e6 100644 --- a/src/testing/pkg/scan/report/manager.go +++ b/src/testing/pkg/scan/report/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package report @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Create(ctx context.Context, r *scan.Report) (string, error) { ret := _m.Called(ctx, r) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *scan.Report) (string, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Create(ctx context.Context, r *scan.Report) (string, error) { func (_m *Manager) Delete(ctx context.Context, uuid string) error { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, uuid) @@ -65,6 +73,10 @@ func (_m *Manager) DeleteByDigests(ctx context.Context, digests ...string) error _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for DeleteByDigests") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { r0 = rf(ctx, digests...) @@ -75,10 +87,32 @@ func (_m *Manager) DeleteByDigests(ctx context.Context, digests ...string) error return r0 } +// DeleteByExtraAttr provides a mock function with given fields: ctx, mimeType, attrName, attrValue +func (_m *Manager) DeleteByExtraAttr(ctx context.Context, mimeType string, attrName string, attrValue string) error { + ret := _m.Called(ctx, mimeType, attrName, attrValue) + + if len(ret) == 0 { + panic("no return value specified for DeleteByExtraAttr") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, mimeType, attrName, attrValue) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // GetBy provides a mock function with given fields: ctx, digest, registrationUUID, mimeTypes func (_m *Manager) GetBy(ctx context.Context, digest string, registrationUUID string, mimeTypes []string) ([]*scan.Report, error) { ret := _m.Called(ctx, digest, registrationUUID, mimeTypes) + if len(ret) == 0 { + panic("no return value specified for GetBy") + } + var r0 []*scan.Report var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, []string) ([]*scan.Report, error)); ok { @@ -105,6 +139,10 @@ func (_m *Manager) GetBy(ctx context.Context, digest string, registrationUUID st func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*scan.Report, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*scan.Report var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scan.Report, error)); ok { @@ -138,6 +176,10 @@ func (_m *Manager) Update(ctx context.Context, r *scan.Report, cols ...string) e _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *scan.Report, ...string) error); ok { r0 = rf(ctx, r, cols...) @@ -152,6 +194,10 @@ func (_m *Manager) Update(ctx context.Context, r *scan.Report, cols ...string) e func (_m *Manager) UpdateReportData(ctx context.Context, uuid string, _a2 string) error { ret := _m.Called(ctx, uuid, _a2) + if len(ret) == 0 { + panic("no return value specified for UpdateReportData") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, uuid, _a2) diff --git a/src/testing/pkg/scan/rest/v1/client.go b/src/testing/pkg/scan/rest/v1/client.go index 1f17ff8e68..1d15012c74 100644 --- a/src/testing/pkg/scan/rest/v1/client.go +++ b/src/testing/pkg/scan/rest/v1/client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 @@ -16,6 +16,10 @@ type Client struct { func (_m *Client) GetMetadata() (*v1.ScannerAdapterMetadata, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetMetadata") + } + var r0 *v1.ScannerAdapterMetadata var r1 error if rf, ok := ret.Get(0).(func() (*v1.ScannerAdapterMetadata, error)); ok { @@ -38,23 +42,27 @@ func (_m *Client) GetMetadata() (*v1.ScannerAdapterMetadata, error) { return r0, r1 } -// GetScanReport provides a mock function with given fields: scanRequestID, reportMIMEType -func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string) (string, error) { - ret := _m.Called(scanRequestID, reportMIMEType) +// GetScanReport provides a mock function with given fields: scanRequestID, reportMIMEType, urlParameter +func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string, urlParameter string) (string, error) { + ret := _m.Called(scanRequestID, reportMIMEType, urlParameter) + + if len(ret) == 0 { + panic("no return value specified for GetScanReport") + } var r0 string var r1 error - if rf, ok := ret.Get(0).(func(string, string) (string, error)); ok { - return rf(scanRequestID, reportMIMEType) + if rf, ok := ret.Get(0).(func(string, string, string) (string, error)); ok { + return rf(scanRequestID, reportMIMEType, urlParameter) } - if rf, ok := ret.Get(0).(func(string, string) string); ok { - r0 = rf(scanRequestID, reportMIMEType) + if rf, ok := ret.Get(0).(func(string, string, string) string); ok { + r0 = rf(scanRequestID, reportMIMEType, urlParameter) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(scanRequestID, reportMIMEType) + if rf, ok := ret.Get(1).(func(string, string, string) error); ok { + r1 = rf(scanRequestID, reportMIMEType, urlParameter) } else { r1 = ret.Error(1) } @@ -66,6 +74,10 @@ func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string) (st func (_m *Client) SubmitScan(req *v1.ScanRequest) (*v1.ScanResponse, error) { ret := _m.Called(req) + if len(ret) == 0 { + panic("no return value specified for SubmitScan") + } + var r0 *v1.ScanResponse var r1 error if rf, ok := ret.Get(0).(func(*v1.ScanRequest) (*v1.ScanResponse, error)); ok { diff --git a/src/testing/pkg/scan/rest/v1/client_pool.go b/src/testing/pkg/scan/rest/v1/client_pool.go index f662ebd5e2..6533473a81 100644 --- a/src/testing/pkg/scan/rest/v1/client_pool.go +++ b/src/testing/pkg/scan/rest/v1/client_pool.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 @@ -16,6 +16,10 @@ type ClientPool struct { func (_m *ClientPool) Get(url string, authType string, accessCredential string, skipCertVerify bool) (v1.Client, error) { ret := _m.Called(url, authType, accessCredential, skipCertVerify) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 v1.Client var r1 error if rf, ok := ret.Get(0).(func(string, string, string, bool) (v1.Client, error)); ok { diff --git a/src/testing/pkg/scan/rest/v1/request_resolver.go b/src/testing/pkg/scan/rest/v1/request_resolver.go index 0f8ad927ec..f5e67b78e2 100644 --- a/src/testing/pkg/scan/rest/v1/request_resolver.go +++ b/src/testing/pkg/scan/rest/v1/request_resolver.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 diff --git a/src/testing/pkg/scan/rest/v1/response_handler.go b/src/testing/pkg/scan/rest/v1/response_handler.go index 632f6c3363..6909341464 100644 --- a/src/testing/pkg/scan/rest/v1/response_handler.go +++ b/src/testing/pkg/scan/rest/v1/response_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 @@ -17,6 +17,10 @@ type responseHandler struct { func (_m *responseHandler) Execute(code int, resp *http.Response) ([]byte, error) { ret := _m.Called(code, resp) + if len(ret) == 0 { + panic("no return value specified for Execute") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(int, *http.Response) ([]byte, error)); ok { diff --git a/src/testing/pkg/scan/scanner/manager.go b/src/testing/pkg/scan/scanner/manager.go index aaadd9f7de..08890ace3f 100644 --- a/src/testing/pkg/scan/scanner/manager.go +++ b/src/testing/pkg/scan/scanner/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scanner @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, registration *daoscanner.Registration) (string, error) { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *daoscanner.Registration) (string, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, registration *daoscanner.Registra func (_m *Manager) DefaultScannerUUID(ctx context.Context) (string, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for DefaultScannerUUID") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { @@ -92,6 +104,10 @@ func (_m *Manager) DefaultScannerUUID(ctx context.Context) (string, error) { func (_m *Manager) Delete(ctx context.Context, registrationUUID string) error { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, registrationUUID) @@ -106,6 +122,10 @@ func (_m *Manager) Delete(ctx context.Context, registrationUUID string) error { func (_m *Manager) Get(ctx context.Context, registrationUUID string) (*daoscanner.Registration, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *daoscanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*daoscanner.Registration, error)); ok { @@ -132,6 +152,10 @@ func (_m *Manager) Get(ctx context.Context, registrationUUID string) (*daoscanne func (_m *Manager) GetDefault(ctx context.Context) (*daoscanner.Registration, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetDefault") + } + var r0 *daoscanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*daoscanner.Registration, error)); ok { @@ -158,6 +182,10 @@ func (_m *Manager) GetDefault(ctx context.Context) (*daoscanner.Registration, er func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*daoscanner.Registration, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*daoscanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*daoscanner.Registration, error)); ok { @@ -184,6 +212,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*daoscanner.Regi func (_m *Manager) SetAsDefault(ctx context.Context, registrationUUID string) error { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for SetAsDefault") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, registrationUUID) @@ -198,6 +230,10 @@ func (_m *Manager) SetAsDefault(ctx context.Context, registrationUUID string) er func (_m *Manager) Update(ctx context.Context, registration *daoscanner.Registration) error { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *daoscanner.Registration) error); ok { r0 = rf(ctx, registration) diff --git a/src/testing/pkg/scheduler/scheduler.go b/src/testing/pkg/scheduler/scheduler.go index 437c7ca19c..0550dcdf4d 100644 --- a/src/testing/pkg/scheduler/scheduler.go +++ b/src/testing/pkg/scheduler/scheduler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scheduler @@ -20,6 +20,10 @@ type Scheduler struct { func (_m *Scheduler) CountSchedules(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for CountSchedules") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Scheduler) CountSchedules(ctx context.Context, query *q.Query) (int64, func (_m *Scheduler) GetSchedule(ctx context.Context, id int64) (*scheduler.Schedule, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetSchedule") + } + var r0 *scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*scheduler.Schedule, error)); ok { @@ -70,6 +78,10 @@ func (_m *Scheduler) GetSchedule(ctx context.Context, id int64) (*scheduler.Sche func (_m *Scheduler) ListSchedules(ctx context.Context, query *q.Query) ([]*scheduler.Schedule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListSchedules") + } + var r0 []*scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scheduler.Schedule, error)); ok { @@ -96,6 +108,10 @@ func (_m *Scheduler) ListSchedules(ctx context.Context, query *q.Query) ([]*sche func (_m *Scheduler) Schedule(ctx context.Context, vendorType string, vendorID int64, cronType string, cron string, callbackFuncName string, callbackFuncParams interface{}, extraAttrs map[string]interface{}) (int64, error) { ret := _m.Called(ctx, vendorType, vendorID, cronType, cron, callbackFuncName, callbackFuncParams, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for Schedule") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, string, string, string, interface{}, map[string]interface{}) (int64, error)); ok { @@ -120,6 +136,10 @@ func (_m *Scheduler) Schedule(ctx context.Context, vendorType string, vendorID i func (_m *Scheduler) UnScheduleByID(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for UnScheduleByID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -134,6 +154,10 @@ func (_m *Scheduler) UnScheduleByID(ctx context.Context, id int64) error { func (_m *Scheduler) UnScheduleByVendor(ctx context.Context, vendorType string, vendorID int64) error { ret := _m.Called(ctx, vendorType, vendorID) + if len(ret) == 0 { + panic("no return value specified for UnScheduleByVendor") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, vendorType, vendorID) diff --git a/src/testing/pkg/securityhub/manager.go b/src/testing/pkg/securityhub/manager.go index 9b47578c09..5050fb40db 100644 --- a/src/testing/pkg/securityhub/manager.go +++ b/src/testing/pkg/securityhub/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package securityhub @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) DangerousArtifacts(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.DangerousArtifact, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for DangerousArtifacts") + } + var r0 []*model.DangerousArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*model.DangerousArtifact, error)); ok { @@ -48,6 +52,10 @@ func (_m *Manager) DangerousArtifacts(ctx context.Context, scannerUUID string, p func (_m *Manager) DangerousCVEs(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*scan.VulnerabilityRecord, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for DangerousCVEs") + } + var r0 []*scan.VulnerabilityRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*scan.VulnerabilityRecord, error)); ok { @@ -74,6 +82,10 @@ func (_m *Manager) DangerousCVEs(ctx context.Context, scannerUUID string, projec func (_m *Manager) ListVuls(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.VulnerabilityItem, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for ListVuls") + } + var r0 []*model.VulnerabilityItem var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*model.VulnerabilityItem, error)); ok { @@ -100,6 +112,10 @@ func (_m *Manager) ListVuls(ctx context.Context, scannerUUID string, projectID i func (_m *Manager) ScannedArtifactsCount(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (int64, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for ScannedArtifactsCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) (int64, error)); ok { @@ -124,6 +140,10 @@ func (_m *Manager) ScannedArtifactsCount(ctx context.Context, scannerUUID string func (_m *Manager) Summary(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (*model.Summary, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for Summary") + } + var r0 *model.Summary var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) (*model.Summary, error)); ok { @@ -150,6 +170,10 @@ func (_m *Manager) Summary(ctx context.Context, scannerUUID string, projectID in func (_m *Manager) TotalArtifactsCount(ctx context.Context, projectID int64) (int64, error) { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for TotalArtifactsCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { @@ -174,6 +198,10 @@ func (_m *Manager) TotalArtifactsCount(ctx context.Context, projectID int64) (in func (_m *Manager) TotalVuls(ctx context.Context, scannerUUID string, projectID int64, tuneCount bool, query *q.Query) (int64, error) { ret := _m.Called(ctx, scannerUUID, projectID, tuneCount, query) + if len(ret) == 0 { + panic("no return value specified for TotalVuls") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, bool, *q.Query) (int64, error)); ok { diff --git a/src/testing/pkg/systemartifact/cleanup/selector.go b/src/testing/pkg/systemartifact/cleanup/selector.go index 5ca86ca76b..68b3f8f20d 100644 --- a/src/testing/pkg/systemartifact/cleanup/selector.go +++ b/src/testing/pkg/systemartifact/cleanup/selector.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cleanup @@ -20,6 +20,10 @@ type Selector struct { func (_m *Selector) List(ctx context.Context) ([]*model.SystemArtifact, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.SystemArtifact, error)); ok { @@ -46,6 +50,10 @@ func (_m *Selector) List(ctx context.Context) ([]*model.SystemArtifact, error) { func (_m *Selector) ListWithFilters(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListWithFilters") + } + var r0 []*model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.SystemArtifact, error)); ok { diff --git a/src/testing/pkg/systemartifact/dao/dao.go b/src/testing/pkg/systemartifact/dao/dao.go index 3222028dda..1d49abae36 100644 --- a/src/testing/pkg/systemartifact/dao/dao.go +++ b/src/testing/pkg/systemartifact/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Create(ctx context.Context, systemArtifact *model.SystemArtifact) (int64, error) { ret := _m.Called(ctx, systemArtifact) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.SystemArtifact) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Create(ctx context.Context, systemArtifact *model.SystemArtifact) func (_m *DAO) Delete(ctx context.Context, vendor string, repository string, digest string) error { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, vendor, repository, digest) @@ -59,6 +67,10 @@ func (_m *DAO) Delete(ctx context.Context, vendor string, repository string, dig func (_m *DAO) Get(ctx context.Context, vendor string, repository string, digest string) (*model.SystemArtifact, error) { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (*model.SystemArtifact, error)); ok { @@ -85,6 +97,10 @@ func (_m *DAO) Get(ctx context.Context, vendor string, repository string, digest func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.SystemArtifact, error)); ok { @@ -111,6 +127,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.SystemArtifac func (_m *DAO) Size(ctx context.Context) (int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Size") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok { diff --git a/src/testing/pkg/systemartifact/manager.go b/src/testing/pkg/systemartifact/manager.go index 392d2803bb..1af03bee80 100644 --- a/src/testing/pkg/systemartifact/manager.go +++ b/src/testing/pkg/systemartifact/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package systemartifact @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) Cleanup(ctx context.Context) (int64, int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Cleanup") + } + var r0 int64 var r1 int64 var r2 error @@ -53,6 +57,10 @@ func (_m *Manager) Cleanup(ctx context.Context) (int64, int64, error) { func (_m *Manager) Create(ctx context.Context, artifactRecord *model.SystemArtifact, reader io.Reader) (int64, error) { ret := _m.Called(ctx, artifactRecord, reader) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.SystemArtifact, io.Reader) (int64, error)); ok { @@ -77,6 +85,10 @@ func (_m *Manager) Create(ctx context.Context, artifactRecord *model.SystemArtif func (_m *Manager) Delete(ctx context.Context, vendor string, repository string, digest string) error { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, vendor, repository, digest) @@ -91,6 +103,10 @@ func (_m *Manager) Delete(ctx context.Context, vendor string, repository string, func (_m *Manager) Exists(ctx context.Context, vendor string, repository string, digest string) (bool, error) { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Exists") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (bool, error)); ok { @@ -115,6 +131,10 @@ func (_m *Manager) Exists(ctx context.Context, vendor string, repository string, func (_m *Manager) GetCleanupCriteria(vendor string, artifactType string) systemartifact.Selector { ret := _m.Called(vendor, artifactType) + if len(ret) == 0 { + panic("no return value specified for GetCleanupCriteria") + } + var r0 systemartifact.Selector if rf, ok := ret.Get(0).(func(string, string) systemartifact.Selector); ok { r0 = rf(vendor, artifactType) @@ -131,6 +151,10 @@ func (_m *Manager) GetCleanupCriteria(vendor string, artifactType string) system func (_m *Manager) GetStorageSize(ctx context.Context) (int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetStorageSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok { @@ -155,6 +179,10 @@ func (_m *Manager) GetStorageSize(ctx context.Context) (int64, error) { func (_m *Manager) GetSystemArtifactProjectNames() []string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetSystemArtifactProjectNames") + } + var r0 []string if rf, ok := ret.Get(0).(func() []string); ok { r0 = rf() @@ -171,6 +199,10 @@ func (_m *Manager) GetSystemArtifactProjectNames() []string { func (_m *Manager) Read(ctx context.Context, vendor string, repository string, digest string) (io.ReadCloser, error) { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Read") + } + var r0 io.ReadCloser var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (io.ReadCloser, error)); ok { diff --git a/src/testing/pkg/task/execution_manager.go b/src/testing/pkg/task/execution_manager.go index 17e912fccf..7960eea185 100644 --- a/src/testing/pkg/task/execution_manager.go +++ b/src/testing/pkg/task/execution_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -22,6 +22,10 @@ type ExecutionManager struct { func (_m *ExecutionManager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -53,6 +57,10 @@ func (_m *ExecutionManager) Create(ctx context.Context, vendorType string, vendo _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, string, ...map[string]interface{}) (int64, error)); ok { @@ -77,6 +85,10 @@ func (_m *ExecutionManager) Create(ctx context.Context, vendorType string, vendo func (_m *ExecutionManager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -91,6 +103,10 @@ func (_m *ExecutionManager) Delete(ctx context.Context, id int64) error { func (_m *ExecutionManager) DeleteByVendor(ctx context.Context, vendorType string, vendorID int64) error { ret := _m.Called(ctx, vendorType, vendorID) + if len(ret) == 0 { + panic("no return value specified for DeleteByVendor") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, vendorType, vendorID) @@ -105,6 +121,10 @@ func (_m *ExecutionManager) DeleteByVendor(ctx context.Context, vendorType strin func (_m *ExecutionManager) Get(ctx context.Context, id int64) (*task.Execution, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *task.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Execution, error)); ok { @@ -131,6 +151,10 @@ func (_m *ExecutionManager) Get(ctx context.Context, id int64) (*task.Execution, func (_m *ExecutionManager) List(ctx context.Context, query *q.Query) ([]*task.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*task.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*task.Execution, error)); ok { @@ -157,6 +181,10 @@ func (_m *ExecutionManager) List(ctx context.Context, query *q.Query) ([]*task.E func (_m *ExecutionManager) MarkDone(ctx context.Context, id int64, message string) error { ret := _m.Called(ctx, id, message) + if len(ret) == 0 { + panic("no return value specified for MarkDone") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, id, message) @@ -171,6 +199,10 @@ func (_m *ExecutionManager) MarkDone(ctx context.Context, id int64, message stri func (_m *ExecutionManager) MarkError(ctx context.Context, id int64, message string) error { ret := _m.Called(ctx, id, message) + if len(ret) == 0 { + panic("no return value specified for MarkError") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, id, message) @@ -185,6 +217,10 @@ func (_m *ExecutionManager) MarkError(ctx context.Context, id int64, message str func (_m *ExecutionManager) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -199,6 +235,10 @@ func (_m *ExecutionManager) Stop(ctx context.Context, id int64) error { func (_m *ExecutionManager) StopAndWait(ctx context.Context, id int64, timeout time.Duration) error { ret := _m.Called(ctx, id, timeout) + if len(ret) == 0 { + panic("no return value specified for StopAndWait") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, time.Duration) error); ok { r0 = rf(ctx, id, timeout) @@ -209,10 +249,32 @@ func (_m *ExecutionManager) StopAndWait(ctx context.Context, id int64, timeout t return r0 } +// StopAndWaitWithError provides a mock function with given fields: ctx, id, timeout, origError +func (_m *ExecutionManager) StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) error { + ret := _m.Called(ctx, id, timeout, origError) + + if len(ret) == 0 { + panic("no return value specified for StopAndWaitWithError") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, time.Duration, error) error); ok { + r0 = rf(ctx, id, timeout, origError) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UpdateExtraAttrs provides a mock function with given fields: ctx, id, extraAttrs func (_m *ExecutionManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error { ret := _m.Called(ctx, id, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for UpdateExtraAttrs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]interface{}) error); ok { r0 = rf(ctx, id, extraAttrs) diff --git a/src/testing/pkg/task/manager.go b/src/testing/pkg/task/manager.go index 9e8dbbd160..d1172aa693 100644 --- a/src/testing/pkg/task/manager.go +++ b/src/testing/pkg/task/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -51,6 +55,10 @@ func (_m *Manager) Create(ctx context.Context, executionID int64, job *task.Job, _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *task.Job, ...map[string]interface{}) (int64, error)); ok { @@ -75,6 +83,10 @@ func (_m *Manager) Create(ctx context.Context, executionID int64, job *task.Job, func (_m *Manager) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType string, status string) ([]int64, error) { ret := _m.Called(ctx, vendorType, status) + if len(ret) == 0 { + panic("no return value specified for ExecutionIDsByVendorAndStatus") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]int64, error)); ok { @@ -101,6 +113,10 @@ func (_m *Manager) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType func (_m *Manager) Get(ctx context.Context, id int64) (*task.Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Task, error)); ok { @@ -127,6 +143,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*task.Task, error) { func (_m *Manager) GetLog(ctx context.Context, id int64) ([]byte, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -153,6 +173,10 @@ func (_m *Manager) GetLog(ctx context.Context, id int64) ([]byte, error) { func (_m *Manager) GetLogByJobID(ctx context.Context, jobID string) ([]byte, error) { ret := _m.Called(ctx, jobID) + if len(ret) == 0 { + panic("no return value specified for GetLogByJobID") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { @@ -179,6 +203,10 @@ func (_m *Manager) GetLogByJobID(ctx context.Context, jobID string) ([]byte, err func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*task.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*task.Task, error)); ok { @@ -205,6 +233,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*task.Task, erro func (_m *Manager) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*task.Task, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for ListScanTasksByReportUUID") + } + var r0 []*task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*task.Task, error)); ok { @@ -231,6 +263,10 @@ func (_m *Manager) ListScanTasksByReportUUID(ctx context.Context, uuid string) ( func (_m *Manager) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -252,6 +288,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *task.Task, props ...string) _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *task.Task, ...string) error); ok { r0 = rf(ctx, _a1, props...) @@ -266,6 +306,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *task.Task, props ...string) func (_m *Manager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error { ret := _m.Called(ctx, id, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for UpdateExtraAttrs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]interface{}) error); ok { r0 = rf(ctx, id, extraAttrs) @@ -280,6 +324,10 @@ func (_m *Manager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs ma func (_m *Manager) UpdateStatusInBatch(ctx context.Context, jobIDs []string, status string, batchSize int) error { ret := _m.Called(ctx, jobIDs, status, batchSize) + if len(ret) == 0 { + panic("no return value specified for UpdateStatusInBatch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string, int) error); ok { r0 = rf(ctx, jobIDs, status, batchSize) diff --git a/src/testing/pkg/user/dao/dao.go b/src/testing/pkg/user/dao/dao.go index 89bcb213af..f46ec54d59 100644 --- a/src/testing/pkg/user/dao/dao.go +++ b/src/testing/pkg/user/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, user *models.User) (int, error) { ret := _m.Called(ctx, user) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.User) (int, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, user *models.User) (int, error) { func (_m *DAO) Delete(ctx context.Context, userID int) error { ret := _m.Called(ctx, userID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, userID) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, userID int) error { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*models.User, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.User, error)); ok { @@ -116,6 +132,10 @@ func (_m *DAO) Update(ctx context.Context, user *models.User, props ...string) e _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.User, ...string) error); ok { r0 = rf(ctx, user, props...) diff --git a/src/testing/pkg/user/manager.go b/src/testing/pkg/user/manager.go index 83b4f9d772..cee323805f 100644 --- a/src/testing/pkg/user/manager.go +++ b/src/testing/pkg/user/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package user @@ -30,6 +30,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query, options ...models. _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...models.Option) (int64, error)); ok { @@ -54,6 +58,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query, options ...models. func (_m *Manager) Create(ctx context.Context, _a1 *commonmodels.User) (int, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *commonmodels.User) (int, error)); ok { @@ -78,6 +86,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *commonmodels.User) (int, err func (_m *Manager) Delete(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -92,6 +104,10 @@ func (_m *Manager) Delete(ctx context.Context, id int) error { func (_m *Manager) DeleteGDPR(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteGDPR") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -106,6 +122,10 @@ func (_m *Manager) DeleteGDPR(ctx context.Context, id int) error { func (_m *Manager) GenerateCheckSum(in string) string { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for GenerateCheckSum") + } + var r0 string if rf, ok := ret.Get(0).(func(string) string); ok { r0 = rf(in) @@ -120,6 +140,10 @@ func (_m *Manager) GenerateCheckSum(in string) string { func (_m *Manager) Get(ctx context.Context, id int) (*commonmodels.User, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *commonmodels.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, int) (*commonmodels.User, error)); ok { @@ -146,6 +170,10 @@ func (_m *Manager) Get(ctx context.Context, id int) (*commonmodels.User, error) func (_m *Manager) GetByName(ctx context.Context, username string) (*commonmodels.User, error) { ret := _m.Called(ctx, username) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *commonmodels.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*commonmodels.User, error)); ok { @@ -179,6 +207,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query, options ...models.O _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 commonmodels.Users var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...models.Option) (commonmodels.Users, error)); ok { @@ -205,6 +237,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query, options ...models.O func (_m *Manager) MatchLocalPassword(ctx context.Context, username string, password string) (*commonmodels.User, error) { ret := _m.Called(ctx, username, password) + if len(ret) == 0 { + panic("no return value specified for MatchLocalPassword") + } + var r0 *commonmodels.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*commonmodels.User, error)); ok { @@ -231,6 +267,10 @@ func (_m *Manager) MatchLocalPassword(ctx context.Context, username string, pass func (_m *Manager) Onboard(ctx context.Context, _a1 *commonmodels.User) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Onboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *commonmodels.User) error); ok { r0 = rf(ctx, _a1) @@ -245,6 +285,10 @@ func (_m *Manager) Onboard(ctx context.Context, _a1 *commonmodels.User) error { func (_m *Manager) SetSysAdminFlag(ctx context.Context, id int, admin bool) error { ret := _m.Called(ctx, id, admin) + if len(ret) == 0 { + panic("no return value specified for SetSysAdminFlag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, bool) error); ok { r0 = rf(ctx, id, admin) @@ -259,6 +303,10 @@ func (_m *Manager) SetSysAdminFlag(ctx context.Context, id int, admin bool) erro func (_m *Manager) UpdatePassword(ctx context.Context, id int, newPassword string) error { ret := _m.Called(ctx, id, newPassword) + if len(ret) == 0 { + panic("no return value specified for UpdatePassword") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, newPassword) @@ -280,6 +328,10 @@ func (_m *Manager) UpdateProfile(ctx context.Context, _a1 *commonmodels.User, co _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdateProfile") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *commonmodels.User, ...string) error); ok { r0 = rf(ctx, _a1, col...) diff --git a/src/testing/pkg/usergroup/fake_usergroup_manager.go b/src/testing/pkg/usergroup/fake_usergroup_manager.go index 8d2b83a637..8f1bf2d8fc 100644 --- a/src/testing/pkg/usergroup/fake_usergroup_manager.go +++ b/src/testing/pkg/usergroup/fake_usergroup_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package usergroup @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, userGroup model.UserGroup) (int, error) { ret := _m.Called(ctx, userGroup) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, model.UserGroup) (int, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, userGroup model.UserGroup) (int, func (_m *Manager) Delete(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int) error { func (_m *Manager) Get(ctx context.Context, id int) (*model.UserGroup, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.UserGroup var r1 error if rf, ok := ret.Get(0).(func(context.Context, int) (*model.UserGroup, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int) (*model.UserGroup, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.UserGroup var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.UserGroup, error)); ok { @@ -134,6 +154,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.UserGroup func (_m *Manager) Onboard(ctx context.Context, g *model.UserGroup) error { ret := _m.Called(ctx, g) + if len(ret) == 0 { + panic("no return value specified for Onboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.UserGroup) error); ok { r0 = rf(ctx, g) @@ -148,6 +172,10 @@ func (_m *Manager) Onboard(ctx context.Context, g *model.UserGroup) error { func (_m *Manager) Populate(ctx context.Context, userGroups []model.UserGroup) ([]int, error) { ret := _m.Called(ctx, userGroups) + if len(ret) == 0 { + panic("no return value specified for Populate") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, []model.UserGroup) ([]int, error)); ok { @@ -174,6 +202,10 @@ func (_m *Manager) Populate(ctx context.Context, userGroups []model.UserGroup) ( func (_m *Manager) UpdateName(ctx context.Context, id int, groupName string) error { ret := _m.Called(ctx, id, groupName) + if len(ret) == 0 { + panic("no return value specified for UpdateName") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, groupName) diff --git a/tests/apitests/python/library/quota_sorting.py b/tests/apitests/python/library/quota_sorting.py new file mode 100644 index 0000000000..c8d7cf1fcd --- /dev/null +++ b/tests/apitests/python/library/quota_sorting.py @@ -0,0 +1,21 @@ +import base +from v2_swagger_client.rest import ApiException + +class QuotaSorting(base.Base): + def __init__(self): + super(QuotaSorting,self).__init__(api_type="quota") + + def list_quotas_with_sorting(self, expect_status_code=200, **kwargs): + params = {} + if "sort" in kwargs: + params["sort"] = kwargs["sort"] + if "reference" in kwargs: + params["reference"] = kwargs["reference"] + + try: + resp_data, status_code, _ = self._get_client(**kwargs).list_quotas_with_http_info(**params) + except ApiException as e: + raise Exception(r"Error out with exception. Exception status: {}; exception reason: {}; exception body: {}", e.status, e.reason, e.body) + base._assert_status_code(expect_status_code, status_code) + + return resp_data diff --git a/tests/apitests/python/library/scan_stop.py b/tests/apitests/python/library/scan_stop.py index 9f4bfd85ce..e002239b27 100644 --- a/tests/apitests/python/library/scan_stop.py +++ b/tests/apitests/python/library/scan_stop.py @@ -11,7 +11,9 @@ class StopScan(base.Base, object): def stop_scan_artifact(self, project_name, repo_name, reference, expect_status_code = 202, expect_response_body = None, **kwargs): try: - data, status_code, _ = self._get_client(**kwargs).stop_scan_artifact_with_http_info(project_name, repo_name, reference) + scanType = v2_swagger_client.ScanType() + scanType.scan_type = "vulnerability" + data, status_code, _ = self._get_client(**kwargs).stop_scan_artifact_with_http_info(project_name, repo_name, reference, scanType) except ApiException as e: base._assert_status_code(expect_status_code, e.status) if expect_response_body is not None: diff --git a/tests/apitests/python/test_project_permission.py b/tests/apitests/python/test_project_permission.py index 988d09a00e..491eba2dcb 100644 --- a/tests/apitests/python/test_project_permission.py +++ b/tests/apitests/python/test_project_permission.py @@ -89,9 +89,12 @@ copy_artifact = Permission("{}/projects/{}/repositories/target_repo/artifacts?fr delete_artifact = Permission("{}/projects/{}/repositories/target_repo/artifacts/{}".format(harbor_base_url, project_name, source_artifact_tag), "DELETE", 200) # 6. Resource scan actions: ['read', 'create', 'stop'] +stop_scan_payload = { + "scan_type": "vulnerability" +} create_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) -stop_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) -read_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/0/log".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "get", 404) +stop_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202, stop_scan_payload) +read_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/83be44fd-1234-5678-b49f-4b6d6e8f5730/log".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "get", 404) # 7. Resource tag actions: ['list', 'create', 'delete'] tag_payload = { "name": "test-{}".format(int(random.randint(1000, 9999))) } @@ -249,16 +252,22 @@ resource_permissions = { "log": [list_log], "notification-policy": [create_webhook, list_webhook, read_webhook, update_webhook, list_webhook_executions, list_webhook_executions_tasks, read_webhook_executions_tasks, list_webhook_events, delete_webhook] } -resource_permissions["all"] = [item for sublist in resource_permissions.values() for item in sublist] def main(): + global resources # Declare resources as a global variable + + if str(resources) == "all": + resources = ','.join(str(key) for key in resource_permissions.keys()) + for resource in resources.split(","): for permission in resource_permissions[resource]: print("=================================================") print("call: {} {}".format(permission.method, permission.url)) print("payload: {}".format(json.dumps(permission.payload))) - print("response: {}".format(permission.call().text)) + resp = permission.call() + print("response: {}".format(resp.text)) + print("response status code: {}".format(resp.status_code)) print("=================================================\n") diff --git a/tests/apitests/python/test_quota_sorting.py b/tests/apitests/python/test_quota_sorting.py new file mode 100644 index 0000000000..1cbc5ee379 --- /dev/null +++ b/tests/apitests/python/test_quota_sorting.py @@ -0,0 +1,86 @@ +from __future__ import absolute_import + +import unittest + +from testutils import harbor_server, suppress_urllib3_warning +from testutils import TEARDOWN +from testutils import ADMIN_CLIENT +from library.project import Project +from library.user import User +from library.repository import Repository +from library.repository import push_self_build_image_to_project +from library.quota_sorting import QuotaSorting + +class TestQuotaSorting(unittest.TestCase): + @suppress_urllib3_warning + def setUp(self): + self.project = Project() + self.user = User() + self.repo = Repository() + self.quota_sorting = QuotaSorting() + + @unittest.skipIf(TEARDOWN == False, "Test data won't be erased.") + def tearDown(self): + #1. Delete repository(RA) by user(UA); + self.repo.delete_repository(TestQuotaSorting.project_1_name, TestQuotaSorting.repo_name_1.split('/')[1], **TestQuotaSorting.user_global_client) + self.repo.delete_repository(TestQuotaSorting.project_2_name, TestQuotaSorting.repo_name_2.split('/')[1], **TestQuotaSorting.user_global_client) + + #2. Delete project(PA); + self.project.delete_project(TestQuotaSorting.project_1_id, **TestQuotaSorting.user_global_client) + self.project.delete_project(TestQuotaSorting.project_2_id, **TestQuotaSorting.user_global_client) + + #3. Delete user(UA); + self.user.delete_user(TestQuotaSorting.user_001_id, **ADMIN_CLIENT) + + def testQuotaSorting(self): + """ + Test case: + Quota Sorting + Test step and expected result: + 1. Create a new user(UA); + 2. Create two new private projects(PA) by user(UA); + 3. Push two images to projects(PA) respectively by user(UA) + 4. Get quota list with sort=used.storage, the used storage should be in ascending order + 5. Get quota list with sort=-used.storage, the used storage should be in descending order + Tear down: + 1. Delete repository(RA) by user(UA); + 2. Delete project(PA); + 3. Delete user(UA); + """ + url = ADMIN_CLIENT["endpoint"] + user_001_password = "Aa123456" + global_admin_client = dict(endpoint=ADMIN_CLIENT["endpoint"], username=ADMIN_CLIENT["username"], passwor=ADMIN_CLIENT["password"], reference="project") + + #1. Create user-001 + TestQuotaSorting.user_001_id, user_001_name = self.user.create_user(user_password=user_001_password, **ADMIN_CLIENT) + TestQuotaSorting.user_global_client = dict(endpoint=url, username=user_001_name, password=user_001_password) + + #2. Create private project_1 and private project_2 + TestQuotaSorting.project_1_id, TestQuotaSorting.project_1_name = self.project.create_project(metadata={"public": "false"}, **TestQuotaSorting.user_global_client) + TestQuotaSorting.project_2_id, TestQuotaSorting.project_2_name = self.project.create_project(metadata={"public": "false"}, **TestQuotaSorting.user_global_client) + + #3. Push images to project_1 and project_2 respectively + image1 = "alpine" + tag1 = "2.6" + TestQuotaSorting.repo_name_1, _ = push_self_build_image_to_project(TestQuotaSorting.project_1_name, harbor_server, user_001_name, user_001_password, image1, tag1) + image2 = "photon" + tag2 = "2.0" + TestQuotaSorting.repo_name_2, _ = push_self_build_image_to_project(TestQuotaSorting.project_2_name, harbor_server, user_001_name, user_001_password, image2, tag2) + + #4. Check whether quota list is in ascending order + global_admin_client["sort"] = "used.storage" + res_ascending = self.quota_sorting.list_quotas_with_sorting(expect_status_code=200, **global_admin_client) + self.assertTrue(len(res_ascending) >= 2) + for idx in range(1, len(res_ascending)): + self.assertTrue(res_ascending[idx - 1].to_dict()["used"]["storage"] <= res_ascending[idx].to_dict()["used"]["storage"]) + + #5. Check whether quota list is in descending order + global_admin_client["sort"] = "-used.storage" + res_descending = self.quota_sorting.list_quotas_with_sorting(expect_status_code=200, **global_admin_client) + self.assertTrue(len(res_descending) >= 2) + for idx in range(1, len(res_descending)): + self.assertTrue(res_descending[idx - 1].to_dict()["used"]["storage"] >= res_descending[idx].to_dict()["used"]["storage"]) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/apitests/python/test_scan_data_export.py b/tests/apitests/python/test_scan_data_export.py index ad9fa249fe..d8d2ef9e53 100644 --- a/tests/apitests/python/test_scan_data_export.py +++ b/tests/apitests/python/test_scan_data_export.py @@ -93,7 +93,7 @@ class TestScanDataExport(unittest.TestCase): # 10. Wait for the export scan data execution to succeed execution = None - for i in range(5): + for i in range(15): print("wait for the job to finish:", i) execution = self.scan_data_export.get_scan_data_export_execution(execution_id, **user_client) if execution.status == "Success": diff --git a/tests/apitests/python/test_user_limited_guest_get_repository.py b/tests/apitests/python/test_user_limited_guest_get_repository.py new file mode 100644 index 0000000000..d29b936faa --- /dev/null +++ b/tests/apitests/python/test_user_limited_guest_get_repository.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import +import unittest + + +from testutils import ADMIN_CLIENT, suppress_urllib3_warning +from testutils import harbor_server +from testutils import admin_user +from testutils import admin_pwd +from testutils import created_project +from testutils import created_user +from testutils import TEARDOWN +from library.repository import push_self_build_image_to_project +from library.repository import Repository + + +class TestLimitedGuestGetRepository(unittest.TestCase): + + + @suppress_urllib3_warning + def setUp(self): + self.repository = Repository() + + @unittest.skipIf(TEARDOWN == False, "Test data won't be erased.") + def tearDown(self): + print("Case completed") + + def testLimitedGuestGetRepository(self): + """ + Test case: + Limited Guest GetRepository + Test step and expected result: + 1. Create a new user(UA) + 2. Create a private project(PA) + 3. Add (UA) as "Limited Guest" to this (PA) + 4. Push an image to project(PA) + 5. Call the "GetRepository" API, it should return 200 status code and project_id should be as expected, and the name should be "ProjectName/ImageName" + 6. Delete repository(RA) + """ + url = ADMIN_CLIENT["endpoint"] + user_001_password = "Aa123456" + # 1. Create a new user(UA) + with created_user(user_001_password) as (user_id, user_name): + #2. Create a new private project(PA) by user(UA); + #3. Add user(UA) as a member of project(PA) with "Limited Guest" role; + with created_project(metadata={"public": "false"}, user_id=user_id, member_role_id=5) as (project_id, project_name): + #4. Push an image to project(PA) by user(UA), then check the project quota usage; + image, tag = "goharbor/alpine", "3.10" + push_self_build_image_to_project(project_name, harbor_server, admin_user, admin_pwd, image, tag) + + #5. Call the "GetRepository" API, it should return 200 status code and the "name" attribute is "ProjectName/ImageName" + USER_CLIENT=dict(endpoint=url, username=user_name, password=user_001_password) + repository_data = self.repository.get_repository(project_name, "goharbor%2Falpine", **USER_CLIENT) + self.assertEqual(repository_data.project_id, project_id) + self.assertEqual(repository_data.name, project_name + "/" + image) + + #6. Delete repository(RA) + self.repository.delete_repository(project_name, "goharbor%2Falpine", **ADMIN_CLIENT) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/ci/api_common_install.sh b/tests/ci/api_common_install.sh index 7aa8c002e5..470d58fa4c 100755 --- a/tests/ci/api_common_install.sh +++ b/tests/ci/api_common_install.sh @@ -24,24 +24,27 @@ python --version # in container. So the current work round is read DNS server # from system and set the value in /etc/docker/daemon.json. -ip addr -docker_config_file="/etc/docker/daemon.json" -dns_ip_string=$(netplan ip leases eth0 | grep -i dns | awk -F = '{print $2}' | tr " " "\n" | sed 's/,/","/g') -dns=[\"${dns_ip_string}\"] -echo dns=${dns} +# ip addr +# docker_config_file="/etc/docker/daemon.json" +# dns_ip_string=$(netplan ip leases eth0 | grep -i dns | awk -F = '{print $2}' | tr " " "\n" | sed 's/,/","/g') +# dns=[\"${dns_ip_string}\"] +# echo dns=${dns} -cat $docker_config_file -if [ -f $docker_config_file ];then - if [ $(cat /etc/docker/daemon.json |grep \"dns\" |wc -l) -eq 0 ];then - sudo sed "s/}/,\n \"dns\": $dns\n}/" -i $docker_config_file - fi -else - echo "{\"dns\": $dns}" > $docker_config_file -fi -cat $docker_config_file -sudo systemctl stop docker -sudo systemctl start docker -sleep 2 +# cat $docker_config_file +# if [ -f $docker_config_file ];then +# if [ $(cat /etc/docker/daemon.json |grep \"dns\" |wc -l) -eq 0 ];then +# sudo sed "s/}/,\n \"dns\": $dns\n}/" -i $docker_config_file +# fi +# else +# echo "{\"dns\": $dns}" > $docker_config_file +# fi +# cat $docker_config_file +# sudo systemctl stop docker +# sudo systemctl start docker +# # debug +# sudo systemctl status docker.service +# sudo journalctl -xeu docker.service +# sleep 2 #------------------------------------------------------------# sudo ./tests/hostcfg.sh diff --git a/tests/ci/distro_installer.sh b/tests/ci/distro_installer.sh index b946b461a6..cab510c9d4 100755 --- a/tests/ci/distro_installer.sh +++ b/tests/ci/distro_installer.sh @@ -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.21.5 COMPILETAG=compile_golangimage TRIVYFLAG=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.21.5 COMPILETAG=compile_golangimage TRIVYFLAG=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.22.3 COMPILETAG=compile_golangimage TRIVYFLAG=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.22.3 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false diff --git a/tests/ci/ut_install.sh b/tests/ci/ut_install.sh index 38cfa8cf20..6702bb3aae 100755 --- a/tests/ci/ut_install.sh +++ b/tests/ci/ut_install.sh @@ -5,19 +5,19 @@ set -e sudo apt-get update && sudo apt-get install -y libldap2-dev sudo go env -w GO111MODULE=auto -go get github.com/docker/distribution -go get github.com/docker/libtrust -go get golang.org/x/lint/golint -go get github.com/GeertJohan/fgt -go get github.com/dghubble/sling -set +e -go get github.com/stretchr/testify -go get golang.org/x/tools/cmd/cover -go get github.com/mattn/goveralls -go get -u github.com/client9/misspell/cmd/misspell +pwd +# cd ./src +# go get github.com/docker/distribution@latest +# go get github.com/docker/libtrust@latest +# set +e +# go get github.com/stretchr/testify@v1.8.4 +go install golang.org/x/tools/cmd/cover@latest +go install github.com/mattn/goveralls@latest +go install github.com/client9/misspell/cmd/misspell@latest set -e +# cd ../ # binary will be $(go env GOPATH)/bin/golangci-lint -# go install/go get installation aren't guaranteed to work. We recommend using binary installation. +# go get installation aren't guaranteed to work. We recommend using binary installation. curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2 sudo service postgresql stop || echo no postgresql need to be stopped sleep 2 diff --git a/tests/resources/Harbor-Pages/Administration-Project-Quotas.robot b/tests/resources/Harbor-Pages/Administration-Project-Quotas.robot new file mode 100644 index 0000000000..a7a515c8be --- /dev/null +++ b/tests/resources/Harbor-Pages/Administration-Project-Quotas.robot @@ -0,0 +1,35 @@ +# Copyright Project Harbor Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +*** Settings *** +Documentation This resource provides any keywords related to the Harbor private registry appliance +Resource ../../resources/Util.robot + +*** Variables *** + +*** Keywords *** +Switch to Project Quotas Tag + Retry Element Click xpath=${administration_project_quotas_tag_xpath} + Sleep 1 + +Check Project Quota Sorting + [Arguments] ${proj1} ${proj2} + # check project quota sorting in ascending order + Retry Element Click xpath=${sort_used_storage_button} + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[2]//clr-dg-cell[1]//a[contains(text(), '${proj1}')] + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[3]//clr-dg-cell[1]//a[contains(text(), '${proj2}')] + # check project quota sorting in descending order + Retry Element Click xpath=${sort_used_storage_button} + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[1]//clr-dg-cell[1]//a[contains(text(), '${proj2}')] + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[2]//clr-dg-cell[1]//a[contains(text(), '${proj1}')] diff --git a/tests/resources/Harbor-Pages/Administration-Project-Quotas_Elements.robot b/tests/resources/Harbor-Pages/Administration-Project-Quotas_Elements.robot new file mode 100644 index 0000000000..b73926f78c --- /dev/null +++ b/tests/resources/Harbor-Pages/Administration-Project-Quotas_Elements.robot @@ -0,0 +1,20 @@ +# Copyright Project Harbor Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +*** Settings *** +Documentation This resource provides any keywords related to the Harbor private registry appliance + +*** Variables *** +${administration_project_quotas_tag_xpath} //clr-vertical-nav-group-children/a[contains(.,'Project Quotas')] +${sort_used_storage_button} //div[@class='datagrid-table']//div[@class='datagrid-header']//button[normalize-space()='Storage'] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Project-Repository.robot b/tests/resources/Harbor-Pages/Project-Repository.robot index ab000f87a3..04c0b355b3 100644 --- a/tests/resources/Harbor-Pages/Project-Repository.robot +++ b/tests/resources/Harbor-Pages/Project-Repository.robot @@ -37,6 +37,7 @@ Scan Artifact Retry Element Click ${scan_artifact_btn} Stop Scan Artifact + Retry Element Click ${artifact_action_xpath} Retry Element Click ${stop_scan_artifact_btn} Check Scan Artifact Job Status Is Stopped diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index adedc655b9..4d469f2580 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -421,7 +421,7 @@ Export CVEs Retry Button Click ${export_cve_btn} Retry Text Input ${export_cve_filter_repo_input} ${repositories} Retry Text Input ${export_cve_filter_tag_input} ${tags} - Select Filter Label @{labels} + Select Filter Label For CVE Export @{labels} Retry Text Input ${export_cve_filter_cveid_input} ${cve_ids} Retry Double Keywords When Error Retry Button Click ${export_btn} Retry Wait Until Page Contains Trigger exporting CVEs successfully! diff --git a/tests/resources/Harbor-Pages/Project_Robot_Account.robot b/tests/resources/Harbor-Pages/Project_Robot_Account.robot index cb30c08309..e5f3299dca 100644 --- a/tests/resources/Harbor-Pages/Project_Robot_Account.robot +++ b/tests/resources/Harbor-Pages/Project_Robot_Account.robot @@ -24,8 +24,8 @@ Create A Project Robot Account ${permission_count}= Create Dictionary ${total}= Set Variable 0 IF '${first_resource}' == 'all' - Set To Dictionary ${permission_count} all=56 - ${total}= Set Variable 56 + Set To Dictionary ${permission_count} all=59 + ${total}= Set Variable 59 Retry Element Click //span[text()='Select all'] ELSE FOR ${item} IN @{resources} diff --git a/tests/resources/Harbor-Pages/Replication_Elements.robot b/tests/resources/Harbor-Pages/Replication_Elements.robot index 88013c4249..fa61c82f35 100644 --- a/tests/resources/Harbor-Pages/Replication_Elements.robot +++ b/tests/resources/Harbor-Pages/Replication_Elements.robot @@ -64,7 +64,7 @@ ${replication_mode_radio_pull} //clr-main-container//hbr-create-edit-rule//labe ${filter_name_id} //input[@id='filter_name'] ${filter_tag_model_select} //div[@class='filterSelect ng-star-inserted'][2]//select ${filter_tag_id} //input[@id='filter_tag'] -${filter_label_xpath} //form//clr-dropdown[contains(@class,'dropdown')] +${filter_label_xpath} //form//clr-dropdown[contains(@class,'dropdown')]//clr-icon ${filter_label_model_select} //div[@class='filterSelect ng-star-inserted'][3]//select ${rule_resource_selector} //*[@id='select_resource'] ${trigger_mode_selector} //*[@id='ruleTrigger'] diff --git a/tests/resources/Harbor-Pages/SecurityHub.robot b/tests/resources/Harbor-Pages/SecurityHub.robot index a0b2932840..16366fc2a0 100644 --- a/tests/resources/Harbor-Pages/SecurityHub.robot +++ b/tests/resources/Harbor-Pages/SecurityHub.robot @@ -273,3 +273,12 @@ Check The Quick Search Should Be Equal As Strings ${cve_input_value} ${cve} ${row_count}= Get Element Count ${vulnerabilities_datagrid_row} Retry Wait Element Count //div[@class='datagrid']//clr-dg-cell[1]//a[text()='${cve}'] ${row_count} + +Select Filter Label For CVE Export + [Arguments] @{labels} + Retry Element Click ${vulnerabilities_filter_label_xpath} + FOR ${label} IN @{labels} + Log ${label} + Retry Element Click //hbr-label-piece//span[contains(text(), '${label}')] + END + Retry Element Click ${vulnerabilities_filter_label_xpath} diff --git a/tests/resources/Harbor-Pages/SecurityHub_Elements.robot b/tests/resources/Harbor-Pages/SecurityHub_Elements.robot index 1056784221..ade73bc6f8 100644 --- a/tests/resources/Harbor-Pages/SecurityHub_Elements.robot +++ b/tests/resources/Harbor-Pages/SecurityHub_Elements.robot @@ -28,3 +28,4 @@ ${vulnerabilities_count_xpath} //clr-dg-footer//div[contains(@class,'datagrid-f ${vulnerabilities_filter_select} (//form//div[@class='clr-select-wrapper']//select) ${vulnerabilities_filter_input} (//form[contains(@class,'clr-form')]//input) ${vulnerabilities_datagrid_row} //clr-datagrid//clr-dg-row +${vulnerabilities_filter_label_xpath} //form//clr-dropdown[contains(@class,'dropdown')] diff --git a/tests/resources/Harbor-Pages/Vulnerability.robot b/tests/resources/Harbor-Pages/Vulnerability.robot index f5f9d90192..b7fb6d430f 100644 --- a/tests/resources/Harbor-Pages/Vulnerability.robot +++ b/tests/resources/Harbor-Pages/Vulnerability.robot @@ -66,6 +66,7 @@ Vulnerability Not Ready Project Hint Switch To Scanners Page Retry Element Click xpath=//clr-main-container//clr-vertical-nav//a[contains(.,'Interrogation')] + Retry Element Click xpath=//app-interrogation-services//a[normalize-space()='Scanners'] Retry Wait Until Page Contains Element ${set_default_scanner} Should Display The Default Trivy Scanner diff --git a/tests/resources/Harbor-Pages/Vulnerability_Elements.robot b/tests/resources/Harbor-Pages/Vulnerability_Elements.robot index e64594a876..593c9d1a75 100644 --- a/tests/resources/Harbor-Pages/Vulnerability_Elements.robot +++ b/tests/resources/Harbor-Pages/Vulnerability_Elements.robot @@ -51,3 +51,4 @@ ${scanner_password_input} //*[@id='scanner-password'] ${scanner_token_input} //*[@id='scanner-token'] ${scanner_apikey_input} //*[@id='scanner-apiKey'] ${scanner_set_default_btn} //*[@id='set-default'] +${scanner_list_refresh_btn} //span[@class='refresh-btn']//clr-icon[@role='none'] diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot index b368e9dbbe..d394a64304 100644 --- a/tests/resources/Util.robot +++ b/tests/resources/Util.robot @@ -50,6 +50,8 @@ Resource Harbor-Pages/Replication.robot Resource Harbor-Pages/Replication_Elements.robot Resource Harbor-Pages/UserProfile.robot Resource Harbor-Pages/UserProfile_Elements.robot +Resource Harbor-Pages/Administration-Project-Quotas.robot +Resource Harbor-Pages/Administration-Project-Quotas_Elements.robot Resource Harbor-Pages/Administration-Users.robot Resource Harbor-Pages/Administration-Users_Elements.robot Resource Harbor-Pages/GC.robot @@ -74,6 +76,7 @@ Resource Harbor-Pages/Job_Service_Dashboard_Elements.robot Resource Harbor-Pages/SecurityHub.robot Resource Harbor-Pages/SecurityHub_Elements.robot Resource Harbor-Pages/Verify.robot +Resource Harbor-Pages/Vulnerability_Elements.robot Resource Docker-Util.robot Resource CNAB_Util.robot Resource Helm-Util.robot diff --git a/tests/robot-cases/Group0-BAT/API_DB.robot b/tests/robot-cases/Group0-BAT/API_DB.robot index 8a46efcd26..f1a92b7205 100644 --- a/tests/robot-cases/Group0-BAT/API_DB.robot +++ b/tests/robot-cases/Group0-BAT/API_DB.robot @@ -20,6 +20,10 @@ Test Case - Garbage Collection [Tags] gc Harbor API Test ./tests/apitests/python/test_garbage_collection.py +Test Case - Quota Sorting + [Tags] quota_sorting + Harbor API Test ./tests/apitests/python/test_quota_sorting.py + Test Case - Add Private Project Member and Check User Can See It [Tags] private_member Harbor API Test ./tests/apitests/python/test_add_member_to_private_project.py @@ -206,3 +210,7 @@ Test Case - Banner Message Test Case - User CRUD [Tags] user_crud Harbor API Test ./tests/apitests/python/test_user_crud.py + +Test Case - Limited Guest GetRepository + [Tags] limited_guest_getrepository + Harbor API Test ./tests/apitests/python/test_user_limited_guest_get_repository.py diff --git a/tests/robot-cases/Group1-Nightly/Common.robot b/tests/robot-cases/Group1-Nightly/Common.robot index d6915db204..bcee6a16bb 100644 --- a/tests/robot-cases/Group1-Nightly/Common.robot +++ b/tests/robot-cases/Group1-Nightly/Common.robot @@ -745,7 +745,7 @@ Test Case - System Robot Account ${robot_account_name} ${token}= Create A System Robot Account sys2${d} days days=2 description=For testing cover_all_project_resources=${true} Push image ${ip} '${robot_account_name}' ${token} project${d} hello-world:latest - Retry Wait Element Visible //clr-dg-row[.//clr-dg-cell[contains(.,'${robot_account_name}')] and .//clr-icon[contains(@class, 'color-green')] and .//span[text()='All projects with'] and .//button[text()=' 56 PERMISSION(S) '] and .//span[contains(.,'1d 23h')] and .//clr-dg-cell[text()='For testing'] and .//clr-dg-cell//span[text()=' None ']] + Retry Wait Element Visible //clr-dg-row[.//clr-dg-cell[contains(.,'${robot_account_name}')] and .//clr-icon[contains(@class, 'color-green')] and .//span[text()='All projects with'] and .//button[text()=' 59 PERMISSION(S) '] and .//span[contains(.,'1d 23h')] and .//clr-dg-cell[text()='For testing'] and .//clr-dg-cell//span[text()=' None ']] Retry Action Keyword Check System Robot Account API Permission ${robot_account_name} ${token} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} all 1 Retry Action Keyword Check Project Robot Account API Permission ${robot_account_name} ${token} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project_id} ${project_name} hello-world latest all Close Browser @@ -885,13 +885,13 @@ Test Case - Audit Log And Purge # pull artifact Docker Pull ${ip}/project${d}/${image}:${tag1} Docker Logout ${ip} - Verify Log ${user} project${d}/${image}:${sha256} artifact pull + Verify Log ${user} project${d}/${image}@${sha256} artifact pull Go Into Repo project${d} ${image} # delete artifact @{tag_list} Create List ${tag1} Multi-delete Artifact @{tag_list} Switch To Logs - Verify Log ${user} project${d}/${image}:${sha256} artifact delete + Verify Log ${user} project${d}/${image}@${sha256} artifact delete Go Into Project project${d} # delete repository Delete Repo project${d} ${image} @@ -1151,8 +1151,8 @@ Test Case - Retain Image Last Pull Time Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] Should Be Empty ${last_pull_time} Switch To Configuration System Setting Set Up Retain Image Last Pull Time disable @@ -1160,8 +1160,8 @@ Test Case - Retain Image Last Pull Time Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] Should Not Be Empty ${last_pull_time} Close Browser diff --git a/tests/robot-cases/Group1-Nightly/Common_GC.robot b/tests/robot-cases/Group1-Nightly/Common_GC.robot index 53befb63f6..f1fae64422 100644 --- a/tests/robot-cases/Group1-Nightly/Common_GC.robot +++ b/tests/robot-cases/Group1-Nightly/Common_GC.robot @@ -23,6 +23,25 @@ ${SSH_USER} root ${HARBOR_ADMIN} admin *** Test Cases *** +Test Case - Project Quota Sorting + [Tags] project_quota_sorting + Init Chrome Driver + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + ${d1}= Get Current Date result_format=%m%s + Create An New Project And Go Into Project project${d1} + Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d1} alpine 2.6 2.6 + ${d2}= Get Current Date result_format=%m%s + Create An New Project And Go Into Project project${d2} + Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d2} photon 2.0 2.0 + Switch to Project Quotas Tag + Check Project Quota Sorting project${d1} project${d2} + Go Into Project project${d1} + Delete Repo project${d1} alpine + Go Into Project project${d2} + Delete Repo project${d2} photon + GC Now + Close Browser + Test Case - Garbage Collection Init Chrome Driver ${d}= Get Current Date result_format=%m%s diff --git a/tests/robot-cases/Group1-Nightly/OIDC.robot b/tests/robot-cases/Group1-Nightly/OIDC.robot index f4b11079b3..a29d1064ba 100644 --- a/tests/robot-cases/Group1-Nightly/OIDC.robot +++ b/tests/robot-cases/Group1-Nightly/OIDC.robot @@ -26,6 +26,18 @@ Test Case - Get Harbor Version #Just get harbor version and log it Get Harbor Version +Test Case - Update OIDC Provider Name + [Tags] oidc_provider_name + Init Chrome Driver + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true} + # Set OIDC Provider Name to TestDex + Switch To Configuration Authentication + Retry Text Input //input[@id='oidcName'] TestDex + Retry Element Click ${config_auth_save_button_xpath} + Logout Harbor + Retry Wait Until Page Contains Element //span[normalize-space()='LOGIN WITH TestDex'] + Close Browser + Test Case - OIDC User Sign In #Sign in with all 9 users is for user population, other test cases might use these users. Sign In Harbor With OIDC User ${HARBOR_URL} diff --git a/tests/robot-cases/Group1-Nightly/Trivy.robot b/tests/robot-cases/Group1-Nightly/Trivy.robot index 3db9513ed7..bcfff07d24 100644 --- a/tests/robot-cases/Group1-Nightly/Trivy.robot +++ b/tests/robot-cases/Group1-Nightly/Trivy.robot @@ -182,7 +182,7 @@ Test Case - External Scanner CRUD Filter Scanner By Name scanner${d} Filter Scanner By Endpoint ${SCANNER_ENDPOINT} Retry Wait Element Count //clr-dg-row 1 - Retry Wait Until Page Contains Element //clr-dg-row[.//span[text()='scanner${d}'] and .//clr-dg-cell[text()='${SCANNER_ENDPOINT}'] and .//span[text()='Healthy'] and .//clr-dg-cell[text()='None']] + Retry Double Keywords When Error Retry Element Click xpath=${scanner_list_refresh_btn} Retry Wait Until Page Contains Element //clr-dg-row[.//span[text()='scanner${d}'] and .//clr-dg-cell[text()='${SCANNER_ENDPOINT}'] and .//span[text()='Healthy'] and .//clr-dg-cell[text()='None']] # Delete this scanner Delete Scanner scanner${d} Close Browser diff --git a/tests/test-engine-image/Dockerfile.common b/tests/test-engine-image/Dockerfile.common index 713ad234fa..b5151ccf1d 100644 --- a/tests/test-engine-image/Dockerfile.common +++ b/tests/test-engine-image/Dockerfile.common @@ -22,15 +22,15 @@ RUN apt-get update && apt-get install -y software-properties-common && \ RUN pwd && mkdir /tool/binary && \ # Install CONTAINERD - CONTAINERD_VERSION=1.7.8 && \ + CONTAINERD_VERSION=1.7.14 && \ wget https://github.com/containerd/containerd/releases/download/v$CONTAINERD_VERSION/containerd-$CONTAINERD_VERSION-linux-amd64.tar.gz && \ tar zxvf containerd-$CONTAINERD_VERSION-linux-amd64.tar.gz && \ cd bin && cp -f containerd ctr /tool/binary/ && \ # docker compose - curl -L "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m)" -o /tool/binary/docker-compose && \ + curl -L "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /tool/binary/docker-compose && \ chmod +x /tool/binary/docker-compose && \ # Install helm - HELM_VERSION=3.13.1 && wget https://get.helm.sh/helm-v$HELM_VERSION-linux-amd64.tar.gz && \ + HELM_VERSION=3.14.3 && wget https://get.helm.sh/helm-v$HELM_VERSION-linux-amd64.tar.gz && \ tar zxvf helm-v$HELM_VERSION-linux-amd64.tar.gz && \ ls || pwd && \ mv linux-amd64/helm /tool/binary/helm && \ @@ -54,13 +54,13 @@ RUN pwd && mkdir /tool/binary && \ WASM_TO_OCI_VERSION=0.1.2 && wget https://github.com/engineerd/wasm-to-oci/releases/download/v${WASM_TO_OCI_VERSION}/linux-amd64-wasm-to-oci && \ chmod +x linux-amd64-wasm-to-oci && mv linux-amd64-wasm-to-oci /tool/binary/wasm-to-oci && \ # Install imgpkg - IMGPKG_VERSION=0.39.0 && wget https://github.com/vmware-tanzu/carvel-imgpkg/releases/download/v$IMGPKG_VERSION/imgpkg-linux-amd64 && \ + IMGPKG_VERSION=0.41.1 && wget https://github.com/vmware-tanzu/carvel-imgpkg/releases/download/v$IMGPKG_VERSION/imgpkg-linux-amd64 && \ mv imgpkg-linux-amd64 /tool/binary/imgpkg && chmod +x /tool/binary/imgpkg && \ # Install cosign - COSIGN_VERSION=2.2.0 && wget https://github.com/sigstore/cosign/releases/download/v$COSIGN_VERSION/cosign-linux-amd64 && \ + COSIGN_VERSION=2.2.3 && wget https://github.com/sigstore/cosign/releases/download/v$COSIGN_VERSION/cosign-linux-amd64 && \ mv cosign-linux-amd64 /tool/binary/cosign && chmod +x /tool/binary/cosign && \ # # Install notation - NOTATION_VERSION=1.0.0 && wget https://github.com/notaryproject/notation/releases/download/v$NOTATION_VERSION/notation_${NOTATION_VERSION}_linux_amd64.tar.gz && \ + NOTATION_VERSION=1.1.0 && wget https://github.com/notaryproject/notation/releases/download/v$NOTATION_VERSION/notation_${NOTATION_VERSION}_linux_amd64.tar.gz && \ tar zxvf notation_${NOTATION_VERSION}_linux_amd64.tar.gz && \ mv notation /tool/binary/notation && chmod +x /tool/binary/notation && \ pwd diff --git a/tests/test-engine-image/Dockerfile.ui_test b/tests/test-engine-image/Dockerfile.ui_test index 89f5535e06..9a391089e3 100644 --- a/tests/test-engine-image/Dockerfile.ui_test +++ b/tests/test-engine-image/Dockerfile.ui_test @@ -41,8 +41,8 @@ RUN pip3 install --upgrade pip pyasn1 google-apitools==0.5.31 gsutil \ requests dbbot robotframework-seleniumlibrary robotframework-pabot \ robotframework-JSONLibrary hurry.filesize --upgrade && \ apt-get clean all -# Upgrade chromedriver version to 119.0.6045.105 -RUN wget -N https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/119.0.6045.105/linux64/chromedriver-linux64.zip && \ +# Upgrade chromedriver version to 123.0.6312.86 +RUN wget -N https://storage.googleapis.com/chrome-for-testing-public/123.0.6312.86/linux64/chromedriver-linux64.zip && \ unzip -j chromedriver-linux64.zip && \ chmod +x chromedriver && \ mv -f chromedriver /usr/local/share/chromedriver && \ @@ -51,7 +51,7 @@ RUN wget -N https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/119.0.60 RUN pwd && ls && \ # Install docker - DOCKER_VERSION=24.0.2 && wget https://download.docker.com/linux/static/stable/x86_64/docker-$DOCKER_VERSION.tgz && \ + DOCKER_VERSION=26.0.0 && wget https://download.docker.com/linux/static/stable/x86_64/docker-$DOCKER_VERSION.tgz && \ tar --strip-components=1 -xvzf docker-$DOCKER_VERSION.tgz -C /usr/bin && \ rm docker-$DOCKER_VERSION.tgz diff --git a/tools/migrate_chart/Dockerfile b/tools/migrate_chart/Dockerfile index 53c1183c5e..91f9c813b0 100644 --- a/tools/migrate_chart/Dockerfile +++ b/tools/migrate_chart/Dockerfile @@ -9,6 +9,7 @@ ADD https://get.helm.sh/helm-v3.9.1-linux-amd64.tar.gz / RUN tar zxvf /helm-v3.9.1-linux-amd64.tar.gz && \ pip install click==7.1.2 && \ pip install requests==2.24.0 && \ + pip install pyyaml && \ chmod +x /migrate_chart.sh ./migrate_chart.py ENTRYPOINT [ "/migrate_chart.py" ] \ No newline at end of file diff --git a/tools/migrate_chart/migrate_chart.py b/tools/migrate_chart/migrate_chart.py index 70a1f00cdd..71bfc44700 100644 --- a/tools/migrate_chart/migrate_chart.py +++ b/tools/migrate_chart/migrate_chart.py @@ -3,7 +3,10 @@ import subprocess import signal import sys +import os from pathlib import Path +import tarfile +import yaml import click import requests @@ -32,24 +35,51 @@ def graceful_exit(signum, frame): signal.signal(signal.SIGINT, graceful_exit) signal.signal(signal.SIGTERM, graceful_exit) +def find_chart_yaml(tar, path=''): + # Iterate through the members of the tarfile + for member in tar.getmembers(): + # If the member is a directory, recursively search within it + if member.isdir(): + find_chart_yaml(tar, os.path.join(path, member.name)) + # If the member is a file and its name is 'chart.yaml', return its path + if "Chart.yaml" in member.name: + return os.path.join(path, member.name) + +def read_chart_version(chart_tgz_path): + # Open the chart tgz file + with tarfile.open(chart_tgz_path, 'r:gz') as tar: + # Find the path to chart.yaml within the tarball + chart_yaml_path = find_chart_yaml(tar) + if chart_yaml_path: + # Extract the chart.yaml file + chart_yaml_file = tar.extractfile(chart_yaml_path) + if chart_yaml_file is not None: + # Load the YAML content from chart.yaml + chart_data = yaml.safe_load(chart_yaml_file) + # Read the version from chart.yaml + version = chart_data.get('version') + name = chart_data.get('name') + return name, version + else: + raise Exception("Failed to read chart.yaml from the chart tgz file. filename {}".format(chart_tgz_path)) + else: + raise Exception("chart.yaml not found in the chart tgz file. filename {}".format(chart_tgz_path)) + class ChartV2: def __init__(self, filepath:Path): self.filepath = filepath self.project = self.filepath.parts[-2] - parts = self.filepath.stem.split('-') - flag = False + self.name = "" + self.version = "" try: - for i in range(len(parts)-1, -1, -1): - if parts[i][0].isnumeric() or ((parts[i][0]=='v' or parts[i][0]=='v') and parts[i][1].isnumeric()) : - self.name, self.version = '-'.join(parts[:i]), '-'.join(parts[i:]) - flag = True - break - if not flag: + self.name, self.version = read_chart_version(filepath) + if self.name == "" or self.version == "" or self.name is None or self.version is None : raise Exception('chart name: {} is illegal'.format('-'.join(parts))) except Exception as e: click.echo("Skipped chart: {} due to illegal chart name. Error: {}".format(filepath, e), err=True) return + def __check_exist(self, hostname, username, password): return requests.get(CHART_URL_PATTERN.format( host=hostname, @@ -90,6 +120,9 @@ def migrate(hostname, username, password): item_show_func=lambda x: "{}/{}:{} total errors: {}".format(x.project, x.name, x.version, len(errs)) if x else '') as bar: for chart in bar: try: + if chart.name == "" or chart.version == "" : + print("skip the chart {} has no name or version info".format(chart.filepath)) + continue result = chart.migrate(hostname, username, password) if result.stderr: errs.append("chart: {name}:{version} in {project} has err: {err}".format( @@ -99,10 +132,11 @@ def migrate(hostname, username, password): err=result.stderr )) except Exception as e: - errs.append("chart: {name}:{version} in {project} has err: {err}".format( + errs.append("chart: {name}:{version} in {project}, path {path} has err: {err}".format( name=chart.name, version=chart.version, project=chart.project, + path = chart.filepath, err=e)) click.echo("Migration is Done.") print_exist_errs() diff --git a/tools/migrate_chart/test_migrate_chart.py b/tools/migrate_chart/test_migrate_chart.py new file mode 100644 index 0000000000..b3d5e01ac5 --- /dev/null +++ b/tools/migrate_chart/test_migrate_chart.py @@ -0,0 +1,45 @@ + +import unittest +import semver +import tempfile +import os +import tarfile +import shutil +from pathlib import Path +from migrate_chart import extract_chart_name_and_version +from migrate_chart import read_chart_version + +class TestExtractChartNameAndVersion(unittest.TestCase): + def test_valid_chart_name(self): + filepath = Path("my-project/my-chart-v1.0.0.tgz") + name, version = extract_chart_name_and_version(filepath) + self.assertEqual(name, "my-chart") + self.assertEqual(version, "v1.0.0") + + def test_invalid_chart_name(self): + filepath = Path("my-project/mychart.tgz") + name, version = extract_chart_name_and_version(filepath) + self.assertIsNone(name) + self.assertIsNone(version) + + # def test_pure_digit(self): + # filepath = Path("my-project/my-chart-8.0.0-5.tgz") + # name, version = extract_chart_name_and_version(filepath) + # self.assertEqual(name, "my-chart") + # self.assertEqual(version, "8.0.0") + + # def test_digit_startv(self): + # filepath = Path("my-project/my-chart-v8.0.0-5.tgz") + # name, version = extract_chart_name_and_version(filepath) + # self.assertEqual(name, "my-chart") + # self.assertEqual(version, "8.0.0") + + def test_parse_version(self): + temp_dir = tempfile.mkdtemp() + file_name = "/Users/daojunz/Downloads/cert-manager/sample/cert-manager-8.0.0-5.tgz" + name, version = read_chart_version(file_name) + print(name) + print(version) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file