From 8e4d2d578c06499044c553d6f4fa4e6cb828ae86 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Wed, 8 Feb 2023 12:53:36 +0800 Subject: [PATCH] remove chartmuseum backend Harbor deprecates chartmuseum as of v2.8.0 Epic: https://github.com/goharbor/harbor/issues/17958 Discussion: https://github.com/goharbor/harbor/discussions/15057 Signed-off-by: Wang Yan --- .github/workflows/build-package.yml | 4 +- Makefile | 24 +- api/v2.0/swagger.yaml | 5 - make/harbor.yml.tmpl | 7 +- make/install.sh | 17 +- make/photon/Makefile | 25 +- make/photon/chartserver/Dockerfile | 23 - make/photon/chartserver/Dockerfile.base | 6 - make/photon/chartserver/builder | 34 - make/photon/chartserver/compile.sh | 36 - make/photon/chartserver/docker-entrypoint.sh | 8 - make/photon/chartserver/redis.patch | 77 --- make/photon/prepare/commands/prepare.py | 13 +- make/photon/prepare/g.py | 1 - .../version_1_10_0/harbor.yml.jinja | 2 - .../migrations/version_1_9_0/harbor.yml.jinja | 2 - .../migrations/version_2_0_0/harbor.yml.jinja | 2 - .../migrations/version_2_1_0/harbor.yml.jinja | 2 - .../migrations/version_2_2_0/harbor.yml.jinja | 2 - .../migrations/version_2_3_0/harbor.yml.jinja | 2 - .../migrations/version_2_4_0/harbor.yml.jinja | 2 - .../migrations/version_2_5_0/harbor.yml.jinja | 2 - .../migrations/version_2_6_0/harbor.yml.jinja | 2 - .../migrations/version_2_7_0/harbor.yml.jinja | 2 - make/photon/prepare/models.py | 7 - make/photon/prepare/scripts/gencert.sh | 11 - .../prepare/templates/chartserver/env.jinja | 59 -- make/photon/prepare/templates/core/env.jinja | 3 - .../docker_compose/docker-compose.yml.jinja | 58 +- .../templates/nginx/nginx.http.conf.jinja | 22 - .../templates/nginx/nginx.https.conf.jinja | 24 - make/photon/prepare/utils/chart.py | 122 ---- make/photon/prepare/utils/configs.py | 14 +- make/photon/prepare/utils/core.py | 10 +- make/photon/prepare/utils/docker_compose.py | 4 +- src/chartserver/base_test.go | 29 - src/chartserver/cache.go | 229 ------ src/chartserver/cache_test.go | 92 --- src/chartserver/chart_operator.go | 262 ------- src/chartserver/chart_operator_test.go | 48 -- src/chartserver/client.go | 148 ---- src/chartserver/controller.go | 84 --- src/chartserver/controller_test.go | 21 - src/chartserver/handler_interface.go | 97 --- src/chartserver/handler_manipulation.go | 144 ---- src/chartserver/handler_manipulation_test.go | 88 --- src/chartserver/handler_proxy_traffic.go | 12 - src/chartserver/handler_proxy_traffic_test.go | 138 ---- src/chartserver/handler_repo.go | 219 ------ src/chartserver/handler_repo_test.go | 32 - src/chartserver/handler_utility.go | 233 ------- src/chartserver/handler_utility_test.go | 114 --- src/chartserver/redis_sentinel.go | 254 ------- src/chartserver/reverse_proxy.go | 241 ------- src/chartserver/reverse_proxy_test.go | 55 -- src/chartserver/utils.go | 141 ---- src/chartserver/utils_test.go | 129 ---- src/common/const.go | 7 - src/common/rbac/const.go | 49 +- src/common/rbac/project/rbac_role.go | 52 -- src/common/rbac/project/rbac_util.go | 6 - src/common/utils/test/test.go | 1 - .../artifact/processor/chart/chart.go | 72 +- .../artifact/processor/chart/chart_test.go | 67 +- .../artifact/processor/processor.go | 2 +- src/controller/event/handler/init.go | 4 - .../event/handler/replication/event/event.go | 4 +- .../handler/replication/event/handler.go | 3 +- .../event/handler/webhook/chart/chart.go | 122 ---- .../event/handler/webhook/chart/chart_test.go | 183 ----- src/controller/event/metadata/chart.go | 76 -- src/controller/event/metadata/chart_test.go | 73 -- src/controller/event/topic.go | 18 - src/controller/health/checker.go | 15 - src/controller/replication/flow/copy_test.go | 10 - src/controller/replication/flow/stage.go | 55 +- src/controller/replication/flow/stage_test.go | 6 +- .../replication/transfer/chart/transfer.go | 199 ------ .../transfer/chart/transfer_test.go | 117 ---- src/controller/systeminfo/controller.go | 2 - src/controller/systeminfo/controller_test.go | 2 - src/core/api/api_test.go | 25 - src/core/api/base.go | 21 - src/core/api/chart_label.go | 114 --- src/core/api/chart_label_test.go | 226 ------ src/core/api/chart_repository.go | 651 ------------------ src/core/api/chart_repository_test.go | 234 ------- src/core/api/harborapi_test.go | 22 - .../job/impl/replication/replication.go | 31 +- .../job/impl/replication/replication_test.go | 15 - src/lib/config/metadata/metadata_test.go | 1 - src/lib/config/metadata/metadatalist.go | 3 +- src/lib/config/systemconfig.go | 16 - src/lib/config/test/userconfig_test.go | 5 +- src/lib/selector/candidate.go | 4 +- src/pkg/artifact/dao/model.go | 2 +- src/pkg/artifact/model.go | 2 +- src/pkg/chart/model.go | 39 -- src/pkg/chart/operator.go | 114 --- src/pkg/chart/opetator_test.go | 28 - src/pkg/clients/core/chart.go | 40 -- src/pkg/clients/core/client.go | 9 - src/pkg/notification/job/dao/dao_test.go | 8 +- src/pkg/notification/notification.go | 3 - src/pkg/notification/policy/dao/dao_test.go | 8 +- src/pkg/notification/policy/manager_test.go | 4 +- src/pkg/project/models/project.go | 1 - src/pkg/rbac/dao/dao_test.go | 8 - src/pkg/reg/adapter/adapter.go | 9 - src/pkg/reg/adapter/artifacthub/adapter.go | 107 --- .../reg/adapter/artifacthub/adapter_test.go | 132 ---- .../reg/adapter/artifacthub/chart_registry.go | 157 ----- src/pkg/reg/adapter/artifacthub/client.go | 128 ---- src/pkg/reg/adapter/artifacthub/consts.go | 29 - src/pkg/reg/adapter/artifacthub/model.go | 82 --- src/pkg/reg/adapter/harbor/base/adapter.go | 12 - .../reg/adapter/harbor/base/adapter_test.go | 13 +- .../reg/adapter/harbor/base/chart_registry.go | 248 ------- .../harbor/base/chart_registry_test.go | 191 ----- src/pkg/reg/adapter/harbor/base/client.go | 11 - src/pkg/reg/adapter/harbor/v1/adapter.go | 1 - src/pkg/reg/adapter/harbor/v2/adapter.go | 1 - src/pkg/reg/adapter/helmhub/adapter.go | 107 --- src/pkg/reg/adapter/helmhub/chart.go | 44 -- src/pkg/reg/adapter/helmhub/chart_registry.go | 151 ---- src/pkg/reg/adapter/helmhub/client.go | 120 ---- src/pkg/reg/adapter/helmhub/consts.go | 12 - src/pkg/reg/adapter/tencentcr/adapter.go | 1 - .../reg/adapter/tencentcr/chart_registry.go | 270 -------- .../adapter/tencentcr/chart_registry_test.go | 173 ----- src/pkg/reg/filter/artifact.go | 7 - src/pkg/reg/filter/resource.go | 28 - src/pkg/reg/manager.go | 4 - src/pkg/reg/model/policy.go | 2 +- src/pkg/reg/model/resource.go | 1 - src/pkg/retention/dep/client_test.go | 21 +- src/pkg/retention/models.go | 1 - src/pkg/scan/rest/v1/models.go | 1 - src/server/middleware/csrf/csrf.go | 1 - src/server/middleware/metric/metric.go | 13 +- .../middleware/security/idtoken_test.go | 7 - src/server/middleware/security/oidc_cli.go | 4 +- src/server/route.go | 18 - src/server/v2.0/handler/model/project.go | 1 - src/server/v2.0/handler/project.go | 17 +- src/server/v2.0/handler/search.go | 56 +- src/server/v2.0/handler/systeminfo.go | 1 - src/server/v2.0/route/legacy.go | 35 - src/server/v2.0/route/route.go | 1 - src/testing/chart_utility.go | 340 --------- src/testing/clients/dumb_core_client.go | 15 - .../controller/chartmuseum/controller.go | 56 -- src/testing/pkg/chart/operator.go | 33 - tests/apitests/python/library/base.py | 1 - tests/apitests/python/library/chart.py | 46 -- tests/apitests/python/library/webhook.py | 4 +- .../apitests/python/test_list_helm_charts.py | 68 -- ...chart_by_helm2_helm3_with_robot_Account.py | 11 +- tests/apitests/python/test_robot_account.py | 25 +- tests/ci/api_common_install.sh | 2 +- tests/ci/api_run.sh | 2 +- tests/ci/distro_installer.sh | 4 +- .../Harbor-Pages/Project-Helmcharts.robot | 60 -- .../Project-Helmcharts_Elements.robot | 44 -- .../Harbor-Pages/Vulnerability.robot | 6 - tests/resources/Harbor-Util.robot | 28 +- tests/resources/Helm-Util.robot | 17 - tests/resources/Nightly-Util.robot | 2 - tests/resources/Util.robot | 2 - tests/robot-cases/Group0-BAT/API_DB.robot | 8 - .../Group1-Nightly/Chartmuseum.robot | 10 - tests/robot-cases/Group1-Nightly/Common.robot | 23 - .../robot-cases/Group1-Nightly/Upgrade.robot | 3 - 173 files changed, 133 insertions(+), 9118 deletions(-) delete mode 100644 make/photon/chartserver/Dockerfile delete mode 100644 make/photon/chartserver/Dockerfile.base delete mode 100755 make/photon/chartserver/builder delete mode 100644 make/photon/chartserver/compile.sh delete mode 100644 make/photon/chartserver/docker-entrypoint.sh delete mode 100644 make/photon/chartserver/redis.patch delete mode 100644 make/photon/prepare/templates/chartserver/env.jinja delete mode 100644 make/photon/prepare/utils/chart.py delete mode 100644 src/chartserver/base_test.go delete mode 100644 src/chartserver/cache.go delete mode 100644 src/chartserver/cache_test.go delete mode 100644 src/chartserver/chart_operator.go delete mode 100644 src/chartserver/chart_operator_test.go delete mode 100644 src/chartserver/client.go delete mode 100644 src/chartserver/controller.go delete mode 100644 src/chartserver/controller_test.go delete mode 100644 src/chartserver/handler_interface.go delete mode 100644 src/chartserver/handler_manipulation.go delete mode 100644 src/chartserver/handler_manipulation_test.go delete mode 100644 src/chartserver/handler_proxy_traffic.go delete mode 100644 src/chartserver/handler_proxy_traffic_test.go delete mode 100644 src/chartserver/handler_repo.go delete mode 100644 src/chartserver/handler_repo_test.go delete mode 100644 src/chartserver/handler_utility.go delete mode 100644 src/chartserver/handler_utility_test.go delete mode 100644 src/chartserver/redis_sentinel.go delete mode 100644 src/chartserver/reverse_proxy.go delete mode 100644 src/chartserver/reverse_proxy_test.go delete mode 100644 src/chartserver/utils.go delete mode 100644 src/chartserver/utils_test.go delete mode 100644 src/controller/event/handler/webhook/chart/chart.go delete mode 100644 src/controller/event/handler/webhook/chart/chart_test.go delete mode 100644 src/controller/event/metadata/chart.go delete mode 100644 src/controller/event/metadata/chart_test.go delete mode 100644 src/controller/replication/transfer/chart/transfer.go delete mode 100644 src/controller/replication/transfer/chart/transfer_test.go delete mode 100644 src/core/api/chart_label.go delete mode 100644 src/core/api/chart_label_test.go delete mode 100755 src/core/api/chart_repository.go delete mode 100644 src/core/api/chart_repository_test.go delete mode 100644 src/pkg/chart/model.go delete mode 100644 src/pkg/chart/operator.go delete mode 100644 src/pkg/chart/opetator_test.go delete mode 100644 src/pkg/clients/core/chart.go delete mode 100644 src/pkg/reg/adapter/artifacthub/adapter.go delete mode 100644 src/pkg/reg/adapter/artifacthub/adapter_test.go delete mode 100644 src/pkg/reg/adapter/artifacthub/chart_registry.go delete mode 100644 src/pkg/reg/adapter/artifacthub/client.go delete mode 100644 src/pkg/reg/adapter/artifacthub/consts.go delete mode 100644 src/pkg/reg/adapter/artifacthub/model.go delete mode 100644 src/pkg/reg/adapter/harbor/base/chart_registry.go delete mode 100644 src/pkg/reg/adapter/harbor/base/chart_registry_test.go delete mode 100644 src/pkg/reg/adapter/helmhub/adapter.go delete mode 100644 src/pkg/reg/adapter/helmhub/chart.go delete mode 100644 src/pkg/reg/adapter/helmhub/chart_registry.go delete mode 100644 src/pkg/reg/adapter/helmhub/client.go delete mode 100644 src/pkg/reg/adapter/helmhub/consts.go delete mode 100644 src/pkg/reg/adapter/tencentcr/chart_registry.go delete mode 100644 src/pkg/reg/adapter/tencentcr/chart_registry_test.go delete mode 100755 src/server/v2.0/route/legacy.go delete mode 100644 src/testing/chart_utility.go delete mode 100644 src/testing/controller/chartmuseum/controller.go delete mode 100644 src/testing/pkg/chart/operator.go delete mode 100644 tests/apitests/python/library/chart.py delete mode 100644 tests/apitests/python/test_list_helm_charts.py delete mode 100644 tests/resources/Harbor-Pages/Project-Helmcharts.robot delete mode 100644 tests/resources/Harbor-Pages/Project-Helmcharts_Elements.robot diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index a430737880f4..1df94b3861db 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -88,8 +88,8 @@ jobs: else build_base_params=" BUILD_BASE=true PUSHBASEIMAGE=true REGISTRYUSER=\"${{ secrets.DOCKER_HUB_USERNAME }}\" REGISTRYPASSWORD=\"${{ secrets.DOCKER_HUB_PASSWORD }}\"" fi - sudo make package_offline GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=${Harbor_Build_Base_Tag} VERSIONTAG=${Harbor_Assets_Version} PKGVERSIONTAG=${Harbor_Package_Version} NOTARYFLAG=true CHARTFLAG=true TRIVYFLAG=true HTTPPROXY= ${build_base_params} - sudo make package_online GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=${Harbor_Build_Base_Tag} VERSIONTAG=${Harbor_Assets_Version} PKGVERSIONTAG=${Harbor_Package_Version} NOTARYFLAG=true CHARTFLAG=true TRIVYFLAG=true HTTPPROXY= ${build_base_params} + sudo make package_offline GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=${Harbor_Build_Base_Tag} VERSIONTAG=${Harbor_Assets_Version} PKGVERSIONTAG=${Harbor_Package_Version} NOTARYFLAG=true TRIVYFLAG=true HTTPPROXY= ${build_base_params} + sudo make package_online GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=${Harbor_Build_Base_Tag} VERSIONTAG=${Harbor_Assets_Version} PKGVERSIONTAG=${Harbor_Package_Version} NOTARYFLAG=true TRIVYFLAG=true HTTPPROXY= ${build_base_params} harbor_offline_build_bundle=$(basename harbor-offline-installer-*.tgz) harbor_online_build_bundle=$(basename harbor-online-installer-*.tgz) echo "Package name is: $harbor_offline_build_bundle" diff --git a/Makefile b/Makefile index 8243c1da368c..2291bff450ab 100644 --- a/Makefile +++ b/Makefile @@ -82,8 +82,6 @@ TRIVYFLAG=false HTTPPROXY= BUILDBIN=true NPM_REGISTRY=https://registry.npmjs.org -# enable/disable chart repo supporting -CHARTFLAG=false BUILDTARGET=build GEN_TLS= @@ -94,7 +92,7 @@ VERSIONTAG=dev BUILD_BASE=true PUSHBASEIMAGE=false BASEIMAGETAG=dev -BUILDBASETARGET=chartserver trivy-adapter core db jobservice log nginx notary-server notary-signer portal prepare redis registry registryctl exporter +BUILDBASETARGET=trivy-adapter core db jobservice log nginx notary-server notary-signer portal prepare redis registry registryctl exporter IMAGENAMESPACE=goharbor BASEIMAGENAMESPACE=goharbor # #input true/false only @@ -112,17 +110,10 @@ NOTARYMIGRATEVERSION=v4.11.0 TRIVYVERSION=v0.35.0 TRIVYADAPTERVERSION=v0.30.6 -# version of chartmuseum for pulling the source code -CHARTMUSEUM_SRC_TAG=v0.14.0 - -# version of chartmuseum -CHARTMUSEUMVERSION=$(CHARTMUSEUM_SRC_TAG)-redis - # version of registry for pulling the source code REGISTRY_SRC_TAG=v2.8.0 # dependency binaries -CHARTURL=https://storage.googleapis.com/harbor-builds/bin/chartmuseum/release-${CHARTMUSEUMVERSION}/chartm NOTARYURL=https://storage.googleapis.com/harbor-builds/bin/notary/release-${NOTARYVERSION}/binary-bundle.tgz REGISTRYURL=https://storage.googleapis.com/harbor-builds/bin/registry/release-${REGISTRYVERSION}/registry TRIVY_DOWNLOAD_URL=https://github.com/aquasecurity/trivy/releases/download/$(TRIVYVERSION)/trivy_$(TRIVYVERSION:v%=%)_Linux-64bit.tar.gz @@ -134,7 +125,6 @@ REGISTRY_VERSION: $(REGISTRYVERSION) NOTARY_VERSION: $(NOTARYVERSION) TRIVY_VERSION: $(TRIVYVERSION) TRIVY_ADAPTER_VERSION: $(TRIVYADAPTERVERSION) -CHARTMUSEUM_VERSION: $(CHARTMUSEUMVERSION) endef # docker parameters @@ -216,10 +206,6 @@ endif ifeq ($(TRIVYFLAG), true) PREPARECMD_PARA+= --with-trivy endif -# append chartmuseum parameters if set -ifeq ($(CHARTFLAG), true) - PREPARECMD_PARA+= --with-chartmuseum -endif # makefile MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon @@ -234,7 +220,6 @@ DOCKERIMAGENAME_CORE=$(IMAGENAMESPACE)/harbor-core DOCKERIMAGENAME_JOBSERVICE=$(IMAGENAMESPACE)/harbor-jobservice DOCKERIMAGENAME_LOG=$(IMAGENAMESPACE)/harbor-log DOCKERIMAGENAME_DB=$(IMAGENAMESPACE)/harbor-db -DOCKERIMAGENAME_CHART_SERVER=$(IMAGENAMESPACE)/chartmuseum-photon DOCKERIMAGENAME_REGCTL=$(IMAGENAMESPACE)/harbor-registryctl DOCKERIMAGENAME_EXPORTER=$(IMAGENAMESPACE)/harbor-exporter @@ -295,10 +280,6 @@ endif ifeq ($(TRIVYFLAG), true) DOCKERSAVE_PARA+= $(IMAGENAMESPACE)/trivy-adapter-photon:$(VERSIONTAG) endif -# append chartmuseum parameters if set -ifeq ($(CHARTFLAG), true) - DOCKERSAVE_PARA+= $(DOCKERIMAGENAME_CHART_SERVER):$(VERSIONTAG) -endif RUNCONTAINER=$(DOCKERCMD) run --rm -u $(shell id -u):$(shell id -g) -v $(BUILDPATH):$(BUILDPATH) -w $(BUILDPATH) @@ -430,9 +411,8 @@ build: -e TRIVYVERSION=$(TRIVYVERSION) -e TRIVYADAPTERVERSION=$(TRIVYADAPTERVERSION) \ -e VERSIONTAG=$(VERSIONTAG) \ -e BUILDBIN=$(BUILDBIN) \ - -e CHARTMUSEUMVERSION=$(CHARTMUSEUMVERSION) -e CHARTMUSEUM_SRC_TAG=$(CHARTMUSEUM_SRC_TAG) -e DOCKERIMAGENAME_CHART_SERVER=$(DOCKERIMAGENAME_CHART_SERVER) \ -e NPM_REGISTRY=$(NPM_REGISTRY) -e BASEIMAGETAG=$(BASEIMAGETAG) -e IMAGENAMESPACE=$(IMAGENAMESPACE) -e BASEIMAGENAMESPACE=$(BASEIMAGENAMESPACE) \ - -e CHARTURL=$(CHARTURL) -e NOTARYURL=$(NOTARYURL) -e REGISTRYURL=$(REGISTRYURL) \ + -e NOTARYURL=$(NOTARYURL) -e REGISTRYURL=$(REGISTRYURL) \ -e TRIVY_DOWNLOAD_URL=$(TRIVY_DOWNLOAD_URL) -e TRIVY_ADAPTER_DOWNLOAD_URL=$(TRIVY_ADAPTER_DOWNLOAD_URL) \ -e PULL_BASE_FROM_DOCKERHUB=$(PULL_BASE_FROM_DOCKERHUB) -e BUILD_BASE=$(BUILD_BASE) \ -e REGISTRYUSER=$(REGISTRYUSER) -e REGISTRYPASSWORD=$(REGISTRYPASSWORD) \ diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 789c63df7086..8bd354ced267 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -7646,11 +7646,6 @@ definitions: x-nullable: true x-omitempty: true description: If the Harbor instance is deployed with nested notary. - with_chartmuseum: - type: boolean - x-nullable: true - x-omitempty: true - description: If the Harbor instance is deployed with nested chartmuseum. registry_url: type: string x-nullable: true diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 41292d924e84..6be5e418a804 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -56,7 +56,7 @@ data_volume: /data # Uncomment storage_service setting If you want to using external storage # 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. +# # of registry'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 @@ -117,10 +117,6 @@ notification: # Maximum retry count for webhook job webhook_job_max_retry: 10 -chart: - # Change the value of absolute_url to enabled can enable absolute url in chart - absolute_url: disabled - # Log configurations log: # options are debug, info, warning, error, fatal @@ -187,7 +183,6 @@ _version: 2.7.0 # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # trivy_db_index: 5 # idle_timeout_seconds: 30 diff --git a/make/install.sh b/make/install.sh index 2bdf8c853fda..dd327d9f60ac 100755 --- a/make/install.sh +++ b/make/install.sh @@ -9,8 +9,7 @@ set +o noglob usage=$'Please set hostname and other necessary attributes in harbor.yml first. DO NOT use localhost or 127.0.0.1 for hostname, because Harbor needs to be accessed by external clients. Please set --with-notary if needs enable Notary in Harbor, and set ui_url_protocol/ssl_cert/ssl_cert_key in harbor.yml bacause notary must run under https. -Please set --with-trivy if needs enable Trivy in Harbor -Please set --with-chartmuseum if needs enable Chartmuseum in Harbor' +Please set --with-trivy if needs enable Trivy in Harbor' item=0 # notary is not enabled by default @@ -19,8 +18,6 @@ with_notary=$false with_clair=$false # trivy is not enabled by default with_trivy=$false -# chartmuseum is not enabled by default -with_chartmuseum=$false # flag to using docker compose v1 or v2, default would using v1 docker-compose DOCKER_COMPOSE=docker-compose @@ -36,8 +33,6 @@ while [ $# -gt 0 ]; do with_clair=true;; --with-trivy) with_trivy=true;; - --with-chartmuseum) - with_chartmuseum=true;; *) note "$usage" exit 1;; @@ -83,10 +78,6 @@ if [ $with_trivy ] then prepare_para="${prepare_para} --with-trivy" fi -if [ $with_chartmuseum ] -then - prepare_para="${prepare_para} --with-chartmuseum" -fi ./prepare $prepare_para echo "" @@ -99,12 +90,6 @@ fi echo "" h2 "[Step $item]: starting Harbor ..." -if [ $with_chartmuseum ] -then - warn " - Chartmusuem will be deprecated as of Harbor v2.6.0 and start to be removed in v2.8.0 or later. - Please see discussion here for more details. https://github.com/goharbor/harbor/discussions/15057" -fi if [ $with_notary ] then warn " diff --git a/make/photon/Makefile b/make/photon/Makefile index 94f3f993aae0..fa355bda2271 100644 --- a/make/photon/Makefile +++ b/make/photon/Makefile @@ -117,14 +117,6 @@ DOCKERFILEPATH_EXPORTER=$(DOCKERFILEPATH)/$(EXPORTER) DOCKERFILENAME_EXPORTER=Dockerfile DOCKERIMAGENAME_EXPORTER=$(IMAGENAMESPACE)/harbor-$(EXPORTER) -# for chart server (chartmuseum) -CHARTSERVER=chartserver -DOCKERFILEPATH_CHART_SERVER=$(DOCKERFILEPATH)/$(CHARTSERVER) -DOCKERFILENAME_CHART_SERVER=Dockerfile -CHART_SERVER_CODE_BASE=https://github.com/helm/chartmuseum.git -CHART_SERVER_MAIN_PATH=cmd/chartmuseum -CHART_SERVER_BIN_NAME=chartm - _build_prepare: @$(call _build_base,$(PREPARE),$(DOCKERFILEPATH_PREPARE)) @echo "building prepare container for photon..." @@ -184,21 +176,6 @@ _build_trivy_adapter: echo "Done." ; \ fi -_build_chart_server: - @if [ "$(CHARTFLAG)" = "true" ] ; then \ - $(call _build_base,$(CHARTSERVER),$(DOCKERFILEPATH_CHART_SERVER)); \ - if [ "$(BUILDBIN)" != "true" ] ; then \ - rm -rf $(DOCKERFILEPATH_CHART_SERVER)/binary && mkdir -p $(DOCKERFILEPATH_CHART_SERVER)/binary && \ - $(call _get_binary, $(CHARTURL), $(DOCKERFILEPATH_CHART_SERVER)/binary/chartm); \ - else \ - cd $(DOCKERFILEPATH_CHART_SERVER) && $(DOCKERFILEPATH_CHART_SERVER)/builder $(GOBUILDIMAGE) $(CHART_SERVER_CODE_BASE) $(CHARTMUSEUM_SRC_TAG) $(CHART_SERVER_MAIN_PATH) $(CHART_SERVER_BIN_NAME) && cd - ; \ - fi ; \ - echo "building chartmuseum container for photon..." ; \ - $(DOCKERBUILD_WITH_PULL_PARA) --build-arg harbor_base_image_version=$(BASEIMAGETAG) --build-arg harbor_base_namespace=$(BASEIMAGENAMESPACE) -f $(DOCKERFILEPATH_CHART_SERVER)/$(DOCKERFILENAME_CHART_SERVER) -t $(DOCKERIMAGENAME_CHART_SERVER):$(VERSIONTAG) . ; \ - rm -rf $(DOCKERFILEPATH_CHART_SERVER)/binary; \ - echo "Done." ; \ - fi - _build_nginx: @$(call _build_base,$(NGINX),$(DOCKERFILEPATH_NGINX)) @echo "building nginx container for photon..." @@ -284,7 +261,7 @@ define _build_base fi endef -build: _build_prepare _build_db _build_portal _build_core _build_jobservice _build_log _build_nginx _build_registry _build_registryctl _build_notary _build_trivy_adapter _build_redis _build_chart_server _compile_and_build_exporter +build: _build_prepare _build_db _build_portal _build_core _build_jobservice _build_log _build_nginx _build_registry _build_registryctl _build_notary _build_trivy_adapter _build_redis _compile_and_build_exporter @if [ -n "$(REGISTRYUSER)" ] && [ -n "$(REGISTRYPASSWORD)" ] ; then \ docker logout ; \ fi diff --git a/make/photon/chartserver/Dockerfile b/make/photon/chartserver/Dockerfile deleted file mode 100644 index f377739255d4..000000000000 --- a/make/photon/chartserver/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -ARG harbor_base_image_version -ARG harbor_base_namespace -FROM ${harbor_base_namespace}/harbor-chartserver-base:${harbor_base_image_version} - -COPY ./make/photon/chartserver/binary/chartm /home/chart/ -COPY ./make/photon/chartserver/docker-entrypoint.sh /home/chart/ -COPY ./make/photon/common/install_cert.sh /home/chart/ - -RUN chown -R chart:chart /etc/pki/tls/certs \ - && chown -R chart:chart /home/chart \ - && chmod u+x /home/chart/chartm \ - && chmod u+x /home/chart/docker-entrypoint.sh \ - && chmod u+x /home/chart/install_cert.sh - -USER chart - -WORKDIR /home/chart - -ENTRYPOINT ["./docker-entrypoint.sh"] - -VOLUME ["/chart_storage"] - -HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -sS http://localhost:9999/health || curl -k -sS https://localhost:9443/health || exit 1 diff --git a/make/photon/chartserver/Dockerfile.base b/make/photon/chartserver/Dockerfile.base deleted file mode 100644 index 890ba6e2f4a3..000000000000 --- a/make/photon/chartserver/Dockerfile.base +++ /dev/null @@ -1,6 +0,0 @@ -FROM photon:4.0 - -RUN tdnf install -y shadow >>/dev/null\ - && tdnf clean all \ - && groupadd -r -g 10000 chart \ - && useradd --no-log-init -m -g 10000 -u 10000 chart diff --git a/make/photon/chartserver/builder b/make/photon/chartserver/builder deleted file mode 100755 index 765566120f46..000000000000 --- a/make/photon/chartserver/builder +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -set +e - -usage(){ - echo "Usage: builder " - echo "e.g: builder golang:1.19.4 github.com/helm/chartmuseum v0.14.0 cmd/chartmuseum chartm" - exit 1 -} - -if [ $# != 5 ]; then - usage -fi - -GOLANG_IMAGE="$1" -GIT_PATH="$2" -CODE_VERSION="$3" -MAIN_GO_PATH="$4" -BIN_NAME="$5" - -set -e - -cd `dirname $0` -cur=$PWD - -mkdir -p binary -rm -rf binary/$BIN_NAME || true -cp compile.sh binary/ -cp *.patch binary/ - -docker run -i --rm -v $cur/binary:/go/bin --name golang_code_builder $GOLANG_IMAGE /bin/bash /go/bin/compile.sh $GIT_PATH $CODE_VERSION $MAIN_GO_PATH $BIN_NAME - -#Clear -#docker rm -f golang_code_builder diff --git a/make/photon/chartserver/compile.sh b/make/photon/chartserver/compile.sh deleted file mode 100644 index 07a18d388cec..000000000000 --- a/make/photon/chartserver/compile.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -set +e - -usage(){ - echo "Usage: compile.sh " - echo "e.g: compile.sh github.com/helm/chartmuseum v0.14.0 cmd/chartmuseum chartm" - exit 1 -} - -if [ $# != 4 ]; then - usage -fi - -GIT_PATH="$1" -VERSION="$2" -MAIN_GO_PATH="$3" -BIN_NAME="$4" - -#Get the source code -git clone $GIT_PATH src_code -ls -SRC_PATH=$(pwd)/src_code -set -e - -#Checkout the released tag branch -cd $SRC_PATH -git checkout tags/$VERSION -b $VERSION - -#Patch -for p in $(ls /go/bin/*.patch); do - git apply $p || exit /b 1 -done - -#Compile -cd $SRC_PATH/$MAIN_GO_PATH && go build -a -o $BIN_NAME -mv $BIN_NAME /go/bin/ diff --git a/make/photon/chartserver/docker-entrypoint.sh b/make/photon/chartserver/docker-entrypoint.sh deleted file mode 100644 index 0849a61b72b4..000000000000 --- a/make/photon/chartserver/docker-entrypoint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -e - - -/home/chart/install_cert.sh - -#Start the server process -exec /home/chart/chartm diff --git a/make/photon/chartserver/redis.patch b/make/photon/chartserver/redis.patch deleted file mode 100644 index d584cd9eb81e..000000000000 --- a/make/photon/chartserver/redis.patch +++ /dev/null @@ -1,77 +0,0 @@ -diff --git a/cmd/chartmuseum/main.go b/cmd/chartmuseum/main.go -index 8fd7349..eb67ddf 100644 ---- a/cmd/chartmuseum/main.go -+++ b/cmd/chartmuseum/main.go -@@ -282,12 +282,23 @@ func storeFromConfig(conf *config.Config) cache.Store { - switch cacheFlag { - case "redis": - store = redisCacheFromConfig(conf) -+ case "redis_sentinel": -+ store = redisSentinelCacheFromConfig(conf) - default: - crash("Unsupported cache store: ", cacheFlag) - } - - return store - } -+func redisSentinelCacheFromConfig(conf *config.Config) cache.Store { -+ crashIfConfigMissingVars(conf, []string{"cache.redis.addr", "cache.redis.mastername"}) -+ return cache.Store(cache.NewRedisSentinelStore( -+ conf.GetString("cache.redis.mastername"), -+ strings.Split(conf.GetString("cache.redis.addr"), ","), -+ conf.GetString("cache.redis.password"), -+ conf.GetInt("cache.redis.db"), -+ )) -+} - - func redisCacheFromConfig(conf *config.Config) cache.Store { - crashIfConfigMissingVars(conf, []string{"cache.redis.addr"}) -diff --git a/pkg/cache/redis_sentinel.go b/pkg/cache/redis_sentinel.go -new file mode 100644 -index 0000000..0c73427 ---- /dev/null -+++ b/pkg/cache/redis_sentinel.go -@@ -0,0 +1,18 @@ -+package cache -+ -+import ( -+ "github.com/go-redis/redis" -+) -+ -+// NewRedisStore creates a new RedisStore -+func NewRedisSentinelStore(masterName string, sentinelAddrs []string, password string, db int) *RedisStore { -+ store := &RedisStore{} -+ redisClientOptions := &redis.FailoverOptions{ -+ MasterName: masterName, -+ SentinelAddrs: sentinelAddrs, -+ Password: password, -+ DB: db, -+ } -+ store.Client = redis.NewFailoverClient(redisClientOptions) -+ return store -+} -diff --git a/pkg/config/vars.go b/pkg/config/vars.go -index 638eb49..4f036c9 100644 ---- a/pkg/config/vars.go -+++ b/pkg/config/vars.go -@@ -246,10 +246,19 @@ var configVars = map[string]configVar{ - Default: "", - CLIFlag: cli.StringFlag{ - Name: "cache-redis-addr", -- Usage: "address of Redis service (host:port)", -+ Usage: "address of Redis service (host:port), addresses of Redis+Sentinel service (host1:port1,host2:port2)", - EnvVar: "CACHE_REDIS_ADDR", - }, - }, -+ "cache.redis.mastername": { -+ Type: stringType, -+ Default: "", -+ CLIFlag: cli.StringFlag{ -+ Name: "cache-redis-mastername", -+ Usage: "address of Redis+Sentinel mastername", -+ EnvVar: "CACHE_REDIS_MASTERNAME", -+ }, -+ }, - "cache.redis.password": { - Type: stringType, - Default: "", diff --git a/make/photon/prepare/commands/prepare.py b/make/photon/prepare/commands/prepare.py index 6c464a186f9a..0c18429a0b4a 100644 --- a/make/photon/prepare/commands/prepare.py +++ b/make/photon/prepare/commands/prepare.py @@ -15,7 +15,6 @@ from utils.core import prepare_core from utils.notary import prepare_notary from utils.log import prepare_log_configs -from utils.chart import prepare_chartmuseum from utils.docker_compose import prepare_docker_compose from utils.nginx import prepare_nginx, nginx_confd_dir from utils.redis import prepare_redis @@ -30,11 +29,10 @@ @click.option('--conf', default=input_config_path, help="the path of Harbor configuration file") @click.option('--with-notary', is_flag=True, help="the Harbor instance is to be deployed with notary") @click.option('--with-trivy', is_flag=True, help="the Harbor instance is to be deployed with Trivy") -@click.option('--with-chartmuseum', is_flag=True, help="the Harbor instance is to be deployed with chart repository supporting") -def prepare(conf, with_notary, with_trivy, with_chartmuseum): +def prepare(conf, with_notary, with_trivy): delfile(config_dir) - config_dict = parse_yaml_config(conf, with_notary=with_notary, with_trivy=with_trivy, with_chartmuseum=with_chartmuseum) + config_dict = parse_yaml_config(conf, with_notary=with_notary, with_trivy=with_trivy) try: validate(config_dict, notary_mode=with_notary) except Exception as e: @@ -45,7 +43,7 @@ def prepare(conf, with_notary, with_trivy, with_chartmuseum): prepare_portal(config_dict) prepare_log_configs(config_dict) prepare_nginx(config_dict) - prepare_core(config_dict, with_notary=with_notary, with_trivy=with_trivy, with_chartmuseum=with_chartmuseum) + prepare_core(config_dict, with_notary=with_notary, with_trivy=with_trivy) prepare_registry(config_dict) prepare_registry_ctl(config_dict) prepare_db(config_dict) @@ -72,7 +70,4 @@ def prepare(conf, with_notary, with_trivy, with_chartmuseum): if with_trivy: prepare_trivy_adapter(config_dict) - if with_chartmuseum: - prepare_chartmuseum(config_dict) - - prepare_docker_compose(config_dict, with_trivy, with_notary, with_chartmuseum) + prepare_docker_compose(config_dict, with_trivy, with_notary) diff --git a/make/photon/prepare/g.py b/make/photon/prepare/g.py index 45234c15748c..85c4085f583b 100644 --- a/make/photon/prepare/g.py +++ b/make/photon/prepare/g.py @@ -57,7 +57,6 @@ 'jobservice', 'registry', 'registryctl', - 'chartmuseum', 'notary-server', 'notary-signer', 'trivy-adapter', diff --git a/make/photon/prepare/migrations/version_1_10_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_1_10_0/harbor.yml.jinja index b79fee3ee4f6..2a3eb162628b 100644 --- a/make/photon/prepare/migrations/version_1_10_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_1_10_0/harbor.yml.jinja @@ -287,7 +287,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} clair_db_index: 4 {% else %} # Umcomments external_redis if using external Redis server @@ -298,7 +297,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # clair_db_index: 4 {% endif %} diff --git a/make/photon/prepare/migrations/version_1_9_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_1_9_0/harbor.yml.jinja index 45c39bff4e15..80244a7d151d 100644 --- a/make/photon/prepare/migrations/version_1_9_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_1_9_0/harbor.yml.jinja @@ -226,7 +226,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} {% else %} # Umcomments external_redis if using external Redis server # external_redis: @@ -236,7 +235,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 {% endif %} {% if uaa is defined %} diff --git a/make/photon/prepare/migrations/version_2_0_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_0_0/harbor.yml.jinja index ef3f0970dc67..1acfdef03a54 100644 --- a/make/photon/prepare/migrations/version_2_0_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_0_0/harbor.yml.jinja @@ -330,7 +330,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} clair_db_index: {{ external_redis.clair_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 @@ -348,7 +347,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # clair_db_index: 4 # trivy_db_index: 5 # idle_timeout_seconds: 30 diff --git a/make/photon/prepare/migrations/version_2_1_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_1_0/harbor.yml.jinja index 8222c34f245e..347d1617113f 100644 --- a/make/photon/prepare/migrations/version_2_1_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_1_0/harbor.yml.jinja @@ -380,7 +380,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} clair_db_index: {{ external_redis.clair_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 @@ -398,7 +397,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # clair_db_index: 4 # trivy_db_index: 5 # idle_timeout_seconds: 30 diff --git a/make/photon/prepare/migrations/version_2_2_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_2_0/harbor.yml.jinja index d082dcf244a2..f5ec35ea9354 100644 --- a/make/photon/prepare/migrations/version_2_2_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_2_0/harbor.yml.jinja @@ -357,7 +357,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 {% else %} @@ -374,7 +373,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # trivy_db_index: 5 # idle_timeout_seconds: 30 {% endif %} diff --git a/make/photon/prepare/migrations/version_2_3_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_3_0/harbor.yml.jinja index 6bf422a21dc4..282f2732579e 100644 --- a/make/photon/prepare/migrations/version_2_3_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_3_0/harbor.yml.jinja @@ -357,7 +357,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 {% else %} @@ -374,7 +373,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # trivy_db_index: 5 # idle_timeout_seconds: 30 {% endif %} diff --git a/make/photon/prepare/migrations/version_2_4_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_4_0/harbor.yml.jinja index 2b2ac793c1ea..8f4767295a24 100644 --- a/make/photon/prepare/migrations/version_2_4_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_4_0/harbor.yml.jinja @@ -363,7 +363,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 {% else %} @@ -380,7 +379,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # trivy_db_index: 5 # idle_timeout_seconds: 30 {% endif %} diff --git a/make/photon/prepare/migrations/version_2_5_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_5_0/harbor.yml.jinja index 5e7b63e15d29..d3722b9f1943 100644 --- a/make/photon/prepare/migrations/version_2_5_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_5_0/harbor.yml.jinja @@ -377,7 +377,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 {% else %} @@ -394,7 +393,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # trivy_db_index: 5 # idle_timeout_seconds: 30 {% endif %} diff --git a/make/photon/prepare/migrations/version_2_6_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_6_0/harbor.yml.jinja index b97c0a34b5e3..f409ffa9f29b 100644 --- a/make/photon/prepare/migrations/version_2_6_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_6_0/harbor.yml.jinja @@ -377,7 +377,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 {% else %} @@ -394,7 +393,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # trivy_db_index: 5 # idle_timeout_seconds: 30 {% endif %} diff --git a/make/photon/prepare/migrations/version_2_7_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_7_0/harbor.yml.jinja index 3be0173c1c66..782ec79271d7 100644 --- a/make/photon/prepare/migrations/version_2_7_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_7_0/harbor.yml.jinja @@ -384,7 +384,6 @@ external_redis: # 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 }} - chartmuseum_db_index: {{ external_redis.chartmuseum_db_index }} trivy_db_index: 5 idle_timeout_seconds: 30 {% else %} @@ -401,7 +400,6 @@ external_redis: # # db_index 0 is for core, it's unchangeable # registry_db_index: 1 # jobservice_db_index: 2 -# chartmuseum_db_index: 3 # trivy_db_index: 5 # idle_timeout_seconds: 30 {% endif %} diff --git a/make/photon/prepare/models.py b/make/photon/prepare/models.py index 1e04b8f959bf..ab45c1aca941 100644 --- a/make/photon/prepare/models.py +++ b/make/photon/prepare/models.py @@ -29,11 +29,6 @@ class InternalTLS: 'notary_server.crt', 'notary_server.key' } - chart_museum_filename = { - 'chartmuseum.crt', - 'chartmuseum.key' - } - db_certs_filename = { 'harbor_db.crt', 'harbor_db.key' } @@ -47,8 +42,6 @@ def __init__(self, tls_enabled=False, verify_client_cert=False, tls_dir='', data self.required_filenames = self.harbor_certs_filename if kwargs.get('with_notary'): self.required_filenames.update(self.notary_certs_filename) - if kwargs.get('with_chartmuseum'): - self.required_filenames.update(self.chart_museum_filename) if kwargs.get('with_trivy'): self.required_filenames.update(self.trivy_certs_filename) if not kwargs.get('external_database'): diff --git a/make/photon/prepare/scripts/gencert.sh b/make/photon/prepare/scripts/gencert.sh index 65476083f83d..0e910820f0ae 100755 --- a/make/photon/prepare/scripts/gencert.sh +++ b/make/photon/prepare/scripts/gencert.sh @@ -115,17 +115,6 @@ echo subjectAltName = DNS.1:notary-server > extfile.cnf openssl x509 -req -days $DAYS -sha256 -in notary_server.csr -CA harbor_internal_ca.crt -CAkey harbor_internal_ca.key -CAcreateserial -extfile extfile.cnf -out notary_server.crt -# generate chartmuseum key -openssl req -new \ - -newkey rsa:4096 -nodes -sha256 -keyout chartmuseum.key \ - -out chartmuseum.csr \ - -subj "/C=CN/ST=Beijing/L=Beijing/O=VMware/CN=chartmuseum" - -# sign chartmuseum csr with CA certificate and key -echo subjectAltName = DNS.1:chartmuseum > extfile.cnf -openssl x509 -req -days $DAYS -sha256 -in chartmuseum.csr -CA harbor_internal_ca.crt -CAkey harbor_internal_ca.key -CAcreateserial -extfile extfile.cnf -out chartmuseum.crt - - # generate harbor_db key openssl req -new \ -newkey rsa:4096 -nodes -sha256 -keyout harbor_db.key \ diff --git a/make/photon/prepare/templates/chartserver/env.jinja b/make/photon/prepare/templates/chartserver/env.jinja deleted file mode 100644 index 9eec3c25d201..000000000000 --- a/make/photon/prepare/templates/chartserver/env.jinja +++ /dev/null @@ -1,59 +0,0 @@ -## Settings should be set -{%if internal_tls.enabled %} -PORT=9443 -TLS_CERT=/etc/harbor/ssl/chartmuseum.crt -TLS_KEY=/etc/harbor/ssl/chartmuseum.key -# Uncomment if need mTLS -# TLS_CA_CERT=/etc/harbor/ssl/harbor_internal_ca.crt -{% else %} -PORT=9999 -{% endif %} - -# Only support redis now. If redis is setup, then enable cache -CACHE={{cache_store}} -{% if cache_redis_mastername %} -CACHE_REDIS_ADDR={{cache_redis_addr}} -CACHE_REDIS_MASTERNAME={{cache_redis_mastername}} -CACHE_REDIS_PASSWORD={{cache_redis_password}} -CACHE_REDIS_DB={{cache_redis_db_index}} -{% else %} -CACHE_REDIS_ADDR={{cache_redis_addr}} -CACHE_REDIS_PASSWORD={{cache_redis_password}} -CACHE_REDIS_DB={{cache_redis_db_index}} -{% endif %} - -# Credential for internal communication -BASIC_AUTH_USER=chart_controller -BASIC_AUTH_PASS={{core_secret}} - -# Multiple tenants -# Must be set with 1 to support project namespace -DEPTH=1 - -# Backend storage driver: e.g. "local", "amazon", "google" etc. -STORAGE={{storage_driver}} -{% if storage_driver == "amazon" %} -AWS_SDK_LOAD_CONFIG=1 -{% endif %} -# Storage driver settings -{{all_storage_driver_configs}} -## Settings with default values. Just put here for future changes -DEBUG=false -LOG_JSON=true -DISABLE_METRICS=false -DISABLE_API=false -DISABLE_STATEFILES=false -ALLOW_OVERWRITE=true -{% if chart_absolute_url %} -CHART_URL={{public_url}}/chartrepo -{% else %} -CHART_URL= -{% endif %} -AUTH_ANONYMOUS_GET=false -CONTEXT_PATH= -INDEX_LIMIT=0 -MAX_STORAGE_OBJECTS=0 -MAX_UPLOAD_SIZE=20971520 -CHART_POST_FORM_FIELD_NAME=chart -PROV_POST_FORM_FIELD_NAME=prov -STORAGE_TIMESTAMP_TOLERANCE=1s diff --git a/make/photon/prepare/templates/core/env.jinja b/make/photon/prepare/templates/core/env.jinja index 5ea5d3734bf9..49c46298c59f 100644 --- a/make/photon/prepare/templates/core/env.jinja +++ b/make/photon/prepare/templates/core/env.jinja @@ -2,7 +2,6 @@ CONFIG_PATH=/etc/core/app.conf UAA_CA_ROOT=/etc/core/certificates/uaa_ca.pem _REDIS_URL_CORE={{redis_url_core}} SYNC_QUOTA=true -CHART_CACHE_DRIVER={{chart_cache_driver}} _REDIS_URL_REG={{redis_url_reg}} LOG_LEVEL={{log_level}} @@ -35,9 +34,7 @@ NOTARY_URL={{notary_url}} REGISTRY_STORAGE_PROVIDER_NAME={{storage_provider_name}} READ_ONLY=false RELOAD_KEY={{reload_key}} -CHART_REPOSITORY_URL={{chart_repository_url}} REGISTRY_CONTROLLER_URL={{registry_controller_url}} -WITH_CHARTMUSEUM={{with_chartmuseum}} REGISTRY_CREDENTIAL_USERNAME={{registry_username}} REGISTRY_CREDENTIAL_PASSWORD={{registry_password}} CSRF_KEY={{csrf_key}} diff --git a/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja b/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja index 26d76a3a0ad5..2cb15629f4f6 100644 --- a/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja +++ b/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja @@ -186,11 +186,6 @@ services: harbor: {% if with_notary %} harbor-notary: -{% endif %} -{% if with_chartmuseum %} - harbor-chartmuseum: - aliases: - - harbor-core {% endif %} depends_on: - log @@ -291,11 +286,6 @@ services: - {{data_volume}}/redis:/var/lib/redis networks: harbor: - {% if with_chartmuseum %} - harbor-chartmuseum: - aliases: - - redis - {% endif %} depends_on: - log logging: @@ -477,49 +467,6 @@ services: env_file: ./common/config/trivy-adapter/env {% endif %} -{% if with_chartmuseum %} - chartmuseum: - container_name: chartmuseum - image: goharbor/chartmuseum-photon:{{chartmuseum_version}} - restart: always - cap_drop: - - ALL - cap_add: - - CHOWN - - DAC_OVERRIDE - - SETGID - - SETUID - networks: - - harbor-chartmuseum - depends_on: - - log - volumes: - - {{data_volume}}/chart_storage:/chart_storage:z - - ./common/config/chartserver:/etc/chartserver:z - - type: bind - source: ./common/config/shared/trust-certificates - target: /harbor_cust_cert -{%if internal_tls.enabled %} - - type: bind - source: {{internal_tls.chartmuseum_crt_path}} - target: /etc/harbor/ssl/chartmuseum.crt - - type: bind - source: {{internal_tls.chartmuseum_key_path}} - target: /etc/harbor/ssl/chartmuseum.key -{% endif %} -{% if gcs_keyfile %} - - type: bind - source: {{gcs_keyfile}} - target: /etc/chartserver/gcs.key -{% endif %} - logging: - driver: "syslog" - options: - syslog-address: "tcp://localhost:1514" - tag: "chartmuseum" - env_file: - ./common/config/chartserver/env -{% endif %} {% if metric.enabled %} exporter: image: goharbor/harbor-exporter:{{version}} @@ -553,7 +500,4 @@ networks: notary-sig: external: false {% endif %} -{% if with_chartmuseum %} - harbor-chartmuseum: - external: false -{% endif %} + diff --git a/make/photon/prepare/templates/nginx/nginx.http.conf.jinja b/make/photon/prepare/templates/nginx/nginx.http.conf.jinja index 427b3af08c55..6022b3ac9411 100644 --- a/make/photon/prepare/templates/nginx/nginx.http.conf.jinja +++ b/make/photon/prepare/templates/nginx/nginx.http.conf.jinja @@ -125,28 +125,6 @@ http { proxy_request_buffering off; } - location /chartrepo/ { -{% if internal_tls.enabled %} - proxy_pass https://core/chartrepo/; - - proxy_ssl_certificate /etc/harbor/tls/proxy.crt; - proxy_ssl_certificate_key /etc/harbor/tls/proxy.key; - proxy_ssl_trusted_certificate /harbor_cust_cert/harbor_internal_ca.crt; - proxy_ssl_verify_depth 2; - proxy_ssl_verify on; - proxy_ssl_session_reuse on; -{% else %} - proxy_pass http://core/chartrepo/; -{% endif %} - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $x_forwarded_proto; - - proxy_buffering off; - proxy_request_buffering off; - } - location /v1/ { return 404; } diff --git a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja index ce712beb154d..6573d32040ed 100644 --- a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja +++ b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja @@ -149,30 +149,6 @@ http { proxy_request_buffering off; } - location /chartrepo/ { -{% if internal_tls.enabled %} - proxy_pass https://core/chartrepo/; - - proxy_ssl_certificate /etc/harbor/tls/proxy.crt; - proxy_ssl_certificate_key /etc/harbor/tls/proxy.key; - proxy_ssl_trusted_certificate /harbor_cust_cert/harbor_internal_ca.crt; - proxy_ssl_verify_depth 2; - proxy_ssl_verify on; - proxy_ssl_session_reuse on; -{% else %} - proxy_pass http://core/chartrepo/; -{% endif %} - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $x_forwarded_proto; - - proxy_cookie_path / "/; Secure"; - - proxy_buffering off; - proxy_request_buffering off; - } - location /v1/ { return 404; } diff --git a/make/photon/prepare/utils/chart.py b/make/photon/prepare/utils/chart.py deleted file mode 100644 index abcef200d8d2..000000000000 --- a/make/photon/prepare/utils/chart.py +++ /dev/null @@ -1,122 +0,0 @@ -import os -from urllib.parse import urlsplit -from g import templates_dir, config_dir, data_dir, DEFAULT_UID, DEFAULT_GID - -from .jinja import render_jinja -from .misc import prepare_dir - -chart_museum_temp_dir = os.path.join(templates_dir, "chartserver") -chart_museum_env_temp = os.path.join(chart_museum_temp_dir, "env.jinja") - -chart_museum_config_dir = os.path.join(config_dir, "chartserver") -chart_museum_env = os.path.join(config_dir, "chartserver", "env") - -chart_museum_data_dir = os.path.join(data_dir, 'chart_storage') - - -def parse_redis(redis_url_chart): - u = urlsplit(redis_url_chart) - if not u.scheme or u.scheme == 'redis': - return { - 'cache_store': 'redis', - 'cache_redis_addr': u.netloc.split('@')[-1], - 'cache_redis_password': u.password or '', - 'cache_redis_db_index': u.path and int(u.path[1:]) or 0, - } - elif u.scheme == 'redis+sentinel': - return { - 'cache_store': 'redis_sentinel', - 'cache_redis_mastername': u.path.split('/')[1], - 'cache_redis_addr': u.netloc.split('@')[-1], - 'cache_redis_password': u.password or '', - 'cache_redis_db_index': len(u.path.split('/')) == 3 and int(u.path.split('/')[2]) or 0, - } - else: - raise Exception('bad redis url for chart:' + redis_url_chart) - - -def prepare_chartmuseum(config_dict): - storage_provider_name = config_dict['storage_provider_name'] - storage_provider_config_map = config_dict['storage_provider_config'] - - prepare_dir(chart_museum_data_dir, uid=DEFAULT_UID, gid=DEFAULT_GID) - prepare_dir(chart_museum_config_dir) - - # process redis info - cache_redis_ops = parse_redis(config_dict['redis_url_chart']) - - - # process storage info - #default using local file system - storage_driver = "local" - # storage provider configurations - # please be aware that, we do not check the validations of the values for the specified keys - # convert the configs to config map - storage_provider_config_options = [] - if storage_provider_name == 's3': - # aws s3 storage - storage_driver = "amazon" - storage_provider_config_options.append("STORAGE_AMAZON_BUCKET=%s" % (storage_provider_config_map.get("bucket") or '') ) - storage_provider_config_options.append("STORAGE_AMAZON_PREFIX=%s" % (storage_provider_config_map.get("rootdirectory") or '') ) - storage_provider_config_options.append("STORAGE_AMAZON_REGION=%s" % (storage_provider_config_map.get("region") or '') ) - storage_provider_config_options.append("STORAGE_AMAZON_ENDPOINT=%s" % (storage_provider_config_map.get("regionendpoint") or '') ) - storage_provider_config_options.append("AWS_ACCESS_KEY_ID=%s" % (storage_provider_config_map.get("accesskey") or '') ) - storage_provider_config_options.append("AWS_SECRET_ACCESS_KEY=%s" % (storage_provider_config_map.get("secretkey") or '') ) - elif storage_provider_name == 'gcs': - # google cloud storage - storage_driver = "google" - storage_provider_config_options.append("STORAGE_GOOGLE_BUCKET=%s" % ( storage_provider_config_map.get("bucket") or '') ) - storage_provider_config_options.append("STORAGE_GOOGLE_PREFIX=%s" % ( storage_provider_config_map.get("rootdirectory") or '') ) - - if storage_provider_config_map.get("keyfile"): - storage_provider_config_options.append('GOOGLE_APPLICATION_CREDENTIALS=%s' % '/etc/chartserver/gcs.key') - elif storage_provider_name == 'azure': - # azure storage - storage_driver = "microsoft" - storage_provider_config_options.append("STORAGE_MICROSOFT_CONTAINER=%s" % ( storage_provider_config_map.get("container") or '') ) - storage_provider_config_options.append("AZURE_STORAGE_ACCOUNT=%s" % ( storage_provider_config_map.get("accountname") or '') ) - storage_provider_config_options.append("AZURE_STORAGE_ACCESS_KEY=%s" % ( storage_provider_config_map.get("accountkey") or '') ) - storage_provider_config_options.append("STORAGE_MICROSOFT_PREFIX=/azure/harbor/charts") - elif storage_provider_name == 'swift': - # open stack swift - storage_driver = "openstack" - storage_provider_config_options.append("STORAGE_OPENSTACK_CONTAINER=%s" % ( storage_provider_config_map.get("container") or '') ) - storage_provider_config_options.append("STORAGE_OPENSTACK_PREFIX=%s" % ( storage_provider_config_map.get("rootdirectory") or '') ) - storage_provider_config_options.append("STORAGE_OPENSTACK_REGION=%s" % ( storage_provider_config_map.get("region") or '') ) - storage_provider_config_options.append("OS_AUTH_URL=%s" % ( storage_provider_config_map.get("authurl") or '') ) - storage_provider_config_options.append("OS_USERNAME=%s" % ( storage_provider_config_map.get("username") or '') ) - storage_provider_config_options.append("OS_PASSWORD=%s" % ( storage_provider_config_map.get("password") or '') ) - storage_provider_config_options.append("OS_PROJECT_ID=%s" % ( storage_provider_config_map.get("tenantid") or '') ) - storage_provider_config_options.append("OS_PROJECT_NAME=%s" % ( storage_provider_config_map.get("tenant") or '') ) - storage_provider_config_options.append("OS_DOMAIN_ID=%s" % ( storage_provider_config_map.get("domainid") or '') ) - storage_provider_config_options.append("OS_DOMAIN_NAME=%s" % ( storage_provider_config_map.get("domain") or '') ) - elif storage_provider_name == 'oss': - # aliyun OSS - storage_driver = "alibaba" - bucket = storage_provider_config_map.get("bucket") or '' - endpoint = storage_provider_config_map.get("endpoint") or '' - if endpoint.startswith(bucket + "."): - endpoint = endpoint.replace(bucket + ".", "") - storage_provider_config_options.append("STORAGE_ALIBABA_BUCKET=%s" % bucket ) - storage_provider_config_options.append("STORAGE_ALIBABA_ENDPOINT=%s" % endpoint ) - storage_provider_config_options.append("STORAGE_ALIBABA_PREFIX=%s" % ( storage_provider_config_map.get("rootdirectory") or '') ) - storage_provider_config_options.append( - "ALIBABA_CLOUD_ACCESS_KEY_ID=%s" % (storage_provider_config_map.get("accesskeyid") or '')) - storage_provider_config_options.append( - "ALIBABA_CLOUD_ACCESS_KEY_SECRET=%s" % (storage_provider_config_map.get("accesskeysecret") or '')) - else: - # use local file system - storage_provider_config_options.append("STORAGE_LOCAL_ROOTDIR=/chart_storage") - - # generate storage provider configuration - all_storage_provider_configs = ('\n').join(storage_provider_config_options) - - render_jinja( - chart_museum_env_temp, - chart_museum_env, - storage_driver=storage_driver, - all_storage_driver_configs=all_storage_provider_configs, - public_url=config_dict['public_url'], - chart_absolute_url=config_dict['chart_absolute_url'], - internal_tls=config_dict['internal_tls'], - **cache_redis_ops) diff --git a/make/photon/prepare/utils/configs.py b/make/photon/prepare/utils/configs.py index a951b95f8cc2..c537fa0cc58c 100644 --- a/make/photon/prepare/utils/configs.py +++ b/make/photon/prepare/utils/configs.py @@ -97,7 +97,7 @@ def parse_versions(): return versions -def parse_yaml_config(config_file_path, with_notary, with_trivy, with_chartmuseum): +def parse_yaml_config(config_file_path, with_notary, with_trivy): ''' :param configs: config_parser object :returns: dict of configs @@ -116,7 +116,6 @@ def parse_yaml_config(config_file_path, with_notary, with_trivy, with_chartmuseu 'jobservice_url': 'http://jobservice:8080', 'trivy_adapter_url': 'http://trivy-adapter:8080', 'notary_url': 'http://notary-server:4443', - 'chart_repository_url': 'http://chartmuseum:9999' } config_dict['hostname'] = configs["hostname"] @@ -236,13 +235,6 @@ def parse_yaml_config(config_file_path, with_notary, with_trivy, with_chartmuseu config_dict['trivy_insecure'] = trivy_configs.get("insecure") or False config_dict['trivy_timeout'] = trivy_configs.get("timeout") or '5m0s' - # Chart configs - chart_configs = configs.get("chart") or {} - if chart_configs.get('absolute_url') == 'enabled': - config_dict['chart_absolute_url'] = True - else: - config_dict['chart_absolute_url'] = False - # jobservice config js_config = configs.get('jobservice') or {} config_dict['max_job_workers'] = js_config["max_job_workers"] @@ -333,7 +325,6 @@ def parse_yaml_config(config_file_path, with_notary, with_trivy, with_chartmuseu configs['data_volume'], with_notary=with_notary, with_trivy=with_trivy, - with_chartmuseum=with_chartmuseum, external_database=config_dict['external_database']) else: config_dict['internal_tls'] = InternalTLS() @@ -359,7 +350,6 @@ def parse_yaml_config(config_file_path, with_notary, with_trivy, with_chartmuseu config_dict['jobservice_url'] = 'https://jobservice:8443' config_dict['trivy_adapter_url'] = 'https://trivy-adapter:8443' # config_dict['notary_url'] = 'http://notary-server:4443' - config_dict['chart_repository_url'] = 'https://chartmuseum:9443' # purge upload configs purge_upload_config = configs.get('upload_purging') @@ -450,7 +440,6 @@ def get_redis_configs(external_redis=None, with_trivy=True): 'password': '', 'registry_db_index': 1, 'jobservice_db_index': 2, - 'chartmuseum_db_index': 3, 'trivy_db_index': 5, 'idle_timeout_seconds': 30, } @@ -459,7 +448,6 @@ def get_redis_configs(external_redis=None, with_trivy=True): redis.update({key: value for (key, value) in external_redis.items() if value}) configs['redis_url_core'] = get_redis_url(0, redis) - configs['redis_url_chart'] = get_redis_url(redis['chartmuseum_db_index'], redis) configs['redis_url_js'] = get_redis_url(redis['jobservice_db_index'], redis) configs['redis_url_reg'] = get_redis_url(redis['registry_db_index'], redis) diff --git a/make/photon/prepare/utils/core.py b/make/photon/prepare/utils/core.py index ee29f400a3a2..137c4b24febb 100644 --- a/make/photon/prepare/utils/core.py +++ b/make/photon/prepare/utils/core.py @@ -13,24 +13,16 @@ ca_download_dir = os.path.join(data_dir, 'ca_download') -def prepare_core(config_dict, with_notary, with_trivy, with_chartmuseum): +def prepare_core(config_dict, with_notary, with_trivy): prepare_dir(ca_download_dir, uid=DEFAULT_UID, gid=DEFAULT_GID) prepare_dir(core_config_dir) # Render Core - # set cache for chart repo server - # default set 'memory' mode, if redis is configured then set to 'redis' - if len(config_dict['redis_url_core']) > 0: - chart_cache_driver = "redis" - else: - chart_cache_driver = "memory" render_jinja( core_env_template_path, core_conf_env, - chart_cache_driver=chart_cache_driver, with_notary=with_notary, with_trivy=with_trivy, - with_chartmuseum=with_chartmuseum, csrf_key=generate_random_string(32), **config_dict) diff --git a/make/photon/prepare/utils/docker_compose.py b/make/photon/prepare/utils/docker_compose.py index 0121175dc3a9..c517840ac2a7 100644 --- a/make/photon/prepare/utils/docker_compose.py +++ b/make/photon/prepare/utils/docker_compose.py @@ -8,7 +8,7 @@ docker_compose_yml_path = '/compose_location/docker-compose.yml' # render docker-compose -def prepare_docker_compose(configs, with_trivy, with_notary, with_chartmuseum): +def prepare_docker_compose(configs, with_trivy, with_notary): versions = parse_versions() VERSION_TAG = versions.get('VERSION_TAG') or 'dev' @@ -18,7 +18,6 @@ def prepare_docker_compose(configs, with_trivy, with_notary, with_chartmuseum): 'redis_version': VERSION_TAG, 'notary_version': VERSION_TAG, 'trivy_adapter_version': VERSION_TAG, - 'chartmuseum_version': VERSION_TAG, 'data_volume': configs['data_volume'], 'log_location': configs['log_location'], 'protocol': configs['protocol'], @@ -27,7 +26,6 @@ def prepare_docker_compose(configs, with_trivy, with_notary, with_chartmuseum): 'external_database': configs['external_database'], 'with_notary': with_notary, 'with_trivy': with_trivy, - 'with_chartmuseum': with_chartmuseum } # if configs.get('registry_custom_ca_bundle_path'): diff --git a/src/chartserver/base_test.go b/src/chartserver/base_test.go deleted file mode 100644 index 5fcea12b1cf2..000000000000 --- a/src/chartserver/base_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package chartserver - -import ( - "net/http/httptest" - "net/url" - - "github.com/goharbor/harbor/src/testing" -) - -// createMockObjects create mock objects for chart repo related testing. -func createMockObjects() (*httptest.Server, *Controller, error) { - s := httptest.NewServer(testing.MockChartRepoHandler) - backendURL, err := url.Parse(s.URL) - if err != nil { - s.Close() - return nil, nil, err - } - - mockController, err := NewController(backendURL) - if err != nil { - s.Close() - return nil, nil, err - } - - return s, mockController, nil -} - -// Http client -var httpClient = NewChartClient(nil) diff --git a/src/chartserver/cache.go b/src/chartserver/cache.go deleted file mode 100644 index db27c2f7f897..000000000000 --- a/src/chartserver/cache.go +++ /dev/null @@ -1,229 +0,0 @@ -package chartserver - -import ( - "context" - "encoding/json" - "errors" - "math" - "time" - - beego_cache "github.com/beego/beego/v2/client/cache" - // Enable redis cache adaptor - _ "github.com/beego/beego/v2/client/cache/redis" - - hlog "github.com/goharbor/harbor/src/lib/log" -) - -const ( - standardExpireTime = 3600 * time.Second - redisENVKey = "_REDIS_URL_CORE" - cacheDriverENVKey = "CHART_CACHE_DRIVER" // "memory" or "redis" - cacheDriverMem = "memory" - cacheDriverRedis = "redis" - cacheDriverRedisSentinel = "redis_sentinel" - cacheCollectionName = "helm_chart_cache" - maxTry = 10 -) - -// ChartCache is designed to cache some processed data for repeated accessing -// to improve the performance -type ChartCache struct { - // Cache driver - cache beego_cache.Cache - - // Keep the driver type - driverType string - - // To indicate if the chart cache is enabled - isEnabled bool -} - -// ChartCacheConfig keeps the configurations of ChartCache -type ChartCacheConfig struct { - // Only support 'in-memory' and 'redis' now - DriverType string - - // Align with config - Config string -} - -// NewChartCache is constructor of ChartCache -// If return nil, that means no cache is enabled for chart repository server -func NewChartCache(config *ChartCacheConfig) *ChartCache { - // Never return nil object - chartCache := &ChartCache{ - isEnabled: false, - } - - // Double check the configurations are what we want - if config == nil { - return chartCache - } - - if config.DriverType != cacheDriverMem && config.DriverType != cacheDriverRedis && config.DriverType != cacheDriverRedisSentinel { - return chartCache - } - - if config.DriverType == cacheDriverRedis || config.DriverType == cacheDriverRedisSentinel { - if len(config.Config) == 0 { - return chartCache - } - } - - // Try to create the upstream cache - cache := initCacheDriver(config) - if cache == nil { - return chartCache - } - - // Cache enabled - chartCache.isEnabled = true - chartCache.driverType = config.DriverType - chartCache.cache = cache - - return chartCache -} - -// IsEnabled to indicate if the chart cache is successfully enabled -// The cache may be disabled if -// -// user does not set -// wrong configurations -func (chc *ChartCache) IsEnabled() bool { - return chc.isEnabled -} - -// PutChart caches the detailed data of chart version -func (chc *ChartCache) PutChart(chart *ChartVersionDetails) { - ctx := context.Background() - // If cache is not enabled, do nothing - if !chc.IsEnabled() { - return - } - - // As it's a valid json data anymore when retrieving back from redis cache, - // here we use separate methods to handle the data according to the driver type - if chart != nil { - var err error - - switch chc.driverType { - case cacheDriverMem: - // Directly put object in - err = chc.cache.Put(ctx, chart.Metadata.Digest, chart, standardExpireTime) - case cacheDriverRedis, cacheDriverRedisSentinel: - // Marshal to json data before saving - var jsonData []byte - if jsonData, err = json.Marshal(chart); err == nil { - err = chc.cache.Put(ctx, chart.Metadata.Digest, jsonData, standardExpireTime) - } - default: - // Should not reach here, but still put guard code here - err = errors.New("meet invalid cache driver") - } - - if err != nil { - // Just logged - hlog.Errorf("Failed to cache chart object with error: %s\n", err) - hlog.Warningf("If cache driver is using 'redis', please check the related configurations or the network connection") - } - } -} - -// GetChart trys to retrieve it from the cache -// If hit, return the cached item; -// otherwise, nil object is returned -func (chc *ChartCache) GetChart(chartDigest string) *ChartVersionDetails { - // If cache is not enabled, do nothing - ctx := context.Background() - if !chc.IsEnabled() { - return nil - } - - object, err := chc.cache.Get(ctx, chartDigest) - if err != nil { - hlog.Warningf("Failed to get cache value by key with error: %s", err) - } - if object != nil { - // Try to convert data - // First try the normal way - if chartDetails, ok := object.(*ChartVersionDetails); ok { - return chartDetails - } - - // Maybe json bytes - if bytes, yes := object.([]byte); yes { - chartDetails := &ChartVersionDetails{} - err := json.Unmarshal(bytes, chartDetails) - if err == nil { - return chartDetails - } - // Just logged the error - hlog.Errorf("Failed to retrieve chart from cache with error: %s", err) - } - } - - return nil -} - -// Initialize the cache driver based on the config -func initCacheDriver(cacheConfig *ChartCacheConfig) beego_cache.Cache { - switch cacheConfig.DriverType { - case cacheDriverMem: - hlog.Info("Enable memory cache for chart caching") - return beego_cache.NewMemoryCache() - case cacheDriverRedis: - // New with retry - count := 0 - for { - count++ - redisCache, err := beego_cache.NewCache(cacheDriverRedis, cacheConfig.Config) - if err != nil { - // Just logged - hlog.Errorf("Failed to initialize redis cache: %s", err) - - if count < maxTry { - <-time.After(time.Duration(backoff(count)) * time.Second) - continue - } - - return nil - } - - hlog.Info("Enable redis cache for chart caching") - return redisCache - } - case cacheDriverRedisSentinel: - // New with retry - count := 0 - for { - count++ - redisCache, err := beego_cache.NewCache(cacheDriverRedisSentinel, cacheConfig.Config) - if err != nil { - // Just logged - hlog.Errorf("Failed to initialize redis sentinel cache: %s", err) - - if count < maxTry { - <-time.After(time.Duration(backoff(count)) * time.Second) - continue - } - - return nil - } - - hlog.Info("Enable redis sentinel cache for chart caching") - return redisCache - } - default: - break - } - - // Any other cases - hlog.Info("No cache is enabled for chart caching") - return nil -} - -// backoff: fast->slow->fast -func backoff(count int) int { - f := 5 - math.Abs((float64)(count)-5) - return (int)(math.Pow(2, f)) -} diff --git a/src/chartserver/cache_test.go b/src/chartserver/cache_test.go deleted file mode 100644 index 6b304410c461..000000000000 --- a/src/chartserver/cache_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package chartserver - -import ( - "encoding/json" - "testing" - - "helm.sh/helm/v3/pkg/chart" - helm_repo "helm.sh/helm/v3/pkg/repo" -) - -var ( - mockChart = &ChartVersionDetails{ - Metadata: &helm_repo.ChartVersion{ - Metadata: &chart.Metadata{ - Name: "mock_chart", - Version: "0.1.0", - }, - Digest: "mock_digest", - }, - Dependencies: make([]*chart.Dependency, 0), - } -) - -// Test the no cache set scenario -func TestNoCache(t *testing.T) { - chartCache := NewChartCache(nil) - if chartCache == nil { - t.Fatalf("cache instance should not be nil") - } - - if chartCache.IsEnabled() { - t.Fatal("chart cache should not be enabled") - } -} - -// Test the in memory cache -func TestInMemoryCache(t *testing.T) { - chartCache := NewChartCache(&ChartCacheConfig{ - DriverType: cacheDriverMem, - }) - if chartCache == nil { - t.Fatalf("cache instance should not be nil") - } - - if !chartCache.IsEnabled() { - t.Fatal("chart cache should be enabled") - } - - if chartCache.driverType != cacheDriverMem { - t.Fatalf("expect driver type %s but got %s", cacheDriverMem, chartCache.driverType) - } - - chartCache.PutChart(mockChart) - theCachedChart := chartCache.GetChart(mockChart.Metadata.Digest) - if theCachedChart == nil || theCachedChart.Metadata.Name != mockChart.Metadata.Name { - t.Fatal("In memory cache does work") - } -} - -// Test redis cache -// Failed to config redis cache and then use in memory instead -func TestRedisCache(t *testing.T) { - redisConfigV := make(map[string]string) - redisConfigV["key"] = cacheCollectionName - redisConfigV["conn"] = ":6379" - redisConfigV["dbNum"] = "0" - redisConfigV["password"] = "" - - redisConfig, _ := json.Marshal(redisConfigV) - - chartCache := NewChartCache(&ChartCacheConfig{ - DriverType: cacheDriverRedis, - Config: string(redisConfig), - }) - if chartCache == nil { - t.Fatalf("cache instance should not be nil") - } - - if !chartCache.IsEnabled() { - t.Fatal("chart cache should be enabled") - } - - if chartCache.driverType != cacheDriverRedis { - t.Fatalf("expect driver type '%s' but got '%s'", cacheDriverRedis, chartCache.driverType) - } - - chartCache.PutChart(mockChart) - theCachedChart := chartCache.GetChart(mockChart.Metadata.Digest) - if theCachedChart == nil || theCachedChart.Metadata.Name != mockChart.Metadata.Name { - t.Fatal("In memory cache does work") - } -} diff --git a/src/chartserver/chart_operator.go b/src/chartserver/chart_operator.go deleted file mode 100644 index 6ae7a05484f1..000000000000 --- a/src/chartserver/chart_operator.go +++ /dev/null @@ -1,262 +0,0 @@ -package chartserver - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "sort" - "strings" - "time" - - "github.com/Masterminds/semver" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - helm_repo "helm.sh/helm/v3/pkg/repo" - - hlog "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/pkg/label/model" -) - -const ( - readmeFileName = "README.md" - valuesFileName = "values.yaml" -) - -// ChartVersion extends the helm ChartVersion with additional labels -type ChartVersion struct { - helm_repo.ChartVersion - Labels []*model.Label `json:"labels"` -} - -// ChartVersions is an array of extended ChartVersion -type ChartVersions []*ChartVersion - -// ChartVersionDetails keeps the detailed data info of the chart version -type ChartVersionDetails struct { - Metadata *helm_repo.ChartVersion `json:"metadata"` - Dependencies []*chart.Dependency `json:"dependencies"` - Values map[string]interface{} `json:"values"` - Files map[string]string `json:"files"` - Security *SecurityReport `json:"security"` - Labels []*model.Label `json:"labels"` -} - -// SecurityReport keeps the info related with security -// e.g.: digital signature, vulnerability scanning etc. -type SecurityReport struct { - Signature *DigitalSignature `json:"signature"` -} - -// DigitalSignature used to indicate if the chart has been signed -type DigitalSignature struct { - Signed bool `json:"signed"` - Provenance string `json:"prov_file"` -} - -// ChartInfo keeps the information of the chart -type ChartInfo struct { - Name string `json:"name"` - TotalVersions uint32 `json:"total_versions"` - LatestVersion string `json:"latest_version"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - Icon string `json:"icon"` - Home string `json:"home"` - Deprecated bool `json:"deprecated"` -} - -// ChartOperator is designed to process the contents of -// the specified chart version to get more details -type ChartOperator struct{} - -// GetChartDetails parse the details from the provided content bytes -func (cho *ChartOperator) GetChartDetails(content []byte) (*ChartVersionDetails, error) { - chartData, err := cho.GetChartData(content) - if err != nil { - return nil, err - } - dependencies := chartData.Metadata.Dependencies - var values map[string]interface{} - var buf bytes.Buffer - files := make(map[string]string) - // Parse values - if chartData.Values != nil { - // values = parseRawValues([]byte(chartData.Values.GetRaw())) - if len(chartData.Values) > 0 { - c := chartutil.Values(chartData.Values) - ValYaml, err := c.YAML() - if err != nil { - return nil, err - } - err = c.Encode(&buf) - if err != nil { - return nil, err - } - values = parseRawValues(buf.Bytes()) - // Append values.yaml file - files[valuesFileName] = ValYaml - } - } - - // Append other files like 'README.md' - for _, v := range chartData.Files { - if v.Name == readmeFileName { - files[readmeFileName] = string(v.Data) - break - } - } - - theChart := &ChartVersionDetails{ - Dependencies: dependencies, - Values: values, - Files: files, - } - - return theChart, nil -} - -// GetChartList returns a reorganized chart list -func (cho *ChartOperator) GetChartList(content []byte) ([]*ChartInfo, error) { - if len(content) == 0 { - return nil, errors.New("zero content") - } - - allCharts := make(map[string]helm_repo.ChartVersions) - if err := json.Unmarshal(content, &allCharts); err != nil { - return nil, err - } - - chartList := make([]*ChartInfo, 0) - for key, chartVersions := range allCharts { - lVersion, oVersion := getTheTwoCharts(chartVersions) - if lVersion != nil && oVersion != nil { - chartInfo := &ChartInfo{ - Name: key, - TotalVersions: uint32(len(chartVersions)), - } - chartInfo.Created = oVersion.Created - chartInfo.Home = lVersion.Home - chartInfo.Icon = lVersion.Icon - chartInfo.Deprecated = lVersion.Deprecated - chartInfo.LatestVersion = lVersion.Version - chartList = append(chartList, chartInfo) - } - } - - // Sort the chart list by the updated time which is the create time - // of the latest version of the chart. - sort.Slice(chartList, func(i, j int) bool { - if chartList[i].Updated.Equal(chartList[j].Updated) { - return strings.Compare(chartList[i].Name, chartList[j].Name) < 0 - } - - return chartList[i].Updated.After(chartList[j].Updated) - }) - - return chartList, nil -} - -// GetChartData returns raw data of chart -func (cho *ChartOperator) GetChartData(content []byte) (*chart.Chart, error) { - if len(content) == 0 { - return nil, errors.New("zero content") - } - - reader := bytes.NewReader(content) - chartData, err := loader.LoadArchive(reader) - if err != nil { - return nil, err - } - - return chartData, nil -} - -// GetChartVersions returns the chart versions -func (cho *ChartOperator) GetChartVersions(content []byte) (ChartVersions, error) { - if len(content) == 0 { - return nil, errors.New("zero content") - } - - chartVersions := make(ChartVersions, 0) - if err := json.Unmarshal(content, &chartVersions); err != nil { - return nil, err - } - - return chartVersions, nil -} - -// Get the latest and oldest chart versions -func getTheTwoCharts(chartVersions helm_repo.ChartVersions) (latestChart *helm_repo.ChartVersion, oldestChart *helm_repo.ChartVersion) { - if len(chartVersions) == 1 { - return chartVersions[0], chartVersions[0] - } - - for _, chartVersion := range chartVersions { - currentV, err := semver.NewVersion(chartVersion.Version) - if err != nil { - // ignore it, just logged - hlog.Warningf("Malformed semversion %s for the chart %s", chartVersion.Version, chartVersion.Name) - continue - } - - // Find latest chart - if latestChart == nil { - latestChart = chartVersion - } else { - lVersion, err := semver.NewVersion(latestChart.Version) - if err != nil { - // ignore it, just logged - hlog.Warningf("Malformed semversion %s for the chart %s", latestChart.Version, chartVersion.Name) - continue - } - if lVersion.LessThan(currentV) { - latestChart = chartVersion - } - } - - if oldestChart == nil { - oldestChart = chartVersion - } else { - if oldestChart.Created.After(chartVersion.Created) { - oldestChart = chartVersion - } - } - } - - return latestChart, oldestChart -} - -// Parse the raw values to value map -func parseRawValues(rawValue []byte) map[string]interface{} { - valueMap := make(map[string]interface{}) - - if len(rawValue) == 0 { - return valueMap - } - values, err := chartutil.ReadValues(rawValue) - if err != nil || len(values) == 0 { - return valueMap - } - - readValue(values, "", valueMap) - - return valueMap -} - -// Recursively read value -func readValue(values map[string]interface{}, keyPrefix string, valueMap map[string]interface{}) { - for key, value := range values { - longKey := key - if keyPrefix != "" { - longKey = fmt.Sprintf("%s.%s", keyPrefix, key) - } - - if subValues, ok := value.(map[string]interface{}); ok { - readValue(subValues, longKey, valueMap) - } else { - valueMap[longKey] = value - } - } -} diff --git a/src/chartserver/chart_operator_test.go b/src/chartserver/chart_operator_test.go deleted file mode 100644 index 639db485b078..000000000000 --- a/src/chartserver/chart_operator_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package chartserver - -import ( - "testing" - - htesting "github.com/goharbor/harbor/src/testing" -) - -func TestGetChartDetails(t *testing.T) { - chartOpr := ChartOperator{} - chartDetails, err := chartOpr.GetChartDetails(htesting.HelmChartContent) - if err != nil { - t.Fatal(err) - } - - if len(chartDetails.Dependencies) == 0 { - t.Fatal("At least 1 dependency exitsing, but we got 0 now") - } - - if len(chartDetails.Values) == 0 { - t.Fatal("At least 1 value existing, but we got 0 now") - } - - if chartDetails.Values["adminserver.adminPassword"] != "Harbor12345" { - t.Fatalf("The value of 'adminserver.adminPassword' should be 'Harbor12345' but we got '%s' now", chartDetails.Values["adminserver.adminPassword"]) - } -} - -func TestGetChartList(t *testing.T) { - chartOpr := ChartOperator{} - infos, err := chartOpr.GetChartList(htesting.ChartListContent) - if err != nil { - t.Fatal(err) - } - - if len(infos) != 2 { - t.Fatalf("Length of chart list should be 2, but we got %d now", len(infos)) - } - - firstInSortedList := infos[0] - if firstInSortedList.Name != "harbor" { - t.Fatalf("Expect the fist item of the sorted list to be 'harbor' but got '%s'", firstInSortedList.Name) - } - - if firstInSortedList.LatestVersion != "0.2.0" { - t.Fatalf("Expect latest version '0.2.0' but got '%s'", firstInSortedList.LatestVersion) - } -} diff --git a/src/chartserver/client.go b/src/chartserver/client.go deleted file mode 100644 index ece73e717d01..000000000000 --- a/src/chartserver/client.go +++ /dev/null @@ -1,148 +0,0 @@ -package chartserver - -import ( - "fmt" - "io" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - - commonhttp "github.com/goharbor/harbor/src/common/http" - "github.com/goharbor/harbor/src/lib/errors" - tracelib "github.com/goharbor/harbor/src/lib/trace" -) - -const ( - clientTimeout = 30 * time.Second - maxIdleConnections = 10 - idleConnectionTimeout = 30 * time.Second -) - -var ( - once sync.Once - chartTransport http.RoundTripper -) - -// ChartClient is a http client to get the content from the external http server -type ChartClient struct { - // HTTP client - httpClient *http.Client - - // Auth info - credential *Credential -} - -// NewChartClient is constructor of ChartClient -// credential can be nil -func NewChartClient(credential *Credential) *ChartClient { // Create http client with customized timeouts - once.Do(func() { - chartTransport = commonhttp.NewTransport( - commonhttp.WithMaxIdleConns(maxIdleConnections), - commonhttp.WithIdleconnectionTimeout(idleConnectionTimeout), - ) - if tracelib.Enabled() { - chartTransport = otelhttp.NewTransport(chartTransport) - } - }) - - client := &http.Client{ - Timeout: clientTimeout, - Transport: chartTransport, - } - - return &ChartClient{ - httpClient: client, - credential: credential, - } -} - -// GetContent get the bytes from the specified url -func (cc *ChartClient) GetContent(addr string) ([]byte, error) { - response, err := cc.sendRequest(addr, http.MethodGet, nil) - if err != nil { - err = errors.Wrap(err, "get content failed") - return nil, err - } - - content, err := io.ReadAll(response.Body) - if err != nil { - err = errors.Wrap(err, "Read response body error") - return nil, err - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - text, err := extractError(content) - if err != nil { - err = errors.Wrap(err, "Extract content error failed") - return nil, err - } - return nil, &commonhttp.Error{ - Code: response.StatusCode, - Message: text, - } - } - return content, nil -} - -// DeleteContent sends deleting request to the addr to delete content -func (cc *ChartClient) DeleteContent(addr string) error { - response, err := cc.sendRequest(addr, http.MethodDelete, nil) - if err != nil { - return err - } - - content, err := io.ReadAll(response.Body) - if err != nil { - return err - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - text, err := extractError(content) - if err != nil { - return err - } - return &commonhttp.Error{ - Code: response.StatusCode, - Message: text, - } - } - - return nil -} - -// sendRequest sends requests to the addr with the specified spec -func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader) (*http.Response, error) { - if len(strings.TrimSpace(addr)) == 0 { - return nil, errors.New("empty url is not allowed") - } - - fullURI, err := url.Parse(addr) - if err != nil { - err = errors.Wrap(err, "Invalid url") - return nil, err - } - - request, err := http.NewRequest(method, addr, body) - if err != nil { - return nil, err - } - - // Set basic auth - if cc.credential != nil { - request.SetBasicAuth(cc.credential.Username, cc.credential.Password) - } - - response, err := cc.httpClient.Do(request) - if err != nil { - err = errors.Wrap(err, fmt.Sprintf("send request %s %s failed", method, fullURI.Path)) - return nil, err - } - - return response, nil -} diff --git a/src/chartserver/controller.go b/src/chartserver/controller.go deleted file mode 100644 index 18ebd69c0408..000000000000 --- a/src/chartserver/controller.go +++ /dev/null @@ -1,84 +0,0 @@ -package chartserver - -import ( - "errors" - "fmt" - "net/http" - "net/url" - "os" - - hlog "github.com/goharbor/harbor/src/lib/log" -) - -const ( - userName = "chart_controller" - passwordKey = "CORE_SECRET" -) - -// Credential keeps the username and password for the basic auth -type Credential struct { - Username string - Password string -} - -// Controller is used to handle flows of related requests based on the corresponding handlers -// A reverse proxy will be created and managed to proxy the related traffics between API and -// backend chart server -type Controller struct { - // Proxy used to to transfer the traffic of requests - // It's mainly used to talk to the backend chart server - trafficProxy *ProxyEngine - - // Parse and process the chart version to provide required info data - chartOperator *ChartOperator - - // HTTP client used to call the realted APIs of the backend chart repositories - apiClient *ChartClient - - // The access endpoint of the backend chart repository server - backendServerAddress *url.URL - - // Cache the chart data - chartCache *ChartCache -} - -// NewController is constructor of the chartserver.Controller -func NewController(backendServer *url.URL, middlewares ...func(http.Handler) http.Handler) (*Controller, error) { - if backendServer == nil { - return nil, errors.New("failed to create chartserver.Controller: backend sever address is required") - } - - // Try to create credential - cred := &Credential{ - Username: userName, - Password: os.Getenv(passwordKey), - } - - // Creat cache - cacheCfg, err := getCacheConfig() - if err != nil { - // just log the error - // will not break the whole flow if failed to create cache - hlog.Errorf("failed to get cache configuration with error: %s", err) - } - cache := NewChartCache(cacheCfg) - if !cache.IsEnabled() { - hlog.Info("No cache is enabled for chart caching") - } - - return &Controller{ - backendServerAddress: backendServer, - // Use customized reverse proxy - trafficProxy: NewProxyEngine(backendServer, cred, middlewares...), - // Initialize chart operator for use - chartOperator: &ChartOperator{}, - // Create http client with customized timeouts - apiClient: NewChartClient(cred), - chartCache: cache, - }, nil -} - -// APIPrefix returns the API prefix path of calling backend chart service. -func (c *Controller) APIPrefix(namespace string) string { - return fmt.Sprintf("%s/api/%s/charts", c.backendServerAddress.String(), namespace) -} diff --git a/src/chartserver/controller_test.go b/src/chartserver/controller_test.go deleted file mode 100644 index 31837db59c25..000000000000 --- a/src/chartserver/controller_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package chartserver - -import ( - "fmt" - "testing" -) - -// Test controller -func TestController(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - prefix := c.APIPrefix("fake") - expected := fmt.Sprintf("%s/api/%s/charts", s.URL, "fake") - if prefix != expected { - t.Fatalf("expect '%s' but got '%s'", expected, prefix) - } -} diff --git a/src/chartserver/handler_interface.go b/src/chartserver/handler_interface.go deleted file mode 100644 index 93d9c116cd8c..000000000000 --- a/src/chartserver/handler_interface.go +++ /dev/null @@ -1,97 +0,0 @@ -package chartserver - -import ( - "net/http" - - "helm.sh/helm/v3/cmd/helm/search" - helm_repo "helm.sh/helm/v3/pkg/repo" -) - -// ServiceHandler defines the related methods to handle kinds of chart service requests. -type ServiceHandler interface { - // ListCharts lists all the charts under the specified namespace. - // - // namespace string: the chart namespace. - // - // If succeed, a chart info list with nil error will be returned; - // otherwise, a non-nil error will be got. - ListCharts(namespace string) ([]*ChartInfo, error) - - // Get all the chart versions of the specified chart under the namespace. - // - // namespace string: the chart namespace. - // chartName string: the name of the chart, e.g: "harbor" - // - // If succeed, a chart version list with nil error will be returned; - // otherwise, a non-nil error will be got. - GetChart(namespace, chartName string) (helm_repo.ChartVersions, error) - - // Get the detailed info of the specified chart version under the namespace. - // The detailed info includes chart summary, dependencies, values and signature status etc. - // - // namespace string: the chart namespace. - // chartName string: the name of the chart, e.g: "harbor" - // version string: the SemVer version of the chart, e.g: "0.2.0" - // - // If succeed, chart version details with nil error will be returned; - // otherwise, a non-nil error will be got. - GetChartVersionDetails(namespace, chartName, version string) (*ChartVersionDetails, error) - - // SearchChart search charts in the specified namespaces with the keyword q. - // RegExp mode is enabled as default. - // For each chart, only the latest version will shown in the result list if matched to avoid duplicated entries. - // Keep consistent with `helm search` command. - // - // q string : the searching keyword - // namespaces []string : the search namespace scope - // - // If succeed, a search result list with nil error will be returned; - // otherwise, a non-nil error will be got. - SearchChart(q string, namespaces []string) ([]*search.Result, error) - - // GetIndexFile will read the index.yaml under all namespaces and merge them as a single one - // Please be aware that, to support this function, the backend chart repository server should - // enable multi-tenancies - // - // namespaces []string : all the namespaces with accessing permissions - // - // If succeed, a unified merged index file with nil error will be returned; - // otherwise, a non-nil error will be got. - GetIndexFile(namespaces []string) (*helm_repo.IndexFile, error) - - // Get the chart summary of the specified chart version. - // - // namespace string: the chart namespace. - // chartName string: the name of the chart, e.g: "harbor" - // version string: the SemVer version of the chart, e.g: "0.2.0" - // - // If succeed, chart version summary with nil error will be returned; - // otherwise, a non-nil error will be got. - GetChartVersion(namespace, name, version string) (*helm_repo.ChartVersion, error) - - // DeleteChart deletes all the chart versions of the specified chart under the namespace. - // - // namespace string: the chart namespace. - // chartName string: the name of the chart, e.g: "harbor" - // - // If succeed, a nil error will be returned; - // otherwise, a non-nil error will be got. - DeleteChart(namespace, chartName string) error - - // GetCountOfCharts calculates and returns the total count of charts under the specified namespaces. - // - // namespaces []string : the namespaces to count charts - // - // If succeed, a unsigned integer with nil error will be returned; - // otherwise, a non-nil error will be got. - GetCountOfCharts(namespaces []string) (uint64, error) -} - -// ProxyTrafficHandler defines the handler methods to handle the proxy traffic. -type ProxyTrafficHandler interface { - // Proxy the traffic to the backended server - // - // Req *http.Request : The incoming http request - // w http.ResponseWriter : The response writer reference - ProxyTraffic(w http.ResponseWriter, req *http.Request) -} diff --git a/src/chartserver/handler_manipulation.go b/src/chartserver/handler_manipulation.go deleted file mode 100644 index c7629671f139..000000000000 --- a/src/chartserver/handler_manipulation.go +++ /dev/null @@ -1,144 +0,0 @@ -package chartserver - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "os" - "strings" - - "github.com/ghodss/yaml" - helm_repo "helm.sh/helm/v3/pkg/repo" - - commonhttp "github.com/goharbor/harbor/src/common/http" - rep_event "github.com/goharbor/harbor/src/controller/event/handler/replication/event" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/lib/orm" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -// ListCharts gets the chart list under the namespace -// See @ServiceHandler.ListCharts -func (c *Controller) ListCharts(namespace string) ([]*ChartInfo, error) { - if len(strings.TrimSpace(namespace)) == 0 { - return nil, errors.New("empty namespace when getting chart list") - } - - content, err := c.apiClient.GetContent(c.APIPrefix(namespace)) - if err != nil { - return nil, err - } - - return c.chartOperator.GetChartList(content) -} - -// GetChart returns all the chart versions under the specified chart -// See @ServiceHandler.GetChart -func (c *Controller) GetChart(namespace, chartName string) (ChartVersions, error) { - if len(namespace) == 0 { - return nil, errors.New("empty name when getting chart versions") - } - - if len(chartName) == 0 { - return nil, errors.New("no chart name specified") - } - - url := fmt.Sprintf("%s/%s", c.APIPrefix(namespace), chartName) - data, err := c.apiClient.GetContent(url) - if err != nil { - return nil, err - } - - versions := make(ChartVersions, 0) - if err := json.Unmarshal(data, &versions); err != nil { - return nil, err - } - - return versions, nil -} - -// DeleteChartVersion will delete the specified version of the chart -// See @ServiceHandler.DeleteChartVersion -func (c *Controller) DeleteChartVersion(namespace, chartName, version string) error { - if len(namespace) == 0 { - return errors.New("empty namespace when deleting chart version") - } - - if len(chartName) == 0 || len(version) == 0 { - return errors.New("invalid chart for deleting") - } - - url := fmt.Sprintf("/api/chartrepo/%s/charts/%s/%s", namespace, chartName, version) - req, _ := http.NewRequest(http.MethodDelete, url, nil) - w := httptest.NewRecorder() - - c.trafficProxy.ServeHTTP(w, req) - - if w.Code != http.StatusOK { - text, err := extractError(w.Body.Bytes()) - if err != nil { - return err - } - return &commonhttp.Error{ - Code: w.Code, - Message: text, - } - } - - // send notification to replication handler - // Todo: it used as the replacement of webhook, will be removed when webhook to be introduced. - if os.Getenv("UTTEST") != "true" { - go func() { - e := &rep_event.Event{ - Type: rep_event.EventTypeChartDelete, - Resource: &model.Resource{ - Type: model.ResourceTypeChart, - Deleted: true, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: fmt.Sprintf("%s/%s", namespace, chartName), - }, - Artifacts: []*model.Artifact{ - { - Tags: []string{version}, - }, - }, - }, - }, - } - if err := rep_event.Handle(orm.Context(), e); err != nil { - log.Errorf("failed to handle event: %v", err) - } - }() - } - - return nil -} - -// GetChartVersion returns the summary of the specified chart version. -// See @ServiceHandler.GetChartVersion -func (c *Controller) GetChartVersion(namespace, name, version string) (*helm_repo.ChartVersion, error) { - if len(namespace) == 0 { - return nil, errors.New("empty namespace when getting summary of chart version") - } - - if len(name) == 0 || len(version) == 0 { - return nil, errors.New("invalid chart when getting summary") - } - - url := fmt.Sprintf("%s/%s/%s", c.APIPrefix(namespace), name, version) - - content, err := c.apiClient.GetContent(url) - if err != nil { - return nil, err - } - - chartVersion := &helm_repo.ChartVersion{} - if err := yaml.Unmarshal(content, chartVersion); err != nil { - return nil, err - } - - return chartVersion, nil -} diff --git a/src/chartserver/handler_manipulation_test.go b/src/chartserver/handler_manipulation_test.go deleted file mode 100644 index e55bc524f393..000000000000 --- a/src/chartserver/handler_manipulation_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package chartserver - -import ( - "testing" -) - -// Test get /api/:repo/charts/harbor -func TestGetChart(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - versions, err := c.GetChart("repo1", "harbor") - if err != nil { - t.Fatal(err) - } - - if len(versions) != 2 { - t.Fatalf("expect 2 chart versions of harbor but got %d", len(versions)) - } -} - -// Test delete /api/:repo/charts/harbor/0.2.0 -func TestDeleteChartVersion(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - if err := c.DeleteChartVersion("repo1", "harbor", "0.2.0"); err != nil { - t.Fatal(err) - } -} - -// Test get /api/:repo/charts -func TestRetrieveChartList(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - chartList, err := c.ListCharts("repo1") - if err != nil { - t.Fatal(err) - } - - if len(chartList) != 2 { - t.Fatalf("Expect to get 2 charts in the list but got %d", len(chartList)) - } - - foundItem := false - for _, chartInfo := range chartList { - if chartInfo.Name == "hello-helm" && chartInfo.TotalVersions == 2 { - foundItem = true - break - } - } - - if !foundItem { - t.Fatalf("Expect chart 'hello-helm' with 2 versions but got nothing") - } -} - -// Test the GetChartVersion in utility handler -func TestGetChartVersionSummary(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - chartV, err := c.GetChartVersion("repo1", "harbor", "0.2.0") - if err != nil { - t.Fatal(err) - } - - if chartV.Name != "harbor" { - t.Fatalf("expect chart name 'harbor' but got '%s'", chartV.Name) - } - - if chartV.Version != "0.2.0" { - t.Fatalf("expect chart version '0.2.0' but got '%s'", chartV.Version) - } -} diff --git a/src/chartserver/handler_proxy_traffic.go b/src/chartserver/handler_proxy_traffic.go deleted file mode 100644 index 72b9147fc9b3..000000000000 --- a/src/chartserver/handler_proxy_traffic.go +++ /dev/null @@ -1,12 +0,0 @@ -package chartserver - -import ( - "net/http" -) - -// ProxyTraffic implements the interface method. -func (c *Controller) ProxyTraffic(w http.ResponseWriter, req *http.Request) { - if c.trafficProxy != nil { - c.trafficProxy.ServeHTTP(w, req) - } -} diff --git a/src/chartserver/handler_proxy_traffic_test.go b/src/chartserver/handler_proxy_traffic_test.go deleted file mode 100644 index 3d51894fe31a..000000000000 --- a/src/chartserver/handler_proxy_traffic_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package chartserver - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/ghodss/yaml" - helm_repo "helm.sh/helm/v3/pkg/repo" - - htesting "github.com/goharbor/harbor/src/testing" -) - -// The frontend server -var frontServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - mockController.ProxyTraffic(w, r) -})) - -var mockServer *httptest.Server -var mockController *Controller - -// Prepare case -func TestStartMockServers(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - mockController = c - mockServer = s - - frontServer.Start() -} - -// Test /health -func TestGetHealthOfBaseHandler(t *testing.T) { - content, err := httpClient.GetContent(fmt.Sprintf("%s/api/chartrepo/health", frontServer.URL)) - if err != nil { - t.Fatal(err) - } - - status := make(map[string]interface{}) - if err := json.Unmarshal(content, &status); err != nil { - t.Fatalf("Unmarshal error: %s, %s", err, content) - } - healthy, ok := status["health"].(bool) - if !ok || !healthy { - t.Fatalf("Expect healthy of server to be 'true' but got %v", status["health"]) - } -} - -// Get /repo1/index.yaml -func TestGetIndexYamlByRepo(t *testing.T) { - indexFile, err := getIndexYaml("/chartrepo/repo1/index.yaml") - if err != nil { - t.Fatal(err) - } - - if len(indexFile.Entries) != 3 { - t.Fatalf("Expect index file with 3 entries, but got %d", len(indexFile.Entries)) - } -} - -// Test download /:repo/charts/chart.tar -// Use this case to test the proxy function -func TestDownloadChart(t *testing.T) { - content, err := httpClient.GetContent(fmt.Sprintf("%s/chartrepo/repo1/charts/harbor-0.2.0.tgz", frontServer.URL)) - if err != nil { - t.Fatal(err) - } - - gotSize := len(content) - expectSize := len(htesting.HelmChartContent) - - if gotSize != expectSize { - t.Fatalf("Expect %d bytes data but got %d bytes", expectSize, gotSize) - } -} - -// Get /api/repo1/charts/harbor -// 401 will be rewritten to 500 with specified error -func TestResponseRewrite(t *testing.T) { - response, err := http.Get(fmt.Sprintf("%s/chartrepo/repo3/charts/harbor-0.8.1.tgz", frontServer.URL)) - if err != nil { - t.Fatal(err) - } - - if response.StatusCode != http.StatusInternalServerError { - t.Fatalf("Expect status code 500 but got %d", response.StatusCode) - } - - bytes, err := io.ReadAll(response.Body) - if err != nil { - t.Fatalf("Read bytes from http response failed with error: %s", err) - } - defer response.Body.Close() - - errObj := make(map[string]interface{}) - if err = json.Unmarshal(bytes, &errObj); err != nil { - t.Fatalf("Unmarshal error: %s", err) - } - - if msg, ok := errObj["error"]; !ok { - t.Fatal("Expect an error message from server but got nothing") - } else { - if !strings.Contains(msg.(string), "operation request from unauthorized source is rejected") { - t.Fatal("Missing the required error message") - } - } -} - -// Clear env -func TestStopMockServers(t *testing.T) { - frontServer.Close() - mockServer.Close() -} - -// Utility method for getting index yaml file -func getIndexYaml(path string) (*helm_repo.IndexFile, error) { - content, err := httpClient.GetContent(fmt.Sprintf("%s%s", frontServer.URL, path)) - if err != nil { - return nil, err - } - - indexFile := &helm_repo.IndexFile{} - if err := yaml.Unmarshal(content, indexFile); err != nil { - return nil, fmt.Errorf("unmarshal error: %s", err) - } - - if indexFile == nil { - return nil, fmt.Errorf("got nil index yaml file") - } - - return indexFile, nil -} diff --git a/src/chartserver/handler_repo.go b/src/chartserver/handler_repo.go deleted file mode 100644 index a333d9e746b6..000000000000 --- a/src/chartserver/handler_repo.go +++ /dev/null @@ -1,219 +0,0 @@ -package chartserver - -import ( - "fmt" - "path" - "strings" - "sync" - "time" - - "github.com/ghodss/yaml" - helm_repo "helm.sh/helm/v3/pkg/repo" - - hlog "github.com/goharbor/harbor/src/lib/log" -) - -const ( - maxWorkers = 10 - - // Keep consistent with 'helm search' command - searchMaxScore = 25 -) - -// Result returned by worker -type processedResult struct { - namespace string - indexFileOfRepo *helm_repo.IndexFile -} - -// GetIndexFile will read the index.yaml under all namespaces and merge them as a single one -// Please be aware that, to support this function, the backend chart repository server should -// enable multi-tenancies -// -// See @ServiceHandler.GetIndexFile -func (c *Controller) GetIndexFile(namespaces []string) (*helm_repo.IndexFile, error) { - if len(namespaces) == 0 { - return emptyIndexFile(), nil - } - - return c.getIndexYaml(namespaces) -} - -// getIndexYaml will get the index yaml files for all the namespaces and merge them -// as one unified index yaml file. -func (c *Controller) getIndexYaml(namespaces []string) (*helm_repo.IndexFile, error) { - // The final merged index file - mergedIndexFile := &helm_repo.IndexFile{ - APIVersion: "v1", - Entries: make(map[string]helm_repo.ChartVersions), - Generated: time.Now().Round(time.Second), - PublicKeys: []string{}, - } - - // Sync the output results from the retriever - resultChan := make(chan *processedResult, 1) - // Receive error - errorChan := make(chan error, 1) - // Signal chan for merging work - mergeDone := make(chan struct{}, 1) - // Total projects/namespaces - total := len(namespaces) - // Initialize - initialItemCount := maxWorkers - if total < maxWorkers { - initialItemCount = total - } - // Retrieve index.yaml for repositories - workerPool := make(chan struct{}, initialItemCount) - - // Add initial tokens to the worker - for i := 0; i < initialItemCount; i++ { - workerPool <- struct{}{} - } - // Track all the background threads - waitGroup := new(sync.WaitGroup) - - // Start the index files merging thread - go func() { - defer func() { - mergeDone <- struct{}{} - }() - - for res := range resultChan { - c.mergeIndexFile(res.namespace, mergedIndexFile, res.indexFileOfRepo) - } - }() - - // Retrieve the index files for the repositories - // and blocking here - var err error -LOOP: - for _, ns := range namespaces { - // Check if error has occurred in some goroutines - select { - case err = <-errorChan: - break LOOP - default: - // do nothing - } - - // Apply one token before processing - <-workerPool - - waitGroup.Add(1) - go func(ns string) { - defer func() { - waitGroup.Done() // done - // Return the worker back to the worker - workerPool <- struct{}{} - }() - - indexFile, err := c.getIndexYamlWithNS(ns) - if err != nil { - if len(errorChan) == 0 { - // Only need one error as failure signal - errorChan <- err - } - return - } - - // Output - resultChan <- &processedResult{ - namespace: ns, - indexFileOfRepo: indexFile, - } - }(ns) - } - - // Hold util all the retrieving work are done - waitGroup.Wait() - - // close merge channel - close(resultChan) - - // Wait until merging thread quit - <-mergeDone - - // All the threads are done - // Make sure error in the chan is read - if err == nil && len(errorChan) > 0 { - err = <-errorChan - } - - // Met an error - if err != nil { - return nil, err - } - - // Remove duplicated keys in public key list - hash := make(map[string]string) - for _, key := range mergedIndexFile.PublicKeys { - hash[key] = key - } - mergedIndexFile.PublicKeys = []string{} - for k := range hash { - mergedIndexFile.PublicKeys = append(mergedIndexFile.PublicKeys, k) - } - - return mergedIndexFile, nil -} - -// Get the index yaml file under the specified namespace from the backend server -func (c *Controller) getIndexYamlWithNS(namespace string) (*helm_repo.IndexFile, error) { - // Join url path - url := path.Join(namespace, "index.yaml") - url = fmt.Sprintf("%s/%s", c.backendServerAddress.String(), url) - hlog.Debugf("Getting index.yaml from '%s'", url) - - content, err := c.apiClient.GetContent(url) - if err != nil { - return nil, err - } - - // Traverse to index file object for merging - indexFile := helm_repo.NewIndexFile() - if err := yaml.Unmarshal(content, indexFile); err != nil { - return nil, err - } - - return indexFile, nil -} - -// Merge the content of mergingIndexFile to the baseIndex -// The chart url should be without --chart-url prefix -func (c *Controller) mergeIndexFile(namespace string, - baseIndex *helm_repo.IndexFile, - mergingIndexFile *helm_repo.IndexFile) { - // Append entries - for chartName, chartVersions := range mergingIndexFile.Entries { - nameWithNS := fmt.Sprintf("%s/%s", namespace, chartName) - for _, version := range chartVersions { - version.Name = nameWithNS - // Currently there is only one url - for index, url := range version.URLs { - if !strings.HasPrefix(url, "http") { - version.URLs[index] = path.Join(namespace, url) - } - } - } - - // Appended - baseIndex.Entries[nameWithNS] = chartVersions - } - - // Update generated time - if mergingIndexFile.Generated.After(baseIndex.Generated) { - baseIndex.Generated = mergingIndexFile.Generated - } - - // Merge public keys - baseIndex.PublicKeys = append(baseIndex.PublicKeys, mergingIndexFile.PublicKeys...) -} - -// Generate empty index file -func emptyIndexFile() *helm_repo.IndexFile { - emptyIndexFile := &helm_repo.IndexFile{} - emptyIndexFile.Generated = time.Now().Round(time.Second) - - return emptyIndexFile -} diff --git a/src/chartserver/handler_repo_test.go b/src/chartserver/handler_repo_test.go deleted file mode 100644 index 69f83f230b43..000000000000 --- a/src/chartserver/handler_repo_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package chartserver - -import "testing" - -// Test get /index.yaml -func TestGetIndexFile(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - namespaces := []string{"repo1", "repo2"} - indexFile, err := c.GetIndexFile(namespaces) - if err != nil { - t.Fatal(err) - } - - if len(indexFile.Entries) != 5 { - t.Fatalf("Expect index file with 5 entries, but got %d", len(indexFile.Entries)) - } - - _, ok := indexFile.Entries["repo1/harbor"] - if !ok { - t.Fatal("Expect chart entry 'repo1/harbor' but got nothing") - } - - _, ok = indexFile.Entries["repo2/harbor"] - if !ok { - t.Fatal("Expect chart entry 'repo2/harbor' but got nothing") - } -} diff --git a/src/chartserver/handler_utility.go b/src/chartserver/handler_utility.go deleted file mode 100644 index bd7c70825276..000000000000 --- a/src/chartserver/handler_utility.go +++ /dev/null @@ -1,233 +0,0 @@ -package chartserver - -import ( - "fmt" - "path" - "strings" - "sync" - - "helm.sh/helm/v3/cmd/helm/search" - - "github.com/goharbor/harbor/src/lib/config" - "github.com/goharbor/harbor/src/lib/errors" - hlog "github.com/goharbor/harbor/src/lib/log" -) - -const ( - maxDeletionThreads = 10 -) - -// GetCountOfCharts calculates and returns the total count of charts under the specified namespaces. -// See @ServiceHandler.GetCountOfCharts -func (c *Controller) GetCountOfCharts(namespaces []string) (uint64, error) { - if len(namespaces) == 0 { - return 0, nil // Directly return 0 instead of non-nil error - } - - indexFile, err := c.getIndexYaml(namespaces) - if err != nil { - return 0, err - } - - return (uint64)(len(indexFile.Entries)), nil -} - -// DeleteChart deletes all the chart versions of the specified chart under the namespace. -// See @ServiceHandler.DeleteChart -func (c *Controller) DeleteChart(namespace, chartName string) error { - if len(strings.TrimSpace(namespace)) == 0 { - return errors.New("empty namespace when deleting chart") - } - - if len(strings.TrimSpace(chartName)) == 0 { - return errors.New("empty chart name when deleting chart") - } - - url := fmt.Sprintf("%s/%s", c.APIPrefix(namespace), chartName) - - content, err := c.apiClient.GetContent(url) - if err != nil { - return err - } - - allVersions, err := c.chartOperator.GetChartVersions(content) - if err != nil { - return err - } - - // Let's delete the versions in parallel - // The number of goroutine is controlled by the const maxDeletionThreads - qSize := len(allVersions) - if qSize > maxDeletionThreads { - qSize = maxDeletionThreads - } - tokenQueue := make(chan struct{}, qSize) - errChan := make(chan error, 1) - waitGroup := new(sync.WaitGroup) - waitGroup.Add(len(allVersions)) - - // Append initial tokens - for i := 0; i < qSize; i++ { - tokenQueue <- struct{}{} - } - - // Collect errors - errs := make([]error, 0) - errWrapper := make(chan error, 1) - go func() { - defer func() { - // pass to the out func - if len(errs) > 0 { - errWrapper <- fmt.Errorf("%v", errs) - } - close(errWrapper) - }() - - for deletionErr := range errChan { - errs = append(errs, deletionErr) - } - }() - - // Schedule deletion tasks - for _, deletingVersion := range allVersions { - // Apply for token first - // If no available token, pending here - <-tokenQueue - - // Got one token - go func(deletingVersion *ChartVersion) { - defer func() { - // return the token back - tokenQueue <- struct{}{} - - // done - waitGroup.Done() - }() - - if err := c.DeleteChartVersion(namespace, chartName, deletingVersion.Version); err != nil { - errChan <- err - } - }(deletingVersion) - } - - // Wait all goroutines are done - waitGroup.Wait() - // Safe to quit error collection goroutine - close(errChan) - - err = <-errWrapper - - return err -} - -// GetChartVersionDetails get the specified version for one chart -// This handler should return the details of the chart version, -// maybe including metadata,dependencies and values etc. -// See @ServiceHandler.GetChartVersionDetails -func (c *Controller) GetChartVersionDetails(namespace, chartName, version string) (*ChartVersionDetails, error) { - chartV, err := c.GetChartVersion(namespace, chartName, version) - if err != nil { - return nil, err - } - - // Query cache - chartDetails := c.chartCache.GetChart(chartV.Digest) - if chartDetails == nil { - // NOT hit!! - content, err := c.getChartVersionContent(namespace, chartV.URLs[0]) - if err != nil { - return nil, err - } - - // Process bytes and get more details of chart version - chartDetails, err = c.chartOperator.GetChartDetails(content) - if err != nil { - return nil, err - } - chartDetails.Metadata = chartV - - // Put it into the cache for next access - c.chartCache.PutChart(chartDetails) - } else { - // Just logged - hlog.Debugf("Get detailed data from cache for chart: %s:%s (%s)", - chartDetails.Metadata.Name, - chartDetails.Metadata.Version, - chartDetails.Metadata.Digest) - } - // The change of prov file will not cause any influence to the digest of chart, - // and then the digital signature status should be not cached - // - // Generate the security report - // prov file share same endpoint with the chart version - // Just add .prov suffix to the chart version to form the path of prov file - // Anyway, there will be a report about the digital signature status - chartDetails.Security = &SecurityReport{ - Signature: &DigitalSignature{ - Signed: false, - }, - } - // Try to get the prov file to confirm if it is exitsing - provFilePath := fmt.Sprintf("%s.prov", chartV.URLs[0]) - provBytes, err := c.getChartVersionContent(namespace, provFilePath) - if err == nil && len(provBytes) > 0 { - chartDetails.Security.Signature.Signed = true - chartDetails.Security.Signature.Provenance = provFilePath - } else { - // Just log it - hlog.Debugf("Failed to get prov file for chart %s with error: %s, got %d bytes", chartV.Name, err.Error(), len(provBytes)) - } - - return chartDetails, nil -} - -// SearchChart search charts in the specified namespaces with the keyword q. -// RegExp mode is enabled as default. -// For each chart, only the latest version will shown in the result list if matched to avoid duplicated entries. -// Keep consistent with `helm search` command. -func (c *Controller) SearchChart(q string, namespaces []string) ([]*search.Result, error) { - if len(q) == 0 || len(namespaces) == 0 { - // Return empty list - return []*search.Result{}, nil - } - - // Get the merged index yaml file of the namespaces - ind, err := c.getIndexYaml(namespaces) - if err != nil { - return nil, err - } - - // Build the search index - index := search.NewIndex() - // As the repo name is already merged into the index yaml, we use empty repo name. - // Set 'All' to false to return only one version for each chart. - index.AddRepo("", ind, false) - - // Search - // RegExp is enabled - results, err := index.Search(q, searchMaxScore, true) - if err != nil { - return nil, err - } - - // Sort results. - search.SortScore(results) - - return results, nil -} - -// Get the content bytes of the chart version -func (c *Controller) getChartVersionContent(namespace string, subPath string) ([]byte, error) { - var url string - if strings.HasPrefix(subPath, "http") { - extEndpoint, err := config.ExtEndpoint() - if err != nil { - return nil, errors.Wrap(err, "can not get ext endpoint") - } - url = strings.TrimPrefix(subPath, fmt.Sprintf("%s/%s", extEndpoint, "chartrepo/")) - } else { - url = path.Join(namespace, subPath) - } - url = fmt.Sprintf("%s/%s", c.backendServerAddress.String(), url) - return c.apiClient.GetContent(url) -} diff --git a/src/chartserver/handler_utility_test.go b/src/chartserver/handler_utility_test.go deleted file mode 100644 index d27e17329cb9..000000000000 --- a/src/chartserver/handler_utility_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package chartserver - -import ( - "testing" -) - -// Test the function GetCountOfCharts -func TestGetCountOfCharts(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - count, err := c.GetCountOfCharts([]string{}) - if err != nil { - t.Fatalf("expect nil error but got %s", err) - } - if count != 0 { - t.Fatalf("expect 0 but got %d", count) - } - - namespaces := []string{"repo1", "repo2"} - count, err = c.GetCountOfCharts(namespaces) - if err != nil { - t.Fatalf("expect nil error but got %s", err) - } - - if count != 5 { - t.Fatalf("expect 5 but got %d", count) - } - - _, err = c.GetCountOfCharts([]string{"not-existing-ns"}) - if err == nil { - t.Fatal("expect non-nil error but got nil one") - } -} - -// Test the function DeleteChart -func TestDeleteChart(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - if err := c.DeleteChart("repo1", "harbor"); err != nil { - t.Fatal(err) - } -} - -// Test get /api/:repo/charts/:chart_name/:version -func TestGetChartVersion(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - chartVersion, err := c.GetChartVersionDetails("repo1", "harbor", "0.2.0") - if err != nil { - t.Fatal(err) - } - - if chartVersion.Metadata.Name != "harbor" { - t.Fatalf("Expect harbor chart version but got %s", chartVersion.Metadata.Name) - } - - if chartVersion.Metadata.Version != "0.2.0" { - t.Fatalf("Expect version '0.2.0' but got version %s", chartVersion.Metadata.Version) - } - - if len(chartVersion.Dependencies) != 1 { - t.Fatalf("Expect 1 dependency but got %d ones", len(chartVersion.Dependencies)) - } - - if len(chartVersion.Values) != 99 { - t.Fatalf("Expect 99 k-v values but got %d", len(chartVersion.Values)) - } -} - -// Test get /api/:repo/charts/:chart_name/:version with none-existing version -func TestGetChartVersionWithError(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - _, err = c.GetChartVersionDetails("repo1", "harbor", "1.0.0") - if err == nil { - t.Fatal("Expect an error but got nil") - } -} - -// Test the chart searching -func TestChartSearching(t *testing.T) { - s, c, err := createMockObjects() - if err != nil { - t.Fatal(err) - } - defer s.Close() - - namespaces := []string{"repo1", "repo2"} - q := "harbor" - - results, err := c.SearchChart(q, namespaces) - if err != nil { - t.Fatalf("expect nil error but got '%s'", err) - } - - if len(results) != 2 { - t.Fatalf("expect 2 results but got %d", len(results)) - } -} diff --git a/src/chartserver/redis_sentinel.go b/src/chartserver/redis_sentinel.go deleted file mode 100644 index 4e2b984b79af..000000000000 --- a/src/chartserver/redis_sentinel.go +++ /dev/null @@ -1,254 +0,0 @@ -package chartserver - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "strconv" - "strings" - "time" - - "github.com/FZambia/sentinel" - "github.com/beego/beego/v2/client/cache" - "github.com/gomodule/redigo/redis" -) - -var ( - // DefaultKey the collection name of redis for cache adapter. - DefaultKey = "beecacheRedis" -) - -// Cache is Redis cache adapter. -type Cache struct { - p *redis.Pool // redis connection pool - conninfo string - dbNum int - key string - password string - maxIdle int - masterName string -} - -// NewRedisCache create new redis cache with default collection name. -func NewRedisCache() cache.Cache { - return &Cache{key: DefaultKey} -} - -// actually do the redis cmds, args[0] must be the key name. -func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) { - if len(args) < 1 { - return nil, errors.New("missing required arguments") - } - args[0] = rc.associate(args[0]) - c := rc.p.Get() - defer c.Close() - - return c.Do(commandName, args...) -} - -// associate with config key. -func (rc *Cache) associate(originKey interface{}) string { - return fmt.Sprintf("%s:%s", rc.key, originKey) -} - -// Get cache from redis. -func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { - v, err := rc.do("GET", key) - if err != nil { - return nil, err - } - return v, err -} - -// GetMulti get cache from redis. -func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { - c := rc.p.Get() - defer c.Close() - var args []interface{} - for _, key := range keys { - args = append(args, rc.associate(key)) - } - values, err := redis.Values(c.Do("MGET", args...)) - if err != nil { - return nil, err - } - return values, nil -} - -// Put put cache to redis. -func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { - _, err := rc.do("SETEX", key, int64(timeout/time.Second), val) - return err -} - -// Delete delete cache in redis. -func (rc *Cache) Delete(ctx context.Context, key string) error { - _, err := rc.do("DEL", key) - return err -} - -// IsExist check cache's existence in redis. -func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { - v, err := redis.Bool(rc.do("EXISTS", key)) - if err != nil { - return false, err - } - return v, nil -} - -// Incr increase counter in redis. -func (rc *Cache) Incr(ctx context.Context, key string) error { - _, err := redis.Bool(rc.do("INCRBY", key, 1)) - return err -} - -// Decr decrease counter in redis. -func (rc *Cache) Decr(ctx context.Context, key string) error { - _, err := redis.Bool(rc.do("INCRBY", key, -1)) - return err -} - -// ClearAll clean all cache in redis. delete this redis collection. -func (rc *Cache) ClearAll(ctx context.Context) error { - c := rc.p.Get() - defer c.Close() - cachedKeys, err := redis.Strings(c.Do("KEYS", rc.key+":*")) - if err != nil { - return err - } - for _, str := range cachedKeys { - if _, err = c.Do("DEL", str); err != nil { - return err - } - } - return err -} - -// StartAndGC start redis cache adapter. -// config is like {"key":"collection key","conn":"connection info","dbNum":"0","masterName":"mymaster"} -// the cache item in redis are stored forever, -// so no gc operation. -func (rc *Cache) StartAndGC(config string) error { - var cf map[string]string - err := json.Unmarshal([]byte(config), &cf) - if err != nil { - return err - } - - if _, ok := cf["key"]; !ok { - cf["key"] = DefaultKey - } - if _, ok := cf["masterName"]; !ok { - return errors.New("config has no masterName") - } - if _, ok := cf["conn"]; !ok { - return errors.New("config has no conn key") - } - - // Format redis://@: - cf["conn"] = strings.Replace(cf["conn"], "redis://", "", 1) - cf["conn"] = strings.Replace(cf["conn"], "redis_sentinel://", "", 1) - if i := strings.Index(cf["conn"], "@"); i > -1 { - cf["password"] = cf["conn"][0:i] - cf["conn"] = cf["conn"][i+1:] - } - - if _, ok := cf["dbNum"]; !ok { - cf["dbNum"] = "0" - } - if _, ok := cf["password"]; !ok { - cf["password"] = "" - } - if _, ok := cf["maxIdle"]; !ok { - cf["maxIdle"] = "3" - } - rc.key = cf["key"] - rc.masterName = cf["masterName"] - rc.conninfo = cf["conn"] - rc.dbNum, _ = strconv.Atoi(cf["dbNum"]) - rc.password = cf["password"] - rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"]) - - rc.connectInit() - - c := rc.p.Get() - defer c.Close() - - return c.Err() -} - -// connect to redis. -func (rc *Cache) connectInit() { - dialFunc := func() (c redis.Conn, err error) { - c, err = redis.Dial("tcp", rc.conninfo) - if err != nil { - return nil, err - } - - if rc.password != "" { - if _, err := c.Do("AUTH", rc.password); err != nil { - c.Close() - return nil, err - } - } - - _, selecterr := c.Do("SELECT", rc.dbNum) - if selecterr != nil { - c.Close() - return nil, selecterr - } - return - } - // initialize a new pool - rc.p = &redis.Pool{ - MaxIdle: rc.maxIdle, - IdleTimeout: 180 * time.Second, - Dial: dialFunc, - } - - var sentinelOptions []redis.DialOption - - redisOptions := sentinelOptions - - if rc.password != "" { - redisOptions = append(redisOptions, redis.DialPassword(rc.password)) - } - - redisOptions = append(redisOptions, redis.DialDatabase(rc.dbNum)) - sntnl := &sentinel.Sentinel{ - Addrs: strings.Split(rc.conninfo, ","), - MasterName: rc.masterName, - Dial: func(addr string) (redis.Conn, error) { - fmt.Println("chart dial redis sentinel:", addr) - c, err := redis.Dial("tcp", addr, sentinelOptions...) - if err != nil { - return nil, err - } - return c, nil - }, - } - - rc.p = &redis.Pool{ - Dial: func() (redis.Conn, error) { - masterAddr, err := sntnl.MasterAddr() - if err != nil { - return nil, err - } - fmt.Println("chart dial redis master:", masterAddr, "db:", rc.dbNum) - return redis.Dial("tcp", masterAddr, redisOptions...) - }, - TestOnBorrow: func(c redis.Conn, t time.Time) error { - if !sentinel.TestRole(c, "master") { - return errors.New("role check failed") - } - return nil - }, - MaxIdle: rc.maxIdle, - IdleTimeout: 180 * time.Second, - } -} - -func init() { - cache.Register("redis_sentinel", NewRedisCache) -} diff --git a/src/chartserver/reverse_proxy.go b/src/chartserver/reverse_proxy.go deleted file mode 100644 index 66baaa2f200d..000000000000 --- a/src/chartserver/reverse_proxy.go +++ /dev/null @@ -1,241 +0,0 @@ -package chartserver - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/http/httputil" - "net/url" - "os" - "strconv" - "strings" - "time" - - "github.com/goharbor/harbor/src/common" - commonhttp "github.com/goharbor/harbor/src/common/http" - rep_event "github.com/goharbor/harbor/src/controller/event/handler/replication/event" - "github.com/goharbor/harbor/src/controller/event/metadata" - hlog "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/lib/orm" - n_event "github.com/goharbor/harbor/src/pkg/notifier/event" -) - -const ( - agentHarbor = "HARBOR" - contentLengthHeader = "Content-Length" - - defaultRepo = "library" - rootUploadingEndpoint = "/api/chartrepo/charts" - chartRepoHealthEndpoint = "/api/chartrepo/health" -) - -// ProxyEngine is used to proxy the related traffics -type ProxyEngine struct { - // The backend target server the traffic will be forwarded to - // Just in case we'll use it - backend *url.URL - - // Use go reverse proxy as engine - engine http.Handler -} - -// NewProxyEngine is constructor of NewProxyEngine -func NewProxyEngine(target *url.URL, cred *Credential, middlewares ...func(http.Handler) http.Handler) *ProxyEngine { - var engine http.Handler - - engine = &httputil.ReverseProxy{ - ErrorLog: log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile), - Director: func(req *http.Request) { - director(target, cred, req) - }, - ModifyResponse: modifyResponse, - Transport: commonhttp.GetHTTPTransport(), - } - - if len(middlewares) > 0 { - hlog.Info("New chart server traffic proxy with middlewares") - for i := len(middlewares) - 1; i >= 0; i-- { - engine = middlewares[i](engine) - } - } - - return &ProxyEngine{ - backend: target, - engine: engine, - } -} - -// ServeHTTP serves the incoming http requests -func (pe *ProxyEngine) ServeHTTP(w http.ResponseWriter, req *http.Request) { - pe.engine.ServeHTTP(w, req) -} - -// Overwrite the http requests -func director(target *url.URL, cred *Credential, req *http.Request) { - // Closure - targetQuery := target.RawQuery - - // Overwrite the request URL to the target path - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host - rewriteURLPath(req) - req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) - if targetQuery == "" || req.URL.RawQuery == "" { - req.URL.RawQuery = targetQuery + req.URL.RawQuery - } else { - req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery - } - if _, ok := req.Header["User-Agent"]; !ok { - req.Header.Set("User-Agent", agentHarbor) - } - - // Add authentication header if it is existing - if cred != nil { - req.SetBasicAuth(cred.Username, cred.Password) - } -} - -// Modify the http response -func modifyResponse(res *http.Response) error { - // Upload chart success, then to the notification to replication handler - if res.StatusCode == http.StatusCreated { - // 201 and has chart_upload_event context - // means this response is for uploading chart success. - chartUploadEvent := res.Request.Context().Value(common.ChartUploadCtxKey) - e, ok := chartUploadEvent.(*rep_event.Event) - if !ok { - hlog.Error("failed to convert chart upload context into replication event.") - } else { - // Todo: it used as the replacement of webhook, will be removed when webhook to be introduced. - go func() { - if err := rep_event.Handle(orm.Context(), e); err != nil { - hlog.Errorf("failed to handle event: %v", err) - } - }() - - // Trigger harbor webhook - if e != nil && e.Resource != nil && e.Resource.Metadata != nil && len(e.Resource.Metadata.Artifacts) > 0 && - len(e.Resource.ExtendedInfo) > 0 { - event := &n_event.Event{} - metaData := &metadata.ChartUploadMetaData{ - ChartMetaData: metadata.ChartMetaData{ - ProjectName: e.Resource.ExtendedInfo["projectName"].(string), - ChartName: e.Resource.ExtendedInfo["chartName"].(string), - Versions: e.Resource.Metadata.Artifacts[0].Tags, - OccurAt: time.Now(), - Operator: e.Resource.ExtendedInfo["operator"].(string), - }, - } - if err := event.Build(metaData); err == nil { - if err := event.Publish(); err != nil { - hlog.Errorf("failed to publish chart upload event: %v", err) - } - } else { - hlog.Errorf("failed to build chart upload event metadata: %v", err) - } - } - } - } - - // Process downloading chart success webhook event - if res.StatusCode == http.StatusOK { - chartDownloadEvent := res.Request.Context().Value(common.ChartDownloadCtxKey) - eventMetaData, ok := chartDownloadEvent.(*metadata.ChartDownloadMetaData) - if ok && eventMetaData != nil { - // Trigger harbor webhook - event := &n_event.Event{} - if err := event.Build(eventMetaData); err == nil { - if err := event.Publish(); err != nil { - hlog.Errorf("failed to publish chart download event: %v", err) - } - } else { - hlog.Errorf("failed to build chart download event metadata: %v", err) - } - } - } - - // Accept cases - // Success or redirect - if res.StatusCode >= http.StatusOK && res.StatusCode <= http.StatusTemporaryRedirect { - return nil - } - - // Detect the 401 code, if it is,overwrite it to 500. - // We also re-write the error content to structural error object - errorObj := make(map[string]string) - if res.StatusCode == http.StatusUnauthorized { - errorObj["error"] = "operation request from unauthorized source is rejected" - res.StatusCode = http.StatusInternalServerError - } else { - // Extract the error and wrap it into the error object - data, err := io.ReadAll(res.Body) - if err != nil { - errorObj["error"] = fmt.Sprintf("%s: %s", res.Status, err.Error()) - } else { - if err := json.Unmarshal(data, &errorObj); err != nil { - errorObj["error"] = string(data) - } - } - } - - content, err := json.Marshal(errorObj) - if err != nil { - return err - } - - size := len(content) - body := io.NopCloser(bytes.NewReader(content)) - res.Body = body - res.ContentLength = int64(size) - res.Header.Set(contentLengthHeader, strconv.Itoa(size)) - - return nil -} - -// Join the path -// Copy from the go reverse proxy -func singleJoiningSlash(a, b string) string { - aslash := strings.HasSuffix(a, "/") - bslash := strings.HasPrefix(b, "/") - switch { - case aslash && bslash: - return a + b[1:] - case !aslash && !bslash: - return a + "/" + b - } - return a + b -} - -// Rewrite the incoming URL with the right backend URL pattern -// Remove 'chartrepo' from the endpoints of manipulation API -// Remove 'chartrepo' from the endpoints of repository services -func rewriteURLPath(req *http.Request) { - incomingURLPath := req.URL.Path - - // Health check endpoint - if incomingURLPath == chartRepoHealthEndpoint { - req.URL.Path = "/health" - return - } - - // Root uploading endpoint - if incomingURLPath == rootUploadingEndpoint { - req.URL.Path = strings.Replace(incomingURLPath, "chartrepo", defaultRepo, 1) - return - } - - // Repository endpoints - if strings.HasPrefix(incomingURLPath, "/chartrepo") { - req.URL.Path = strings.TrimPrefix(incomingURLPath, "/chartrepo") - return - } - - // API endpoints - if strings.HasPrefix(incomingURLPath, "/api/chartrepo") { - req.URL.Path = strings.Replace(incomingURLPath, "/chartrepo", "", 1) - return - } -} diff --git a/src/chartserver/reverse_proxy_test.go b/src/chartserver/reverse_proxy_test.go deleted file mode 100644 index 44cfa0964b61..000000000000 --- a/src/chartserver/reverse_proxy_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package chartserver - -import ( - "net/http" - "testing" -) - -// Test the URL rewrite function -func TestURLRewrite(t *testing.T) { - req, err := createRequest(http.MethodGet, "/api/chartrepo/health") - if err != nil { - t.Fatal(err) - } - rewriteURLPath(req) - if req.URL.Path != "/health" { - t.Fatalf("Expect url format %s but got %s", "/health", req.URL.Path) - } - - req, err = createRequest(http.MethodGet, "/api/chartrepo/library/charts") - if err != nil { - t.Fatal(err) - } - rewriteURLPath(req) - if req.URL.Path != "/api/library/charts" { - t.Fatalf("Expect url format %s but got %s", "/api/library/charts", req.URL.Path) - } - - req, err = createRequest(http.MethodPost, "/api/chartrepo/charts") - if err != nil { - t.Fatal(err) - } - rewriteURLPath(req) - if req.URL.Path != "/api/library/charts" { - t.Fatalf("Expect url format %s but got %s", "/api/library/charts", req.URL.Path) - } - - req, err = createRequest(http.MethodGet, "/chartrepo/library/index.yaml") - if err != nil { - t.Fatal(err) - } - rewriteURLPath(req) - if req.URL.Path != "/library/index.yaml" { - t.Fatalf("Expect url format %s but got %s", "/library/index.yaml", req.URL.Path) - } -} - -func createRequest(method string, url string) (*http.Request, error) { - req, err := http.NewRequest(method, url, nil) - if err != nil { - return nil, err - } - req.RequestURI = url - - return req, nil -} diff --git a/src/chartserver/utils.go b/src/chartserver/utils.go deleted file mode 100644 index 88f572848e1c..000000000000 --- a/src/chartserver/utils.go +++ /dev/null @@ -1,141 +0,0 @@ -package chartserver - -import ( - "encoding/json" - "errors" - "fmt" - "net/url" - "os" - "strconv" - "strings" -) - -// Extract error object '{"error": "****---***"}' from the content if existing -// nil error will be returned if it does exist -func extractError(content []byte) (text string, err error) { - if len(content) == 0 { - return "", nil - } - - errorObj := make(map[string]string) - err = json.Unmarshal(content, &errorObj) - if err != nil { - return "", err - } - - if errText, ok := errorObj["error"]; ok { - return errText, nil - } - - return "", nil -} - -// Parse the redis configuration to the beego cache pattern -// redis://:password@host:6379/1 -// redis+sentinel://anonymous:password@host1:26379,host2:26379/mymaster/1 -func parseRedisConfig(redisConfigV string) (map[string]string, error) { - if len(redisConfigV) == 0 { - return nil, errors.New("empty redis config") - } - - redisConfig := make(map[string]string) - redisConfig["key"] = cacheCollectionName - - if !strings.Contains(redisConfigV, "//") { - redisConfigV = "redis://" + redisConfigV - } - u, err := url.Parse(redisConfigV) - if err != nil { - return nil, fmt.Errorf("bad _REDIS_URL:%s", redisConfigV) - } - if u.Scheme == "redis+sentinel" { - ps := strings.Split(u.Path, "/") - if len(ps) < 2 { - return nil, fmt.Errorf("bad redis sentinel url: no master name, %s", redisConfigV) - } - if _, err := strconv.Atoi(ps[1]); err == nil { - return nil, fmt.Errorf("bad redis sentinel url: master name should not be a number, %s", redisConfigV) - } - redisConfig["conn"] = u.Host - - if u.User != nil { - password, isSet := u.User.Password() - if isSet { - redisConfig["password"] = password - } - } - if len(ps) > 2 { - if _, err := strconv.Atoi(ps[2]); err != nil { - return nil, fmt.Errorf("bad redis sentinel url: bad db, %s", redisConfigV) - } - redisConfig["dbNum"] = ps[2] - } else { - redisConfig["dbNum"] = "0" - } - redisConfig["masterName"] = ps[1] - } else if u.Scheme == "redis" { - redisConfig["conn"] = u.Host // host - if u.User != nil { - password, isSet := u.User.Password() - if isSet { - redisConfig["password"] = password - } - } - if len(u.Path) > 1 { - if _, err := strconv.Atoi(u.Path[1:]); err != nil { - return nil, fmt.Errorf("bad redis url: bad db, %s", redisConfigV) - } - redisConfig["dbNum"] = u.Path[1:] - } else { - redisConfig["dbNum"] = "0" - } - } else { - return nil, fmt.Errorf("bad redis scheme, %s", redisConfigV) - } - - return redisConfig, nil -} - -// What's the cache driver if it is set -func parseCacheDriver() (string, bool) { - driver, ok := os.LookupEnv(cacheDriverENVKey) - return strings.ToLower(driver), ok -} - -// Get and parse the configuration for the chart cache -func getCacheConfig() (*ChartCacheConfig, error) { - driver, isSet := parseCacheDriver() - if !isSet { - return nil, nil - } - - if driver != cacheDriverMem && driver != cacheDriverRedis { - return nil, fmt.Errorf("cache driver '%s' is not supported, only support 'memory' and 'redis'", driver) - } - - if driver == cacheDriverMem { - return &ChartCacheConfig{ - DriverType: driver, - }, nil - } - - redisConfigV := os.Getenv(redisENVKey) - redisCfg, err := parseRedisConfig(redisConfigV) - if err != nil { - return nil, fmt.Errorf("failed to parse redis configurations from '%s' with error: %s", redisCfg, err) - } - if _, isSet := redisCfg["masterName"]; isSet { - driver = cacheDriverRedisSentinel - } - - // Convert config map to string - cfgData, err := json.Marshal(redisCfg) - if err != nil { - return nil, fmt.Errorf("failed to parse redis configurations from '%s' with error: %s", redisCfg, err) - } - - return &ChartCacheConfig{ - DriverType: driver, - Config: string(cfgData), - }, nil -} diff --git a/src/chartserver/utils_test.go b/src/chartserver/utils_test.go deleted file mode 100644 index 19726afe6e82..000000000000 --- a/src/chartserver/utils_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package chartserver - -import ( - "encoding/json" - "testing" -) - -// Test the utility function parseRedisConfig -func TestParseRedisConfig(t *testing.T) { - // Case 1: empty addr - redisAddr := "" - if _, err := parseRedisConfig(redisAddr); err == nil { - t.Fatal("expect non nil error but got nil one if addr is empty") - } - - // Case 2: short pattern, addr:port - redisAddr = "redis:6379" - if parsedConn, err := parseRedisConfig(redisAddr); err != nil { - t.Fatalf("expect nil error but got non nil one if addr is short pattern: %s\n", parsedConn) - } - - // Case 3: long pattern but miss some parts - redisAddr = "redis:6379?idle_timeout_seconds=100" - if parsedConn, err := parseRedisConfig(redisAddr); err != nil { - t.Fatalf("expect nil error but got non nil one if addr is long pattern with some parts missing: %v\n", parsedConn) - } else { - if num, ok := parsedConn["dbNum"]; !ok || num != "0" { - t.Fatalf("expect 'dbNum:0' in the parsed conn str: %v\n", parsedConn) - } - } - - // Case 4: long pattern - redisAddr = ":Passw0rd@redis:6379/1?idle_timeout_seconds=100" - if parsedConn, err := parseRedisConfig(redisAddr); err != nil { - t.Fatal("expect nil error but got non nil one if addr is long pattern") - } else { - if num, ok := parsedConn["dbNum"]; !ok || num != "1" { - t.Fatalf("expect 'dbNum:1' in the parsed conn str: %v", parsedConn) - } - if p, ok := parsedConn["password"]; !ok || p != "Passw0rd" { - t.Fatalf("expect 'password:Passw0rd' in the parsed conn str: %v", parsedConn) - } - } - - // Case 5: sentinel but miss master name - redisAddr = "redis+sentinel://:Passw0rd@redis1:26379,redis2:26379/1?idle_timeout_seconds=100" - if _, err := parseRedisConfig(redisAddr); err == nil { - t.Fatal("expect no master name error but got nil") - } - - // Case 6: sentinel - redisAddr = "redis+sentinel://:Passw0rd@redis1:26379,redis2:26379/mymaster/1?idle_timeout_seconds=100" - if parsedConn, err := parseRedisConfig(redisAddr); err != nil { - t.Fatal("expect nil error but got non nil one if addr is long pattern") - } else { - if num, ok := parsedConn["dbNum"]; !ok || num != "1" { - t.Fatalf("expect 'dbNum:0' in the parsed conn str: %v", parsedConn) - } - if p, ok := parsedConn["password"]; !ok || p != "Passw0rd" { - t.Fatalf("expect 'password:Passw0rd' in the parsed conn str: %v", parsedConn) - } - if v, ok := parsedConn["masterName"]; !ok || v != "mymaster" { - t.Fatalf("expect 'masterName:mymaster' in the parsed conn str: %v", parsedConn) - } - if v, ok := parsedConn["conn"]; !ok || v != "redis1:26379,redis2:26379" { - t.Fatalf("expect 'conn:redis1:26379,redis2:26379' in the parsed conn str: %v", parsedConn) - } - } -} - -func TestGetCacheConfig(t *testing.T) { - t.Run("no cache set", func(t *testing.T) { - cacheConf, err := getCacheConfig() - if err != nil || cacheConf != nil { - t.Fatal("expect nil cache config and nil error but got non-nil one when parsing empty cache settings") - } - }) - - t.Run("unknown cache type", func(t *testing.T) { - t.Setenv(cacheDriverENVKey, "unknown") - _, err := getCacheConfig() - if err == nil { - t.Fatal("expect non-nil error but got nil one when parsing unknown cache type") - } - }) - - t.Run("in memory cache type", func(t *testing.T) { - t.Setenv(cacheDriverENVKey, cacheDriverMem) - memCacheConf, err := getCacheConfig() - if err != nil || memCacheConf == nil || memCacheConf.DriverType != cacheDriverMem { - t.Fatal("expect in memory cache driver but got invalid one") - } - }) - - t.Run("wrong redis cache conf", func(t *testing.T) { - t.Setenv(cacheDriverENVKey, cacheDriverRedis) - t.Setenv(redisENVKey, "") - _, err := getCacheConfig() - if err == nil { - t.Fatal("expect non-nil error but got nil one when parsing a invalid redis cache conf") - } - }) - - t.Run("redis cache conf", func(t *testing.T) { - t.Setenv(cacheDriverENVKey, cacheDriverRedis) - t.Setenv(redisENVKey, ":Passw0rd@redis:6379/1?idle_timeout_seconds=100") - redisConf, err := getCacheConfig() - if err != nil { - t.Fatalf("expect nil error but got non-nil one when parsing valid redis conf") - } - - if redisConf == nil || redisConf.DriverType != cacheDriverRedis { - t.Fatal("expect redis cache driver but got invalid one") - } - - conf := make(map[string]string) - if err = json.Unmarshal([]byte(redisConf.Config), &conf); err != nil { - t.Fatal(err) - } - - if v, ok := conf["conn"]; !ok { - t.Fatal("expect 'conn' filed in the parsed conf but got nothing") - } else { - if v != "redis:6379" { - t.Fatalf("expect %s but got %s", "redis:6379", v) - } - } - }) -} diff --git a/src/common/const.go b/src/common/const.go index 6edecdd6b287..cdb3d809b344 100755 --- a/src/common/const.go +++ b/src/common/const.go @@ -46,7 +46,6 @@ const ( ResourceTypeProject = "p" ResourceTypeRepository = "r" ResourceTypeImage = "i" - ResourceTypeChart = "c" ExtEndpoint = "ext_endpoint" AUTHMode = "auth_mode" @@ -137,9 +136,6 @@ const ( LDAPGroupAdminDn = "ldap_group_admin_dn" LDAPGroupMembershipAttribute = "ldap_group_membership_attribute" DefaultRegistryControllerEndpoint = "http://registryctl:8080" - WithChartMuseum = "with_chartmuseum" - ChartRepoURL = "chart_repository_url" - DefaultChartRepoURL = "http://chartmuseum:9999" DefaultPortalURL = "http://portal:8080" DefaultRegistryCtlURL = "http://registryctl:8080" // Use this prefix to distinguish harbor user, the prefix contains a special character($), so it cannot be registered as a harbor user. @@ -156,9 +152,6 @@ const ( AuthProxyRediretPath = "/c/authproxy/redirect" - ChartUploadCtxKey = contextKey("chart_upload_event") - ChartDownloadCtxKey = contextKey("chart_download_event") - // Global notification enable configuration NotificationEnable = "notification_enable" diff --git a/src/common/rbac/const.go b/src/common/rbac/const.go index f52a40c9666a..b085eeb6cdb0 100755 --- a/src/common/rbac/const.go +++ b/src/common/rbac/const.go @@ -35,32 +35,29 @@ const ( // const resource variables const ( - ResourceAll = Resource("*") // resource match any other resources - ResourceConfiguration = Resource("configuration") // project configuration compatible for portal only - ResourceHelmChart = Resource("helm-chart") - ResourceHelmChartVersion = Resource("helm-chart-version") - ResourceHelmChartVersionLabel = Resource("helm-chart-version-label") - ResourceLabel = Resource("label") - ResourceLog = Resource("log") - ResourceLdapUser = Resource("ldap-user") - ResourceMember = Resource("member") - ResourceMetadata = Resource("metadata") - ResourceQuota = Resource("quota") - ResourceRepository = Resource("repository") - ResourceTagRetention = Resource("tag-retention") - ResourceImmutableTag = Resource("immutable-tag") - ResourceRobot = Resource("robot") - ResourceNotificationPolicy = Resource("notification-policy") - ResourceScan = Resource("scan") - ResourceScanner = Resource("scanner") - ResourceArtifact = Resource("artifact") - ResourceTag = Resource("tag") - ResourceAccessory = Resource("accessory") - ResourceArtifactAddition = Resource("artifact-addition") - ResourceArtifactLabel = Resource("artifact-label") - ResourcePreatPolicy = Resource("preheat-policy") - ResourcePreatInstance = Resource("preheat-instance") - ResourceSelf = Resource("") // subresource for self + ResourceAll = Resource("*") // resource match any other resources + ResourceConfiguration = Resource("configuration") // project configuration compatible for portal only + ResourceLabel = Resource("label") + ResourceLog = Resource("log") + ResourceLdapUser = Resource("ldap-user") + ResourceMember = Resource("member") + ResourceMetadata = Resource("metadata") + ResourceQuota = Resource("quota") + ResourceRepository = Resource("repository") + ResourceTagRetention = Resource("tag-retention") + ResourceImmutableTag = Resource("immutable-tag") + ResourceRobot = Resource("robot") + ResourceNotificationPolicy = Resource("notification-policy") + ResourceScan = Resource("scan") + ResourceScanner = Resource("scanner") + ResourceArtifact = Resource("artifact") + ResourceTag = Resource("tag") + ResourceAccessory = Resource("accessory") + ResourceArtifactAddition = Resource("artifact-addition") + ResourceArtifactLabel = Resource("artifact-label") + ResourcePreatPolicy = Resource("preheat-policy") + ResourcePreatInstance = Resource("preheat-instance") + ResourceSelf = Resource("") // subresource for self ResourceAuditLog = Resource("audit-log") ResourceCatalog = Resource("catalog") diff --git a/src/common/rbac/project/rbac_role.go b/src/common/rbac/project/rbac_role.go index 5d6b5d8c185b..d31ab20a2a0f 100644 --- a/src/common/rbac/project/rbac_role.go +++ b/src/common/rbac/project/rbac_role.go @@ -68,20 +68,6 @@ var ( {Resource: rbac.ResourceImmutableTag, Action: rbac.ActionDelete}, {Resource: rbac.ResourceImmutableTag, Action: rbac.ActionList}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionCreate}, // upload helm chart - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead}, // download helm chart - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionDelete}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionCreate}, // upload helm chart version - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead}, // read and download helm chart version - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionDelete}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate}, - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete}, - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionList}, - {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceConfiguration, Action: rbac.ActionUpdate}, @@ -173,20 +159,6 @@ var ( {Resource: rbac.ResourceImmutableTag, Action: rbac.ActionDelete}, {Resource: rbac.ResourceImmutableTag, Action: rbac.ActionList}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionCreate}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionDelete}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionCreate}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionDelete}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate}, - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete}, - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionList}, - {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceRobot, Action: rbac.ActionRead}, @@ -245,18 +217,6 @@ var ( {Resource: rbac.ResourceTagRetention, Action: rbac.ActionList}, {Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionCreate}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionCreate}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionCreate}, - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionDelete}, - {Resource: rbac.ResourceHelmChartVersionLabel, Action: rbac.ActionList}, - {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceRobot, Action: rbac.ActionRead}, @@ -301,12 +261,6 @@ var ( {Resource: rbac.ResourceRepository, Action: rbac.ActionList}, {Resource: rbac.ResourceRepository, Action: rbac.ActionPull}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList}, - {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceRobot, Action: rbac.ActionRead}, @@ -332,12 +286,6 @@ var ( {Resource: rbac.ResourceRepository, Action: rbac.ActionList}, {Resource: rbac.ResourceRepository, Action: rbac.ActionPull}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList}, - {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, diff --git a/src/common/rbac/project/rbac_util.go b/src/common/rbac/project/rbac_util.go index 7fd21cf7e41e..d93e44d32bd2 100644 --- a/src/common/rbac/project/rbac_util.go +++ b/src/common/rbac/project/rbac_util.go @@ -30,12 +30,6 @@ var ( {Resource: rbac.ResourceRepository, Action: rbac.ActionList}, {Resource: rbac.ResourceRepository, Action: rbac.ActionPull}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChart, Action: rbac.ActionList}, - - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionRead}, - {Resource: rbac.ResourceHelmChartVersion, Action: rbac.ActionList}, - {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, diff --git a/src/common/utils/test/test.go b/src/common/utils/test/test.go index 635ff96211a2..4b946c3ae604 100644 --- a/src/common/utils/test/test.go +++ b/src/common/utils/test/test.go @@ -122,7 +122,6 @@ func GetUnitTestConfig() map[string]interface{} { common.LDAPGroupSearchScope: 2, common.LDAPGroupAdminDn: "cn=harbor_users,ou=groups,dc=example,dc=com", common.WithNotary: "false", - common.WithChartMuseum: "false", common.SelfRegistration: "true", common.WithTrivy: "true", common.TokenServiceURL: "http://core:8080/service/token", diff --git a/src/controller/artifact/processor/chart/chart.go b/src/controller/artifact/processor/chart/chart.go index 8fa213aa27e6..d68419edcb86 100644 --- a/src/controller/artifact/processor/chart/chart.go +++ b/src/controller/artifact/processor/chart/chart.go @@ -16,17 +16,11 @@ package chart import ( "context" - "encoding/json" - "io" - - v1 "github.com/opencontainers/image-spec/specs-go/v1" ps "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" - "github.com/goharbor/harbor/src/pkg/chart" ) // const definitions @@ -43,9 +37,7 @@ const ( ) func init() { - pc := &processor{ - chartOperator: chart.Optr, - } + pc := &processor{} pc.ManifestProcessor = base.NewManifestProcessor() if err := ps.Register(pc, mediaType); err != nil { log.Errorf("failed to register processor for media type %s: %v", mediaType, err) @@ -55,70 +47,10 @@ func init() { type processor struct { *base.ManifestProcessor - chartOperator chart.Operator } func (p *processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*ps.Addition, error) { - if addition != AdditionTypeValues && addition != AdditionTypeReadme && addition != AdditionTypeDependencies { - return nil, errors.New(nil).WithCode(errors.BadRequestCode). - WithMessage("addition %s isn't supported for %s", addition, ArtifactTypeChart) - } - - m, _, err := p.RegCli.PullManifest(artifact.RepositoryName, artifact.Digest) - if err != nil { - return nil, err - } - _, payload, err := m.Payload() - if err != nil { - return nil, err - } - manifest := &v1.Manifest{} - if err := json.Unmarshal(payload, manifest); err != nil { - return nil, err - } - - for _, layer := range manifest.Layers { - // chart do have two layers, one is config, we should resolve the other one. - layerDgst := layer.Digest.String() - if layerDgst != manifest.Config.Digest.String() { - _, blob, err := p.RegCli.PullBlob(artifact.RepositoryName, layerDgst) - if err != nil { - return nil, err - } - 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 - } - - var additionContent []byte - var additionContentType string - - switch addition { - case AdditionTypeValues: - additionContent = []byte(chartDetails.Files[AdditionTypeValues]) - additionContentType = "text/plain; charset=utf-8" - case AdditionTypeReadme: - additionContent = []byte(chartDetails.Files[AdditionTypeReadme]) - additionContentType = "text/markdown; charset=utf-8" - case AdditionTypeDependencies: - additionContent, err = json.Marshal(chartDetails.Dependencies) - if err != nil { - return nil, err - } - additionContentType = "application/json; charset=utf-8" - } - - return &ps.Addition{ - Content: additionContent, - ContentType: additionContentType, - }, nil - } - } + // TODO: imple oci charts return nil, nil } diff --git a/src/controller/artifact/processor/chart/chart_test.go b/src/controller/artifact/processor/chart/chart_test.go index e8e208dd99fc..28669ae8d67a 100644 --- a/src/controller/artifact/processor/chart/chart_test.go +++ b/src/controller/artifact/processor/chart/chart_test.go @@ -15,22 +15,11 @@ package chart import ( - "io" - "strings" "testing" - "github.com/docker/distribution" - v1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/stretchr/testify/suite" - helm_chart "helm.sh/helm/v3/pkg/chart" - "github.com/goharbor/harbor/src/controller/artifact/processor/base" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/artifact" - chartserver "github.com/goharbor/harbor/src/pkg/chart" - "github.com/goharbor/harbor/src/testing/mock" - "github.com/goharbor/harbor/src/testing/pkg/chart" "github.com/goharbor/harbor/src/testing/pkg/registry" + "github.com/stretchr/testify/suite" ) var ( @@ -64,67 +53,15 @@ type processorTestSuite struct { suite.Suite processor *processor regCli *registry.Client - chartOptr *chart.FakeOpertaor } func (p *processorTestSuite) SetupTest() { p.regCli = ®istry.Client{} - p.chartOptr = &chart.FakeOpertaor{} - p.processor = &processor{ - chartOperator: p.chartOptr, - } + p.processor = &processor{} p.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: p.regCli} } func (p *processorTestSuite) TestAbstractAddition() { - // unknown addition - _, err := p.processor.AbstractAddition(nil, nil, "unknown_addition") - p.True(errors.IsErr(err, errors.BadRequestCode)) - - chartDetails := &chartserver.VersionDetails{ - Dependencies: []*helm_chart.Dependency{ - { - Name: "harbor", - Version: "v1.10", - Repository: "github.com/goharbor", - }, - }, - Values: map[string]interface{}{ - "cluster.enable": true, - "cluster.slaveCount": 1, - "image.pullPolicy": "Always", - "master.securityContext.runAsUser": 1001, - }, - Files: map[string]string{ - "README.MD": "This chart bootstraps a [Redis](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.", - "VALUES.YAML": `image:\n ## Bitnami MongoDB registry\n ##\n registry: docker.io\n ## Bitnami MongoDB image name\n ##\n repository: bitnami/mongodb\n ## Bitnami MongoDB image tag\n ## ref: https://hub.docker.com/r/bitnami/mongodb/tags/\n`, - }, - } - - artifact := &artifact.Artifact{} - manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(chartManifest)) - p.Require().Nil(err) - p.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) - p.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(0), io.NopCloser(strings.NewReader(chartYaml)), nil) - p.chartOptr.On("GetDetails").Return(chartDetails, nil) - - // values.yaml - addition, err := p.processor.AbstractAddition(nil, artifact, AdditionTypeValues) - p.Require().Nil(err) - p.Equal("text/plain; charset=utf-8", addition.ContentType) - p.Equal(`image:\n ## Bitnami MongoDB registry\n ##\n registry: docker.io\n ## Bitnami MongoDB image name\n ##\n repository: bitnami/mongodb\n ## Bitnami MongoDB image tag\n ## ref: https://hub.docker.com/r/bitnami/mongodb/tags/\n`, string(addition.Content)) - - // README.md - addition, err = p.processor.AbstractAddition(nil, artifact, AdditionTypeReadme) - p.Require().Nil(err) - p.Equal("text/markdown; charset=utf-8", addition.ContentType) - p.Equal(`This chart bootstraps a [Redis](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.`, string(addition.Content)) - - // README.md - addition, err = p.processor.AbstractAddition(nil, artifact, AdditionTypeDependencies) - p.Require().Nil(err) - p.Equal("application/json; charset=utf-8", addition.ContentType) - p.Equal(`[{"name":"harbor","version":"v1.10","repository":"github.com/goharbor"}]`, string(addition.Content)) } func (p *processorTestSuite) TestGetArtifactType() { diff --git a/src/controller/artifact/processor/processor.go b/src/controller/artifact/processor/processor.go index 7cd41592adca..f40ed9c73c2c 100644 --- a/src/controller/artifact/processor/processor.go +++ b/src/controller/artifact/processor/processor.go @@ -44,7 +44,7 @@ type Processor interface { AbstractMetadata(ctx context.Context, artifact *artifact.Artifact, manifest []byte) error // AbstractAddition abstracts the addition of the artifact. // The additions are different for different artifacts: - // build history for image; values.yaml, readme and dependencies for chart, etc + // build history for image; AbstractAddition(ctx context.Context, artifact *artifact.Artifact, additionType string) (addition *Addition, err error) } diff --git a/src/controller/event/handler/init.go b/src/controller/event/handler/init.go index 12ea88dfac57..d33dc9f3dbd4 100644 --- a/src/controller/event/handler/init.go +++ b/src/controller/event/handler/init.go @@ -9,7 +9,6 @@ import ( "github.com/goharbor/harbor/src/controller/event/handler/p2p" "github.com/goharbor/harbor/src/controller/event/handler/replication" "github.com/goharbor/harbor/src/controller/event/handler/webhook/artifact" - "github.com/goharbor/harbor/src/controller/event/handler/webhook/chart" "github.com/goharbor/harbor/src/controller/event/handler/webhook/quota" "github.com/goharbor/harbor/src/controller/event/handler/webhook/scan" "github.com/goharbor/harbor/src/controller/event/metadata" @@ -24,9 +23,6 @@ func init() { _ = notifier.Subscribe(event.TopicPushArtifact, &artifact.Handler{}) _ = notifier.Subscribe(event.TopicPullArtifact, &artifact.Handler{}) _ = notifier.Subscribe(event.TopicDeleteArtifact, &artifact.Handler{}) - _ = notifier.Subscribe(event.TopicUploadChart, &chart.Handler{}) - _ = notifier.Subscribe(event.TopicDeleteChart, &chart.Handler{}) - _ = notifier.Subscribe(event.TopicDownloadChart, &chart.Handler{}) _ = notifier.Subscribe(event.TopicQuotaExceed, "a.Handler{}) _ = notifier.Subscribe(event.TopicQuotaWarning, "a.Handler{}) _ = notifier.Subscribe(event.TopicScanningFailed, &scan.Handler{}) diff --git a/src/controller/event/handler/replication/event/event.go b/src/controller/event/handler/replication/event/event.go index c412d5d3002c..9c1eb658cdea 100644 --- a/src/controller/event/handler/replication/event/event.go +++ b/src/controller/event/handler/replication/event/event.go @@ -21,11 +21,9 @@ const ( EventTypeArtifactPush = "artifact_push" EventTypeArtifactDelete = "artifact_delete" EventTypeTagDelete = "tag_delete" - EventTypeChartUpload = "chart_upload" - EventTypeChartDelete = "chart_delete" ) -// Event is the model that defines the image/chart pull/push event +// Event is the model that defines the image pull/push event type Event struct { Type string Resource *model.Resource diff --git a/src/controller/event/handler/replication/event/handler.go b/src/controller/event/handler/replication/event/handler.go index 791bd69c9a4f..19b947943370 100644 --- a/src/controller/event/handler/replication/event/handler.go +++ b/src/controller/event/handler/replication/event/handler.go @@ -37,8 +37,7 @@ func Handle(ctx context.Context, event *Event) error { var policies []*repctlmodel.Policy var err error switch event.Type { - case EventTypeArtifactPush, EventTypeChartUpload, EventTypeTagDelete, - EventTypeArtifactDelete, EventTypeChartDelete: + case EventTypeArtifactPush, EventTypeTagDelete, EventTypeArtifactDelete: policies, err = getRelatedPolicies(ctx, event.Resource) default: return fmt.Errorf("unsupported event type %s", event.Type) diff --git a/src/controller/event/handler/webhook/chart/chart.go b/src/controller/event/handler/webhook/chart/chart.go deleted file mode 100644 index 927a22577bb4..000000000000 --- a/src/controller/event/handler/webhook/chart/chart.go +++ /dev/null @@ -1,122 +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 chart - -import ( - "context" - "errors" - "fmt" - - "github.com/goharbor/harbor/src/controller/event" - "github.com/goharbor/harbor/src/controller/event/handler/util" - "github.com/goharbor/harbor/src/controller/project" - "github.com/goharbor/harbor/src/lib/config" - "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/pkg/notification" - "github.com/goharbor/harbor/src/pkg/notifier/model" - proModels "github.com/goharbor/harbor/src/pkg/project/models" -) - -// Handler preprocess chart event data -type Handler struct { -} - -// Name ... -func (cph *Handler) Name() string { - return "ChartWebhook" -} - -// Handle preprocess chart event data and then publish hook event -func (cph *Handler) Handle(ctx context.Context, value interface{}) error { - chartEvent, ok := value.(*event.ChartEvent) - if !ok { - return errors.New("invalid chart event type") - } - - if chartEvent == nil || len(chartEvent.Versions) == 0 || len(chartEvent.ProjectName) == 0 || len(chartEvent.ChartName) == 0 { - return fmt.Errorf("data miss in chart event: %v", chartEvent) - } - - prj, err := project.Ctl.Get(ctx, chartEvent.ProjectName, project.Metadata(true)) - if err != nil { - log.Errorf("failed to find project[%s] for chart event: %v", chartEvent.ProjectName, err) - return err - } - policies, err := notification.PolicyMgr.GetRelatedPolices(ctx, prj.ProjectID, chartEvent.EventType) - if err != nil { - log.Errorf("failed to find policy for %s event: %v", chartEvent.EventType, err) - return err - } - // if cannot find policy including event type in project, return directly - if len(policies) == 0 { - log.Debugf("cannot find policy for %s event: %v", chartEvent.EventType, chartEvent) - return nil - } - - payload, err := constructChartPayload(chartEvent, prj) - if err != nil { - return err - } - - err = util.SendHookWithPolicies(policies, payload, chartEvent.EventType) - if err != nil { - return err - } - - return nil -} - -// IsStateful ... -func (cph *Handler) IsStateful() bool { - return false -} - -func constructChartPayload(event *event.ChartEvent, project *proModels.Project) (*model.Payload, error) { - repoType := proModels.ProjectPrivate - if project.IsPublic() { - repoType = proModels.ProjectPublic - } - - payload := &model.Payload{ - Type: event.EventType, - OccurAt: event.OccurAt.Unix(), - EventData: &model.EventData{ - Repository: &model.Repository{ - Name: event.ChartName, - Namespace: event.ProjectName, - RepoFullName: fmt.Sprintf("%s/%s", event.ProjectName, event.ChartName), - RepoType: repoType, - }, - }, - Operator: event.Operator, - } - - extURL, err := config.ExtEndpoint() - if err != nil { - return nil, fmt.Errorf("get external endpoint failed: %v", err) - } - - resourcePrefix := fmt.Sprintf("%s/chartrepo/%s/charts/%s", extURL, event.ProjectName, event.ChartName) - for _, v := range event.Versions { - resURL := fmt.Sprintf("%s-%s.tgz", resourcePrefix, v) - - resource := &model.Resource{ - Tag: v, - ResourceURL: resURL, - } - payload.EventData.Resources = append(payload.EventData.Resources, resource) - } - return payload, nil -} diff --git a/src/controller/event/handler/webhook/chart/chart_test.go b/src/controller/event/handler/webhook/chart/chart_test.go deleted file mode 100644 index e6814af39aa9..000000000000 --- a/src/controller/event/handler/webhook/chart/chart_test.go +++ /dev/null @@ -1,183 +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 chart - -import ( - "context" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testutils "github.com/goharbor/harbor/src/common/utils/test" - "github.com/goharbor/harbor/src/controller/event" - "github.com/goharbor/harbor/src/controller/project" - "github.com/goharbor/harbor/src/lib/config" - _ "github.com/goharbor/harbor/src/pkg/config/db" - _ "github.com/goharbor/harbor/src/pkg/config/inmemory" - "github.com/goharbor/harbor/src/pkg/notification" - "github.com/goharbor/harbor/src/pkg/notification/policy/model" - proModels "github.com/goharbor/harbor/src/pkg/project/models" - projecttesting "github.com/goharbor/harbor/src/testing/controller/project" - "github.com/goharbor/harbor/src/testing/mock" - testingnotification "github.com/goharbor/harbor/src/testing/pkg/notification/policy" -) - -func TestMain(m *testing.M) { - // do some initialization - testutils.InitDatabaseFromEnv() - os.Exit(m.Run()) -} - -func TestChartPreprocessHandler_Handle(t *testing.T) { - PolicyMgr := notification.PolicyMgr - defer func() { - notification.PolicyMgr = PolicyMgr - }() - policyMgrMock := &testingnotification.Manager{} - notification.PolicyMgr = policyMgrMock - - ProjectCtl := project.Ctl - defer func() { - project.Ctl = ProjectCtl - }() - projectCtl := &projecttesting.Controller{} - project.Ctl = projectCtl - - name := "project_for_test_chart_event_preprocess" - mock.OnAnything(projectCtl, "Get").Return(func(ctx context.Context, projectIDOrName interface{}, options ...project.Option) *proModels.Project { - return &proModels.Project{ - Name: name, - OwnerID: 1, - Metadata: map[string]string{ - proModels.ProMetaEnableContentTrust: "true", - proModels.ProMetaPreventVul: "true", - proModels.ProMetaSeverity: "Low", - proModels.ProMetaReuseSysCVEAllowlist: "false", - }, - } - }, nil) - projectCtl.On("Get") - policyMgrMock.On("GetRelatedPolices", mock.Anything, mock.Anything, mock.Anything).Return([]*model.Policy{ - { - ID: 1, - EventTypes: []string{ - event.TopicUploadChart, - event.TopicDownloadChart, - event.TopicDeleteChart, - event.TopicPushArtifact, - event.TopicPullArtifact, - event.TopicDeleteArtifact, - event.TopicScanningFailed, - event.TopicScanningCompleted, - event.TopicQuotaExceed, - }, - Targets: []model.EventTarget{ - { - Type: "http", - Address: "http://127.0.0.1:8080", - }, - }, - }, - }, nil) - - handler := &Handler{} - config.Init() - - type args struct { - data interface{} - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Handler Want Error 1", - args: args{ - data: nil, - }, - wantErr: true, - }, - { - name: "Handler Want Error 2", - args: args{ - data: &event.ChartEvent{}, - }, - wantErr: true, - }, - { - name: "Handler Want Error 3", - args: args{ - data: &event.ChartEvent{ - Versions: []string{ - "v1.2.1", - }, - ProjectName: "project_for_test_chart_event_preprocess", - }, - }, - wantErr: true, - }, - { - name: "Handler Want Error 4", - args: args{ - data: &event.ChartEvent{ - Versions: []string{ - "v1.2.1", - }, - ProjectName: "project_for_test_chart_event_preprocess_not_exists", - ChartName: "testChart", - }, - }, - wantErr: true, - }, - { - name: "Handler Want Error 5", - args: args{ - data: &event.ChartEvent{ - Versions: []string{ - "v1.2.1", - }, - ProjectName: "project_for_test_chart_event_preprocess", - ChartName: "testChart", - EventType: "uploadChart", - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := handler.Handle(context.TODO(), tt.args.data) - if tt.wantErr { - require.NotNil(t, err, "Error: %s", err) - return - } - assert.Nil(t, err) - }) - } -} - -func TestChartPreprocessHandler_IsStateful(t *testing.T) { - handler := &Handler{} - assert.False(t, handler.IsStateful()) -} - -func TestChartPreprocessHandler_Name(t *testing.T) { - handler := &Handler{} - assert.Equal(t, "ChartWebhook", handler.Name()) -} diff --git a/src/controller/event/metadata/chart.go b/src/controller/event/metadata/chart.go deleted file mode 100644 index 4b5e1c89310c..000000000000 --- a/src/controller/event/metadata/chart.go +++ /dev/null @@ -1,76 +0,0 @@ -package metadata - -import ( - "time" - - event2 "github.com/goharbor/harbor/src/controller/event" - "github.com/goharbor/harbor/src/pkg/notifier/event" -) - -// ChartMetaData defines meta data of chart event -type ChartMetaData struct { - ProjectName string - ChartName string - Versions []string - OccurAt time.Time - Operator string -} - -func (cmd *ChartMetaData) convert(evt *event2.ChartEvent) { - evt.ProjectName = cmd.ProjectName - evt.OccurAt = cmd.OccurAt - evt.Operator = cmd.Operator - evt.ChartName = cmd.ChartName - evt.Versions = cmd.Versions -} - -// ChartUploadMetaData defines meta data of chart upload event -type ChartUploadMetaData struct { - ChartMetaData -} - -// Resolve chart uploading metadata into common chart event -func (cu *ChartUploadMetaData) Resolve(event *event.Event) error { - data := &event2.ChartEvent{ - EventType: event2.TopicUploadChart, - } - cu.convert(data) - - event.Topic = event2.TopicUploadChart - event.Data = data - return nil -} - -// ChartDownloadMetaData defines meta data of chart download event -type ChartDownloadMetaData struct { - ChartMetaData -} - -// Resolve chart download metadata into common chart event -func (cd *ChartDownloadMetaData) Resolve(evt *event.Event) error { - data := &event2.ChartEvent{ - EventType: event2.TopicDownloadChart, - } - cd.convert(data) - - evt.Topic = event2.TopicDownloadChart - evt.Data = data - return nil -} - -// ChartDeleteMetaData defines meta data of chart delete event -type ChartDeleteMetaData struct { - ChartMetaData -} - -// Resolve chart delete metadata into common chart event -func (cd *ChartDeleteMetaData) Resolve(evt *event.Event) error { - data := &event2.ChartEvent{ - EventType: event2.TopicDeleteChart, - } - cd.convert(data) - - evt.Topic = event2.TopicDeleteChart - evt.Data = data - return nil -} diff --git a/src/controller/event/metadata/chart_test.go b/src/controller/event/metadata/chart_test.go deleted file mode 100644 index c6b9af85e986..000000000000 --- a/src/controller/event/metadata/chart_test.go +++ /dev/null @@ -1,73 +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 metadata - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - - event2 "github.com/goharbor/harbor/src/controller/event" - "github.com/goharbor/harbor/src/pkg/notifier/event" -) - -type chartEventTestSuite struct { - suite.Suite -} - -func (r *chartEventTestSuite) TestResolveOfUploadChartEventMetadata() { - e := &event.Event{} - metadata := &ChartUploadMetaData{ - ChartMetaData{ - ProjectName: "library", - ChartName: "redis-v2.0", - Versions: nil, - OccurAt: time.Time{}, - Operator: "admin", - }, - } - err := metadata.Resolve(e) - r.Require().Nil(err) - r.Equal(event2.TopicUploadChart, e.Topic) - r.Require().NotNil(e.Data) - data, ok := e.Data.(*event2.ChartEvent) - r.Require().True(ok) - r.Equal("redis-v2.0", data.ChartName) -} - -func (r *chartEventTestSuite) TestResolveOfDownloadChartEventMetadata() { - e := &event.Event{} - metadata := &ChartDownloadMetaData{ - ChartMetaData{ - ProjectName: "library", - ChartName: "redis-v2.0", - Versions: nil, - OccurAt: time.Time{}, - Operator: "admin", - }, - } - err := metadata.Resolve(e) - r.Require().Nil(err) - r.Equal(event2.TopicDownloadChart, e.Topic) - r.Require().NotNil(e.Data) - data, ok := e.Data.(*event2.ChartEvent) - r.Require().True(ok) - r.Equal("redis-v2.0", data.ChartName) -} - -func TestChartEventTestSuite(t *testing.T) { - suite.Run(t, &chartEventTestSuite{}) -} diff --git a/src/controller/event/topic.go b/src/controller/event/topic.go index 2013918f2d72..de318b2113c9 100644 --- a/src/controller/event/topic.go +++ b/src/controller/event/topic.go @@ -44,9 +44,6 @@ const ( // QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85% TopicQuotaWarning = "QUOTA_WARNING" TopicQuotaExceed = "QUOTA_EXCEED" - TopicUploadChart = "UPLOAD_CHART" - TopicDownloadChart = "DOWNLOAD_CHART" - TopicDeleteChart = "DELETE_CHART" TopicReplication = "REPLICATION" TopicArtifactLabeled = "ARTIFACT_LABELED" TopicTagRetention = "TAG_RETENTION" @@ -302,21 +299,6 @@ func (s *ScanImageEvent) String() string { s.Artifact, s.Operator, s.OccurAt.Format("2006-01-02 15:04:05")) } -// ChartEvent is chart related event data to publish -type ChartEvent struct { - EventType string - ProjectName string - ChartName string - Versions []string - OccurAt time.Time - Operator string -} - -func (c *ChartEvent) String() string { - return fmt.Sprintf("ProjectName-%s ChartName-%s Versions-%s Operator-%s OccurAt-%s", - c.ProjectName, c.ChartName, c.Versions, c.Operator, c.OccurAt.Format("2006-01-02 15:04:05")) -} - // QuotaEvent is project quota related event data to publish type QuotaEvent struct { EventType string diff --git a/src/controller/health/checker.go b/src/controller/health/checker.go index 0843cf4f0133..3aa6cd2677ce 100644 --- a/src/controller/health/checker.go +++ b/src/controller/health/checker.go @@ -146,18 +146,6 @@ func registryCtlHealthChecker() health.Checker { return PeriodicHealthChecker(checker, period) } -func chartmuseumHealthChecker() health.Checker { - url, err := config.GetChartMuseumEndpoint() - if err != nil { - log.Errorf("failed to get the URL of chartmuseum: %v", err) - } - url = url + "/health" - timeout := 60 * time.Second - period := 10 * time.Second - checker := HTTPStatusCodeHealthChecker(http.MethodGet, url, nil, timeout, http.StatusOK) - return PeriodicHealthChecker(checker, period) -} - func notaryHealthChecker() health.Checker { url := config.InternalNotaryEndpoint() + "/_notary_server/health" timeout := 60 * time.Second @@ -203,9 +191,6 @@ func RegisterHealthCheckers() { registry["registryctl"] = registryCtlHealthChecker() registry["database"] = databaseHealthChecker() registry["redis"] = redisHealthChecker() - if config.WithChartMuseum() { - registry["chartmuseum"] = chartmuseumHealthChecker() - } if config.WithNotary() { registry["notary"] = notaryHealthChecker() } diff --git a/src/controller/replication/flow/copy_test.go b/src/controller/replication/flow/copy_test.go index 369fbb9394a9..e0411f9d45f0 100644 --- a/src/controller/replication/flow/copy_test.go +++ b/src/controller/replication/flow/copy_test.go @@ -42,16 +42,6 @@ func (c *copyFlowTestSuite) TestRun() { }, }, nil) adp.On("FetchArtifacts", mock.Anything).Return([]*model.Resource{ - { - Type: model.ResourceTypeChart, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: "library/hello-world", - }, - Vtags: []string{"latest"}, - }, - Override: false, - }, { Type: model.ResourceTypeArtifact, Metadata: &model.ResourceMetadata{ diff --git a/src/controller/replication/flow/stage.go b/src/controller/replication/flow/stage.go index 1e50f37f9ca9..803d6cc26007 100644 --- a/src/controller/replication/flow/stage.go +++ b/src/controller/replication/flow/stage.go @@ -56,57 +56,16 @@ func initialize(policy *repctlmodel.Policy) (adp.Adapter, adp.Adapter, error) { // fetch resources from the source registry func fetchResources(adapter adp.Adapter, policy *repctlmodel.Policy) ([]*model.Resource, error) { - var resTypes []string - for _, filter := range policy.Filters { - if filter.Type == model.FilterTypeResource { - resTypes = append(resTypes, filter.Value.(string)) - } - } - if len(resTypes) == 0 { - info, err := adapter.Info() - if err != nil { - return nil, fmt.Errorf("failed to get the adapter info: %v", err) - } - resTypes = append(resTypes, info.SupportedResourceTypes...) - } - - fetchArtifact := false - fetchChart := false - for _, resType := range resTypes { - if resType == model.ResourceTypeChart { - fetchChart = true - continue - } - fetchArtifact = true - } - var resources []*model.Resource - // artifacts - if fetchArtifact { - reg, ok := adapter.(adp.ArtifactRegistry) - if !ok { - return nil, fmt.Errorf("the adapter doesn't implement the ArtifactRegistry interface") - } - res, err := reg.FetchArtifacts(policy.Filters) - if err != nil { - return nil, fmt.Errorf("failed to fetch artifacts: %v", err) - } - resources = append(resources, res...) - log.Debug("fetch artifacts completed") + reg, ok := adapter.(adp.ArtifactRegistry) + if !ok { + return nil, fmt.Errorf("the adapter doesn't implement the ArtifactRegistry interface") } - // charts - if fetchChart { - reg, ok := adapter.(adp.ChartRegistry) - if !ok { - return nil, fmt.Errorf("the adapter doesn't implement the ChartRegistry interface") - } - res, err := reg.FetchCharts(policy.Filters) - if err != nil { - return nil, fmt.Errorf("failed to fetch charts: %v", err) - } - resources = append(resources, res...) - log.Debug("fetch charts completed") + res, err := reg.FetchArtifacts(policy.Filters) + if err != nil { + return nil, fmt.Errorf("failed to fetch artifacts: %v", err) } + resources = append(resources, res...) log.Debug("fetch resources from the source registry completed") return resources, nil diff --git a/src/controller/replication/flow/stage_test.go b/src/controller/replication/flow/stage_test.go index d558e7b33692..3ff99a209860 100644 --- a/src/controller/replication/flow/stage_test.go +++ b/src/controller/replication/flow/stage_test.go @@ -72,7 +72,7 @@ func (s *stageTestSuite) TestFetchResources() { func (s *stageTestSuite) TestAssembleSourceResources() { resources := []*model.Resource{ { - Type: model.ResourceTypeChart, + Type: model.ResourceTypeArtifact, Metadata: &model.ResourceMetadata{ Repository: &model.Repository{ Name: "library/hello-world", @@ -95,7 +95,7 @@ func (s *stageTestSuite) TestAssembleSourceResources() { func (s *stageTestSuite) TestAssembleDestinationResources() { resources := []*model.Resource{ { - Type: model.ResourceTypeChart, + Type: model.ResourceTypeArtifact, Metadata: &model.ResourceMetadata{ Repository: &model.Repository{ Name: "library/hello-world", @@ -114,7 +114,7 @@ func (s *stageTestSuite) TestAssembleDestinationResources() { res, err := assembleDestinationResources(resources, policy, "") s.Require().Nil(err) s.Len(res, 1) - s.Equal(model.ResourceTypeChart, res[0].Type) + s.Equal(model.ResourceTypeArtifact, res[0].Type) s.Equal("test/hello-world", res[0].Metadata.Repository.Name) s.Equal(1, len(res[0].Metadata.Vtags)) s.Equal("latest", res[0].Metadata.Vtags[0]) diff --git a/src/controller/replication/transfer/chart/transfer.go b/src/controller/replication/transfer/chart/transfer.go deleted file mode 100644 index 8013b026be06..000000000000 --- a/src/controller/replication/transfer/chart/transfer.go +++ /dev/null @@ -1,199 +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 chart - -import ( - "errors" - - trans "github.com/goharbor/harbor/src/controller/replication/transfer" - "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/pkg/reg/adapter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func init() { - if err := trans.RegisterFactory(model.ResourceTypeChart, factory); err != nil { - log.Errorf("failed to register transfer factory: %v", err) - } -} - -func factory(logger trans.Logger, stopFunc trans.StopFunc) (trans.Transfer, error) { - return &transfer{ - logger: logger, - isStopped: stopFunc, - }, nil -} - -type chart struct { - name string - version string - contentURL string -} - -type transfer struct { - logger trans.Logger - isStopped trans.StopFunc - src adapter.ChartRegistry - dst adapter.ChartRegistry -} - -func (t *transfer) Transfer(src *model.Resource, dst *model.Resource, opts *trans.Options) error { - // initialize - if err := t.initialize(src, dst); err != nil { - return err - } - - // delete the chart on destination registry - if dst.Deleted { - return t.delete(&chart{ - name: dst.Metadata.Repository.Name, - version: dst.Metadata.Artifacts[0].Tags[0], - }) - } - - var contentURL string - if len(src.ExtendedInfo) > 0 && src.ExtendedInfo["contentURL"] != nil { - contentURL = src.ExtendedInfo["contentURL"].(string) - } - - srcChart := &chart{ - name: src.Metadata.Repository.Name, - version: src.Metadata.Artifacts[0].Tags[0], - contentURL: contentURL, - } - dstChart := &chart{ - name: dst.Metadata.Repository.Name, - version: dst.Metadata.Artifacts[0].Tags[0], - } - // copy the chart from source registry to the destination - return t.copy(srcChart, dstChart, dst.Override, opts) -} - -func (t *transfer) initialize(src, dst *model.Resource) error { - // create client for source registry - srcReg, err := createRegistry(src.Registry) - if err != nil { - t.logger.Errorf("failed to create client for source registry: %v", err) - return err - } - t.src = srcReg - t.logger.Infof("client for source registry [type: %s, URL: %s, insecure: %v] created", - src.Registry.Type, src.Registry.URL, src.Registry.Insecure) - - // create client for destination registry - dstReg, err := createRegistry(dst.Registry) - if err != nil { - t.logger.Errorf("failed to create client for destination registry: %v", err) - return err - } - t.dst = dstReg - t.logger.Infof("client for destination registry [type: %s, URL: %s, insecure: %v] created", - dst.Registry.Type, dst.Registry.URL, dst.Registry.Insecure) - - return nil -} - -func createRegistry(reg *model.Registry) (adapter.ChartRegistry, error) { - factory, err := adapter.GetFactory(reg.Type) - if err != nil { - return nil, err - } - ad, err := factory.Create(reg) - if err != nil { - return nil, err - } - registry, ok := ad.(adapter.ChartRegistry) - if !ok { - return nil, errors.New("the adapter doesn't implement the \"ChartRegistry\" interface") - } - return registry, nil -} - -func (t *transfer) shouldStop() bool { - isStopped := t.isStopped() - if isStopped { - t.logger.Info("the job is stopped") - } - return isStopped -} - -func (t *transfer) copy(src, dst *chart, override bool, opts *trans.Options) error { - if t.shouldStop() { - return nil - } - t.logger.Infof("copying %s:%s(source registry) to %s:%s(destination registry)...", - src.name, src.version, dst.name, dst.version) - - // check the existence of the chart on the destination registry - exist, err := t.dst.ChartExist(dst.name, dst.version) - if err != nil { - t.logger.Errorf("failed to check the existence of chart %s:%s on the destination registry: %v", dst.name, dst.version, err) - return err - } - if exist { - // the same name chart exists, but not allowed to override - if !override { - t.logger.Warningf("the same name chart %s:%s exists on the destination registry, but the \"override\" is set to false, skip", - dst.name, dst.version) - return nil - } - // the same name chart exists, but allowed to override - t.logger.Warningf("the same name chart %s:%s exists on the destination registry and the \"override\" is set to true, continue...", - dst.name, dst.version) - } - - // copy the chart between the source and destination registries - chart, err := t.src.DownloadChart(src.name, src.version, src.contentURL) - if err != nil { - t.logger.Errorf("failed to download the chart %s:%s: %v", src.name, src.version, err) - return err - } - if opts.Speed > 0 { - t.logger.Infof("limit network speed at %d kb/s", opts.Speed) - chart = trans.NewReader(chart, opts.Speed) - } - defer chart.Close() - - if err = t.dst.UploadChart(dst.name, dst.version, chart); err != nil { - t.logger.Errorf("failed to upload the chart %s:%s: %v", dst.name, dst.version, err) - return err - } - - t.logger.Infof("copy %s:%s(source registry) to %s:%s(destination registry) completed", - src.name, src.version, dst.name, dst.version) - - return nil -} - -func (t *transfer) delete(chart *chart) error { - exist, err := t.dst.ChartExist(chart.name, chart.version) - if err != nil { - t.logger.Errorf("failed to check the existence of chart %s:%s on the destination registry: %v", chart.name, chart.version, err) - return err - } - if !exist { - t.logger.Infof("the chart %s:%s doesn't exist on the destination registry, skip", - chart.name, chart.version) - return nil - } - - t.logger.Infof("deleting the chart %s:%s on the destination registry...", chart.name, chart.version) - if err := t.dst.DeleteChart(chart.name, chart.version); err != nil { - t.logger.Errorf("failed to delete the chart %s:%s on the destination registry: %v", chart.name, chart.version, err) - return err - } - t.logger.Infof("delete the chart %s:%s on the destination registry completed", chart.name, chart.version) - return nil -} diff --git a/src/controller/replication/transfer/chart/transfer_test.go b/src/controller/replication/transfer/chart/transfer_test.go deleted file mode 100644 index 9d70fd87b897..000000000000 --- a/src/controller/replication/transfer/chart/transfer_test.go +++ /dev/null @@ -1,117 +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 chart - -import ( - "bytes" - "io" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - trans "github.com/goharbor/harbor/src/controller/replication/transfer" - "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -type fakeRegistry struct{} - -func (f *fakeRegistry) FetchCharts(filters []*model.Filter) ([]*model.Resource, error) { - return []*model.Resource{ - { - Type: model.ResourceTypeChart, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: "library/harbor", - }, - Vtags: []string{"0.2.0"}, - }, - }, - }, nil -} -func (f *fakeRegistry) ChartExist(name, version string) (bool, error) { - return true, nil -} -func (f *fakeRegistry) DownloadChart(name, version, contentURL string) (io.ReadCloser, error) { - r := io.NopCloser(bytes.NewReader([]byte{'a'})) - return r, nil -} -func (f *fakeRegistry) UploadChart(name, version string, chart io.Reader) error { - return nil -} -func (f *fakeRegistry) DeleteChart(name, version string) error { - return nil -} - -func TestFactory(t *testing.T) { - tr, err := factory(nil, nil) - require.Nil(t, err) - _, ok := tr.(trans.Transfer) - assert.True(t, ok) -} - -func TestShouldStop(t *testing.T) { - // should stop - stopFunc := func() bool { return true } - tr := &transfer{ - logger: log.DefaultLogger(), - isStopped: stopFunc, - } - assert.True(t, tr.shouldStop()) - - // should not stop - stopFunc = func() bool { return false } - tr = &transfer{ - isStopped: stopFunc, - } - assert.False(t, tr.shouldStop()) -} - -func TestCopy(t *testing.T) { - stopFunc := func() bool { return false } - transfer := &transfer{ - logger: log.DefaultLogger(), - isStopped: stopFunc, - src: &fakeRegistry{}, - dst: &fakeRegistry{}, - } - src := &chart{ - name: "library/harbor", - version: "0.2.0", - } - dst := &chart{ - name: "dest/harbor", - version: "0.2.0", - } - err := transfer.copy(src, dst, true, trans.NewOptions()) - assert.Nil(t, err) -} - -func TestDelete(t *testing.T) { - stopFunc := func() bool { return false } - transfer := &transfer{ - logger: log.DefaultLogger(), - isStopped: stopFunc, - src: &fakeRegistry{}, - dst: &fakeRegistry{}, - } - chart := &chart{ - name: "dest/harbor", - version: "0.2.0", - } - err := transfer.delete(chart) - assert.Nil(t, err) -} diff --git a/src/controller/systeminfo/controller.go b/src/controller/systeminfo/controller.go index 111ad9bfb06f..e7124eb70252 100644 --- a/src/controller/systeminfo/controller.go +++ b/src/controller/systeminfo/controller.go @@ -60,7 +60,6 @@ type protectedData struct { HasCARoot bool RegistryStorageProviderName string ReadOnly bool - WithChartMuseum bool NotificationEnable bool } @@ -121,7 +120,6 @@ func (c *controller) GetInfo(ctx context.Context, opt Options) (*Data, error) { res.Protected = &protectedData{ CurrentTime: time.Now(), WithNotary: config.WithNotary(), - WithChartMuseum: config.WithChartMuseum(), ReadOnly: config.ReadOnly(ctx), ExtURL: extURL, RegistryURL: registryURL, diff --git a/src/controller/systeminfo/controller_test.go b/src/controller/systeminfo/controller_test.go index b8cdee801cb4..5a80e35f58e8 100644 --- a/src/controller/systeminfo/controller_test.go +++ b/src/controller/systeminfo/controller_test.go @@ -31,7 +31,6 @@ func (s *sysInfoCtlTestSuite) SetupTest() { common.RegistryStorageProviderName: "filesystem", common.ReadOnly: false, common.NotificationEnable: false, - common.WithChartMuseum: false, common.WithNotary: true, } @@ -77,7 +76,6 @@ func (s *sysInfoCtlTestSuite) TestGetInfo() { HasCARoot: true, RegistryStorageProviderName: "filesystem", ReadOnly: false, - WithChartMuseum: false, NotificationEnable: false, }, }, diff --git a/src/core/api/api_test.go b/src/core/api/api_test.go index e255877d586e..e125ea411a78 100644 --- a/src/core/api/api_test.go +++ b/src/core/api/api_test.go @@ -19,7 +19,6 @@ import ( "io" "net/http" "net/http/httptest" - "net/url" "os" "strconv" "strings" @@ -29,7 +28,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/goharbor/harbor/src/chartserver" "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common/dao" common_http "github.com/goharbor/harbor/src/common/http" @@ -38,7 +36,6 @@ import ( "github.com/goharbor/harbor/src/pkg/member" memberModels "github.com/goharbor/harbor/src/pkg/member/models" "github.com/goharbor/harbor/src/pkg/user" - htesting "github.com/goharbor/harbor/src/testing" ) var ( @@ -315,25 +312,3 @@ func clean() { } } } - -// Provides a mock chart controller for deletable test cases -func mockChartController() (*httptest.Server, *chartserver.Controller, error) { - mockServer := httptest.NewServer(htesting.MockChartRepoHandler) - - var oldController, newController *chartserver.Controller - url, err := url.Parse(mockServer.URL) - if err == nil { - newController, err = chartserver.NewController(url) - } - - if err != nil { - mockServer.Close() - return nil, nil, err - } - - // Override current controller and keep the old one for restoring - oldController = chartController - chartController = newController - - return mockServer, oldController, nil -} diff --git a/src/core/api/base.go b/src/core/api/base.go index ce8b5430c4ff..9c5e82c4b876 100644 --- a/src/core/api/base.go +++ b/src/core/api/base.go @@ -30,7 +30,6 @@ import ( "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/controller/p2p/preheat" projectcontroller "github.com/goharbor/harbor/src/controller/project" - "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/pkg/scheduler" @@ -175,11 +174,6 @@ func (b *BaseController) PopulateUserSession(u models.User) { // Init related objects/configurations for the API controllers func Init() error { - // init chart controller - if err := initChartController(); err != nil { - return err - } - p2pPreheatCallbackFun := func(ctx context.Context, p string) error { param := &preheat.TriggerParam{} if err := json.Unmarshal([]byte(p), param); err != nil { @@ -192,18 +186,3 @@ func Init() error { return err } - -func initChartController() error { - // If chart repository is not enabled then directly return - if !config.WithChartMuseum() { - return nil - } - - chartCtl, err := initializeChartController() - if err != nil { - return err - } - - chartController = chartCtl - return nil -} diff --git a/src/core/api/chart_label.go b/src/core/api/chart_label.go deleted file mode 100644 index afa00aefccb7..000000000000 --- a/src/core/api/chart_label.go +++ /dev/null @@ -1,114 +0,0 @@ -package api - -import ( - "errors" - "fmt" - - "github.com/goharbor/harbor/src/common" - "github.com/goharbor/harbor/src/common/models" - "github.com/goharbor/harbor/src/common/rbac" - "github.com/goharbor/harbor/src/pkg/label/model" - proModels "github.com/goharbor/harbor/src/pkg/project/models" -) - -const ( - versionParam = ":version" - idParam = ":id" -) - -// ChartLabelAPI handles the requests of marking/removing labels to/from charts. -type ChartLabelAPI struct { - LabelResourceAPI - project *proModels.Project - chartFullName string -} - -// Prepare required material for follow-up actions. -func (cla *ChartLabelAPI) Prepare() { - // Super - cla.LabelResourceAPI.Prepare() - - // Check authorization - if !cla.SecurityCtx.IsAuthenticated() { - cla.SendUnAuthorizedError(errors.New("UnAuthorized")) - return - } - - project := cla.GetStringFromPath(namespaceParam) - - // Project should be a valid existing one - existingProject, err := cla.ProjectCtl.Get(cla.Context(), project) - if err != nil { - cla.SendError(err) - return - } - cla.project = existingProject - - // Check the existence of target chart - chartName := cla.GetStringFromPath(nameParam) - version := cla.GetStringFromPath(versionParam) - - if _, err = chartController.GetChartVersion(project, chartName, version); err != nil { - cla.SendNotFoundError(err) - return - } - cla.chartFullName = fmt.Sprintf("%s/%s:%s", project, chartName, version) -} - -func (cla *ChartLabelAPI) requireAccess(action rbac.Action) bool { - return cla.RequireProjectAccess(cla.project.ProjectID, action, rbac.ResourceHelmChartVersionLabel) -} - -// MarkLabel handles the request of marking label to chart. -func (cla *ChartLabelAPI) MarkLabel() { - if !cla.requireAccess(rbac.ActionCreate) { - return - } - - l := &model.Label{} - if err := cla.DecodeJSONReq(l); err != nil { - cla.SendBadRequestError(err) - return - } - - label, ok := cla.validate(l.ID, cla.project.ProjectID) - if !ok { - return - } - - label2Res := &models.ResourceLabel{ - LabelID: label.ID, - ResourceType: common.ResourceTypeChart, - ResourceName: cla.chartFullName, - } - - cla.markLabelToResource(label2Res) -} - -// RemoveLabel handles the request of removing label from chart. -func (cla *ChartLabelAPI) RemoveLabel() { - if !cla.requireAccess(rbac.ActionDelete) { - return - } - - lID, err := cla.GetInt64FromPath(idParam) - if err != nil { - cla.SendInternalServerError(err) - return - } - - label, ok := cla.exists(lID) - if !ok { - return - } - - cla.removeLabelFromResource(common.ResourceTypeChart, cla.chartFullName, label.ID) -} - -// GetLabels gets labels for the specified chart version. -func (cla *ChartLabelAPI) GetLabels() { - if !cla.requireAccess(rbac.ActionList) { - return - } - cla.getLabelsOfResource(common.ResourceTypeChart, cla.chartFullName) -} diff --git a/src/core/api/chart_label_test.go b/src/core/api/chart_label_test.go deleted file mode 100644 index 0456ca2701f0..000000000000 --- a/src/core/api/chart_label_test.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2018 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 api - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/goharbor/harbor/src/chartserver" - "github.com/goharbor/harbor/src/common" - "github.com/goharbor/harbor/src/common/api" - "github.com/goharbor/harbor/src/lib/orm" - pkg_dao "github.com/goharbor/harbor/src/pkg/label/dao" - "github.com/goharbor/harbor/src/pkg/label/model" -) - -var ( - resourceLabelAPIPath = fmt.Sprintf("/api/%s/chartrepo/library/charts/harbor/0.2.0/labels", api.APIVersion) - resourceLabelAPIPathWithFakeProject = fmt.Sprintf("/api/%s/chartrepo/not-exist/charts/harbor/0.2.0/labels", api.APIVersion) - resourceLabelAPIPathWithFakeChart = fmt.Sprintf("/api/%s/chartrepo/library/charts/not-exist/0.2.0/labels", api.APIVersion) - cProLibraryLabelID int64 - mockChartServer *httptest.Server - oldChartController *chartserver.Controller - labelDao pkg_dao.DAO -) - -func TestToStartMockChartService(t *testing.T) { - var err error - mockChartServer, oldChartController, err = mockChartController() - if err != nil { - t.Fatalf("failed to start the mock chart service: %v", err) - } - -} - -func TestAddToChart(t *testing.T) { - labelDao = pkg_dao.New() - cSysLevelLabelID, err := labelDao.Create(orm.Context(), &model.Label{ - Name: "c_sys_level_label", - Level: common.LabelLevelSystem, - }) - require.Nil(t, err) - defer labelDao.Delete(orm.Context(), cSysLevelLabelID) - - cProTestLabelID, err := labelDao.Create(orm.Context(), &model.Label{ - Name: "c_pro_test_label", - Level: common.LabelLevelUser, - Scope: common.LabelScopeProject, - ProjectID: 100, - }) - require.Nil(t, err) - defer labelDao.Delete(orm.Context(), cProTestLabelID) - - cProLibraryLabelID, err = labelDao.Create(orm.Context(), &model.Label{ - Name: "c_pro_library_label", - Level: common.LabelLevelUser, - Scope: common.LabelScopeProject, - ProjectID: 1, - }) - require.Nil(t, err) - - cases := []*codeCheckingCase{ - // 401 - { - request: &testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodPost, - }, - code: http.StatusUnauthorized, - }, - // 403 - { - request: &testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodPost, - credential: projGuest, - }, - code: http.StatusForbidden, - }, - // 500 project doesn't exist - { - request: &testingRequest{ - url: resourceLabelAPIPathWithFakeProject, - method: http.MethodPost, - credential: projDeveloper, - }, - code: http.StatusNotFound, - }, - // 404 chart doesn't exist - { - request: &testingRequest{ - url: resourceLabelAPIPathWithFakeChart, - method: http.MethodPost, - credential: projDeveloper, - }, - code: http.StatusNotFound, - }, - // 400 - { - request: &testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodPost, - credential: projDeveloper, - }, - code: http.StatusBadRequest, - }, - // 404 label doesn't exist - { - request: &testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodPost, - credential: projDeveloper, - bodyJSON: struct { - ID int64 - }{ - ID: 1000, - }, - }, - code: http.StatusNotFound, - }, - // 400 system level label - { - request: &testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodPost, - credential: projDeveloper, - bodyJSON: struct { - ID int64 - }{ - ID: cSysLevelLabelID, - }, - }, - code: http.StatusBadRequest, - }, - // 400 try to add the label of project1 to the image under project2 - { - request: &testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodPost, - credential: projDeveloper, - bodyJSON: struct { - ID int64 - }{ - ID: cProTestLabelID, - }, - }, - code: http.StatusBadRequest, - }, - // 200 - { - request: &testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodPost, - credential: projDeveloper, - bodyJSON: struct { - ID int64 - }{ - ID: cProLibraryLabelID, - }, - }, - code: http.StatusOK, - }, - } - runCodeCheckingCases(t, cases...) -} - -func TestGetOfChart(t *testing.T) { - labels := []*model.Label{} - err := handleAndParse(&testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodGet, - credential: projDeveloper, - }, &labels) - require.Nil(t, err) - require.Equal(t, 1, len(labels)) - assert.Equal(t, cProLibraryLabelID, labels[0].ID) -} - -func TestRemoveFromChart(t *testing.T) { - runCodeCheckingCases(t, &codeCheckingCase{ - request: &testingRequest{ - url: fmt.Sprintf("%s/%d", resourceLabelAPIPath, cProLibraryLabelID), - method: http.MethodDelete, - credential: projDeveloper, - }, - code: http.StatusOK, - }) - - labels := []*model.Label{} - err := handleAndParse(&testingRequest{ - url: resourceLabelAPIPath, - method: http.MethodGet, - credential: projDeveloper, - }, &labels) - require.Nil(t, err) - require.Equal(t, 0, len(labels)) -} - -func TestToStopMockChartService(t *testing.T) { - if mockChartServer != nil { - mockChartServer.Close() - } - - if oldChartController != nil { - chartController = oldChartController - } - labelDao = pkg_dao.New() - labelDao.Delete(orm.Context(), cProLibraryLabelID) -} diff --git a/src/core/api/chart_repository.go b/src/core/api/chart_repository.go deleted file mode 100755 index 77c8638c41a1..000000000000 --- a/src/core/api/chart_repository.go +++ /dev/null @@ -1,651 +0,0 @@ -package api - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/url" - "path" - "strconv" - "strings" - "time" - - "github.com/goharbor/harbor/src/chartserver" - "github.com/goharbor/harbor/src/common" - "github.com/goharbor/harbor/src/common/rbac" - rep_event "github.com/goharbor/harbor/src/controller/event/handler/replication/event" - "github.com/goharbor/harbor/src/controller/event/metadata" - "github.com/goharbor/harbor/src/controller/project" - "github.com/goharbor/harbor/src/core/label" - "github.com/goharbor/harbor/src/lib/config" - hlog "github.com/goharbor/harbor/src/lib/log" - pkg_label "github.com/goharbor/harbor/src/pkg/label" - n_event "github.com/goharbor/harbor/src/pkg/notifier/event" - "github.com/goharbor/harbor/src/pkg/reg/model" - "github.com/goharbor/harbor/src/server/middleware/orm" -) - -const ( - namespaceParam = ":repo" - nameParam = ":name" - filenameParam = ":filename" - defaultRepo = "library" - rootUploadingEndpoint = "/api/chartrepo/charts" - rootIndexEndpoint = "/chartrepo/index.yaml" - chartRepoHealthEndpoint = "/api/chartrepo/health" - - formFieldNameForChart = "chart" - formFiledNameForProv = "prov" - headerContentType = "Content-Type" - contentTypeMultipart = "multipart/form-data" - // chartPackageFileExtension is the file extension used for chart packages - chartPackageFileExtension = "tgz" -) - -// chartController is a singleton instance -var chartController *chartserver.Controller - -// GetChartController returns the chart controller -func GetChartController() *chartserver.Controller { - return chartController -} - -// ChartRepositoryAPI provides related API handlers for the chart repository APIs -type ChartRepositoryAPI struct { - // The base controller to provide common utilities - BaseController - - // For label management - labelManager *label.BaseManager - - // Keep the namespace if existing - namespace string -} - -// Prepare something for the following actions -func (cra *ChartRepositoryAPI) Prepare() { - // Call super prepare method - cra.BaseController.Prepare() - - // Try to extract namespace for parameter of path - // It may not exist - cra.namespace = strings.TrimSpace(cra.GetStringFromPath(namespaceParam)) - - // Check the existence of namespace - // Exclude the following URI - // -/index.yaml - // -/api/chartserver/health - incomingURI := cra.Ctx.Request.URL.Path - if incomingURI == rootUploadingEndpoint { - // Forward to the default repository - cra.namespace = defaultRepo - } - - if incomingURI != rootIndexEndpoint && - incomingURI != chartRepoHealthEndpoint { - if !cra.requireNamespace(cra.namespace) { - return - } - } - - // Init label manager - cra.labelManager = &label.BaseManager{ - LabelMgr: pkg_label.Mgr, - } -} - -func (cra *ChartRepositoryAPI) requireAccess(action rbac.Action, subresource ...rbac.Resource) bool { - if len(subresource) == 0 { - subresource = append(subresource, rbac.ResourceHelmChart) - } - - return cra.RequireProjectAccess(cra.namespace, action, subresource...) -} - -// GetHealthStatus handles GET /api/chartrepo/health -func (cra *ChartRepositoryAPI) GetHealthStatus() { - // Check access - if !cra.SecurityCtx.IsAuthenticated() { - cra.SendUnAuthorizedError(errors.New("unauthorized")) - return - } - - if !cra.SecurityCtx.IsSysAdmin() { - cra.SendForbiddenError(errors.New(cra.SecurityCtx.GetUsername())) - return - } - - // Directly proxy to the backend - chartController.ProxyTraffic(cra.Ctx.ResponseWriter, cra.Ctx.Request) -} - -// GetIndexByRepo handles GET /:repo/index.yaml -func (cra *ChartRepositoryAPI) GetIndexByRepo() { - // Check access - if !cra.requireAccess(rbac.ActionRead) { - return - } - - // Directly proxy to the backend - chartController.ProxyTraffic(cra.Ctx.ResponseWriter, cra.Ctx.Request) -} - -// GetIndex handles GET /index.yaml -func (cra *ChartRepositoryAPI) GetIndex() { - // Check access - if !cra.SecurityCtx.IsAuthenticated() { - cra.SendUnAuthorizedError(errors.New("unauthorized")) - return - } - - if !cra.SecurityCtx.IsSysAdmin() { - cra.SendForbiddenError(errors.New(cra.SecurityCtx.GetUsername())) - return - } - - projects, err := cra.ProjectCtl.List(cra.Context(), nil, project.Metadata(false)) - if err != nil { - cra.SendInternalServerError(err) - return - } - - namespaces := []string{} - for _, r := range projects { - namespaces = append(namespaces, r.Name) - } - - indexFile, err := chartController.GetIndexFile(namespaces) - if err != nil { - cra.SendInternalServerError(err) - return - } - - cra.WriteYamlData(indexFile) -} - -// DownloadChart handles GET /:repo/charts/:filename -func (cra *ChartRepositoryAPI) DownloadChart() { - // Check access - if !cra.requireAccess(rbac.ActionRead) { - return - } - - namespace := cra.GetStringFromPath(namespaceParam) - fileName := cra.GetStringFromPath(filenameParam) - // Add hook event to request context - cra.addDownloadChartEventContext(fileName, namespace, cra.Ctx.Request) - - // Directly proxy to the backend - chartController.ProxyTraffic(cra.Ctx.ResponseWriter, cra.Ctx.Request) -} - -// ListCharts handles GET /api/:repo/charts -func (cra *ChartRepositoryAPI) ListCharts() { - // Check access - if !cra.requireAccess(rbac.ActionList) { - return - } - - charts, err := chartController.ListCharts(cra.namespace) - if err != nil { - cra.ParseAndHandleError("fail to list charts", err) - return - } - - cra.WriteJSONData(charts) -} - -// ListChartVersions GET /api/:repo/charts/:name -func (cra *ChartRepositoryAPI) ListChartVersions() { - // Check access - if !cra.requireAccess(rbac.ActionList, rbac.ResourceHelmChartVersion) { - return - } - - chartName := cra.GetStringFromPath(nameParam) - - versions, err := chartController.GetChart(cra.namespace, chartName) - if err != nil { - cra.ParseAndHandleError("fail to get chart", err) - return - } - - // Append labels - for _, chartVersion := range versions { - labels, err := cra.labelManager.GetLabelsOfResource(common.ResourceTypeChart, chartFullName(cra.namespace, chartVersion.Name, chartVersion.Version)) - if err != nil { - cra.SendInternalServerError(err) - return - } - chartVersion.Labels = labels - } - - cra.WriteJSONData(versions) -} - -// GetChartVersion handles GET /api/:repo/charts/:name/:version -func (cra *ChartRepositoryAPI) GetChartVersion() { - // Check access - if !cra.requireAccess(rbac.ActionRead, rbac.ResourceHelmChartVersion) { - return - } - - // Get other parameters - chartName := cra.GetStringFromPath(nameParam) - version := cra.GetStringFromPath(versionParam) - - chartVersion, err := chartController.GetChartVersionDetails(cra.namespace, chartName, version) - if err != nil { - cra.ParseAndHandleError("fail to get chart version", err) - return - } - - // Append labels - labels, err := cra.labelManager.GetLabelsOfResource(common.ResourceTypeChart, chartFullName(cra.namespace, chartName, version)) - if err != nil { - cra.SendInternalServerError(err) - return - } - chartVersion.Labels = labels - - cra.WriteJSONData(chartVersion) -} - -// DeleteChartVersion handles DELETE /api/:repo/charts/:name/:version -func (cra *ChartRepositoryAPI) DeleteChartVersion() { - // Check access - if !cra.requireAccess(rbac.ActionDelete, rbac.ResourceHelmChartVersion) { - return - } - - // Get other parameters - chartName := cra.GetStringFromPath(nameParam) - version := cra.GetStringFromPath(versionParam) - - // Try to remove labels from deleting chart if existing - if err := cra.removeLabelsFromChart(chartName, version); err != nil { - cra.SendInternalServerError(err) - return - } - - if err := chartController.DeleteChartVersion(cra.namespace, chartName, version); err != nil { - cra.ParseAndHandleError("fail to delete chart version", err) - return - } - - event := &n_event.Event{} - metaData := &metadata.ChartDeleteMetaData{ - ChartMetaData: metadata.ChartMetaData{ - ProjectName: cra.namespace, - ChartName: chartName, - Versions: []string{version}, - OccurAt: time.Now(), - Operator: cra.SecurityCtx.GetUsername(), - }, - } - if err := event.Build(metaData); err == nil { - if err := event.Publish(); err != nil { - hlog.Errorf("failed to publish chart delete event: %v", err) - } - } else { - hlog.Errorf("failed to build chart delete event metadata: %v", err) - } -} - -// UploadChartVersion handles POST /api/:repo/charts -func (cra *ChartRepositoryAPI) UploadChartVersion() { - hlog.Debugf("Header of request of uploading chart: %#v, content-len=%d", cra.Ctx.Request.Header, cra.Ctx.Request.ContentLength) - - // Check access - if !cra.requireAccess(rbac.ActionCreate, rbac.ResourceHelmChartVersion) { - return - } - - // Rewrite file content if the content type is "multipart/form-data" - if isMultipartFormData(cra.Ctx.Request) { - formFiles := make([]formFile, 0) - formFiles = append(formFiles, - formFile{ - formField: formFieldNameForChart, - mustHave: true, - }, - formFile{ - formField: formFiledNameForProv, - }) - if err := cra.rewriteFileContent(formFiles, cra.Ctx.Request); err != nil { - cra.SendInternalServerError(err) - return - } - if err := cra.addEventContext(formFiles, cra.Ctx.Request); err != nil { - hlog.Errorf("Failed to add chart upload context, %v", err) - } - } - - // Directly proxy to the backend - chartController.ProxyTraffic(cra.Ctx.ResponseWriter, cra.Ctx.Request) -} - -// UploadChartProvFile handles POST /api/:repo/prov -func (cra *ChartRepositoryAPI) UploadChartProvFile() { - // Check access - if !cra.requireAccess(rbac.ActionCreate) { - return - } - - // Rewrite file content if the content type is "multipart/form-data" - if isMultipartFormData(cra.Ctx.Request) { - formFiles := make([]formFile, 0) - formFiles = append(formFiles, - formFile{ - formField: formFiledNameForProv, - mustHave: true, - }) - if err := cra.rewriteFileContent(formFiles, cra.Ctx.Request); err != nil { - cra.SendInternalServerError(err) - return - } - } - - // Directly proxy to the backend - chartController.ProxyTraffic(cra.Ctx.ResponseWriter, cra.Ctx.Request) -} - -// DeleteChart deletes all the chart versions of the specified chart. -func (cra *ChartRepositoryAPI) DeleteChart() { - // Check access - if !cra.requireAccess(rbac.ActionDelete) { - return - } - - // Get other parameters from the request - chartName := cra.GetStringFromPath(nameParam) - - // Remove labels from all the deleting chart versions under the chart - chartVersions, err := chartController.GetChart(cra.namespace, chartName) - if err != nil { - cra.ParseAndHandleError("fail to get chart", err) - return - } - - versions := []string{} - for _, chartVersion := range chartVersions { - versions = append(versions, chartVersion.Version) - if err := cra.removeLabelsFromChart(chartName, chartVersion.Version); err != nil { - cra.SendInternalServerError(err) - return - } - } - - if err := chartController.DeleteChart(cra.namespace, chartName); err != nil { - cra.SendInternalServerError(err) - return - } - - event := &n_event.Event{} - metaData := &metadata.ChartDeleteMetaData{ - ChartMetaData: metadata.ChartMetaData{ - ProjectName: cra.namespace, - ChartName: chartName, - Versions: versions, - OccurAt: time.Now(), - Operator: cra.SecurityCtx.GetUsername(), - }, - } - if err := event.Build(metaData); err == nil { - if err := event.Publish(); err != nil { - hlog.Errorf("failed to publish chart delete event: %v", err) - } - } else { - hlog.Errorf("failed to build chart delete event metadata: %v", err) - } -} - -func (cra *ChartRepositoryAPI) removeLabelsFromChart(chartName, version string) error { - // Try to remove labels from deleting chart if existing - resourceID := chartFullName(cra.namespace, chartName, version) - labels, err := cra.labelManager.GetLabelsOfResource(common.ResourceTypeChart, resourceID) - if err == nil && len(labels) > 0 { - for _, l := range labels { - if err := cra.labelManager.RemoveLabelFromResource(common.ResourceTypeChart, resourceID, l.ID); err != nil { - return err - } - } - } - - return nil -} - -// Check if there exists a valid namespace -// Return true if it does -// Return false if it does not -func (cra *ChartRepositoryAPI) requireNamespace(namespace string) bool { - // Actually, never should be like this - if len(namespace) == 0 { - cra.SendBadRequestError(errors.New(":repo should be in the request URL")) - return false - } - - existing, err := cra.ProjectCtl.Exists(cra.Context(), namespace) - if err != nil { - // Check failed with error - cra.SendInternalServerError(fmt.Errorf("failed to check existence of namespace %s with error: %s", namespace, err.Error())) - return false - } - - // Not existing - if !existing { - cra.handleProjectNotFound(namespace) - return false - } - - return true -} - -// formFile is used to represent the uploaded files in the form -type formFile struct { - // form field key contains the form file - formField string - - // flag to indicate if the file identified by the 'formField' - // must exist - mustHave bool -} - -// The func is for event based chart replication policy. -// It will add a context for uploading request with key chart_upload, and consumed by upload response. -func (cra *ChartRepositoryAPI) addEventContext(files []formFile, request *http.Request) error { - if len(files) == 0 { - return nil - } - - for _, f := range files { - if f.formField == formFieldNameForChart { - mFile, _, err := cra.GetFile(f.formField) - if err != nil { - hlog.Errorf("failed to read file content for upload event, %v", err) - return err - } - var Buf bytes.Buffer - _, err = io.Copy(&Buf, mFile) - if err != nil { - hlog.Errorf("failed to copy file content for upload event, %v", err) - return err - } - chartOpr := chartserver.ChartOperator{} - chartDetails, err := chartOpr.GetChartData(Buf.Bytes()) - if err != nil { - hlog.Errorf("failed to get chart content for upload event, %v", err) - return err - } - - extInfo := make(map[string]interface{}) - extInfo["operator"] = cra.SecurityCtx.GetUsername() - extInfo["projectName"] = cra.namespace - extInfo["chartName"] = chartDetails.Metadata.Name - - var public bool - - project, err := cra.ProjectCtl.Get(cra.Context(), cra.namespace) - if err != nil { - hlog.Errorf("failed to check the public of project %s: %v", cra.namespace, err) - public = false - } else { - public = project.IsPublic() - } - - e := &rep_event.Event{ - Type: rep_event.EventTypeChartUpload, - Resource: &model.Resource{ - Type: model.ResourceTypeChart, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: fmt.Sprintf("%s/%s", cra.namespace, chartDetails.Metadata.Name), - Metadata: map[string]interface{}{ - "public": strconv.FormatBool(public), - }, - }, - Artifacts: []*model.Artifact{ - { - Tags: []string{chartDetails.Metadata.Version}, - }, - }, - }, - ExtendedInfo: extInfo, - }, - } - *request = *(request.WithContext(context.WithValue(request.Context(), common.ChartUploadCtxKey, e))) - break - } - } - - return nil -} - -func (cra *ChartRepositoryAPI) addDownloadChartEventContext(fileName, namespace string, request *http.Request) { - chartName, version := parseChartVersionFromFilename(fileName) - event := &metadata.ChartDownloadMetaData{ - ChartMetaData: metadata.ChartMetaData{ - ProjectName: namespace, - ChartName: chartName, - Versions: []string{version}, - OccurAt: time.Now(), - Operator: cra.SecurityCtx.GetUsername(), - }, - } - *request = *(request.WithContext(context.WithValue(request.Context(), common.ChartDownloadCtxKey, event))) -} - -// If the files are uploaded with multipart/form-data mimetype, beego will extract the data -// from the request automatically. Then the request passed to the backend server with proxying -// way will have empty content. -// This method will refill the requests with file content. -func (cra *ChartRepositoryAPI) rewriteFileContent(files []formFile, request *http.Request) error { - if len(files) == 0 { - return nil // no files, early return - } - - var body bytes.Buffer - w := multipart.NewWriter(&body) - defer func() { - if err := w.Close(); err != nil { - // Just log it - hlog.Errorf("Failed to defer close multipart writer with error: %s", err.Error()) - } - }() - - // Process files by key one by one - for _, f := range files { - mFile, mHeader, err := cra.GetFile(f.formField) - - // Handle error case by case - if err != nil { - formatedErr := fmt.Errorf("get file content with multipart header from key '%s' failed with error: %s", f.formField, err.Error()) - if f.mustHave || err != http.ErrMissingFile { - return formatedErr - } - - // Error can be ignored, just log it - hlog.Warning(formatedErr.Error()) - continue - } - - fw, err := w.CreateFormFile(f.formField, mHeader.Filename) - if err != nil { - return fmt.Errorf("create form file with multipart header failed with error: %s", err.Error()) - } - - _, err = io.Copy(fw, mFile) - if err != nil { - return fmt.Errorf("copy file stream in multipart form data failed with error: %s", err.Error()) - } - } - - request.Header.Set(headerContentType, w.FormDataContentType()) - request.ContentLength = -1 - request.Body = io.NopCloser(&body) - - return nil -} - -// Initialize the chart service controller -func initializeChartController() (*chartserver.Controller, error) { - addr, err := config.GetChartMuseumEndpoint() - if err != nil { - return nil, fmt.Errorf("failed to get the endpoint URL of chart storage server: %s", err.Error()) - } - - addr = strings.TrimSuffix(addr, "/") - url, err := url.Parse(addr) - if err != nil { - return nil, errors.New("endpoint URL of chart storage server is malformed") - } - - controller, err := chartserver.NewController(url, orm.Middleware()) - if err != nil { - return nil, errors.New("failed to initialize chart API controller") - } - - hlog.Debugf("Chart storage server is set to %s", url.String()) - hlog.Info("API controller for chart repository server is successfully initialized") - - return controller, nil -} - -// Check if the request content type is "multipart/form-data" -func isMultipartFormData(req *http.Request) bool { - return strings.Contains(req.Header.Get(headerContentType), contentTypeMultipart) -} - -// Return the chart full name -func chartFullName(namespace, chartName, version string) string { - if strings.HasPrefix(chartName, "http") { - return fmt.Sprintf("%s:%s", chartName, version) - } - return fmt.Sprintf("%s/%s:%s", namespace, chartName, version) -} - -// parseChartVersionFromFilename parse chart and version from file name -func parseChartVersionFromFilename(filename string) (string, string) { - noExt := strings.TrimSuffix(path.Base(filename), fmt.Sprintf(".%s", chartPackageFileExtension)) - parts := strings.Split(noExt, "-") - lastIndex := len(parts) - 1 - name := parts[0] - version := "" - - for idx := lastIndex; idx >= 1; idx-- { - if _, err := strconv.Atoi(string(parts[idx][0])); err == nil { // see if this part looks like a version (starts w int) - version = strings.Join(parts[idx:], "-") - name = strings.Join(parts[:idx], "-") - break - } - } - if version == "" { // no parts looked like a real version, just take everything after last hyphen - name = strings.Join(parts[:lastIndex], "-") - version = parts[lastIndex] - } - return name, version -} diff --git a/src/core/api/chart_repository_test.go b/src/core/api/chart_repository_test.go deleted file mode 100644 index fc3a66862bc6..000000000000 --- a/src/core/api/chart_repository_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package api - -import ( - "context" - "errors" - "net/http" - "net/http/httptest" - "testing" - - bcontext "github.com/beego/beego/v2/server/web/context" - - "github.com/goharbor/harbor/src/chartserver" - proModels "github.com/goharbor/harbor/src/pkg/project/models" - projecttesting "github.com/goharbor/harbor/src/testing/controller/project" - "github.com/goharbor/harbor/src/testing/mock" -) - -var ( - crOldController *chartserver.Controller - crMockServer *httptest.Server -) - -func TestIsMultipartFormData(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, "/api/chartrepo/charts", nil) - - req.Header.Set(headerContentType, "application/json") - if isMultipartFormData(req) { - t.Fatal("expect false result but got true") - } - - req.Header.Set(headerContentType, contentTypeMultipart) - if !isMultipartFormData(req) { - t.Fatalf("expect %s result but got %s", contentTypeMultipart, req.Header.Get(headerContentType)) - } -} - -// Test namespace cheking -func TestRequireNamespace(t *testing.T) { - chartAPI := &ChartRepositoryAPI{} - chartAPI.Ctx = bcontext.NewContext() - chartAPI.Ctx.Request = httptest.NewRequest("GET", "/", nil) - - projectCtl := &projecttesting.Controller{} - chartAPI.ProjectCtl = projectCtl - - mock.OnAnything(projectCtl, "List").Return([]*proModels.Project{ - {ProjectID: 0, Name: "library"}, - {ProjectID: 1, Name: "repo2"}, - }, nil) - - mock.OnAnything(projectCtl, "Exists").Return( - func(ctx context.Context, projectIDOrName interface{}) bool { - if projectIDOrName == nil { - return false - } - - if ns, ok := projectIDOrName.(string); ok { - if ns == "library" { - return true - } - - return false - } - - return false - }, - func(ctx context.Context, projectIDOrName interface{}) error { - if projectIDOrName == nil { - return errors.New("nil projectIDOrName") - } - - if _, ok := projectIDOrName.(string); ok { - return nil - } - - return errors.New("unknown type of projectIDOrName") - }, - ) - - if !chartAPI.requireNamespace("library") { - t.Fatal("expect namespace 'library' existing but got false") - } -} - -// Prepare -func TestPrepareEnv(t *testing.T) { - var err error - crMockServer, crOldController, err = mockChartController() - if err != nil { - t.Fatalf("Failed to start mock chart service with error: %s", err) - } -} - -// Test get health -func TestGetHealthStatus(t *testing.T) { - status := make(map[string]interface{}) - err := handleAndParse(&testingRequest{ - url: "/api/chartrepo/health", - method: http.MethodGet, - credential: sysAdmin, - }, &status) - - if err != nil { - t.Fatal(err) - } - - if _, ok := status["health"]; !ok { - t.Fatal("expect 'health' but got nil") - } -} - -// Test get index by repo -func TestGetIndexByRepo(t *testing.T) { - runCodeCheckingCases(t, &codeCheckingCase{ - request: &testingRequest{ - url: "/chartrepo/library/index.yaml", - method: http.MethodGet, - credential: projDeveloper, - }, - code: http.StatusOK, - }) -} - -// Test get index -func TestGetIndex(t *testing.T) { - runCodeCheckingCases(t, &codeCheckingCase{ - request: &testingRequest{ - url: "/chartrepo/index.yaml", - method: http.MethodGet, - credential: sysAdmin, - }, - code: http.StatusOK, - }) -} - -// Test download chart -func TestDownloadChart(t *testing.T) { - runCodeCheckingCases(t, &codeCheckingCase{ - request: &testingRequest{ - url: "/chartrepo/library/charts/harbor-0.2.0.tgz", - method: http.MethodGet, - credential: projDeveloper, - }, - code: http.StatusOK, - }) -} - -// Test get charts -func TesListCharts(t *testing.T) { - charts := make([]*chartserver.ChartInfo, 0) - err := handleAndParse(&testingRequest{ - url: "/api/chartrepo/library/charts", - method: http.MethodGet, - credential: projAdmin, - }, &charts) - - if err != nil { - t.Fatal(err) - } - - if len(charts) != 2 { - t.Fatalf("expect 2 charts but got %d", len(charts)) - } -} - -// Test get chart versions -func TestListChartVersions(t *testing.T) { - chartVersions := make(chartserver.ChartVersions, 0) - err := handleAndParse(&testingRequest{ - url: "/api/chartrepo/library/charts/harbor", - method: http.MethodGet, - credential: projAdmin, - }, &chartVersions) - - if err != nil { - t.Fatal(err) - } - - if len(chartVersions) != 2 { - t.Fatalf("expect 2 chart versions but got %d", len(chartVersions)) - } -} - -// Test get chart version details -func TestGetChartVersion(t *testing.T) { - chartV := &chartserver.ChartVersionDetails{} - err := handleAndParse(&testingRequest{ - url: "/api/chartrepo/library/charts/harbor/0.2.0", - method: http.MethodGet, - credential: projAdmin, - }, chartV) - - if err != nil { - t.Fatal(err) - } - - if chartV.Metadata.Name != "harbor" { - t.Fatalf("expect get chart 'harbor' but got %s", chartV.Metadata.Name) - } - - if chartV.Metadata.Version != "0.2.0" { - t.Fatalf("expect get chart version '0.2.0' but got %s", chartV.Metadata.Version) - } -} - -// Test delete chart version -func TestDeleteChartVersion(t *testing.T) { - runCodeCheckingCases(t, &codeCheckingCase{ - request: &testingRequest{ - url: "/api/chartrepo/library/charts/harbor/0.2.1", - method: http.MethodDelete, - credential: projAdmin, - }, - code: http.StatusOK, - }) -} - -// Test delete chart -func TestDeleteChart(t *testing.T) { - runCodeCheckingCases(t, &codeCheckingCase{ - request: &testingRequest{ - url: "/api/chartrepo/library/charts/harbor", - method: http.MethodDelete, - credential: projAdmin, - }, - code: http.StatusOK, - }) -} - -// Clear -func TestClearEnv(t *testing.T) { - crMockServer.Close() - chartController = crOldController -} diff --git a/src/core/api/harborapi_test.go b/src/core/api/harborapi_test.go index fe35bc898c79..1f674cecf2d1 100644 --- a/src/core/api/harborapi_test.go +++ b/src/core/api/harborapi_test.go @@ -25,7 +25,6 @@ import ( "github.com/beego/beego/v2/server/web" "github.com/dghubble/sling" - "github.com/goharbor/harbor/src/common/api" "github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/job/test" testutils "github.com/goharbor/harbor/src/common/utils/test" @@ -87,27 +86,6 @@ func init() { web.BConfig.WebConfig.Session.SessionOn = true web.TestBeegoInit(apppath) - // Charts are controlled under projects - chartRepositoryAPIType := &ChartRepositoryAPI{} - web.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus") - web.Router("/api/chartrepo/:repo/charts", chartRepositoryAPIType, "get:ListCharts") - web.Router("/api/chartrepo/:repo/charts/:name", chartRepositoryAPIType, "get:ListChartVersions") - web.Router("/api/chartrepo/:repo/charts/:name", chartRepositoryAPIType, "delete:DeleteChart") - web.Router("/api/chartrepo/:repo/charts/:name/:version", chartRepositoryAPIType, "get:GetChartVersion") - web.Router("/api/chartrepo/:repo/charts/:name/:version", chartRepositoryAPIType, "delete:DeleteChartVersion") - web.Router("/api/chartrepo/:repo/charts", chartRepositoryAPIType, "post:UploadChartVersion") - web.Router("/api/chartrepo/:repo/prov", chartRepositoryAPIType, "post:UploadChartProvFile") - web.Router("/api/chartrepo/charts", chartRepositoryAPIType, "post:UploadChartVersion") - - // Repository services - web.Router("/chartrepo/:repo/index.yaml", chartRepositoryAPIType, "get:GetIndexByRepo") - web.Router("/chartrepo/index.yaml", chartRepositoryAPIType, "get:GetIndex") - web.Router("/chartrepo/:repo/charts/:filename", chartRepositoryAPIType, "get:DownloadChart") - // Labels for chart - chartLabelAPIType := &ChartLabelAPI{} - web.Router("/api/"+api.APIVersion+"/chartrepo/:repo/charts/:name/:version/labels", chartLabelAPIType, "get:GetLabels;post:MarkLabel") - web.Router("/api/"+api.APIVersion+"/chartrepo/:repo/charts/:name/:version/labels/:id([0-9]+)", chartLabelAPIType, "delete:RemoveLabel") - web.Router("/api/internal/syncquota", &InternalAPI{}, "post:SyncQuota") // Init user Info diff --git a/src/jobservice/job/impl/replication/replication.go b/src/jobservice/job/impl/replication/replication.go index 4a016617e158..0387485dcfa6 100644 --- a/src/jobservice/job/impl/replication/replication.go +++ b/src/jobservice/job/impl/replication/replication.go @@ -16,14 +16,41 @@ package replication import ( "encoding/json" + "fmt" "github.com/goharbor/harbor/src/controller/replication/transfer" - // import chart transfer - _ "github.com/goharbor/harbor/src/controller/replication/transfer/chart" // import image transfer _ "github.com/goharbor/harbor/src/controller/replication/transfer/image" "github.com/goharbor/harbor/src/jobservice/job" + // register the AliACR adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/aliacr" + // register the AwsEcr adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/awsecr" + // register the AzureAcr adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/azurecr" + // register the DockerHub adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/dockerhub" + // register the DTR adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/dtr" + // register the Github Container Registry adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/githubcr" + // register the GitLab adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/gitlab" + // register the Google Gcr adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/googlegcr" + // register the Harbor adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/harbor" + // register the huawei adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/huawei" + // register the Jfrog Artifactory adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/jfrog" + // register the Native adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/native" + // register the Quay.io adapter + _ "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/model" ) diff --git a/src/jobservice/job/impl/replication/replication_test.go b/src/jobservice/job/impl/replication/replication_test.go index be80389c07cd..90e472e53d64 100644 --- a/src/jobservice/job/impl/replication/replication_test.go +++ b/src/jobservice/job/impl/replication/replication_test.go @@ -49,21 +49,6 @@ func TestParseParam(t *testing.T) { assert.Equal(t, "tom", p.Name) } -func TestParseParams(t *testing.T) { - params := map[string]interface{}{ - "src_resource": `{"type":"chart"}`, - "dst_resource": `{"type":"chart"}`, - "speed": 1024, - "copy_by_chunk": true, - } - res, dst, opts, err := parseParams(params) - require.Nil(t, err) - assert.Equal(t, "chart", string(res.Type)) - assert.Equal(t, "chart", string(dst.Type)) - assert.Equal(t, int32(1024), opts.Speed) - assert.True(t, opts.CopyByChunk) -} - func TestMaxFails(t *testing.T) { rep := &Replication{} assert.Equal(t, uint(3), rep.MaxFails()) diff --git a/src/lib/config/metadata/metadata_test.go b/src/lib/config/metadata/metadata_test.go index 6d0bcd6bfd57..b82fc86bbb0d 100644 --- a/src/lib/config/metadata/metadata_test.go +++ b/src/lib/config/metadata/metadata_test.go @@ -24,7 +24,6 @@ func TestCfgMetaData_InitFromArray(t *testing.T) { testArray := []Item{ {Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", Name: common.AdminInitialPassword, ItemType: &PasswordType{}, Editable: true}, {Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", Name: common.AUTHMode, ItemType: &StringType{}, Editable: false}, - {Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", Name: common.ChartRepoURL, ItemType: &StringType{}, Editable: false}, } curInst := Instance() curInst.initFromArray(testArray) diff --git a/src/lib/config/metadata/metadatalist.go b/src/lib/config/metadata/metadatalist.go index a9acfc070999..7b2b459508a0 100644 --- a/src/lib/config/metadata/metadatalist.go +++ b/src/lib/config/metadata/metadatalist.go @@ -65,8 +65,8 @@ var ( {Name: common.AdminInitialPassword, Scope: SystemScope, Group: BasicGroup, EnvKey: "HARBOR_ADMIN_PASSWORD", DefaultValue: "", ItemType: &PasswordType{}, Editable: true}, {Name: common.AUTHMode, Scope: UserScope, Group: BasicGroup, EnvKey: "AUTH_MODE", DefaultValue: "db_auth", ItemType: &AuthModeType{}, Editable: false, Description: `The auth mode of current system, such as "db_auth", "ldap_auth", "oidc_auth"`}, + {Name: common.PrimaryAuthMode, Scope: UserScope, Group: BasicGroup, EnvKey: "PRIMARY_AUTH_MODE", DefaultValue: "false", ItemType: &BoolType{}, Description: `Use current auth mode as a primary one`}, - {Name: common.ChartRepoURL, Scope: SystemScope, Group: BasicGroup, EnvKey: "CHART_REPOSITORY_URL", DefaultValue: "http://chartmuseum:9999", ItemType: &StringType{}, Editable: false}, {Name: common.TrivyAdapterURL, Scope: SystemScope, Group: TrivyGroup, EnvKey: "TRIVY_ADAPTER_URL", DefaultValue: "http://trivy-adapter:8080", ItemType: &StringType{}, Editable: false}, @@ -144,7 +144,6 @@ var ( {Name: common.OIDCAutoOnboard, Scope: UserScope, Group: OIDCGroup, DefaultValue: "false", ItemType: &BoolType{}, Description: `Auto onboard the OIDC user`}, {Name: common.OIDCExtraRedirectParms, Scope: UserScope, Group: OIDCGroup, DefaultValue: "{}", ItemType: &StringToStringMapType{}, Description: `Extra parameters to add when redirect request to OIDC provider`}, - {Name: common.WithChartMuseum, Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_CHARTMUSEUM", DefaultValue: "false", ItemType: &BoolType{}, Editable: true}, {Name: common.WithTrivy, Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_TRIVY", DefaultValue: "false", ItemType: &BoolType{}, Editable: true}, {Name: common.WithNotary, Scope: SystemScope, Group: BasicGroup, EnvKey: "WITH_NOTARY", DefaultValue: "false", ItemType: &BoolType{}, Editable: true}, // the unit of expiration is days diff --git a/src/lib/config/systemconfig.go b/src/lib/config/systemconfig.go index b6dda1826780..bb122eeeab42 100644 --- a/src/lib/config/systemconfig.go +++ b/src/lib/config/systemconfig.go @@ -30,7 +30,6 @@ package config import ( "context" - "errors" "os" "strconv" "strings" @@ -152,21 +151,6 @@ func WithTrivy() bool { return DefaultMgr().Get(backgroundCtx, common.WithTrivy).GetBool() } -// WithChartMuseum returns a bool to indicate if chartmuseum is deployed with Harbor. -func WithChartMuseum() bool { - return DefaultMgr().Get(backgroundCtx, common.WithChartMuseum).GetBool() -} - -// GetChartMuseumEndpoint returns the endpoint of the chartmuseum service -// otherwise an non nil error is returned -func GetChartMuseumEndpoint() (string, error) { - chartEndpoint := strings.TrimSpace(DefaultMgr().Get(backgroundCtx, common.ChartRepoURL).GetString()) - if len(chartEndpoint) == 0 { - return "", errors.New("empty chartmuseum endpoint") - } - return chartEndpoint, nil -} - // ExtEndpoint returns the external URL of Harbor: protocol://host:port func ExtEndpoint() (string, error) { return DefaultMgr().Get(backgroundCtx, common.ExtEndpoint).GetString(), nil diff --git a/src/lib/config/test/userconfig_test.go b/src/lib/config/test/userconfig_test.go index 3044058f4296..db5278a1313b 100644 --- a/src/lib/config/test/userconfig_test.go +++ b/src/lib/config/test/userconfig_test.go @@ -42,9 +42,8 @@ func TestConfig(t *testing.T) { dao.PrepareTestData([]string{"delete from properties where k='scan_all_policy'"}, []string{}) defaultCACertPath = path.Join(currPath(), "test", "ca.crt") c := map[string]interface{}{ - common.WithTrivy: false, - common.WithChartMuseum: false, - common.WithNotary: false, + common.WithTrivy: false, + common.WithNotary: false, } Init() diff --git a/src/lib/selector/candidate.go b/src/lib/selector/candidate.go index a9bb06212d9c..a6a0eb7dc37f 100644 --- a/src/lib/selector/candidate.go +++ b/src/lib/selector/candidate.go @@ -26,8 +26,6 @@ import ( const ( // Image kind Image = "image" - // Chart kind - Chart = "chart" ) // Repository of candidate @@ -71,7 +69,7 @@ type Candidate struct { // Repository name Repository string `json:"repository"` // Kind of the candidate - // "image" or "chart" + // "image" Kind string `json:"kind"` // Tags attached with the candidate Tags []string `json:"tags"` diff --git a/src/pkg/artifact/dao/model.go b/src/pkg/artifact/dao/model.go index 4ce548619396..6f55a7b49071 100644 --- a/src/pkg/artifact/dao/model.go +++ b/src/pkg/artifact/dao/model.go @@ -30,7 +30,7 @@ func init() { // Artifact model in database type Artifact struct { ID int64 `orm:"pk;auto;column(id)"` - Type string `orm:"column(type)"` // image or chart + Type string `orm:"column(type)"` // image MediaType string `orm:"column(media_type)"` // the media type of artifact ManifestMediaType string `orm:"column(manifest_media_type)"` // the media type of manifest/index ProjectID int64 `orm:"column(project_id)"` // needed for quota diff --git a/src/pkg/artifact/model.go b/src/pkg/artifact/model.go index 3e4db351055c..64bfe77baa5f 100644 --- a/src/pkg/artifact/model.go +++ b/src/pkg/artifact/model.go @@ -31,7 +31,7 @@ import ( // for all users. type Artifact struct { ID int64 `json:"id"` - Type string `json:"type"` // image, chart, etc + Type string `json:"type"` // image, etc 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 ProjectID int64 `json:"project_id"` diff --git a/src/pkg/chart/model.go b/src/pkg/chart/model.go deleted file mode 100644 index b8009e62b045..000000000000 --- a/src/pkg/chart/model.go +++ /dev/null @@ -1,39 +0,0 @@ -package chart - -import ( - "time" - - helm_chart "helm.sh/helm/v3/pkg/chart" -) - -// VersionDetails keeps the detailed data info of the chart version -type VersionDetails struct { - Dependencies []*helm_chart.Dependency `json:"dependencies"` - Values map[string]interface{} `json:"values"` - Files map[string]string `json:"files"` - Security *SecurityReport `json:"security"` -} - -// SecurityReport keeps the info related with security -// e.g.: digital signature, vulnerability scanning etc. -type SecurityReport struct { - Signature *DigitalSignature `json:"signature"` -} - -// DigitalSignature used to indicate if the chart has been signed -type DigitalSignature struct { - Signed bool `json:"signed"` - Provenance string `json:"prov_file"` -} - -// Info keeps the information of the chart -type Info struct { - Name string `json:"name"` - TotalVersions uint32 `json:"total_versions"` - LatestVersion string `json:"latest_version"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - Icon string `json:"icon"` - Home string `json:"home"` - Deprecated bool `json:"deprecated"` -} diff --git a/src/pkg/chart/operator.go b/src/pkg/chart/operator.go deleted file mode 100644 index 0cdb3c462eef..000000000000 --- a/src/pkg/chart/operator.go +++ /dev/null @@ -1,114 +0,0 @@ -package chart - -import ( - "bytes" - "errors" - "fmt" - "strings" - - helm_chart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -var ( - // Optr is a global chart operator instance - Optr = NewOperator() -) - -const ( - readmeFileName = "README.MD" - valuesFileName = "VALUES.YAML" -) - -// Operator ... -type Operator interface { - // GetChartDetails parse the details from the provided content bytes - GetDetails(content []byte) (*VersionDetails, error) - // FetchLayer the content of layer under the repository - GetData(content []byte) (*helm_chart.Chart, error) -} - -var _ Operator = &operator{} - -// ChartOperator is designed to process the contents of -// the specified chart version to get more details -type operator struct{} - -// NewOperator returns an instance of the default chart opertaor -func NewOperator() Operator { - return &operator{} -} - -// GetDetails parse the details from the provided content bytes -func (cho *operator) GetDetails(content []byte) (*VersionDetails, error) { - chartData, err := cho.GetData(content) - if err != nil { - return nil, err - } - - // Parse the dependencies of chart - depts := make([]*helm_chart.Dependency, 0) - - // for APIVersionV2, the dependency is in the Chart.yaml - if chartData.Metadata.APIVersion == helm_chart.APIVersionV1 { - depts = chartData.Metadata.Dependencies - } - - var values map[string]interface{} - files := make(map[string]string) - // Parse values - if chartData.Values != nil { - readValue(values, "", chartData.Values) - } - - // Append other files like 'README.md' 'values.yaml' - for _, v := range chartData.Raw { - if strings.ToUpper(v.Name) == readmeFileName { - files[readmeFileName] = string(v.Data) - continue - } - if strings.ToUpper(v.Name) == valuesFileName { - files[valuesFileName] = string(v.Data) - continue - } - } - - theChart := &VersionDetails{ - Dependencies: depts, - Values: values, - Files: files, - } - - return theChart, nil -} - -// GetData returns raw data of chart -func (cho *operator) GetData(content []byte) (*helm_chart.Chart, error) { - if len(content) == 0 { - return nil, errors.New("zero content") - } - - reader := bytes.NewReader(content) - chartData, err := loader.LoadArchive(reader) - if err != nil { - return nil, err - } - - return chartData, nil -} - -// Recursively read value -func readValue(values map[string]interface{}, keyPrefix string, valueMap map[string]interface{}) { - for key, value := range values { - longKey := key - if keyPrefix != "" { - longKey = fmt.Sprintf("%s.%s", keyPrefix, key) - } - - if subValues, ok := value.(map[string]interface{}); ok { - readValue(subValues, longKey, valueMap) - } else { - valueMap[longKey] = value - } - } -} diff --git a/src/pkg/chart/opetator_test.go b/src/pkg/chart/opetator_test.go deleted file mode 100644 index 8e2d45fbba24..000000000000 --- a/src/pkg/chart/opetator_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package chart - -import ( - "testing" - - htesting "github.com/goharbor/harbor/src/testing" -) - -func TestGetChartDetails(t *testing.T) { - chartOpr := NewOperator() - _, err := chartOpr.GetDetails(htesting.HelmChartContent) - if err != nil { - t.Fatal(err) - } - - // ToDo add a v3 supported test data - // if len(chartDetails.Dependencies) == 0 { - // t.Fatal("At least 1 dependency exitsing, but we got 0 now") - // } - - // if len(chartDetails.Values) == 0 { - // t.Fatal("At least 1 value existing, but we got 0 now") - // } - - // if chartDetails.Values["adminserver.adminPassword"] != "Harbor12345" { - // t.Fatalf("The value of 'adminserver.adminPassword' should be 'Harbor12345' but we got '%s' now", chartDetails.Values["adminserver.adminPassword"]) - // } -} diff --git a/src/pkg/clients/core/chart.go b/src/pkg/clients/core/chart.go deleted file mode 100644 index 75d8c39836f2..000000000000 --- a/src/pkg/clients/core/chart.go +++ /dev/null @@ -1,40 +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 core - -import ( - "fmt" - - "github.com/goharbor/harbor/src/chartserver" -) - -func (c *client) ListAllCharts(project, repository string) ([]*chartserver.ChartVersion, error) { - url := c.buildURL(fmt.Sprintf("/api/chartrepo/%s/charts/%s", project, repository)) - var charts []*chartserver.ChartVersion - if err := c.httpclient.Get(url, &charts); err != nil { - return nil, err - } - return charts, nil -} - -func (c *client) DeleteChart(project, repository, version string) error { - url := c.buildURL(fmt.Sprintf("/api/chartrepo/%s/charts/%s/%s", project, repository, version)) - return c.httpclient.Delete(url) -} - -func (c *client) DeleteChartRepository(project, repository string) error { - url := c.buildURL(fmt.Sprintf("/api/chartrepo/%s/charts/%s", project, repository)) - return c.httpclient.Delete(url) -} diff --git a/src/pkg/clients/core/client.go b/src/pkg/clients/core/client.go index 91e335c7657b..efd5f75e3322 100644 --- a/src/pkg/clients/core/client.go +++ b/src/pkg/clients/core/client.go @@ -18,7 +18,6 @@ import ( "fmt" "net/http" - "github.com/goharbor/harbor/src/chartserver" chttp "github.com/goharbor/harbor/src/common/http" "github.com/goharbor/harbor/src/common/http/modifier" modelsv2 "github.com/goharbor/harbor/src/controller/artifact" @@ -29,7 +28,6 @@ import ( // and we should expand it when needed type Client interface { ArtifactClient - ChartClient } // ArtifactClient defines the methods that an image client should implement @@ -39,13 +37,6 @@ type ArtifactClient interface { DeleteArtifactRepository(project, repository string) error } -// ChartClient defines the methods that a chart client should implement -type ChartClient interface { - ListAllCharts(project, repository string) ([]*chartserver.ChartVersion, error) - DeleteChart(project, repository, version string) error - DeleteChartRepository(project, repository string) error -} - // New returns an instance of the client which is a default implement for Client func New(url string, httpclient *http.Client, modifiers ...modifier.Modifier) Client { return &client{ diff --git a/src/pkg/notification/job/dao/dao_test.go b/src/pkg/notification/job/dao/dao_test.go index 2841f4f4747c..ba8db1820bc0 100644 --- a/src/pkg/notification/job/dao/dao_test.go +++ b/src/pkg/notification/job/dao/dao_test.go @@ -99,7 +99,7 @@ func (suite *DaoTestSuite) TestGet() { id, err := suite.dao.Create(orm.Context(), &model.Job{ PolicyID: 2222, - EventType: "pushChart", + EventType: "pushImage", NotifyType: "http", Status: "pending", JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}", @@ -109,7 +109,7 @@ func (suite *DaoTestSuite) TestGet() { r, err := suite.dao.Get(orm.Context(), id) suite.Nil(err) - suite.Equal("pushChart", r.EventType) + suite.Equal("pushImage", r.EventType) } func (suite *DaoTestSuite) TestUpdate() { @@ -163,7 +163,7 @@ func (suite *DaoTestSuite) TestDeleteByPolicyID() { func (suite *DaoTestSuite) TestGetLastTriggerJobsGroupByEventType() { _, err := suite.dao.Create(orm.Context(), &model.Job{ PolicyID: 3333, - EventType: "pushChart", + EventType: "pushImage", NotifyType: "http", Status: "pending", JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}", @@ -172,7 +172,7 @@ func (suite *DaoTestSuite) TestGetLastTriggerJobsGroupByEventType() { suite.Nil(err) _, err = suite.dao.Create(orm.Context(), &model.Job{ PolicyID: 3333, - EventType: "pullChart", + EventType: "pushImage", NotifyType: "http", Status: "pending", JobDetail: "{\"type\":\"pushImage\",\"occur_at\":1563536782,\"event_data\":{\"resources\":[{\"digest\":\"sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9\",\"tag\":\"v1.0\",\"resource_url\":\"10.194.32.23/myproj/alpine:v1.0\"}],\"repository\":{\"date_created\":1563505587,\"name\":\"alpine\",\"namespace\":\"myproj\",\"repo_full_name\":\"myproj/alpine\",\"repo_type\":\"private\"}},\"operator\":\"admin\"}", diff --git a/src/pkg/notification/notification.go b/src/pkg/notification/notification.go index b151f30ccb7b..744f086fd9f0 100644 --- a/src/pkg/notification/notification.go +++ b/src/pkg/notification/notification.go @@ -52,9 +52,6 @@ func initSupportedNotifyType() { event.TopicPushArtifact, event.TopicPullArtifact, event.TopicDeleteArtifact, - event.TopicUploadChart, - event.TopicDeleteChart, - event.TopicDownloadChart, event.TopicQuotaExceed, event.TopicQuotaWarning, event.TopicScanningFailed, diff --git a/src/pkg/notification/policy/dao/dao_test.go b/src/pkg/notification/policy/dao/dao_test.go index a2e7a604cc6d..2cf9f3d023cf 100644 --- a/src/pkg/notification/policy/dao/dao_test.go +++ b/src/pkg/notification/policy/dao/dao_test.go @@ -19,7 +19,7 @@ var ( Description: "webhook test policy1 description", ProjectID: 111, TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]", - EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]", + EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"scanningFailed\",\"scanningCompleted\"]", Creator: "no one", CreationTime: time.Now(), UpdateTime: time.Now(), @@ -33,7 +33,7 @@ var ( Description: "webhook test policy2 description", ProjectID: 222, TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]", - EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]", + EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"scanningFailed\",\"scanningCompleted\"]", Creator: "no one", CreationTime: time.Now(), UpdateTime: time.Now(), @@ -47,7 +47,7 @@ var ( Description: "webhook test policy3 description", ProjectID: 333, TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]", - EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]", + EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"scanningFailed\",\"scanningCompleted\"]", Creator: "no one", CreationTime: time.Now(), UpdateTime: time.Now(), @@ -118,7 +118,7 @@ func (suite *DaoTestSuite) TestGet() { Description: "webhook test policy4 description", ProjectID: 444, TargetsDB: "[{\"type\":\"http\",\"address\":\"http://10.173.32.58:9009\",\"token\":\"xxxxxxxxx\",\"skip_cert_verify\":true}]", - EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"uploadChart\",\"deleteChart\",\"downloadChart\",\"scanningFailed\",\"scanningCompleted\"]", + EventTypesDB: "[\"pushImage\",\"pullImage\",\"deleteImage\",\"scanningFailed\",\"scanningCompleted\"]", Creator: "no one", CreationTime: time.Now(), UpdateTime: time.Now(), diff --git a/src/pkg/notification/policy/manager_test.go b/src/pkg/notification/policy/manager_test.go index 35329db42a3f..42d6d99a8c50 100644 --- a/src/pkg/notification/policy/manager_test.go +++ b/src/pkg/notification/policy/manager_test.go @@ -97,14 +97,14 @@ func (m *managerTestSuite) TestGetRelatedPolices() { Name: "policy", ProjectID: 1, Enabled: true, - EventTypesDB: "[\"PULL_IMAGE\",\"PUSH_CHART\"]", + EventTypesDB: "[\"PULL_IMAGE\",\"PUSH_IMAGE\"]", }, { ID: 2, Name: "policy", ProjectID: 1, Enabled: true, - EventTypesDB: "[\"PULL_IMAGE\",\"PUSH_CHART\"]", + EventTypesDB: "[\"PULL_IMAGE\",\"PUSH_IMAGE\"]", }, }, nil) rpers, err := m.mgr.GetRelatedPolices(context.Background(), 1, "PULL_IMAGE") diff --git a/src/pkg/project/models/project.go b/src/pkg/project/models/project.go index 8a1690f88ef7..712bc85eb599 100644 --- a/src/pkg/project/models/project.go +++ b/src/pkg/project/models/project.go @@ -50,7 +50,6 @@ type Project struct { Role int `orm:"-" json:"current_user_role_id"` RoleList []int `orm:"-" json:"current_user_role_ids"` RepoCount int64 `orm:"-" json:"repo_count"` - ChartCount uint64 `orm:"-" json:"chart_count"` Metadata map[string]string `orm:"-" json:"metadata"` CVEAllowlist allowlist.CVEAllowlist `orm:"-" json:"cve_allowlist"` RegistryID int64 `orm:"column(registry_id)" json:"registry_id"` diff --git a/src/pkg/rbac/dao/dao_test.go b/src/pkg/rbac/dao/dao_test.go index 343e949a6265..e709554ea196 100644 --- a/src/pkg/rbac/dao/dao_test.go +++ b/src/pkg/rbac/dao/dao_test.go @@ -103,14 +103,6 @@ func (suite *DaoTestSuite) preparePermissionPolicy() { suite.rbacPolicyID3 = id3 suite.Nil(err) - rp4 := &model.PermissionPolicy{ - Scope: "/project/2", - Resource: "helm-chart", - Action: "create", - } - id4, err := suite.dao.CreateRbacPolicy(orm.Context(), rp4) - suite.rbacPolicyID4 = id4 - suite.Nil(err) } func (suite *DaoTestSuite) TestCreatePermission() { diff --git a/src/pkg/reg/adapter/adapter.go b/src/pkg/reg/adapter/adapter.go index 0798b52e3ded..aadb68ea9fe9 100644 --- a/src/pkg/reg/adapter/adapter.go +++ b/src/pkg/reg/adapter/adapter.go @@ -69,15 +69,6 @@ type ArtifactRegistry interface { ListTags(repository string) (tags []string, err error) } -// ChartRegistry defines the capabilities that a chart registry should have -type ChartRegistry interface { - FetchCharts(filters []*model.Filter) ([]*model.Resource, error) - ChartExist(name, version string) (bool, error) - DownloadChart(name, version, contentURL string) (io.ReadCloser, error) - UploadChart(name, version string, chart io.Reader) error - DeleteChart(name, version string) error -} - // RegisterFactory registers one adapter factory to the registry func RegisterFactory(t string, factory Factory) error { if len(t) == 0 { diff --git a/src/pkg/reg/adapter/artifacthub/adapter.go b/src/pkg/reg/adapter/artifacthub/adapter.go deleted file mode 100644 index 1dfa0f984e9c..000000000000 --- a/src/pkg/reg/adapter/artifacthub/adapter.go +++ /dev/null @@ -1,107 +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 artifacthub - -import ( - "errors" - - "github.com/goharbor/harbor/src/lib/log" - adp "github.com/goharbor/harbor/src/pkg/reg/adapter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func init() { - if err := adp.RegisterFactory(model.RegistryTypeArtifactHub, new(factory)); err != nil { - log.Errorf("failed to register factory for %s: %v", model.RegistryTypeArtifactHub, err) - return - } - log.Infof("the factory for adapter %s registered", model.RegistryTypeArtifactHub) -} - -type factory struct { -} - -// Create ... -func (f *factory) Create(r *model.Registry) (adp.Adapter, error) { - return newAdapter(r) -} - -// AdapterPattern ... -func (f *factory) AdapterPattern() *model.AdapterPattern { - return &model.AdapterPattern{ - EndpointPattern: &model.EndpointPattern{ - EndpointType: model.EndpointPatternTypeFix, - Endpoints: []*model.Endpoint{ - { - Key: "artifacthub.io", - Value: "https://artifacthub.io", - }, - }, - }, - } -} - -var ( - _ adp.Adapter = (*adapter)(nil) - _ adp.ChartRegistry = (*adapter)(nil) -) - -type adapter struct { - registry *model.Registry - client *Client -} - -func newAdapter(registry *model.Registry) (*adapter, error) { - return &adapter{ - registry: registry, - client: newClient(registry), - }, nil -} - -func (a *adapter) Info() (*model.RegistryInfo, error) { - return &model.RegistryInfo{ - Type: model.RegistryTypeArtifactHub, - SupportedResourceTypes: []string{ - model.ResourceTypeChart, - }, - SupportedResourceFilters: []*model.FilterStyle{ - { - Type: model.FilterTypeName, - Style: model.FilterStyleTypeText, - }, - { - Type: model.FilterTypeTag, - Style: model.FilterStyleTypeText, - }, - }, - SupportedTriggers: []string{ - model.TriggerTypeManual, - model.TriggerTypeScheduled, - }, - }, nil -} - -func (a *adapter) PrepareForPush(resources []*model.Resource) error { - return errors.New("not supported") -} - -// HealthCheck checks health status of a registry -func (a *adapter) HealthCheck() (string, error) { - err := a.client.checkHealthy() - if err == nil { - return model.Healthy, nil - } - return model.Unhealthy, err -} diff --git a/src/pkg/reg/adapter/artifacthub/adapter_test.go b/src/pkg/reg/adapter/artifacthub/adapter_test.go deleted file mode 100644 index 8d19754a5a5f..000000000000 --- a/src/pkg/reg/adapter/artifacthub/adapter_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package artifacthub - -import ( - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "gopkg.in/h2non/gock.v1" - - adp "github.com/goharbor/harbor/src/pkg/reg/adapter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func mockRequest() *gock.Request { - return gock.New("https://artifacthub.io") -} - -func getMockAdapter(t *testing.T) *adapter { - ahRegistry := &model.Registry{ - Type: model.RegistryTypeArtifactHub, - URL: "https://artifacthub.io", - } - a, err := newAdapter(ahRegistry) - if err != nil { - t.Fatalf("Failed to call newAdapter(), reason=[%v]", err) - } - gock.InterceptClient(a.client.httpClient) - return a -} - -func TestAdapter_NewAdapter(t *testing.T) { - factory, err := adp.GetFactory("BadName") - assert.Nil(t, factory) - assert.NotNil(t, err) - - factory, err = adp.GetFactory(model.RegistryTypeArtifactHub) - assert.Nil(t, err) - assert.NotNil(t, factory) - - adapter, err := newAdapter(&model.Registry{ - Type: model.RegistryTypeArtifactHub, - URL: "https://artifacthub.io", - }) - assert.Nil(t, err) - assert.NotNil(t, adapter) -} - -func TestAdapter_Info(t *testing.T) { - a := getMockAdapter(t) - info, err := a.Info() - assert.Nil(t, err) - assert.NotNil(t, info) - - assert.EqualValues(t, model.RegistryTypeArtifactHub, info.Type) - assert.EqualValues(t, 1, len(info.SupportedResourceTypes)) - assert.EqualValues(t, model.ResourceTypeChart, info.SupportedResourceTypes[0]) -} - -func TestAdapter_HealthCheck(t *testing.T) { - defer gock.Off() - gock.Observe(gock.DumpRequest) - - mockRequest().Get("/").Reply(http.StatusOK).BodyString("{}") - - a := getMockAdapter(t) - h, err := a.HealthCheck() - assert.Nil(t, err) - assert.EqualValues(t, model.Healthy, h) -} - -func TestAdapter_PrepareForPush(t *testing.T) { - a := getMockAdapter(t) - err := a.PrepareForPush(nil) - assert.NotNil(t, err) -} - -func TestAdapter_ChartExist(t *testing.T) { - defer gock.Off() - gock.Observe(gock.DumpRequest) - - mockRequest().Get("/api/v1/packages/helm/harbor/harbor/1.5.0"). - Reply(http.StatusOK).BodyString("{}") - mockRequest().Get("/api/v1/packages/helm/harbor/not-exists/1.5.0"). - Reply(http.StatusNotFound).BodyString("{}") - mockRequest().Get("/api/v1/packages/helm/harbor/harbor/not-exists"). - Reply(http.StatusNotFound).BodyString("{}") - - a := getMockAdapter(t) - - b, err := a.ChartExist("harbor/harbor", "1.5.0") - assert.Nil(t, err) - assert.True(t, b) - - b, err = a.ChartExist("harbor/not-exists", "1.5.0") - assert.Nil(t, err) - assert.False(t, b) - - b, err = a.ChartExist("harbor/harbor", "not-exists") - assert.Nil(t, err) - assert.False(t, b) -} - -func TestAdapter_DownloadChart(t *testing.T) { - defer gock.Off() - gock.Observe(gock.DumpRequest) - - gock.New("https://helm.goharbor.io/").Get("/harbor-1.5.0.tgz"). - Reply(http.StatusOK).BodyString("{}") - - a := getMockAdapter(t) - data, err := a.DownloadChart("harbor/harbor", "1.5.0", "") - assert.NotNil(t, err) - assert.Nil(t, data) - - data, err = a.DownloadChart("harbor/harbor", "1.5.0", "https://helm.goharbor.io/harbor-1.5.0.tgz") - assert.Nil(t, err) - assert.NotNil(t, data) -} - -func TestAdapter_DeleteChart(t *testing.T) { - a := getMockAdapter(t) - - err := a.DeleteChart("harbor/harbor", "1.5.0") - assert.NotNil(t, err) -} - -func TestAdapter_UploadChart(t *testing.T) { - a := getMockAdapter(t) - - err := a.UploadChart("harbor/harbor", "1.5.0", nil) - assert.NotNil(t, err) -} diff --git a/src/pkg/reg/adapter/artifacthub/chart_registry.go b/src/pkg/reg/adapter/artifacthub/chart_registry.go deleted file mode 100644 index 7bf0d77d249c..000000000000 --- a/src/pkg/reg/adapter/artifacthub/chart_registry.go +++ /dev/null @@ -1,157 +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 artifacthub - -import ( - "fmt" - "io" - "net/http" - - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/reg/filter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func (a *adapter) FetchCharts(filters []*model.Filter) ([]*model.Resource, error) { - charts, err := a.client.getReplicationInfo() - if err != nil { - return nil, err - } - - resources := []*model.Resource{} - var repositories []*model.Repository - var artifacts []*model.Artifact - repoSet := map[string]interface{}{} - versionSet := map[string]interface{}{} - for _, chart := range charts { - name := fmt.Sprintf("%s/%s", chart.Repository, chart.Package) - if _, ok := repoSet[name]; !ok { - repoSet[name] = nil - repositories = append(repositories, &model.Repository{ - Name: name, - }) - } - } - - repositories, err = filter.DoFilterRepositories(repositories, filters) - if err != nil { - return nil, err - } - if len(repositories) == 0 { - return resources, nil - } - - if len(repoSet) != len(repositories) { - repoSet = map[string]interface{}{} - for _, repo := range repositories { - repoSet[repo.Name] = nil - } - } - - for _, chart := range charts { - name := fmt.Sprintf("%s/%s", chart.Repository, chart.Package) - if _, ok := repoSet[name]; ok { - artifacts = append(artifacts, &model.Artifact{ - Tags: []string{chart.Version}, - }) - } - } - - artifacts, err = filter.DoFilterArtifacts(artifacts, filters) - if err != nil { - return nil, err - } - if len(artifacts) == 0 { - return resources, nil - } - - for _, arti := range artifacts { - versionSet[arti.Tags[0]] = nil - } - - for _, chart := range charts { - name := fmt.Sprintf("%s/%s", chart.Repository, chart.Package) - if _, ok := repoSet[name]; !ok { - continue - } - if _, ok := versionSet[chart.Version]; !ok { - continue - } - resources = append(resources, &model.Resource{ - Type: model.ResourceTypeChart, - Registry: a.registry, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: name, - }, - Artifacts: []*model.Artifact{ - { - Tags: []string{chart.Version}, - }, - }, - }, - ExtendedInfo: map[string]interface{}{ - "contentURL": chart.ContentURL, - }, - }) - } - return resources, nil -} - -// ChartExist will never be used, for this function is only used when endpoint is destination -func (a *adapter) ChartExist(name, version string) (bool, error) { - _, err := a.client.getHelmChartVersion(name, version) - if err != nil { - if err == ErrHTTPNotFound { - return false, nil - } - return false, err - } - return true, nil -} - -func (a *adapter) DownloadChart(name, version, contentURL string) (io.ReadCloser, error) { - if len(contentURL) == 0 { - return nil, errors.Errorf("empty chart content url, %s:%s", name, version) - } - return a.download(contentURL) -} - -func (a *adapter) download(contentURL string) (io.ReadCloser, error) { - req, err := http.NewRequest(http.MethodGet, contentURL, nil) - if err != nil { - return nil, err - } - resp, err := a.client.do(req) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("failed to download the chart %s: %d %s", contentURL, resp.StatusCode, string(body)) - } - return resp.Body, nil -} - -func (a *adapter) UploadChart(name, version string, chart io.Reader) error { - return errors.New("not supported") -} - -func (a *adapter) DeleteChart(name, version string) error { - return errors.New("not supported") -} diff --git a/src/pkg/reg/adapter/artifacthub/client.go b/src/pkg/reg/adapter/artifacthub/client.go deleted file mode 100644 index 4aee1c89e129..000000000000 --- a/src/pkg/reg/adapter/artifacthub/client.go +++ /dev/null @@ -1,128 +0,0 @@ -package artifacthub - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - - common_http "github.com/goharbor/harbor/src/common/http" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -// Client is a client to interact with Artifact Hub -type Client struct { - httpClient *http.Client -} - -// newClient creates a new ArtifactHub client. -func newClient(registry *model.Registry) *Client { - return &Client{ - httpClient: &http.Client{ - Transport: common_http.GetHTTPTransport(common_http.WithInsecure(registry.Insecure)), - }, - } -} - -// getHelmVersion get the package version of a helm chart from artifact hub. -func (c *Client) getHelmChartVersion(fullName, version string) (*ChartVersion, error) { - request, err := http.NewRequest(http.MethodGet, baseURL+getHelmVersion(fullName, version), nil) - if err != nil { - return nil, err - } - - resp, err := c.do(request) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusNotFound { - return nil, ErrHTTPNotFound - } else if resp.StatusCode != http.StatusOK { - msg := &Message{} - err = json.Unmarshal(body, msg) - if err != nil { - msg.Message = string(body) - } - return nil, fmt.Errorf("fetch chart version error %d: %s", resp.StatusCode, msg.Message) - } - - chartVersion := &ChartVersion{} - err = json.Unmarshal(body, chartVersion) - if err != nil { - return nil, fmt.Errorf("unmarshal chart version response error: %v", err) - } - - return chartVersion, nil -} - -// getReplicationInfo gets the brief info of all helm chart from artifact hub. -// see https://github.com/artifacthub/hub/issues/997 -func (c *Client) getReplicationInfo() ([]*ChartInfo, error) { - request, err := http.NewRequest(http.MethodGet, baseURL+getReplicationInfo, nil) - if err != nil { - return nil, err - } - - resp, err := c.do(request) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - msg := &Message{} - err = json.Unmarshal(body, msg) - if err != nil { - msg.Message = string(body) - } - return nil, fmt.Errorf("get chart replication info error %d: %s", resp.StatusCode, msg.Message) - } - - var chartInfo []*ChartInfo - err = json.Unmarshal(body, &chartInfo) - if err != nil { - return nil, fmt.Errorf("unmarshal chart replication info error: %v", err) - } - - return chartInfo, nil -} - -func (c *Client) checkHealthy() error { - request, err := http.NewRequest(http.MethodGet, baseURL, nil) - if err != nil { - return err - } - - resp, err := c.do(request) - if err != nil { - return err - } - defer resp.Body.Close() - - _, err = io.ReadAll(resp.Body) - if err != nil { - return err - } - if resp.StatusCode >= 200 && resp.StatusCode < 300 { - return nil - } - return errors.New("artifact hub is unhealthy") -} - -// do work as a proxy of Do function from net.http -func (c *Client) do(req *http.Request) (*http.Response, error) { - return c.httpClient.Do(req) -} diff --git a/src/pkg/reg/adapter/artifacthub/consts.go b/src/pkg/reg/adapter/artifacthub/consts.go deleted file mode 100644 index cc761a68cd9a..000000000000 --- a/src/pkg/reg/adapter/artifacthub/consts.go +++ /dev/null @@ -1,29 +0,0 @@ -package artifacthub - -import ( - "errors" - "fmt" -) - -const ( - baseURL = "https://artifacthub.io" - getReplicationInfo = "/api/v1/harborReplication" -) - -const ( - // HelmChart represents the kind of helm chart in artifact hub - HelmChart = iota - // FalcoRules represents the kind of falco rules in artifact hub - FalcoRules - // OPAPolicies represents the kind of OPA policies in artifact hub - OPAPolicies - // OLMOperators represents the kind of OLM operators in artifact hub - OLMOperators -) - -// ErrHTTPNotFound defines the return error when receiving 404 response code -var ErrHTTPNotFound = errors.New("not found") - -func getHelmVersion(fullName, version string) string { - return fmt.Sprintf("/api/v1/packages/helm/%s/%s", fullName, version) -} diff --git a/src/pkg/reg/adapter/artifacthub/model.go b/src/pkg/reg/adapter/artifacthub/model.go deleted file mode 100644 index d32622d1c258..000000000000 --- a/src/pkg/reg/adapter/artifacthub/model.go +++ /dev/null @@ -1,82 +0,0 @@ -package artifacthub - -// PackageResponse ... -type PackageResponse struct { - Data PackageData `json:"data"` - Metadata Metadata `json:"metadata"` -} - -// PackageData ... -type PackageData struct { - Packages []*Package `json:"packages"` -} - -// Package ... -type Package struct { - PackageID string `json:"package_id"` - Name string `json:"name"` - NormalizedName string `json:"normalized_name"` - Repository *Repository `json:"repository"` -} - -// Repository ... -type Repository struct { - Kind int `json:"kind"` - Name string `json:"name"` - RepositoryID string `json:"repository_id"` -} - -// PackageDetail ... -type PackageDetail struct { - PackageID string `json:"package_id"` - Name string `json:"name"` - NormalizedName string `json:"normalized_name"` - Version string `json:"version"` - AppVersion string `json:"app_version"` - Repository RepositoryDetail `json:"repository"` - AvailableVersions []*Version `json:"available_versions,omitempty"` -} - -// RepositoryDetail ... -type RepositoryDetail struct { - URL string `json:"url"` - Kind int `json:"kind"` - Name string `json:"name"` - RepositoryID string `json:"repository_id"` - VerifiedPublisher bool `json:"verified_publisher"` - Official bool `json:"official"` - Private bool `json:"private"` -} - -// Version ... -type Version struct { - Version string `json:"version"` -} - -// ChartVersion ... -type ChartVersion struct { - PackageID string `json:"package_id"` - Name string `json:"name"` - Version string `json:"version"` - ContentURL string `json:"content_url"` -} - -// Metadata ... -type Metadata struct { - Limit int `json:"limit"` - Offset int `json:"offset"` - Total int `json:"total"` -} - -// Message ... -type Message struct { - Message string `json:"message"` -} - -// ChartInfo ... -type ChartInfo struct { - Repository string `json:"repository"` - Package string `json:"package"` - Version string `json:"version"` - ContentURL string `json:"url"` -} diff --git a/src/pkg/reg/adapter/harbor/base/adapter.go b/src/pkg/reg/adapter/harbor/base/adapter.go index 485cd1d73561..9dea42ac751e 100644 --- a/src/pkg/reg/adapter/harbor/base/adapter.go +++ b/src/pkg/reg/adapter/harbor/base/adapter.go @@ -51,7 +51,6 @@ func New(registry *model.Registry) (*Adapter, error) { Adapter: native.NewAdapterWithAuthorizer(registry, authorizer), Registry: registry, Client: client, - url: registry.URL, httpClient: httpClient, }, nil } @@ -73,7 +72,6 @@ func New(registry *model.Registry) (*Adapter, error) { Adapter: native.NewAdapter(registry), Registry: registry, Client: client, - url: registry.URL, httpClient: httpClient, }, nil } @@ -84,8 +82,6 @@ type Adapter struct { Registry *model.Registry Client *Client - // url and httpClient can be removed if we don't support replicate chartmuseum charts anymore - url string httpClient *common_http.Client } @@ -119,14 +115,6 @@ func (a *Adapter) Info() (*model.RegistryInfo, error) { SupportedCopyByChunk: true, } - enabled, err := a.Client.ChartRegistryEnabled() - if err != nil { - return nil, err - } - if enabled { - info.SupportedResourceTypes = append(info.SupportedResourceTypes, model.ResourceTypeChart) - } - labels, err := a.Client.ListLabels() if err != nil { return nil, err diff --git a/src/pkg/reg/adapter/harbor/base/adapter_test.go b/src/pkg/reg/adapter/harbor/base/adapter_test.go index 2767b158f38b..adb22d5dda01 100644 --- a/src/pkg/reg/adapter/harbor/base/adapter_test.go +++ b/src/pkg/reg/adapter/harbor/base/adapter_test.go @@ -34,14 +34,10 @@ func TestGetAPIVersion(t *testing.T) { } func TestInfo(t *testing.T) { - // chart museum enabled server := test.NewServer(&test.RequestHandlerMapping{ Method: http.MethodGet, Pattern: "/api/systeminfo", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `{"with_chartmuseum":true}` - w.Write([]byte(data)) - }, + Handler: func(w http.ResponseWriter, r *http.Request) {}, }) registry := &model.Registry{ URL: server.URL, @@ -55,18 +51,13 @@ func TestInfo(t *testing.T) { assert.Equal(t, 2, len(info.SupportedTriggers)) assert.Equal(t, 2, len(info.SupportedResourceTypes)) assert.Equal(t, model.ResourceTypeImage, info.SupportedResourceTypes[0]) - assert.Equal(t, model.ResourceTypeChart, info.SupportedResourceTypes[1]) assert.Equal(t, model.RepositoryPathComponentTypeAtLeastTwo, info.SupportedRepositoryPathComponentType) server.Close() - // chart museum disabled server = test.NewServer(&test.RequestHandlerMapping{ Method: http.MethodGet, Pattern: "/api/systeminfo", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `{"with_chartmuseum":false}` - w.Write([]byte(data)) - }, + Handler: func(w http.ResponseWriter, r *http.Request) {}, }) registry = &model.Registry{ URL: server.URL, diff --git a/src/pkg/reg/adapter/harbor/base/chart_registry.go b/src/pkg/reg/adapter/harbor/base/chart_registry.go deleted file mode 100644 index 3928d324f4e3..000000000000 --- a/src/pkg/reg/adapter/harbor/base/chart_registry.go +++ /dev/null @@ -1,248 +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 base - -import ( - "bytes" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/url" - "strings" - - common_http "github.com/goharbor/harbor/src/common/http" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/reg/filter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -type label struct { - Name string `json:"name"` -} - -type chartVersion struct { - Version string `json:"version"` - Labels []*label `json:"labels"` -} - -type chartVersionDetail struct { - Metadata *chartVersionMetadata `json:"metadata"` -} - -type chartVersionMetadata struct { - URLs []string `json:"urls"` -} - -// FetchCharts fetches charts -func (a *Adapter) FetchCharts(filters []*model.Filter) ([]*model.Resource, error) { - projects, err := a.ListProjects(filters) - if err != nil { - return nil, err - } - - resources := []*model.Resource{} - for _, project := range projects { - url := fmt.Sprintf("%s/api/chartrepo/%s/charts", a.Client.GetURL(), project.Name) - repositories := []*model.Repository{} - if err := a.httpClient.Get(url, &repositories); err != nil { - return nil, err - } - if len(repositories) == 0 { - continue - } - for _, repository := range repositories { - repository.Name = fmt.Sprintf("%s/%s", project.Name, repository.Name) - } - repositories, err = filter.DoFilterRepositories(repositories, filters) - if err != nil { - return nil, err - } - - for _, repository := range repositories { - name := strings.SplitN(repository.Name, "/", 2)[1] - url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s", a.Client.GetURL(), project.Name, name) - versions := []*chartVersion{} - if err := a.httpClient.Get(url, &versions); err != nil { - return nil, err - } - if len(versions) == 0 { - continue - } - var artifacts []*model.Artifact - for _, version := range versions { - var labels []string - for _, label := range version.Labels { - labels = append(labels, label.Name) - } - artifacts = append(artifacts, &model.Artifact{ - Tags: []string{version.Version}, - Labels: labels, - }) - } - artifacts, err = filter.DoFilterArtifacts(artifacts, filters) - if err != nil { - return nil, err - } - if len(artifacts) == 0 { - continue - } - - for _, artifact := range artifacts { - resources = append(resources, &model.Resource{ - Type: model.ResourceTypeChart, - Registry: a.Registry, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: repository.Name, - Metadata: project.Metadata, - }, - Artifacts: []*model.Artifact{artifact}, - }, - }) - } - } - } - return resources, nil -} - -// ChartExist checks the existence of the chart -func (a *Adapter) ChartExist(name, version string) (bool, error) { - _, err := a.getChartInfo(name, version) - if err == nil { - return true, nil - } - if httpErr, ok := err.(*common_http.Error); ok && httpErr.Code == http.StatusNotFound { - return false, nil - } - return false, err -} - -func (a *Adapter) getChartInfo(name, version string) (*chartVersionDetail, error) { - project, name, err := parseChartName(name) - if err != nil { - return nil, err - } - url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s/%s", a.Client.GetURL(), project, name, version) - info := &chartVersionDetail{} - if err = a.httpClient.Get(url, info); err != nil { - return nil, err - } - return info, nil -} - -// DownloadChart downloads the specific chart -func (a *Adapter) DownloadChart(name, version, contentURL string) (io.ReadCloser, error) { - info, err := a.getChartInfo(name, version) - if err != nil { - return nil, err - } - if info.Metadata == nil || len(info.Metadata.URLs) == 0 || len(info.Metadata.URLs[0]) == 0 { - return nil, fmt.Errorf("cannot got the download url for chart %s:%s", name, version) - } - - url, err := url.Parse(info.Metadata.URLs[0]) - if err != nil { - return nil, err - } - // relative URL - urlStr := url.String() - if !(url.Scheme == "http" || url.Scheme == "https") { - project, _, err := parseChartName(name) - if err != nil { - return nil, err - } - urlStr = fmt.Sprintf("%s/chartrepo/%s/%s", a.Client.GetURL(), project, urlStr) - } - req, err := http.NewRequest(http.MethodGet, urlStr, nil) - if err != nil { - return nil, err - } - resp, err := a.httpClient.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return nil, errors.Errorf("failed to download the chart %q: %d %s", req.URL.String(), resp.StatusCode, string(body)) - } - return resp.Body, nil -} - -// UploadChart uploads the chart -func (a *Adapter) UploadChart(name, version string, chart io.Reader) error { - project, name, err := parseChartName(name) - if err != nil { - return err - } - - buf := &bytes.Buffer{} - w := multipart.NewWriter(buf) - fw, err := w.CreateFormFile("chart", name+".tgz") - if err != nil { - return err - } - if _, err = io.Copy(fw, chart); err != nil { - return err - } - w.Close() - - url := fmt.Sprintf("%s/api/chartrepo/%s/charts", a.Client.GetURL(), project) - - req, err := http.NewRequest(http.MethodPost, url, buf) - if err != nil { - return err - } - req.Header.Set("Content-Type", w.FormDataContentType()) - resp, err := a.httpClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return &common_http.Error{ - Code: resp.StatusCode, - Message: string(data), - } - } - return nil -} - -// DeleteChart deletes the chart -func (a *Adapter) DeleteChart(name, version string) error { - project, name, err := parseChartName(name) - if err != nil { - return err - } - url := fmt.Sprintf("%s/api/chartrepo/%s/charts/%s/%s", a.Client.GetURL(), project, name, version) - return a.httpClient.Delete(url) -} - -// TODO merge this method and utils.ParseRepository? -func parseChartName(name string) (string, string, error) { - strs := strings.Split(name, "/") - if len(strs) == 2 && len(strs[0]) > 0 && len(strs[1]) > 0 { - return strs[0], strs[1], nil - } - return "", "", fmt.Errorf("invalid chart name format: %s", name) -} diff --git a/src/pkg/reg/adapter/harbor/base/chart_registry_test.go b/src/pkg/reg/adapter/harbor/base/chart_registry_test.go deleted file mode 100644 index 3278737c32b6..000000000000 --- a/src/pkg/reg/adapter/harbor/base/chart_registry_test.go +++ /dev/null @@ -1,191 +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 base - -import ( - "bytes" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/goharbor/harbor/src/common/utils/test" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func TestFetchCharts(t *testing.T) { - server := test.NewServer([]*test.RequestHandlerMapping{ - { - Method: http.MethodGet, - Pattern: "/api/projects", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `[{ - "name": "library", - "metadata": {"public":true} - }]` - w.Write([]byte(data)) - }, - }, - { - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts/harbor", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `[{ - "name": "harbor", - "version":"1.0" - },{ - "name": "harbor", - "version":"2.0" - }]` - w.Write([]byte(data)) - }, - }, - { - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `[{ - "name": "harbor" - }]` - w.Write([]byte(data)) - }, - }, - }...) - defer server.Close() - registry := &model.Registry{ - URL: server.URL, - } - adapter, err := New(registry) - require.Nil(t, err) - // nil filter - resources, err := adapter.FetchCharts(nil) - require.Nil(t, err) - assert.Equal(t, 2, len(resources)) - assert.Equal(t, model.ResourceTypeChart, resources[0].Type) - assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.Name) - assert.Equal(t, 1, len(resources[0].Metadata.Artifacts)) - assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0]) - // not nil filter - filters := []*model.Filter{ - { - Type: model.FilterTypeName, - Value: "library/*", - }, - { - Type: model.FilterTypeTag, - Value: "1.0", - }, - } - resources, err = adapter.FetchCharts(filters) - require.Nil(t, err) - require.Equal(t, 1, len(resources)) - assert.Equal(t, model.ResourceTypeChart, resources[0].Type) - assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.Name) - assert.Equal(t, 1, len(resources[0].Metadata.Artifacts)) - assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0]) -} - -func TestChartExist(t *testing.T) { - server := test.NewServer(&test.RequestHandlerMapping{ - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts/harbor/1.0", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `{ - "metadata": { - "urls":["http://127.0.0.1/charts"] - } - }` - w.Write([]byte(data)) - }, - }) - defer server.Close() - registry := &model.Registry{ - URL: server.URL, - } - adapter, err := New(registry) - require.Nil(t, err) - exist, err := adapter.ChartExist("library/harbor", "1.0") - require.Nil(t, err) - require.True(t, exist) -} - -func TestDownloadChart(t *testing.T) { - server := test.NewServer([]*test.RequestHandlerMapping{ - { - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts/harbor/1.0", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `{ - "metadata": { - "urls":["charts/harbor-1.0.tgz"] - } - }` - w.Write([]byte(data)) - }, - }, - { - Method: http.MethodGet, - Pattern: "/chartrepo/library/charts/harbor-1.0.tgz", - Handler: func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }, - }, - }...) - defer server.Close() - registry := &model.Registry{ - URL: server.URL, - } - adapter, err := New(registry) - require.Nil(t, err) - _, err = adapter.DownloadChart("library/harbor", "1.0", "") - require.Nil(t, err) -} - -func TestUploadChart(t *testing.T) { - server := test.NewServer(&test.RequestHandlerMapping{ - Method: http.MethodPost, - Pattern: "/api/chartrepo/library/charts", - Handler: func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }, - }) - defer server.Close() - registry := &model.Registry{ - URL: server.URL, - } - adapter, err := New(registry) - require.Nil(t, err) - err = adapter.UploadChart("library/harbor", "1.0", bytes.NewBuffer(nil)) - require.Nil(t, err) -} - -func TestDeleteChart(t *testing.T) { - server := test.NewServer(&test.RequestHandlerMapping{ - Method: http.MethodDelete, - Pattern: "/api/chartrepo/library/charts/harbor/1.0", - Handler: func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }, - }) - defer server.Close() - registry := &model.Registry{ - URL: server.URL, - } - adapter, err := New(registry) - require.Nil(t, err) - err = adapter.DeleteChart("library/harbor", "1.0") - require.Nil(t, err) -} diff --git a/src/pkg/reg/adapter/harbor/base/client.go b/src/pkg/reg/adapter/harbor/base/client.go index c6e382105bc4..64f98cfad550 100644 --- a/src/pkg/reg/adapter/harbor/base/client.go +++ b/src/pkg/reg/adapter/harbor/base/client.go @@ -60,17 +60,6 @@ func (c *Client) GetAPIVersion() (string, error) { return "", err } -// ChartRegistryEnabled returns whether the chart registry is enabled for the Harbor instance -func (c *Client) ChartRegistryEnabled() (bool, error) { - sys := &struct { - ChartRegistryEnabled bool `json:"with_chartmuseum"` - }{} - if err := c.C.Get(c.BasePath()+"/systeminfo", sys); err != nil { - return false, err - } - return sys.ChartRegistryEnabled, nil -} - // ListLabels lists system level labels func (c *Client) ListLabels() ([]string, error) { labels := []*struct { diff --git a/src/pkg/reg/adapter/harbor/v1/adapter.go b/src/pkg/reg/adapter/harbor/v1/adapter.go index 101393f2621e..dc945c7b9111 100644 --- a/src/pkg/reg/adapter/harbor/v1/adapter.go +++ b/src/pkg/reg/adapter/harbor/v1/adapter.go @@ -26,7 +26,6 @@ import ( var _ adp.Adapter = &adapter{} var _ adp.ArtifactRegistry = &adapter{} -var _ adp.ChartRegistry = &adapter{} // New creates a Adapter for Harbor 1.x func New(base *base.Adapter) adp.Adapter { diff --git a/src/pkg/reg/adapter/harbor/v2/adapter.go b/src/pkg/reg/adapter/harbor/v2/adapter.go index 9645d3749bb5..bf322295a0c2 100644 --- a/src/pkg/reg/adapter/harbor/v2/adapter.go +++ b/src/pkg/reg/adapter/harbor/v2/adapter.go @@ -27,7 +27,6 @@ import ( var _ adp.Adapter = &adapter{} var _ adp.ArtifactRegistry = &adapter{} -var _ adp.ChartRegistry = &adapter{} // New creates a Adapter for Harbor 2.x func New(base *base.Adapter) adp.Adapter { diff --git a/src/pkg/reg/adapter/helmhub/adapter.go b/src/pkg/reg/adapter/helmhub/adapter.go deleted file mode 100644 index aa95d8a4f6c4..000000000000 --- a/src/pkg/reg/adapter/helmhub/adapter.go +++ /dev/null @@ -1,107 +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 helmhub - -import ( - "errors" - - "github.com/goharbor/harbor/src/lib/log" - adp "github.com/goharbor/harbor/src/pkg/reg/adapter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func init() { - if err := adp.RegisterFactory(model.RegistryTypeHelmHub, new(factory)); err != nil { - log.Errorf("failed to register factory for %s: %v", model.RegistryTypeHelmHub, err) - return - } - log.Infof("the factory for adapter %s registered", model.RegistryTypeHelmHub) -} - -type factory struct { -} - -// Create ... -func (f *factory) Create(r *model.Registry) (adp.Adapter, error) { - return newAdapter(r) -} - -// AdapterPattern ... -func (f *factory) AdapterPattern() *model.AdapterPattern { - return &model.AdapterPattern{ - EndpointPattern: &model.EndpointPattern{ - EndpointType: model.EndpointPatternTypeFix, - Endpoints: []*model.Endpoint{ - { - Key: "hub.helm.sh", - Value: "https://hub.helm.sh", - }, - }, - }, - } -} - -var ( - _ adp.Adapter = (*adapter)(nil) - _ adp.ChartRegistry = (*adapter)(nil) -) - -type adapter struct { - registry *model.Registry - client *Client -} - -func newAdapter(registry *model.Registry) (*adapter, error) { - return &adapter{ - registry: registry, - client: NewClient(registry), - }, nil -} - -func (a *adapter) Info() (*model.RegistryInfo, error) { - return &model.RegistryInfo{ - Type: model.RegistryTypeHelmHub, - SupportedResourceTypes: []string{ - model.ResourceTypeChart, - }, - SupportedResourceFilters: []*model.FilterStyle{ - { - Type: model.FilterTypeName, - Style: model.FilterStyleTypeText, - }, - { - Type: model.FilterTypeTag, - Style: model.FilterStyleTypeText, - }, - }, - SupportedTriggers: []string{ - model.TriggerTypeManual, - model.TriggerTypeScheduled, - }, - }, nil -} - -func (a *adapter) PrepareForPush(resources []*model.Resource) error { - return errors.New("not supported") -} - -// HealthCheck checks health status of a registry -func (a *adapter) HealthCheck() (string, error) { - err := a.client.checkHealthy() - if err == nil { - return model.Healthy, nil - } - return model.Unhealthy, err -} diff --git a/src/pkg/reg/adapter/helmhub/chart.go b/src/pkg/reg/adapter/helmhub/chart.go deleted file mode 100644 index 5c46b9e64494..000000000000 --- a/src/pkg/reg/adapter/helmhub/chart.go +++ /dev/null @@ -1,44 +0,0 @@ -package helmhub - -type chart struct { - ID string `json:"id"` - Type string `json:"type"` -} - -type chartList struct { - Data []*chart `json:"data"` -} - -type chartAttributes struct { - Version string `json:"version"` - URLs []string `json:"urls"` -} - -type chartRepo struct { - Name string `json:"name"` - URL string `json:"url"` -} - -type chartData struct { - Name string `json:"name"` - Repo *chartRepo `json:"repo"` -} - -type chartInfo struct { - Data *chartData `json:"data"` -} - -type chartRelationships struct { - Chart *chartInfo `json:"chart"` -} - -type chartVersion struct { - ID string `json:"id"` - Type string `json:"type"` - Attributes *chartAttributes `json:"attributes"` - Relationships *chartRelationships `json:"relationships"` -} - -type chartVersionList struct { - Data []*chartVersion `json:"data"` -} diff --git a/src/pkg/reg/adapter/helmhub/chart_registry.go b/src/pkg/reg/adapter/helmhub/chart_registry.go deleted file mode 100644 index 171a45675409..000000000000 --- a/src/pkg/reg/adapter/helmhub/chart_registry.go +++ /dev/null @@ -1,151 +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 helmhub - -import ( - "fmt" - "io" - "net/http" - "strings" - - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/lib/log" - "github.com/goharbor/harbor/src/pkg/reg/filter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func (a *adapter) FetchCharts(filters []*model.Filter) ([]*model.Resource, error) { - charts, err := a.client.fetchCharts() - if err != nil { - return nil, err - } - - resources := []*model.Resource{} - var repositories []*model.Repository - for _, chart := range charts.Data { - repositories = append(repositories, &model.Repository{ - Name: chart.ID, - }) - } - - repositories, err = filter.DoFilterRepositories(repositories, filters) - if err != nil { - return nil, err - } - - for _, repository := range repositories { - versionList, err := a.client.fetchChartDetail(repository.Name) - if err != nil { - log.Errorf("fetch chart detail: %v", err) - return nil, err - } - - var artifacts []*model.Artifact - for _, version := range versionList.Data { - artifacts = append(artifacts, &model.Artifact{ - Tags: []string{version.Attributes.Version}, - }) - } - - artifacts, err = filter.DoFilterArtifacts(artifacts, filters) - if err != nil { - return nil, err - } - if len(artifacts) == 0 { - continue - } - - for _, artifact := range artifacts { - resources = append(resources, &model.Resource{ - Type: model.ResourceTypeChart, - Registry: a.registry, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: repository.Name, - }, - Artifacts: []*model.Artifact{artifact}, - }, - }) - } - } - return resources, nil -} - -func (a *adapter) ChartExist(name, version string) (bool, error) { - versionList, err := a.client.fetchChartDetail(name) - if err != nil { - if err == ErrHTTPNotFound { - return false, nil - } - return false, err - } - - for _, v := range versionList.Data { - if v.Attributes.Version == version { - return true, nil - } - } - return false, nil -} - -func (a *adapter) DownloadChart(name, version, contentURL string) (io.ReadCloser, error) { - versionList, err := a.client.fetchChartDetail(name) - if err != nil { - return nil, err - } - - for _, v := range versionList.Data { - if v.Attributes.Version == version { - return a.download(v) - } - } - return nil, errors.New("chart not found") -} - -func (a *adapter) download(version *chartVersion) (io.ReadCloser, error) { - if len(version.Attributes.URLs) == 0 || len(version.Attributes.URLs[0]) == 0 { - return nil, fmt.Errorf("cannot got the download url for chart %s", version.ID) - } - - url := strings.ToLower(version.Attributes.URLs[0]) - if !(strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) { - url = fmt.Sprintf("%s/%s", version.Relationships.Chart.Data.Repo.URL, url) - } - - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, err - } - resp, err := a.client.do(req) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return nil, errors.Errorf("failed to download the chart %q: %d %s", req.URL.String(), resp.StatusCode, string(body)) - } - return resp.Body, nil -} - -func (a *adapter) UploadChart(name, version string, chart io.Reader) error { - return errors.New("not supported") -} - -func (a *adapter) DeleteChart(name, version string) error { - return errors.New("not supported") -} diff --git a/src/pkg/reg/adapter/helmhub/client.go b/src/pkg/reg/adapter/helmhub/client.go deleted file mode 100644 index d918b3717291..000000000000 --- a/src/pkg/reg/adapter/helmhub/client.go +++ /dev/null @@ -1,120 +0,0 @@ -package helmhub - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - - commonhttp "github.com/goharbor/harbor/src/common/http" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -// ErrHTTPNotFound defines the return error when receiving 404 response code -var ErrHTTPNotFound = errors.New("Not Found") - -// Client is a client to interact with HelmHub -type Client struct { - client *http.Client -} - -// NewClient creates a new HelmHub client. -func NewClient(registry *model.Registry) *Client { - return &Client{ - client: &http.Client{ - Transport: commonhttp.GetHTTPTransport(commonhttp.WithInsecure(registry.Insecure)), - }, - } -} - -// fetchCharts fetches the chart list from helm hub. -func (c *Client) fetchCharts() (*chartList, error) { - request, err := http.NewRequest(http.MethodGet, baseURL+listCharts, nil) - if err != nil { - return nil, err - } - - resp, err := c.client.Do(request) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("fetch chart list error %d: %s", resp.StatusCode, string(body)) - } - - list := &chartList{} - err = json.Unmarshal(body, list) - if err != nil { - return nil, fmt.Errorf("unmarshal chart list response error: %v", err) - } - - return list, nil -} - -// fetchChartDetail fetches the chart detail of a chart from helm hub. -func (c *Client) fetchChartDetail(chartName string) (*chartVersionList, error) { - request, err := http.NewRequest(http.MethodGet, baseURL+listVersions(chartName), nil) - if err != nil { - return nil, err - } - - resp, err := c.client.Do(request) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusNotFound { - return nil, ErrHTTPNotFound - } else if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("fetch chart detail error %d: %s", resp.StatusCode, string(body)) - } - - list := &chartVersionList{} - err = json.Unmarshal(body, list) - if err != nil { - return nil, fmt.Errorf("unmarshal chart detail response error: %v", err) - } - - return list, nil -} - -func (c *Client) checkHealthy() error { - request, err := http.NewRequest(http.MethodGet, baseURL, nil) - if err != nil { - return err - } - - resp, err := c.client.Do(request) - if err != nil { - return err - } - defer resp.Body.Close() - - _, err = io.ReadAll(resp.Body) - if err != nil { - return err - } - if resp.StatusCode >= 200 && resp.StatusCode < 300 { - return nil - } - return errors.New("helm hub is unhealthy") -} - -// do work as a proxy of Do function from net.http -func (c *Client) do(req *http.Request) (*http.Response, error) { - return c.client.Do(req) -} diff --git a/src/pkg/reg/adapter/helmhub/consts.go b/src/pkg/reg/adapter/helmhub/consts.go deleted file mode 100644 index dab17bfcf557..000000000000 --- a/src/pkg/reg/adapter/helmhub/consts.go +++ /dev/null @@ -1,12 +0,0 @@ -package helmhub - -import "fmt" - -const ( - baseURL = "https://hub.helm.sh" - listCharts = "/api/chartsvc/v1/charts" -) - -func listVersions(chartName string) string { - return fmt.Sprintf("/api/chartsvc/v1/charts/%s/versions", chartName) -} diff --git a/src/pkg/reg/adapter/tencentcr/adapter.go b/src/pkg/reg/adapter/tencentcr/adapter.go index 02942ac95338..35f97a1429c1 100644 --- a/src/pkg/reg/adapter/tencentcr/adapter.go +++ b/src/pkg/reg/adapter/tencentcr/adapter.go @@ -170,7 +170,6 @@ func (a *adapter) Info() (info *model.RegistryInfo, err error) { Type: model.RegistryTypeTencentTcr, SupportedResourceTypes: []string{ model.ResourceTypeImage, - model.ResourceTypeChart, }, SupportedResourceFilters: []*model.FilterStyle{ { diff --git a/src/pkg/reg/adapter/tencentcr/chart_registry.go b/src/pkg/reg/adapter/tencentcr/chart_registry.go deleted file mode 100644 index ac50501bd7d7..000000000000 --- a/src/pkg/reg/adapter/tencentcr/chart_registry.go +++ /dev/null @@ -1,270 +0,0 @@ -package tencentcr - -import ( - "bytes" - "fmt" - "io" - "mime/multipart" - "net/http" - "strings" - - commonhttp "github.com/goharbor/harbor/src/common/http" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/lib/log" - adp "github.com/goharbor/harbor/src/pkg/reg/adapter" - "github.com/goharbor/harbor/src/pkg/reg/filter" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -const ( - chartListURL = "%s/api/chartrepo/%s/charts" - chartVersionURL = "%s/api/chartrepo/%s/charts/%s" - chartInfoURL = "%s/api/chartrepo/%s/charts/%s/%s" -) - -type tcrChart struct { - APIVersion string `json:"apiVersion"` - Digest string `json:"digest"` - Name string `json:"name"` - URLs []string `json:"urls"` - Version string `json:"version"` -} - -type tcrChartVersionDetail struct { - Metadata *tcrChartVersionMetadata `json:"metadata"` -} -type tcrChartVersionMetadata struct { - URLs []string `json:"urls"` -} - -var _ adp.ChartRegistry = &adapter{} - -func (a *adapter) FetchCharts(filters []*model.Filter) (resources []*model.Resource, err error) { - log.Debugf("[tencent-tcr.FetchCharts]filters: %#v", filters) - // 1. list namespaces via TCR Special API - var nsPattern, _, _ = filterToPatterns(filters) - var nms []string - nms, err = a.listCandidateNamespaces(nsPattern) - if err != nil { - return - } - - return a.fetchCharts(nms, filters) -} - -func (a *adapter) fetchCharts(namespaces []string, filters []*model.Filter) (resources []*model.Resource, err error) { - // 1. list repositories - for _, ns := range namespaces { - var url = fmt.Sprintf(chartListURL, a.registry.URL, ns) - var repositories = []*model.Repository{} - err = a.client.Get(url, &repositories) - log.Debugf("[tencent-tcr.FetchCharts] url=%s, namespace=%s, repositories=%v, error=%v", url, ns, repositories, err) - if err != nil { - return - } - if len(repositories) == 0 { - continue - } - for _, repository := range repositories { - repository.Name = fmt.Sprintf("%s/%s", ns, repository.Name) - } - repositories, err = filter.DoFilterRepositories(repositories, filters) - if err != nil { - return - } - - // 2. list versions - for _, repository := range repositories { - var name = strings.SplitN(repository.Name, "/", 2)[1] - var url = fmt.Sprintf(chartVersionURL, a.registry.URL, ns, name) - var charts = []*tcrChart{} - err = a.client.Get(url, &charts) - if err != nil { - return nil, err - } - if len(charts) == 0 { - continue - } - var artifacts []*model.Artifact - for _, chart := range charts { - artifacts = append(artifacts, &model.Artifact{ - Tags: []string{chart.Version}, - }) - } - artifacts, err = filter.DoFilterArtifacts(artifacts, filters) - if err != nil { - return nil, err - } - if len(artifacts) == 0 { - continue - } - - for _, artifact := range artifacts { - resources = append(resources, &model.Resource{ - Type: model.ResourceTypeChart, - Registry: a.registry, - Metadata: &model.ResourceMetadata{ - Repository: &model.Repository{ - Name: repository.Name, - }, - Artifacts: []*model.Artifact{artifact}, - }, - }) - } - } - } - - return -} - -func (a *adapter) ChartExist(name, version string) (exist bool, err error) { - log.Debugf("[tencent-tcr.ChartExist] name=%s version=%s", name, version) - _, err = a.getChartInfo(name, version) - // if not found, return not exist - if httpErr, ok := err.(*commonhttp.Error); ok && httpErr.Code == http.StatusNotFound { - return false, nil - } - if err != nil { - return - } - exist = true - - return -} - -func (a *adapter) getChartInfo(name, version string) (info *tcrChartVersionDetail, err error) { - var namespace string - var chart string - namespace, chart, err = parseChartName(name) - if err != nil { - return - } - - var url = fmt.Sprintf(chartInfoURL, a.registry.URL, namespace, chart, version) - info = &tcrChartVersionDetail{} - err = a.client.Get(url, info) - if err != nil { - return - } - return -} - -func (a *adapter) DownloadChart(name, version, contentURL string) (rc io.ReadCloser, err error) { - var info *tcrChartVersionDetail - info, err = a.getChartInfo(name, version) - if err != nil { - return - } - if info.Metadata == nil || len(info.Metadata.URLs) == 0 || len(info.Metadata.URLs[0]) == 0 { - return nil, fmt.Errorf("[tencent-tcr.DownloadChart.NO_DOWNLOAD_URL] chart=%s:%s", name, version) - } - - var url = strings.ToLower(info.Metadata.URLs[0]) - // relative URL - if !(strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) { - var namespace string - namespace, _, err = parseChartName(name) - if err != nil { - return - } - url = fmt.Sprintf("%s/chartrepo/%s/%s", a.registry.URL, namespace, url) - } - - var req *http.Request - var resp *http.Response - var body []byte - req, err = http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return - } - resp, err = a.client.Do(req) - if err != nil { - return - } - if resp.StatusCode != http.StatusOK { - body, err = io.ReadAll(resp.Body) - if err != nil { - return - } - err = errors.Errorf("[tencent-tcr.DownloadChart.failed] chart=%q, status=%d, body=%s", req.URL.String(), resp.StatusCode, string(body)) - - return - } - - return resp.Body, nil -} - -func (a *adapter) UploadChart(name, version string, reader io.Reader) (err error) { - var namespace string - var chart string - namespace, chart, err = parseChartName(name) - if err != nil { - return - } - - // 1. write to form-data buffer - var buf = &bytes.Buffer{} - var writer = multipart.NewWriter(buf) - var fw io.Writer - fw, err = writer.CreateFormFile("chart", chart+".tgz") - if err != nil { - return - } - _, err = io.Copy(fw, reader) - if err != nil { - return - } - writer.Close() - - // 2. upload - var url = fmt.Sprintf(chartListURL, a.registry.URL, namespace) - var req *http.Request - var resp *http.Response - req, err = http.NewRequest(http.MethodPost, url, buf) - if err != nil { - return - } - req.Header.Set("Content-Type", writer.FormDataContentType()) - resp, err = a.client.Do(req) - if err != nil { - return - } - defer resp.Body.Close() - - // 3. parse response - var data []byte - data, err = io.ReadAll(resp.Body) - if err != nil { - return - } - - if resp.StatusCode < http.StatusOK || resp.StatusCode > 299 { - err = &commonhttp.Error{ - Code: resp.StatusCode, - Message: string(data), - } - return - } - return -} - -func (a *adapter) DeleteChart(name, version string) (err error) { - var namespace string - var chart string - namespace, chart, err = parseChartName(name) - if err != nil { - return - } - - var url = fmt.Sprintf(chartInfoURL, a.registry.URL, namespace, chart, version) - - return a.client.Delete(url) -} - -func parseChartName(name string) (namespace, chart string, err error) { - strs := strings.Split(name, "/") - if len(strs) == 2 && len(strs[0]) > 0 && len(strs[1]) > 0 { - return strs[0], strs[1], nil - } - return "", "", fmt.Errorf("[tencent-tcr.parseChartName.invalid_name] name=%s", name) -} diff --git a/src/pkg/reg/adapter/tencentcr/chart_registry_test.go b/src/pkg/reg/adapter/tencentcr/chart_registry_test.go deleted file mode 100644 index 4768121f8953..000000000000 --- a/src/pkg/reg/adapter/tencentcr/chart_registry_test.go +++ /dev/null @@ -1,173 +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 tencentcr - -import ( - "bytes" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - commonhttp "github.com/goharbor/harbor/src/common/http" - "github.com/goharbor/harbor/src/common/utils/test" - "github.com/goharbor/harbor/src/pkg/reg/model" -) - -func mockChartClient(registry *model.Registry) *adapter { - return &adapter{ - registry: registry, - client: commonhttp.NewClient( - &http.Client{ - Transport: commonhttp.GetHTTPTransport(commonhttp.WithInsecure(registry.Insecure)), - }, - ), - } -} - -func TestFetchCharts(t *testing.T) { - server := test.NewServer([]*test.RequestHandlerMapping{ - { - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts/harbor", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `[{ - "name": "harbor", - "version":"1.0" - },{ - "name": "harbor", - "version":"2.0" - }]` - w.Write([]byte(data)) - }, - }, - { - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `[{ - "name": "harbor" - }]` - w.Write([]byte(data)) - }, - }, - }...) - defer server.Close() - var adapter = mockChartClient(&model.Registry{URL: server.URL}) - - // nil filter - resources, err := adapter.fetchCharts([]string{"library"}, nil) - require.Nil(t, err) - assert.Equal(t, 2, len(resources)) - assert.Equal(t, model.ResourceTypeChart, resources[0].Type) - assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.Name) - assert.Equal(t, 1, len(resources[0].Metadata.Artifacts)) - assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0]) - // not nil filter - filters := []*model.Filter{ - { - Type: model.FilterTypeName, - Value: "library/*", - }, - { - Type: model.FilterTypeTag, - Value: "1.0", - }, - } - resources, err = adapter.fetchCharts([]string{"library"}, filters) - require.Nil(t, err) - require.Equal(t, 1, len(resources)) - assert.Equal(t, model.ResourceTypeChart, resources[0].Type) - assert.Equal(t, "library/harbor", resources[0].Metadata.Repository.Name) - assert.Equal(t, 1, len(resources[0].Metadata.Artifacts)) - assert.Equal(t, "1.0", resources[0].Metadata.Artifacts[0].Tags[0]) -} - -func TestChartExist(t *testing.T) { - server := test.NewServer(&test.RequestHandlerMapping{ - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts/harbor/1.0", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `{ - "metadata": { - "urls":["http://127.0.0.1/charts"] - } - }` - w.Write([]byte(data)) - }, - }) - defer server.Close() - var adapter = mockChartClient(&model.Registry{URL: server.URL}) - var exist, err = adapter.ChartExist("library/harbor", "1.0") - require.Nil(t, err) - require.True(t, exist) -} - -func TestDownloadChart(t *testing.T) { - server := test.NewServer([]*test.RequestHandlerMapping{ - { - Method: http.MethodGet, - Pattern: "/api/chartrepo/library/charts/harbor/1.0", - Handler: func(w http.ResponseWriter, r *http.Request) { - data := `{ - "metadata": { - "urls":["charts/harbor-1.0.tgz"] - } - }` - w.Write([]byte(data)) - }, - }, - { - Method: http.MethodGet, - Pattern: "/chartrepo/library/charts/harbor-1.0.tgz", - Handler: func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }, - }, - }...) - defer server.Close() - var adapter = mockChartClient(&model.Registry{URL: server.URL}) - var _, err = adapter.DownloadChart("library/harbor", "1.0", "") - require.Nil(t, err) -} - -func TestUploadChart(t *testing.T) { - server := test.NewServer(&test.RequestHandlerMapping{ - Method: http.MethodPost, - Pattern: "/api/chartrepo/library/charts", - Handler: func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }, - }) - defer server.Close() - var adapter = mockChartClient(&model.Registry{URL: server.URL}) - var err = adapter.UploadChart("library/harbor", "1.0", bytes.NewBuffer(nil)) - require.Nil(t, err) -} - -func TestDeleteChart(t *testing.T) { - server := test.NewServer(&test.RequestHandlerMapping{ - Method: http.MethodDelete, - Pattern: "/api/chartrepo/library/charts/harbor/1.0", - Handler: func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }, - }) - defer server.Close() - var adapter = mockChartClient(&model.Registry{URL: server.URL}) - var err = adapter.DeleteChart("library/harbor", "1.0") - require.Nil(t, err) -} diff --git a/src/pkg/reg/filter/artifact.go b/src/pkg/reg/filter/artifact.go index bf0e19903ecd..66a18b6e309c 100644 --- a/src/pkg/reg/filter/artifact.go +++ b/src/pkg/reg/filter/artifact.go @@ -46,13 +46,6 @@ func BuildArtifactFilters(filters []*model.Filter) (ArtifactFilters, error) { pattern: filter.Value.(string), decoration: filter.Decoration, } - case model.FilterTypeResource: - v := filter.Value.(string) - if v != model.ResourceTypeArtifact && v != model.ResourceTypeChart { - f = &artifactTypeFilter{ - types: []string{v}, - } - } } if f != nil { fs = append(fs, f) diff --git a/src/pkg/reg/filter/resource.go b/src/pkg/reg/filter/resource.go index 2f8631a02f23..0829bf7c933e 100644 --- a/src/pkg/reg/filter/resource.go +++ b/src/pkg/reg/filter/resource.go @@ -57,33 +57,5 @@ func DoFilterResources(resources []*model.Resource, filters []*model.Filter) ([] }) } - // remove this after we deprecate chart museum - return filterByResourceType(result, filters) -} - -// After we deprecated chart museum, the resource types model.ResourceTypeArtifact and model.ResourceTypeChart -// are useless, this function should be removed as well -func filterByResourceType(resources []*model.Resource, filters []*model.Filter) ([]*model.Resource, error) { - var resourceType string - for _, filter := range filters { - if filter.Type == model.FilterTypeResource { - // model.ResourceTypeImage is handled by artifact filters in function "DoFilterResources" - if filter.Value.(string) == model.ResourceTypeArtifact || filter.Value.(string) == model.ResourceTypeChart { - resourceType = filter.Value.(string) - } - break - } - } - // no resource type, return the candidates directly - if len(resourceType) == 0 { - return resources, nil - } - - var result []*model.Resource - for _, resource := range resources { - if resource.Type == resourceType { - result = append(result, resource) - } - } return result, nil } diff --git a/src/pkg/reg/manager.go b/src/pkg/reg/manager.go index af1f57c4c1da..7d74408e569b 100644 --- a/src/pkg/reg/manager.go +++ b/src/pkg/reg/manager.go @@ -25,8 +25,6 @@ import ( // register the AliACR adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/aliacr" - // register the Artifact Hub adapter - _ "github.com/goharbor/harbor/src/pkg/reg/adapter/artifacthub" // register the AwsEcr adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/awsecr" // register the AzureAcr adapter @@ -43,8 +41,6 @@ import ( _ "github.com/goharbor/harbor/src/pkg/reg/adapter/googlegcr" // register the Harbor adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/harbor" - // register the Helm Hub adapter - _ "github.com/goharbor/harbor/src/pkg/reg/adapter/helmhub" // register the huawei adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/huawei" // register the Jfrog Artifactory adapter diff --git a/src/pkg/reg/model/policy.go b/src/pkg/reg/model/policy.go index b367f82bafa1..e7352c16fb96 100644 --- a/src/pkg/reg/model/policy.go +++ b/src/pkg/reg/model/policy.go @@ -50,7 +50,7 @@ func (f *Filter) Validate() error { } if f.Type == FilterTypeResource { rt := value - if !(rt == ResourceTypeArtifact || rt == ResourceTypeImage || rt == ResourceTypeChart) { + if !(rt == ResourceTypeArtifact || rt == ResourceTypeImage) { return errors.New(nil).WithCode(errors.BadRequestCode). WithMessage("invalid resource filter: %s", value) } diff --git a/src/pkg/reg/model/resource.go b/src/pkg/reg/model/resource.go index 0ceec8e4b316..6cc5a0d29930 100644 --- a/src/pkg/reg/model/resource.go +++ b/src/pkg/reg/model/resource.go @@ -23,7 +23,6 @@ import ( const ( ResourceTypeArtifact = "artifact" ResourceTypeImage = "image" - ResourceTypeChart = "chart" ) // Resource represents the general replicating content diff --git a/src/pkg/retention/dep/client_test.go b/src/pkg/retention/dep/client_test.go index 874c0f88f4f0..10b0e2634ac5 100644 --- a/src/pkg/retention/dep/client_test.go +++ b/src/pkg/retention/dep/client_test.go @@ -18,13 +18,6 @@ import ( "net/http" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/repo" - - "github.com/goharbor/harbor/src/chartserver" jmodels "github.com/goharbor/harbor/src/common/job/models" modelsv2 "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/tag" @@ -32,6 +25,9 @@ import ( "github.com/goharbor/harbor/src/lib/selector" model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag" "github.com/goharbor/harbor/src/testing/clients" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) type fakeCoreClient struct { @@ -51,17 +47,6 @@ func (f *fakeCoreClient) ListAllArtifacts(project, repository string) ([]*models return []*modelsv2.Artifact{image}, nil } -func (f *fakeCoreClient) ListAllCharts(project, repository string) ([]*chartserver.ChartVersion, error) { - metadata := &chart.Metadata{ - Name: "1.0", - } - chart := &chartserver.ChartVersion{} - chart.ChartVersion = repo.ChartVersion{ - Metadata: metadata, - } - return []*chartserver.ChartVersion{chart}, nil -} - type fakeJobserviceClient struct{} func (f *fakeJobserviceClient) SubmitJob(*jmodels.JobData) (string, error) { diff --git a/src/pkg/retention/models.go b/src/pkg/retention/models.go index f1fb27456e90..0af81d9a4dc9 100644 --- a/src/pkg/retention/models.go +++ b/src/pkg/retention/models.go @@ -24,7 +24,6 @@ const ( ExecutionStatusStopped string = "Stopped" CandidateKindImage string = "image" - CandidateKindChart string = "chart" ExecutionTriggerManual string = "Manual" ExecutionTriggerSchedule string = "Schedule" diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index 3ea3a77de0c7..c7579cd746e6 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -143,7 +143,6 @@ type Artifact struct { // For example, `library/oracle/nosql`. Repository string `json:"repository"` // The info used to identify the version of the artifact, - // e.g: tag of image or version of the chart. Tag string `json:"tag"` // The artifact's digest, consisting of an algorithm and hex portion. // For example, `sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b`, diff --git a/src/server/middleware/csrf/csrf.go b/src/server/middleware/csrf/csrf.go index a380393ca93e..b04f23788eeb 100644 --- a/src/server/middleware/csrf/csrf.go +++ b/src/server/middleware/csrf/csrf.go @@ -74,7 +74,6 @@ func csrfSkipper(req *http.Request) bool { path := req.URL.Path if (strings.HasPrefix(path, "/v2/") || strings.HasPrefix(path, "/api/") || - strings.HasPrefix(path, "/chartrepo/") || strings.HasPrefix(path, "/service/")) && !lib.GetCarrySession(req.Context()) { return true } diff --git a/src/server/middleware/metric/metric.go b/src/server/middleware/metric/metric.go index 6d200739d13f..dfd43b3ff230 100644 --- a/src/server/middleware/metric/metric.go +++ b/src/server/middleware/metric/metric.go @@ -4,7 +4,6 @@ import ( "context" "net/http" "strconv" - "strings" "time" "github.com/goharbor/harbor/src/lib" @@ -38,10 +37,6 @@ func SetMetricOpID(ctx context.Context, value string) { } } -func isChartMuseumURL(url string) bool { - return strings.HasPrefix(url, "/chartrepo/") || strings.HasPrefix(url, "/api/chartrepo/") -} - func instrumentHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { metric.TotalInFlightGauge.Inc() @@ -50,12 +45,8 @@ func instrumentHandler(next http.Handler) http.Handler { ctx := context.WithValue(r.Context(), contextOpIDKey{}, &op) next.ServeHTTP(rc, r.WithContext(ctx)) if len(op) == 0 { - if isChartMuseumURL(r.URL.Path) { - op = "chartmuseum" - } else { - // From swagger's perspective the operation of this legacy URL is unknown - op = "unknown" - } + // From swagger's perspective the operation of this legacy URL is unknown + op = "unknown" } metric.TotalReqDurSummary.WithLabelValues(r.Method, op).Observe(time.Since(now).Seconds()) metric.TotalReqCnt.WithLabelValues(r.Method, strconv.Itoa(rc.StatusCode), op).Inc() diff --git a/src/server/middleware/security/idtoken_test.go b/src/server/middleware/security/idtoken_test.go index e2c2662e09b3..ce23044ceb0c 100644 --- a/src/server/middleware/security/idtoken_test.go +++ b/src/server/middleware/security/idtoken_test.go @@ -34,13 +34,6 @@ func TestIDToken(t *testing.T) { ctx := idToken.Generate(req) assert.Nil(t, ctx) - // not the candidate request - req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1/chartrepo/", nil) - require.Nil(t, err) - req = req.WithContext(lib.WithAuthMode(req.Context(), common.DBAuth)) - ctx = idToken.Generate(req) - assert.Nil(t, ctx) - // contains no authorization header req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1/api/projects/", nil) require.Nil(t, err) diff --git a/src/server/middleware/security/oidc_cli.go b/src/server/middleware/security/oidc_cli.go index 64498a2a00c6..bc3d9aa4e4bc 100644 --- a/src/server/middleware/security/oidc_cli.go +++ b/src/server/middleware/security/oidc_cli.go @@ -76,9 +76,7 @@ func (o *oidcCli) valid(req *http.Request) bool { path := strings.TrimSuffix(req.URL.Path, "/") if path == "/service/token" || - strings.HasPrefix(path, "/v2") || - strings.HasPrefix(path, "/chartrepo") || - strings.HasPrefix(path, "/api/chartrepo") { + strings.HasPrefix(path, "/v2") { // The request was sent by CLI to upload/download artifacts return true } diff --git a/src/server/route.go b/src/server/route.go index a57210ea870d..dc74f42fe177 100644 --- a/src/server/route.go +++ b/src/server/route.go @@ -24,7 +24,6 @@ import ( "github.com/goharbor/harbor/src/core/controllers" "github.com/goharbor/harbor/src/core/service/notifications/jobs" "github.com/goharbor/harbor/src/core/service/token" - "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/server/handler" "github.com/goharbor/harbor/src/server/router" ) @@ -60,23 +59,6 @@ func registerRoutes() { web.Router("/service/token", &token.Handler{}) - // chart repository services - if config.WithChartMuseum() { - chartRepositoryAPIType := &api.ChartRepositoryAPI{} - web.Router("/chartrepo/:repo/index.yaml", chartRepositoryAPIType, "get:GetIndexByRepo") - web.Router("/chartrepo/index.yaml", chartRepositoryAPIType, "get:GetIndex") - web.Router("/chartrepo/:repo/charts/:filename", chartRepositoryAPIType, "get:DownloadChart") - web.Router("/api/chartrepo/health", chartRepositoryAPIType, "get:GetHealthStatus") - web.Router("/api/chartrepo/:repo/charts", chartRepositoryAPIType, "get:ListCharts") - web.Router("/api/chartrepo/:repo/charts/:name", chartRepositoryAPIType, "get:ListChartVersions") - web.Router("/api/chartrepo/:repo/charts/:name", chartRepositoryAPIType, "delete:DeleteChart") - web.Router("/api/chartrepo/:repo/charts/:name/:version", chartRepositoryAPIType, "get:GetChartVersion") - web.Router("/api/chartrepo/:repo/charts/:name/:version", chartRepositoryAPIType, "delete:DeleteChartVersion") - web.Router("/api/chartrepo/:repo/charts", chartRepositoryAPIType, "post:UploadChartVersion") - web.Router("/api/chartrepo/:repo/prov", chartRepositoryAPIType, "post:UploadChartProvFile") - web.Router("/api/chartrepo/charts", chartRepositoryAPIType, "post:UploadChartVersion") - } - // Error pages web.ErrorController(&controllers.ErrorController{}) } diff --git a/src/server/v2.0/handler/model/project.go b/src/server/v2.0/handler/model/project.go index 24c1d9a97672..1321f2fc0b50 100644 --- a/src/server/v2.0/handler/model/project.go +++ b/src/server/v2.0/handler/model/project.go @@ -61,7 +61,6 @@ func (p *Project) ToSwagger() *models.Project { } return &models.Project{ - ChartCount: int64(p.ChartCount), CreationTime: strfmt.DateTime(p.CreationTime), CurrentUserRoleID: int64(p.Role), CurrentUserRoleIds: currentUserRoleIds, diff --git a/src/server/v2.0/handler/project.go b/src/server/v2.0/handler/project.go index bf90167d53e1..692a26d6f630 100644 --- a/src/server/v2.0/handler/project.go +++ b/src/server/v2.0/handler/project.go @@ -37,7 +37,6 @@ import ( "github.com/goharbor/harbor/src/controller/retention" "github.com/goharbor/harbor/src/controller/scanner" "github.com/goharbor/harbor/src/controller/user" - "github.com/goharbor/harbor/src/core/api" "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" @@ -371,8 +370,7 @@ func (a *projectAPI) GetProjectSummary(ctx context.Context, params operation.Get } summary := &models.ProjectSummary{ - ChartCount: int64(p.ChartCount), - RepoCount: p.RepoCount, + RepoCount: p.RepoCount, } var fetchSummaries []func(context.Context, *project.Project, *models.ProjectSummary) @@ -668,9 +666,6 @@ func (a *projectAPI) deletable(ctx context.Context, projectNameOrID interface{}) if p.RepoCount > 0 { result.Deletable = false result.Message = "the project contains repositories, can not be deleted" - } else if p.ChartCount > 0 { - result.Deletable = false - result.Message = "the project contains helm charts, can not be deleted" } return p, result, nil @@ -743,16 +738,6 @@ func (a *projectAPI) populateProperties(ctx context.Context, p *project.Project) } p.RepoCount = total - // Populate chart count property - if config.WithChartMuseum() { - count, err := api.GetChartController().GetCountOfCharts([]string{p.Name}) - if err != nil { - err = errors.Wrap(err, fmt.Sprintf("get chart count of project %d failed", p.ProjectID)) - return err - } - - p.ChartCount = count - } return nil } diff --git a/src/server/v2.0/handler/search.go b/src/server/v2.0/handler/search.go index fda9f6f4233f..ecae3f8f0cc2 100644 --- a/src/server/v2.0/handler/search.go +++ b/src/server/v2.0/handler/search.go @@ -22,7 +22,6 @@ import ( "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" - "helm.sh/helm/v3/cmd/helm/search" "github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security/local" @@ -30,9 +29,6 @@ import ( "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/project" "github.com/goharbor/harbor/src/controller/repository" - "github.com/goharbor/harbor/src/core/api" - "github.com/goharbor/harbor/src/lib" - "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/q" @@ -46,11 +42,6 @@ func newSearchAPI() *searchAPI { artifactCtl: artifact.Ctl, projectCtl: project.Ctl, repositoryCtl: repository.Ctl, - - chartMuseumEnabled: config.WithChartMuseum(), - searchCharts: func(q string, namespaces []string) ([]*search.Result, error) { - return api.GetChartController().SearchChart(q, namespaces) - }, } } @@ -59,9 +50,6 @@ type searchAPI struct { artifactCtl artifact.Controller projectCtl project.Controller repositoryCtl repository.Controller - - chartMuseumEnabled bool - searchCharts func(string, []string) ([]*search.Result, error) } func (s *searchAPI) Search(ctx context.Context, params operation.SearchParams) middleware.Responder { @@ -91,10 +79,7 @@ func (s *searchAPI) Search(ctx context.Context, params operation.SearchParams) m } projectResult := []*models.Project{} - proNames := []string{} for _, p := range projects { - proNames = append(proNames, p.Name) - if params.Q != "" && !strings.Contains(p.Name, params.Q) { continue } @@ -125,16 +110,9 @@ func (s *searchAPI) Search(ctx context.Context, params operation.SearchParams) m return s.SendError(ctx, errors.Wrap(err, "failed to filter repositories")) } - chartResult, err := s.filterCharts(ctx, params.Q, proNames) - if err != nil { - log.Errorf("failed to filter charts: %v", err) - return s.SendError(ctx, errors.Wrap(err, "failed to filter charts")) - } - return newSearchOK().WithPayload(&models.Search{ Project: projectResult, Repository: repositoryResult, - Chart: chartResult, }) } @@ -187,34 +165,7 @@ func (s *searchAPI) filterRepositories(ctx context.Context, projects []*project. return result, nil } -func (s *searchAPI) filterCharts(ctx context.Context, q string, namespaces []string) ([]*models.SearchResult, error) { - if !s.chartMuseumEnabled { - return nil, nil - } - - result := []*models.SearchResult{} - if len(namespaces) == 0 { - return result, nil - } - - charts, err := s.searchCharts(q, namespaces) - if err != nil { - return nil, err - } - - for _, chart := range charts { - var entry models.SearchResult - if err := lib.JSONCopy(&entry, chart); err != nil { - return nil, err - } - - result = append(result, &entry) - } - - return result, nil -} - -// searchOK removing the chart from the response when the chartmuseum is disabled +// searchOK ... type searchOK struct { Payload interface{} } @@ -222,7 +173,6 @@ type searchOK struct { func (o *searchOK) WithPayload(payload *models.Search) *searchOK { if payload != nil { p := &struct { - Chart *[]*models.SearchResult `json:"chart,omitempty"` Project []*models.Project `json:"project"` Repository []*models.SearchRepository `json:"repository"` }{ @@ -230,10 +180,6 @@ func (o *searchOK) WithPayload(payload *models.Search) *searchOK { Repository: payload.Repository, } - if payload.Chart != nil { - p.Chart = &payload.Chart - } - o.Payload = p } diff --git a/src/server/v2.0/handler/systeminfo.go b/src/server/v2.0/handler/systeminfo.go index 5962107bfae4..5b032c1cd7a6 100644 --- a/src/server/v2.0/handler/systeminfo.go +++ b/src/server/v2.0/handler/systeminfo.go @@ -88,7 +88,6 @@ func (s *sysInfoAPI) convertInfo(d *si.Data) *models.GeneralInfo { res.ProjectCreationRestriction = &d.Protected.ProjectCreationRestrict res.ExternalURL = &d.Protected.ExtURL res.RegistryURL = &d.Protected.RegistryURL - res.WithChartmuseum = &d.Protected.WithChartMuseum res.WithNotary = &d.Protected.WithNotary res.ReadOnly = &d.Protected.ReadOnly res.RegistryStorageProviderName = &d.Protected.RegistryStorageProviderName diff --git a/src/server/v2.0/route/legacy.go b/src/server/v2.0/route/legacy.go deleted file mode 100755 index c31b02ec4a08..000000000000 --- a/src/server/v2.0/route/legacy.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2018 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 route - -import ( - "github.com/beego/beego/v2/server/web" - - "github.com/goharbor/harbor/src/core/api" - "github.com/goharbor/harbor/src/lib/config" -) - -// RegisterRoutes for Harbor legacy APIs -func registerLegacyRoutes() { - version := APIVersion - - // APIs for chart repository - if config.WithChartMuseum() { - // Labels for chart - chartLabelAPIType := &api.ChartLabelAPI{} - web.Router("/api/"+version+"/chartrepo/:repo/charts/:name/:version/labels", chartLabelAPIType, "get:GetLabels;post:MarkLabel") - web.Router("/api/"+version+"/chartrepo/:repo/charts/:name/:version/labels/:id([0-9]+)", chartLabelAPIType, "delete:RemoveLabel") - } -} diff --git a/src/server/v2.0/route/route.go b/src/server/v2.0/route/route.go index b9e0df2a90f3..51e697b7b9e1 100644 --- a/src/server/v2.0/route/route.go +++ b/src/server/v2.0/route/route.go @@ -27,7 +27,6 @@ const ( // RegisterRoutes for Harbor v2.0 APIs func RegisterRoutes() { - registerLegacyRoutes() router.NewRoute().Path("/api/" + APIVersion + "/*"). Middleware(apiversion.Middleware(APIVersion)). Handler(handler.New()) diff --git a/src/testing/chart_utility.go b/src/testing/chart_utility.go deleted file mode 100644 index 9f3ad16c74da..000000000000 --- a/src/testing/chart_utility.go +++ /dev/null @@ -1,340 +0,0 @@ -package testing - -/** - * testing package provides utility functions for the UT tests - */ - -import ( - "encoding/json" - "net/http" - "strings" - - hlog "github.com/goharbor/harbor/src/lib/log" -) - -// MockChartRepoHandler is the backend chart server handler -var MockChartRepoHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - hlog.Infof("Incoming testing request: %s", r.RequestURI) - switch r.RequestURI { - case "/health": - if r.Method == http.MethodGet { - mockStatus := make(map[string]interface{}) - mockStatus["health"] = true - data, _ := json.Marshal(mockStatus) - w.Write(data) - - return - } - case "/repo1/index.yaml", "/library/index.yaml": - if r.Method == http.MethodGet { - w.Write([]byte(repo1IndexYaml)) - return - } - case "/repo2/index.yaml": - if r.Method == http.MethodGet { - w.Write([]byte(repo2IndexYaml)) - return - } - case "/not-existing-ns/index.yaml": - if r.Method == http.MethodGet { - w.WriteHeader(http.StatusNotFound) - return - } - case "/repo1/charts/harbor-0.2.0.tgz", - "/library/charts/harbor-0.2.0.tgz": - if r.Method == http.MethodGet { - w.Write(HelmChartContent) - return - } - case "/repo1/charts/harbor-0.2.0.tgz.prov", - "/library/charts/harbor-0.2.0.tgz.prov": - if r.Method == http.MethodGet { - w.Write(HelmChartProvContent) - return - } - case "/api/repo1/charts", "/api/library/charts": - if r.Method == http.MethodGet { - w.Write(ChartListContent) - return - } - case "/api/repo1/charts/harbor/0.2.0", - "/api/library/charts/harbor/0.2.0": - if r.Method == http.MethodGet { - w.Write([]byte(harborChartV)) - return - } - if r.Method == http.MethodDelete { - w.WriteHeader(http.StatusOK) - return - } - case "/api/repo1/charts/harbor/1.0.0": - if r.Method == http.MethodGet { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte("Not found")) - return - } - case "/api/repo1/charts/harbor/0.2.1", - "/api/library/charts/harbor/0.2.1": - if r.Method == http.MethodDelete { - w.WriteHeader(http.StatusOK) - return - } - case "/api/repo1/charts/harbor": - if r.Method == http.MethodGet { - w.Write([]byte(chartVersionsOfHarbor)) - return - } - case "/api/library/charts/harbor": - if r.Method == http.MethodGet { - w.Write([]byte(chartVersionsOfHarbor)) - return - } - if r.Method == http.MethodDelete { - w.WriteHeader(http.StatusOK) - } - case "/repo3/charts/harbor-0.8.1.tgz": - if r.Method == http.MethodGet { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("Unauthorized")) - return - } - case "/api/project_for_test_deletable/charts": - if r.Method == http.MethodGet { - w.Write([]byte("{}")) - return - } - default: - if r.Method == http.MethodGet { - if strings.HasSuffix(r.RequestURI, "/index.yaml") { - w.Write([]byte(repo2IndexYaml)) - return - } - } - } - - w.WriteHeader(http.StatusNotImplemented) - w.Write([]byte("not supported")) -}) - -// Mock content -var chartVersionsOfHarbor = ` - [ - { - "name": "harbor", - "home": "https://github.com/vmware/harbor", - "sources": [ - "https://github.com/vmware/harbor/tree/master/contrib/helm/harbor" - ], - "version": "0.2.1", - "description": "An Enterprise-class Docker Registry by VMware", - "keywords": [ - "vmware", - "docker", - "registry", - "harbor" - ], - "maintainers": [ - { - "name": "Jesse Hu", - "email": "huh@vmware.com" - }, - { - "name": "paulczar", - "email": "username.taken@gmail.com" - } - ], - "engine": "gotpl", - "icon": "https://raw.githubusercontent.com/goharbor/website/master/docs/img/harbor_logo.png", - "appVersion": "1.5.0", - "urls": [ - "charts/harbor-0.2.1.tgz" - ], - "created": "2018-08-29T10:26:29.625749155Z", - "digest": "2538edf4ddb797af8e025f3bd6226270440110bbdb689bad48656a519a154236" - }, - { - "name": "harbor", - "home": "https://github.com/vmware/harbor", - "sources": [ - "https://github.com/vmware/harbor/tree/master/contrib/helm/harbor" - ], - "version": "0.2.0", - "description": "An Enterprise-class Docker Registry by VMware", - "keywords": [ - "vmware", - "docker", - "registry", - "harbor" - ], - "maintainers": [ - { - "name": "Jesse Hu", - "email": "huh@vmware.com" - }, - { - "name": "paulczar", - "email": "username.taken@gmail.com" - } - ], - "engine": "gotpl", - "icon": "https://raw.githubusercontent.com/goharbor/website/master/docs/img/harbor_logo.png", - "appVersion": "1.5.0", - "urls": [ - "charts/harbor-0.2.0.tgz" - ], - "created": "2018-08-29T10:26:21.141611102Z", - "digest": "fc8aae8dade9f0dfca12e9f1085081c49843d30a063a3fa7eb42497e3ceb277c" - } - ] - ` -var harborChartV = ` - { - "name": "harbor", - "home": "https://github.com/goharbor/harbor", - "sources": [ - "https://github.com/goharbor/harbor/tree/master/contrib/helm/harbor" - ], - "version": "0.2.0", - "description": "An Enterprise-class Docker Registry by VMware", - "keywords": [ - "vmware", - "docker", - "registry", - "harbor" - ], - "maintainers": [ - { - "name": "Jesse Hu", - "email": "huh@vmware.com" - }, - { - "name": "paulczar", - "email": "username.taken@gmail.com" - } - ], - "engine": "gotpl", - "icon": "https://raw.githubusercontent.com/goharbor/website/master/docs/img/harbor_logo.png", - "appVersion": "1.5.0", - "urls": [ - "charts/harbor-0.2.0.tgz" - ], - "created": "2018-07-05T19:45:09.077735818+08:00", - "digest": "758ae429f362200a7941c600c8112cc3122723b99ffa5713a0902902da9949ba" - } - ` -var repo1IndexYaml = ` - apiVersion: v1 - entries: - harbor: - - appVersion: 1.5.0 - created: "2018-07-05T19:45:09.077735818+08:00" - description: An Enterprise-class Docker Registry by VMware - digest: 758ae429f362200a7941c600c8112cc3122723b99ffa5713a0902902da9949ba - engine: gotpl - home: https://github.com/goharbor/harbor - icon: https://raw.githubusercontent.com/goharbor/website/master/docs/img/harbor_logo.png - keywords: - - vmware - - docker - - registry - - harbor - maintainers: - - email: huh@vmware.com - name: Jesse Hu - - email: username.taken@gmail.com - name: paulczar - name: harbor - sources: - - https://github.com/goharbor/harbor/tree/master/contrib/helm/harbor - urls: - - charts/harbor-0.2.0.tgz - version: 0.2.0 - hello-helm: - - apiVersion: v1 - appVersion: "1.0" - created: "2018-06-21T17:58:20.233212807+08:00" - description: A Helm chart for Kubernetes - digest: fab923eae3668e0d480e9dec110d814a7599789c327d907ae1afb7ab575dc60d - name: hello-helm - urls: - - charts/hello-helm-0.1.0.tgz - version: 0.1.0 - myhelm: - - apiVersion: v1 - appVersion: "1.2" - created: "2018-07-03T21:07:08.840026536+08:00" - description: A Helm chart for Kubernetes, juts a test package - digest: de0c7a2866df7582d0a8f1b68c4aa839023517bb548e552f0d4c3c358f1dddb4 - name: myhelm - urls: - - charts/myhelm-1.2.0.tgz - version: 1.2.0 - - apiVersion: v1 - appVersion: "1.2" - created: "2018-07-03T21:01:11.959890639+08:00" - description: A Helm chart for Kubernetes, juts a test package - digest: 5994787296f89b798acfc14e88a1728b734d00c7d1b2ea6c01493dde7c411448 - name: myhelm - urls: - - charts/myhelm-1.1.4.tgz - version: 1.1.4 - generated: "2018-07-05T19:45:32+08:00" - ` - -var repo2IndexYaml = ` - apiVersion: v1 - entries: - harbor: - - appVersion: 1.5.0 - created: "2018-07-05T19:48:20.169806519+08:00" - description: An Enterprise-class Docker Registry by VMware - digest: 758ae429f362200a7941c600c8112cc3122723b99ffa5713a0902902da9949ba - engine: gotpl - home: https://github.com/goharbor/harbor - icon: https://raw.githubusercontent.com/goharbor/website/master/docs/img/harbor_logo.png - keywords: - - vmware - - docker - - registry - - harbor - maintainers: - - email: huh@vmware.com - name: Jesse Hu - - email: username.taken@gmail.com - name: paulczar - name: harbor - sources: - - https://github.com/goharbor/harbor/tree/master/contrib/helm/harbor - urls: - - charts/harbor-0.2.0.tgz - version: 0.2.0 - hello-helm: - - apiVersion: v1 - appVersion: "1.2" - created: "2018-07-10T13:44:50.350222695+08:00" - description: A Helm chart for Kubernetes - digest: 0a0ab3eb95d634f7a19fcc0905149c2ead1812edbbd7e5ca815656d75553be4e - name: hello-helm - urls: - - charts/hello-helm-0.1.8.tgz - version: 0.1.8 - - apiVersion: v1 - appVersion: "1.0" - created: "2018-06-21T17:51:40.194375417+08:00" - description: A Helm chart for Kubernetes - digest: fab923eae3668e0d480e9dec110d814a7599789c327d907ae1afb7ab575dc60d - name: hello-helm - urls: - - charts/hello-helm-0.1.0.tgz - version: 0.1.0 - generated: "2018-07-10T13:45:05+08:00" - ` - -// ChartListContent is the mock data of chart list -var ChartListContent = []byte{0x7b, 0x22, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x22, 0x2c, 0x22, 0x68, 0x6f, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x22, 0x2c, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2f, 0x68, 0x65, 0x6c, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x22, 0x5d, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x30, 0x2e, 0x32, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x6e, 0x20, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x2d, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x20, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x20, 0x62, 0x79, 0x20, 0x56, 0x4d, 0x77, 0x61, 0x72, 0x65, 0x22, 0x2c, 0x22, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x22, 0x2c, 0x22, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x22, 0x2c, 0x22, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x22, 0x5d, 0x2c, 0x22, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x4a, 0x65, 0x73, 0x73, 0x65, 0x20, 0x48, 0x75, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x3a, 0x22, 0x68, 0x75, 0x68, 0x40, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x22, 0x7d, 0x2c, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x70, 0x61, 0x75, 0x6c, 0x63, 0x7a, 0x61, 0x72, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x3a, 0x22, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x74, 0x61, 0x6b, 0x65, 0x6e, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x22, 0x7d, 0x5d, 0x2c, 0x22, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x22, 0x3a, 0x22, 0x67, 0x6f, 0x74, 0x70, 0x6c, 0x22, 0x2c, 0x22, 0x69, 0x63, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2f, 0x69, 0x6d, 0x67, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x6e, 0x67, 0x22, 0x2c, 0x22, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x31, 0x2e, 0x35, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x75, 0x72, 0x6c, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x2d, 0x30, 0x2e, 0x32, 0x2e, 0x30, 0x2e, 0x74, 0x67, 0x7a, 0x22, 0x5d, 0x2c, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x32, 0x30, 0x31, 0x38, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x35, 0x54, 0x31, 0x39, 0x3a, 0x34, 0x38, 0x3a, 0x32, 0x30, 0x2e, 0x31, 0x36, 0x39, 0x38, 0x30, 0x36, 0x35, 0x31, 0x39, 0x2b, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x22, 0x2c, 0x22, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x22, 0x37, 0x35, 0x38, 0x61, 0x65, 0x34, 0x32, 0x39, 0x66, 0x33, 0x36, 0x32, 0x32, 0x30, 0x30, 0x61, 0x37, 0x39, 0x34, 0x31, 0x63, 0x36, 0x30, 0x30, 0x63, 0x38, 0x31, 0x31, 0x32, 0x63, 0x63, 0x33, 0x31, 0x32, 0x32, 0x37, 0x32, 0x33, 0x62, 0x39, 0x39, 0x66, 0x66, 0x61, 0x35, 0x37, 0x31, 0x33, 0x61, 0x30, 0x39, 0x30, 0x32, 0x39, 0x30, 0x32, 0x64, 0x61, 0x39, 0x39, 0x34, 0x39, 0x62, 0x61, 0x22, 0x7d, 0x5d, 0x2c, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2d, 0x68, 0x65, 0x6c, 0x6d, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2d, 0x68, 0x65, 0x6c, 0x6d, 0x22, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x30, 0x2e, 0x31, 0x2e, 0x38, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x20, 0x48, 0x65, 0x6c, 0x6d, 0x20, 0x63, 0x68, 0x61, 0x72, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x22, 0x2c, 0x22, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x76, 0x31, 0x22, 0x2c, 0x22, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x31, 0x2e, 0x32, 0x22, 0x2c, 0x22, 0x75, 0x72, 0x6c, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2d, 0x68, 0x65, 0x6c, 0x6d, 0x2d, 0x30, 0x2e, 0x31, 0x2e, 0x38, 0x2e, 0x74, 0x67, 0x7a, 0x22, 0x5d, 0x2c, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x32, 0x30, 0x31, 0x38, 0x2d, 0x30, 0x37, 0x2d, 0x31, 0x30, 0x54, 0x31, 0x33, 0x3a, 0x34, 0x34, 0x3a, 0x35, 0x30, 0x2e, 0x33, 0x35, 0x30, 0x32, 0x32, 0x32, 0x36, 0x39, 0x35, 0x2b, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x22, 0x2c, 0x22, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x22, 0x30, 0x61, 0x30, 0x61, 0x62, 0x33, 0x65, 0x62, 0x39, 0x35, 0x64, 0x36, 0x33, 0x34, 0x66, 0x37, 0x61, 0x31, 0x39, 0x66, 0x63, 0x63, 0x30, 0x39, 0x30, 0x35, 0x31, 0x34, 0x39, 0x63, 0x32, 0x65, 0x61, 0x64, 0x31, 0x38, 0x31, 0x32, 0x65, 0x64, 0x62, 0x62, 0x64, 0x37, 0x65, 0x35, 0x63, 0x61, 0x38, 0x31, 0x35, 0x36, 0x35, 0x36, 0x64, 0x37, 0x35, 0x35, 0x35, 0x33, 0x62, 0x65, 0x34, 0x65, 0x22, 0x7d, 0x2c, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2d, 0x68, 0x65, 0x6c, 0x6d, 0x22, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x30, 0x2e, 0x31, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x20, 0x48, 0x65, 0x6c, 0x6d, 0x20, 0x63, 0x68, 0x61, 0x72, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x22, 0x2c, 0x22, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x76, 0x31, 0x22, 0x2c, 0x22, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x75, 0x72, 0x6c, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2d, 0x68, 0x65, 0x6c, 0x6d, 0x2d, 0x30, 0x2e, 0x31, 0x2e, 0x30, 0x2e, 0x74, 0x67, 0x7a, 0x22, 0x5d, 0x2c, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x32, 0x30, 0x31, 0x38, 0x2d, 0x30, 0x36, 0x2d, 0x32, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x35, 0x31, 0x3a, 0x34, 0x30, 0x2e, 0x31, 0x39, 0x34, 0x33, 0x37, 0x35, 0x34, 0x31, 0x37, 0x2b, 0x30, 0x38, 0x3a, 0x30, 0x30, 0x22, 0x2c, 0x22, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x62, 0x39, 0x32, 0x33, 0x65, 0x61, 0x65, 0x33, 0x36, 0x36, 0x38, 0x65, 0x30, 0x64, 0x34, 0x38, 0x30, 0x65, 0x39, 0x64, 0x65, 0x63, 0x31, 0x31, 0x30, 0x64, 0x38, 0x31, 0x34, 0x61, 0x37, 0x35, 0x39, 0x39, 0x37, 0x38, 0x39, 0x63, 0x33, 0x32, 0x37, 0x64, 0x39, 0x30, 0x37, 0x61, 0x65, 0x31, 0x61, 0x66, 0x62, 0x37, 0x61, 0x62, 0x35, 0x37, 0x35, 0x64, 0x63, 0x36, 0x30, 0x64, 0x22, 0x7d, 0x5d, 0x7d} - -// HelmChartProvContent is mock data of chart tgz... -var HelmChartProvContent = []byte{0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x47, 0x50, 0x20, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x20, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x48, 0x61, 0x73, 0x68, 0x3a, 0x20, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x0a, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x2e, 0x30, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x41, 0x6e, 0x20, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x2d, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x20, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x20, 0x62, 0x79, 0x20, 0x56, 0x4d, 0x77, 0x61, 0x72, 0x65, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x3a, 0x20, 0x67, 0x6f, 0x74, 0x70, 0x6c, 0x0a, 0x68, 0x6f, 0x6d, 0x65, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x0a, 0x69, 0x63, 0x6f, 0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x72, 0x61, 0x77, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x75, 0x73, 0x65, 0x72, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2f, 0x69, 0x6d, 0x67, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x6e, 0x67, 0x0a, 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x3a, 0x0a, 0x2d, 0x20, 0x2d, 0x20, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x0a, 0x2d, 0x20, 0x2d, 0x20, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x0a, 0x2d, 0x20, 0x2d, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x0a, 0x2d, 0x20, 0x2d, 0x20, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x3a, 0x0a, 0x2d, 0x20, 0x2d, 0x20, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x3a, 0x20, 0x68, 0x75, 0x68, 0x40, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x20, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x4a, 0x65, 0x73, 0x73, 0x65, 0x20, 0x48, 0x75, 0x0a, 0x2d, 0x20, 0x2d, 0x20, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x3a, 0x20, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x74, 0x61, 0x6b, 0x65, 0x6e, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x20, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x70, 0x61, 0x75, 0x6c, 0x63, 0x7a, 0x61, 0x72, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x3a, 0x0a, 0x2d, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2f, 0x68, 0x65, 0x6c, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x30, 0x2e, 0x32, 0x2e, 0x30, 0x0a, 0x0a, 0x2e, 0x2e, 0x2e, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x3a, 0x0a, 0x20, 0x20, 0x68, 0x61, 0x72, 0x62, 0x6f, 0x72, 0x2d, 0x30, 0x2e, 0x32, 0x2e, 0x30, 0x2e, 0x74, 0x67, 0x7a, 0x3a, 0x20, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x3a, 0x37, 0x65, 0x63, 0x36, 0x31, 0x62, 0x65, 0x61, 0x38, 0x33, 0x61, 0x35, 0x36, 0x39, 0x61, 0x37, 0x65, 0x32, 0x34, 0x62, 0x31, 0x66, 0x36, 0x32, 0x37, 0x63, 0x64, 0x65, 0x62, 0x62, 0x61, 0x39, 0x35, 0x63, 0x36, 0x39, 0x62, 0x63, 0x30, 0x30, 0x35, 0x33, 0x37, 0x63, 0x63, 0x31, 0x34, 0x32, 0x31, 0x61, 0x39, 0x37, 0x36, 0x35, 0x31, 0x64, 0x38, 0x37, 0x30, 0x30, 0x35, 0x37, 0x35, 0x31, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x47, 0x50, 0x20, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x77, 0x73, 0x44, 0x63, 0x42, 0x41, 0x45, 0x42, 0x43, 0x67, 0x41, 0x51, 0x42, 0x51, 0x4a, 0x62, 0x59, 0x74, 0x7a, 0x51, 0x43, 0x52, 0x44, 0x41, 0x6e, 0x76, 0x37, 0x7a, 0x30, 0x54, 0x61, 0x46, 0x6b, 0x77, 0x41, 0x41, 0x4d, 0x51, 0x55, 0x4d, 0x41, 0x4b, 0x48, 0x4e, 0x67, 0x45, 0x72, 0x44, 0x49, 0x76, 0x51, 0x44, 0x56, 0x6f, 0x53, 0x56, 0x6e, 0x76, 0x63, 0x52, 0x48, 0x58, 0x51, 0x4d, 0x0a, 0x4e, 0x33, 0x78, 0x53, 0x76, 0x69, 0x58, 0x50, 0x34, 0x36, 0x63, 0x70, 0x36, 0x36, 0x50, 0x53, 0x73, 0x77, 0x4c, 0x49, 0x50, 0x56, 0x63, 0x46, 0x6d, 0x6c, 0x33, 0x2f, 0x4c, 0x34, 0x32, 0x74, 0x54, 0x4e, 0x49, 0x4e, 0x4f, 0x6e, 0x32, 0x4a, 0x68, 0x64, 0x6a, 0x58, 0x43, 0x51, 0x30, 0x55, 0x47, 0x36, 0x43, 0x4c, 0x6f, 0x77, 0x37, 0x66, 0x74, 0x67, 0x6e, 0x79, 0x68, 0x4b, 0x2b, 0x49, 0x0a, 0x65, 0x5a, 0x65, 0x63, 0x73, 0x47, 0x56, 0x32, 0x73, 0x51, 0x31, 0x6e, 0x37, 0x46, 0x33, 0x5a, 0x72, 0x4f, 0x35, 0x45, 0x58, 0x59, 0x70, 0x71, 0x78, 0x41, 0x32, 0x4c, 0x77, 0x43, 0x35, 0x63, 0x43, 0x32, 0x61, 0x47, 0x69, 0x76, 0x62, 0x54, 0x6a, 0x70, 0x53, 0x7a, 0x4d, 0x2f, 0x4b, 0x4a, 0x55, 0x63, 0x2f, 0x6b, 0x4c, 0x34, 0x41, 0x75, 0x55, 0x55, 0x63, 0x48, 0x4c, 0x4f, 0x65, 0x6f, 0x0a, 0x46, 0x6a, 0x62, 0x6a, 0x78, 0x55, 0x39, 0x31, 0x33, 0x68, 0x43, 0x75, 0x75, 0x41, 0x55, 0x36, 0x39, 0x41, 0x48, 0x79, 0x66, 0x6b, 0x6e, 0x6e, 0x51, 0x6b, 0x6d, 0x6f, 0x43, 0x74, 0x4c, 0x79, 0x36, 0x53, 0x43, 0x34, 0x59, 0x66, 0x52, 0x73, 0x75, 0x54, 0x32, 0x58, 0x72, 0x41, 0x4e, 0x36, 0x72, 0x4b, 0x77, 0x45, 0x50, 0x64, 0x2f, 0x37, 0x4d, 0x59, 0x67, 0x69, 0x33, 0x78, 0x30, 0x79, 0x0a, 0x62, 0x2b, 0x6a, 0x62, 0x5a, 0x4a, 0x6d, 0x42, 0x76, 0x74, 0x79, 0x62, 0x31, 0x2b, 0x78, 0x73, 0x6f, 0x37, 0x42, 0x79, 0x4b, 0x54, 0x72, 0x44, 0x75, 0x72, 0x7a, 0x4a, 0x55, 0x64, 0x33, 0x46, 0x54, 0x74, 0x4d, 0x73, 0x46, 0x4a, 0x72, 0x44, 0x66, 0x75, 0x4d, 0x44, 0x6b, 0x54, 0x4a, 0x66, 0x4c, 0x61, 0x53, 0x57, 0x35, 0x36, 0x71, 0x70, 0x41, 0x68, 0x51, 0x53, 0x65, 0x34, 0x34, 0x56, 0x0a, 0x65, 0x4d, 0x55, 0x55, 0x53, 0x39, 0x6e, 0x6e, 0x58, 0x56, 0x74, 0x51, 0x56, 0x32, 0x6a, 0x5a, 0x42, 0x57, 0x75, 0x43, 0x48, 0x65, 0x6e, 0x2f, 0x4b, 0x65, 0x6b, 0x77, 0x4a, 0x50, 0x34, 0x31, 0x54, 0x34, 0x38, 0x44, 0x77, 0x5a, 0x6f, 0x44, 0x65, 0x38, 0x2b, 0x35, 0x5a, 0x59, 0x37, 0x73, 0x30, 0x35, 0x59, 0x79, 0x74, 0x49, 0x52, 0x4b, 0x6a, 0x66, 0x74, 0x65, 0x5a, 0x32, 0x65, 0x46, 0x0a, 0x33, 0x55, 0x42, 0x68, 0x5a, 0x30, 0x34, 0x43, 0x55, 0x70, 0x66, 0x6e, 0x33, 0x58, 0x76, 0x78, 0x50, 0x73, 0x34, 0x53, 0x35, 0x66, 0x36, 0x75, 0x6a, 0x68, 0x53, 0x79, 0x56, 0x4c, 0x5a, 0x74, 0x49, 0x65, 0x45, 0x66, 0x75, 0x68, 0x42, 0x5a, 0x52, 0x33, 0x77, 0x70, 0x6c, 0x39, 0x58, 0x55, 0x73, 0x45, 0x78, 0x64, 0x31, 0x52, 0x73, 0x66, 0x48, 0x4b, 0x51, 0x50, 0x34, 0x30, 0x6e, 0x6f, 0x0a, 0x2b, 0x49, 0x35, 0x55, 0x7a, 0x59, 0x52, 0x55, 0x58, 0x52, 0x62, 0x75, 0x39, 0x61, 0x41, 0x71, 0x71, 0x68, 0x4b, 0x76, 0x33, 0x55, 0x6c, 0x6a, 0x71, 0x2f, 0x51, 0x33, 0x72, 0x65, 0x6a, 0x70, 0x35, 0x63, 0x47, 0x49, 0x2f, 0x78, 0x34, 0x6c, 0x49, 0x7a, 0x4c, 0x59, 0x77, 0x64, 0x34, 0x63, 0x64, 0x6e, 0x41, 0x58, 0x42, 0x75, 0x37, 0x34, 0x4d, 0x74, 0x33, 0x58, 0x51, 0x6e, 0x6d, 0x55, 0x0a, 0x72, 0x56, 0x35, 0x79, 0x47, 0x35, 0x49, 0x50, 0x67, 0x4c, 0x64, 0x71, 0x70, 0x4b, 0x36, 0x30, 0x41, 0x78, 0x69, 0x7a, 0x6a, 0x50, 0x45, 0x71, 0x64, 0x37, 0x6d, 0x6f, 0x4e, 0x77, 0x6f, 0x46, 0x7a, 0x42, 0x52, 0x74, 0x4b, 0x33, 0x57, 0x65, 0x4b, 0x51, 0x3d, 0x3d, 0x0a, 0x3d, 0x6d, 0x39, 0x4e, 0x54, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x50, 0x47, 0x50, 0x20, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d} - -// HelmChartContent is mock data of chart tgz... -var HelmChartContent = []byte{0x1f, 0x8b, 0x8, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x29, 0x0, 0x2b, 0x61, 0x48, 0x52, 0x30, 0x63, 0x48, 0x4d, 0x36, 0x4c, 0x79, 0x39, 0x35, 0x62, 0x33, 0x56, 0x30, 0x64, 0x53, 0x35, 0x69, 0x5a, 0x53, 0x39, 0x36, 0x4f, 0x56, 0x56, 0x36, 0x4d, 0x57, 0x6c, 0x6a, 0x61, 0x6e, 0x64, 0x79, 0x54, 0x51, 0x6f, 0x3d, 0x48, 0x65, 0x6c, 0x6d, 0x0, 0xec, 0xfd, 0x7b, 0x77, 0xe2, 0x38, 0xb3, 0x28, 0xe, 0xef, 0xbf, 0xf9, 0x14, 0x7a, 0xd3, 0xe7, 0x9c, 0x99, 0xee, 0x34, 0x60, 0xee, 0x97, 0x77, 0xcf, 0xde, 0xf, 0xb7, 0x0, 0xe1, 0x1a, 0x2e, 0x49, 0xc8, 0x9c, 0x59, 0x89, 0xb0, 0x5, 0x28, 0xd8, 0x96, 0x63, 0xd9, 0x10, 0x32, 0x3d, 0xe7, 0xb3, 0xff, 0x96, 0x24, 0xdb, 0x18, 0x30, 0x21, 0xf7, 0x9e, 0x79, 0x76, 0xb4, 0xd6, 0x4c, 0x7, 0x5b, 0x55, 0x2a, 0xc9, 0x55, 0xa5, 0x52, 0xa9, 0x54, 0x9a, 0x41, 0x73, 0x4c, 0xcc, 0x68, 0x69, 0x6, 0x4d, 0x2b, 0xb2, 0x82, 0x9a, 0xfa, 0x1f, 0x6f, 0x5f, 0x24, 0x49, 0x92, 0x32, 0xa9, 0x14, 0xff, 0x57, 0x92, 0xa4, 0xed, 0x7f, 0xa5, 0x4c, 0x2c, 0xe3, 0xfd, 0xcd, 0x9f, 0xc7, 0xe2, 0x89, 0x44, 0xe6, 0x3f, 0xc0, 0x3b, 0x90, 0xb2, 0x5b, 0x6c, 0x6a, 0x41, 0xf3, 0x3f, 0x24, 0xe9, 0xb5, 0x78, 0xb6, 0x3b, 0xf7, 0xf, 0x29, 0xd0, 0x30, 0xce, 0x91, 0x49, 0x31, 0xd1, 0xf3, 0x20, 0x16, 0x49, 0x45, 0xa4, 0x90, 0x82, 0xa8, 0x6c, 0x62, 0xc3, 0xe2, 0x8f, 0xa, 0x3a, 0xa8, 0xe8, 0x16, 0x32, 0xd, 0x13, 0x53, 0x14, 0x96, 0x55, 0x48, 0x29, 0x28, 0x13, 0x79, 0x8e, 0x4c, 0xd0, 0x43, 0x53, 0x4c, 0x2d, 0x73, 0x5, 0xc6, 0x2b, 0x70, 0xde, 0x5a, 0x42, 0x13, 0x85, 0x90, 0x3e, 0xc5, 0x3a, 0xca, 0x83, 0x29, 0xb1, 0xc, 0x35, 0x34, 0x23, 0x1a, 0xca, 0x83, 0x99, 0x65, 0x19, 0x34, 0x1f, 0x8d, 0x4e, 0xb1, 0x35, 0xb3, 0xc7, 0x11, 0x99, 0x68, 0xd1, 0x85, 0xc6, 0x6a, 0x47, 0x5, 0xeb, 0x85, 0xb0, 0xcc, 0x1a, 0x3a, 0x54, 0x2d, 0x3a, 0x56, 0xc9, 0x38, 0xaa, 0x41, 0x6a, 0x21, 0x33, 0xaa, 0x10, 0x99, 0x46, 0xb1, 0x36, 0x75, 0xde, 0x5d, 0xab, 0x64, 0x4a, 0x22, 0x86, 0x3e, 0xd, 0xcd, 0xd1, 0x6a, 0x49, 0x4c, 0x85, 0xe6, 0x43, 0x61, 0x20, 0xc0, 0x43, 0x61, 0xa0, 0x70, 0x7a, 0x43, 0x61, 0x60, 0x3a, 0x14, 0x87, 0xc2, 0xc0, 0x69, 0x5b, 0x83, 0x58, 0xb7, 0x20, 0xd6, 0x91, 0xc9, 0x41, 0x90, 0x6, 0xb1, 0x9a, 0x7, 0x33, 0x7b, 0xf6, 0x2f, 0x1, 0xcd, 0xe8, 0x8, 0x1, 0xa0, 0x43, 0xd6, 0x93, 0x53, 0x44, 0x29, 0x2, 0x35, 0x7b, 0x5d, 0xd1, 0xa6, 0xc8, 0x64, 0xef, 0x22, 0x16, 0x9c, 0x23, 0xfd, 0x5f, 0x53, 0xf6, 0x74, 0x3, 0xc4, 0x80, 0xb6, 0x2a, 0x3f, 0x40, 0x33, 0x24, 0x7e, 0x3a, 0xad, 0x52, 0x62, 0x9b, 0x32, 0xe2, 0x2d, 0x1e, 0xec, 0xb6, 0x65, 0x22, 0xe4, 0x76, 0x5b, 0x26, 0xba, 0x65, 0xe2, 0x71, 0x74, 0x86, 0x54, 0xcd, 0x1d, 0xbd, 0x85, 0xfb, 0xf1, 0xa4, 0x48, 0x3c, 0x22, 0x85, 0x9e, 0xfd, 0xfd, 0x9d, 0x66, 0x16, 0x50, 0xb5, 0x11, 0x7d, 0x27, 0x5, 0xf0, 0xb8, 0xfc, 0xc7, 0xd3, 0x99, 0xf4, 0x8e, 0xfc, 0xa7, 0x63, 0xf1, 0x4f, 0xf9, 0xff, 0x88, 0xf2, 0x5, 0x94, 0x88, 0x3e, 0xc1, 0x53, 0xdb, 0x44, 0xc0, 0x60, 0xbc, 0x44, 0x2d, 0xa4, 0x83, 0x73, 0xa2, 0xda, 0x1a, 0xa2, 0xec, 0x9, 0x80, 0x86, 0xa1, 0x62, 0x19, 0x32, 0x7d, 0x10, 0xfa, 0xf2, 0x5, 0x14, 0xd6, 0x3f, 0x29, 0xb0, 0x66, 0xd0, 0x2, 0x26, 0xba, 0xb3, 0xb1, 0x89, 0x0, 0xb5, 0x88, 0x9, 0xa7, 0x8, 0xcc, 0xe0, 0x2, 0x1, 0x8, 0x6e, 0x16, 0x2, 0xc9, 0xd, 0x50, 0xd0, 0x4, 0xeb, 0x98, 0x41, 0x80, 0xe5, 0xc, 0xcb, 0x33, 0xb0, 0xc4, 0xaa, 0xa, 0xc6, 0x88, 0xc9, 0x8f, 0xc2, 0x70, 0x2e, 0x67, 0x48, 0x7, 0x37, 0x5e, 0xf3, 0x32, 0x8a, 0x20, 0x1d, 0x8e, 0x55, 0xa4, 0xdc, 0x0, 0x4c, 0x1, 0x45, 0x16, 0xb0, 0x8, 0xb0, 0x4c, 0x1b, 0x45, 0x58, 0x6d, 0x74, 0xf, 0x35, 0x43, 0x45, 0xa1, 0x2f, 0x40, 0x5b, 0xd1, 0x3b, 0x35, 0x1f, 0xfa, 0x2, 0x0, 0x70, 0x1a, 0x13, 0x3f, 0x0, 0x50, 0xa0, 0x5, 0xf3, 0xac, 0x72, 0xd7, 0x45, 0x6a, 0x39, 0x9d, 0x2, 0x7d, 0x87, 0xcc, 0x12, 0x53, 0x66, 0xac, 0x4a, 0x7d, 0x22, 0x28, 0x44, 0xca, 0x77, 0xb7, 0xf, 0xfc, 0x5d, 0x9b, 0xcb, 0xec, 0x7f, 0xfa, 0x1f, 0xfd, 0x97, 0x53, 0xdf, 0x21, 0xe9, 0x28, 0x7c, 0x14, 0x4, 0x72, 0x74, 0xf4, 0xdd, 0xe9, 0xa8, 0x82, 0x29, 0xeb, 0x7, 0x5, 0xca, 0x4a, 0x87, 0x1a, 0x96, 0x81, 0x61, 0x92, 0x5, 0x66, 0x2, 0x8b, 0xf5, 0xa9, 0x83, 0xcb, 0xd6, 0x9d, 0xd6, 0xc1, 0xaf, 0xd6, 0xc, 0x31, 0x52, 0xa0, 0xad, 0x5a, 0x5f, 0x1, 0x31, 0xdd, 0x66, 0x74, 0x5b, 0x55, 0xbf, 0x3, 0x9d, 0xec, 0x34, 0x5, 0xa8, 0x81, 0x64, 0x80, 0x79, 0x2f, 0x0, 0xab, 0xfd, 0x1d, 0xc8, 0x33, 0x42, 0x28, 0xd6, 0xa7, 0xc0, 0x87, 0x6b, 0xdd, 0x2a, 0x32, 0x23, 0x0, 0xfc, 0x3a, 0x35, 0xe2, 0x80, 0xe8, 0xa0, 0x70, 0xd1, 0x67, 0xd4, 0x43, 0x5d, 0x81, 0xa6, 0x2, 0xc4, 0xd7, 0x5, 0xa0, 0xda, 0xa8, 0x7c, 0x67, 0xaf, 0xc0, 0xff, 0x1, 0x1d, 0x3, 0xe9, 0x7d, 0xb, 0xca, 0xf3, 0xaf, 0xa1, 0x2f, 0x5f, 0x9c, 0x81, 0xf5, 0x93, 0x90, 0x67, 0x3, 0xe0, 0x3c, 0x87, 0xb2, 0x8c, 0x28, 0x6d, 0x11, 0x5, 0xe5, 0x41, 0xf, 0x41, 0xe5, 0xc2, 0xc4, 0x16, 0xea, 0xe8, 0x32, 0x72, 0xe1, 0xf0, 0x3, 0xca, 0x83, 0x58, 0x15, 0x87, 0x58, 0x33, 0x6b, 0x9e, 0x33, 0x91, 0xd0, 0x86, 0x9c, 0x8b, 0x10, 0xb5, 0x28, 0x80, 0xba, 0x2, 0x54, 0xac, 0x61, 0x2b, 0x90, 0xfd, 0x4c, 0x34, 0x11, 0x93, 0x45, 0x3e, 0x1a, 0x9d, 0xdb, 0x63, 0x64, 0xea, 0xc8, 0x42, 0x34, 0x82, 0x89, 0x98, 0x14, 0x98, 0x46, 0xe, 0x4f, 0x6d, 0xac, 0xa0, 0xa8, 0x4c, 0x34, 0xc3, 0xb6, 0x50, 0xd8, 0x6d, 0x81, 0x46, 0x45, 0x2f, 0x7c, 0x4c, 0xe3, 0xbd, 0x72, 0xd9, 0xc6, 0x25, 0xc2, 0xfd, 0xd, 0x80, 0x86, 0x34, 0x62, 0xae, 0xf2, 0x20, 0x9e, 0x4a, 0xb7, 0xb0, 0xf7, 0x54, 0x36, 0xec, 0x3c, 0x88, 0x49, 0x92, 0x16, 0xa, 0xf9, 0xb8, 0x36, 0x1f, 0x2, 0xc0, 0x61, 0xdc, 0x3c, 0x67, 0xd7, 0x50, 0xe8, 0xb, 0x18, 0xcc, 0x10, 0xb0, 0xe0, 0x14, 0x4c, 0x88, 0x9, 0x6a, 0x5c, 0xd9, 0x3a, 0x13, 0x12, 0xc0, 0x1a, 0x9c, 0x22, 0x1a, 0x9, 0x9, 0x15, 0x5c, 0x67, 0xbf, 0x6, 0x70, 0x9a, 0x7, 0xff, 0xc7, 0x99, 0xd5, 0xf8, 0xfb, 0x6b, 0x6, 0xbb, 0xe0, 0x33, 0x73, 0x58, 0x66, 0x46, 0x5a, 0xd8, 0x80, 0x96, 0x3c, 0x73, 0x31, 0x9f, 0x9c, 0x95, 0xdb, 0x7e, 0xd4, 0x14, 0x99, 0xb, 0x2c, 0xa3, 0x48, 0x8, 0xdd, 0x5b, 0x6c, 0x6e, 0x52, 0xcb, 0x84, 0xcd, 0x71, 0xee, 0xd4, 0x13, 0xd1, 0x56, 0x11, 0x85, 0x3f, 0x9, 0x31, 0x39, 0x12, 0x55, 0xba, 0xc4, 0xb4, 0x98, 0x9c, 0x31, 0x8e, 0xe1, 0x7f, 0xef, 0xe2, 0xfb, 0xe, 0x54, 0xc4, 0xa4, 0x1a, 0x69, 0x86, 0xb5, 0x2, 0x78, 0xc2, 0xeb, 0x3a, 0xef, 0x38, 0x28, 0x61, 0xe2, 0x3c, 0x26, 0xb6, 0xae, 0x0, 0x8b, 0x84, 0xbe, 0x0, 0x83, 0xe1, 0xc9, 0x4a, 0xd1, 0x64, 0x32, 0x11, 0xf2, 0xb7, 0x93, 0x7, 0x89, 0x78, 0x46, 0x92, 0x42, 0x7e, 0x29, 0x62, 0x23, 0xf5, 0x1d, 0xac, 0x88, 0xd, 0x14, 0xa2, 0xff, 0x62, 0x1, 0x1d, 0x21, 0x86, 0x45, 0xbc, 0x56, 0x69, 0xc9, 0xb4, 0xa2, 0x96, 0x4a, 0x1b, 0x68, 0x15, 0x95, 0x61, 0xc9, 0xb4, 0xbe, 0x83, 0xb1, 0x6d, 0x1, 0xcd, 0xa6, 0x16, 0x80, 0x8a, 0x12, 0xfa, 0xe2, 0x92, 0xca, 0x87, 0x2, 0x52, 0x80, 0x75, 0x8a, 0x64, 0xdb, 0x64, 0x1f, 0x9e, 0x4f, 0xf7, 0x18, 0x51, 0xde, 0xa3, 0x15, 0xb1, 0xbd, 0xa1, 0x97, 0x55, 0x8c, 0x74, 0x2b, 0x12, 0x72, 0xeb, 0xba, 0xb6, 0x4c, 0x1e, 0x4c, 0xa0, 0x4a, 0x51, 0x68, 0x8a, 0x74, 0x64, 0x42, 0xb, 0x95, 0x90, 0x69, 0xe1, 0x9, 0x63, 0x3f, 0x44, 0x9d, 0x4f, 0x2a, 0xc6, 0x7d, 0xd0, 0xec, 0x3, 0x79, 0xfd, 0xd2, 0x37, 0x64, 0x11, 0xfe, 0x5e, 0x26, 0x9a, 0x46, 0x74, 0x6e, 0x5, 0x0, 0x32, 0x71, 0xba, 0x21, 0x88, 0xd6, 0xd8, 0xf7, 0xe3, 0x3, 0xb8, 0xf9, 0x8d, 0x0, 0x1c, 0x93, 0x5, 0x8a, 0x84, 0x44, 0xdd, 0x7c, 0x48, 0xf4, 0x39, 0x1f, 0xe2, 0x9d, 0xce, 0xbb, 0x1f, 0x9c, 0x22, 0xd9, 0x44, 0x16, 0x98, 0xa3, 0x15, 0xd7, 0x9e, 0xbc, 0x61, 0xa4, 0xcb, 0xe6, 0x8a, 0x1b, 0x6c, 0x11, 0xd0, 0x62, 0x4d, 0x8c, 0x99, 0xfe, 0x65, 0x5d, 0xd7, 0xa7, 0xac, 0xf5, 0x58, 0x1a, 0x30, 0xd6, 0xa1, 0x91, 0x90, 0x80, 0x66, 0x68, 0x81, 0x4e, 0xac, 0x30, 0xc, 0x3b, 0x43, 0x35, 0x47, 0x2b, 0xa7, 0x1, 0x8a, 0x0, 0xd4, 0x75, 0x62, 0x39, 0xa, 0x1e, 0xaa, 0x2a, 0x59, 0x72, 0x62, 0x5d, 0xe3, 0x89, 0x7d, 0x99, 0x25, 0x31, 0xe7, 0x60, 0x8c, 0x66, 0x98, 0x7d, 0xee, 0x19, 0x2, 0xcc, 0xec, 0xbb, 0xf, 0x7d, 0x1, 0x58, 0x9f, 0x9a, 0x88, 0x52, 0xc0, 0xd, 0x15, 0xa2, 0xaa, 0xc8, 0x64, 0x43, 0xcc, 0x9f, 0xed, 0xca, 0x6, 0xf0, 0x37, 0xc4, 0x5e, 0x3, 0x17, 0x3e, 0xb2, 0x29, 0xd8, 0x94, 0xaa, 0x61, 0x13, 0x29, 0xd8, 0x44, 0xb2, 0x95, 0x7, 0x47, 0xc, 0xf8, 0xe8, 0x91, 0xea, 0x86, 0x49, 0xee, 0x57, 0xe1, 0x31, 0x51, 0x56, 0x61, 0xa1, 0x74, 0x8e, 0x24, 0x51, 0x9d, 0x53, 0x19, 0x79, 0x6, 0x50, 0x8, 0x2a, 0x1a, 0xe3, 0x10, 0x73, 0x81, 0x4c, 0x46, 0x20, 0x17, 0x48, 0x41, 0xa9, 0x89, 0xc, 0x42, 0xb1, 0xc5, 0x55, 0xc3, 0x86, 0xc1, 0x16, 0xf6, 0xc1, 0xf0, 0x9a, 0x16, 0x13, 0xe9, 0x6f, 0xdb, 0x22, 0xcd, 0x5f, 0x19, 0xb6, 0xaa, 0x76, 0x89, 0x8a, 0xe5, 0x55, 0x1e, 0xd4, 0x27, 0x6d, 0x62, 0x75, 0x4d, 0x44, 0x91, 0x6e, 0xb1, 0xa1, 0x62, 0xa6, 0x64, 0x8d, 0x50, 0xd6, 0x61, 0xaa, 0x59, 0x46, 0x44, 0x5b, 0x9, 0xb9, 0x65, 0x26, 0xe2, 0x91, 0x5b, 0x41, 0x48, 0xd4, 0x51, 0x3c, 0xe5, 0x3d, 0x19, 0x52, 0x64, 0x32, 0x10, 0x3e, 0x43, 0x5e, 0x73, 0x5a, 0xfe, 0x15, 0x8, 0xda, 0xa7, 0x6a, 0x1e, 0x1c, 0x71, 0x6e, 0xf7, 0x9e, 0x9d, 0x98, 0x44, 0xcb, 0x83, 0x23, 0xe, 0x5, 0xfe, 0x73, 0x2f, 0x8e, 0xff, 0xf2, 0x0, 0xea, 0xa, 0xd2, 0x2d, 0x6c, 0xad, 0xd8, 0x94, 0xe7, 0x3d, 0x73, 0x44, 0x2a, 0xf, 0x8e, 0x4e, 0x36, 0xb0, 0x77, 0x97, 0xca, 0x16, 0xd7, 0x19, 0x90, 0x52, 0x66, 0xae, 0x33, 0x56, 0x60, 0xad, 0x74, 0x9d, 0xdf, 0x79, 0x47, 0x94, 0x62, 0xf1, 0x44, 0x32, 0xc5, 0x5e, 0xda, 0xd6, 0x8c, 0x35, 0x24, 0x66, 0x1, 0x31, 0xc7, 0x1c, 0x29, 0xe3, 0x6b, 0xf6, 0x9c, 0xa1, 0xa7, 0x48, 0x9d, 0x38, 0x12, 0xc, 0xc5, 0xb2, 0xe5, 0x88, 0xe8, 0xec, 0x85, 0xaa, 0x40, 0x43, 0x7c, 0x2f, 0xdb, 0x64, 0xdd, 0x65, 0xbf, 0x99, 0xb5, 0xcd, 0xff, 0xe5, 0x9f, 0x48, 0x70, 0x6, 0x45, 0xd0, 0x94, 0x67, 0xe5, 0xb6, 0xd3, 0xf, 0xf7, 0xc1, 0x9a, 0x1e, 0xe7, 0xf1, 0x18, 0x52, 0xe4, 0xab, 0x35, 0xc1, 0xaa, 0xc5, 0x87, 0xfb, 0x57, 0x32, 0xbe, 0x45, 0xb2, 0xc5, 0x27, 0xc5, 0xdf, 0xd8, 0x84, 0x40, 0xf4, 0xaf, 0xa2, 0x8a, 0x8d, 0x19, 0xb4, 0x8d, 0x15, 0x7, 0xaf, 0x4c, 0xc, 0x46, 0x7c, 0x5c, 0xfc, 0xb4, 0xb0, 0x86, 0x88, 0xcd, 0x3e, 0x61, 0x4a, 0x3c, 0x58, 0x20, 0x13, 0x4f, 0x56, 0x4c, 0xeb, 0xe4, 0xc1, 0xd1, 0xc0, 0x61, 0xf4, 0xb5, 0x15, 0xc3, 0xcd, 0x1a, 0x26, 0x80, 0x70, 0x6d, 0xab, 0x59, 0x8e, 0xe9, 0x13, 0xf2, 0xd9, 0x40, 0x7c, 0x8a, 0xe2, 0x53, 0xac, 0xf8, 0x1b, 0x80, 0x2f, 0x1, 0x13, 0x37, 0x38, 0x30, 0x71, 0x83, 0xad, 0x89, 0x9b, 0x61, 0xf1, 0xcd, 0x98, 0xec, 0xa7, 0x6f, 0xc2, 0xe4, 0x3f, 0xb7, 0xe7, 0x4b, 0xe7, 0xe1, 0x7a, 0xba, 0x4, 0x40, 0x27, 0xa, 0xea, 0x23, 0x15, 0xc9, 0x16, 0x31, 0xf3, 0xe0, 0xcf, 0xbf, 0x42, 0x0, 0x58, 0x44, 0x45, 0xa6, 0xa3, 0x7, 0xc0, 0xef, 0x7f, 0xb0, 0x2f, 0x3e, 0xe1, 0x16, 0xe3, 0x8a, 0x57, 0x60, 0xd3, 0xfe, 0x2d, 0x19, 0x3b, 0xd3, 0x4d, 0xe8, 0x4b, 0x68, 0xfd, 0xe3, 0xc9, 0x52, 0xe9, 0x83, 0x7f, 0xb9, 0x50, 0xa, 0xed, 0xb9, 0xc5, 0xc4, 0xe2, 0x61, 0x8, 0x0, 0xd, 0xde, 0x5f, 0x10, 0x73, 0xce, 0x96, 0x92, 0x20, 0xc5, 0x66, 0xb8, 0x2d, 0xeb, 0x62, 0xdb, 0xb6, 0x8, 0xb2, 0x2c, 0x5e, 0x3f, 0x50, 0xc3, 0x7a, 0xe8, 0x4b, 0xc8, 0xc6, 0x4f, 0x1e, 0x18, 0x1b, 0xbf, 0xdb, 0x80, 0x18, 0x26, 0x5e, 0x40, 0xb, 0x35, 0xd0, 0xaa, 0x8b, 0xb4, 0x3c, 0xf8, 0xc1, 0xb1, 0x85, 0x59, 0x29, 0x56, 0xaa, 0xf5, 0x36, 0xe8, 0xf5, 0xb, 0xa0, 0xdb, 0xab, 0x9f, 0x17, 0x6, 0x15, 0xd0, 0xa8, 0x8c, 0xf8, 0x1b, 0x5e, 0xa7, 0x55, 0xaf, 0x9f, 0x36, 0xa, 0xf5, 0x62, 0xa1, 0xd0, 0x28, 0x15, 0xa6, 0x95, 0x42, 0xf2, 0x62, 0x34, 0xbe, 0x57, 0xcc, 0x93, 0x6a, 0x35, 0xdd, 0xd3, 0x27, 0xab, 0x51, 0x43, 0xad, 0x8d, 0x5a, 0xcd, 0x84, 0x5a, 0xb9, 0xb3, 0xa, 0x39, 0x79, 0x74, 0x71, 0xd1, 0x59, 0xe9, 0x95, 0x5c, 0x11, 0xc1, 0x8a, 0x19, 0x95, 0x5b, 0x7a, 0x4b, 0xc8, 0x6a, 0xd1, 0x8c, 0x29, 0x4a, 0x2e, 0x16, 0x6d, 0x6b, 0xe9, 0x1e, 0x1e, 0xcd, 0xce, 0x16, 0x83, 0xf2, 0x30, 0xde, 0x90, 0xd3, 0x6d, 0x74, 0x7b, 0x77, 0xab, 0xa8, 0xf8, 0x22, 0x55, 0xcc, 0xdd, 0xf, 0xc9, 0x79, 0x3, 0xae, 0x4c, 0x9c, 0xed, 0xc, 0xb3, 0x31, 0x98, 0x3d, 0xc7, 0x97, 0xa8, 0x3d, 0x6d, 0x2c, 0x4b, 0xdd, 0x1e, 0x47, 0x52, 0xc0, 0x83, 0x81, 0xa, 0x63, 0xf, 0xe4, 0x32, 0x55, 0xd6, 0x17, 0xe4, 0xde, 0xe8, 0xe4, 0xaa, 0x9, 0xf5, 0x7e, 0xd5, 0x5e, 0xc, 0x1a, 0x97, 0xb2, 0x24, 0x2f, 0xb3, 0xed, 0xdb, 0xb3, 0x72, 0xe1, 0xd2, 0x80, 0xe5, 0xf1, 0xc3, 0xe9, 0xa8, 0x39, 0xa7, 0xb3, 0x12, 0xb2, 0x57, 0xe5, 0xdc, 0x58, 0x26, 0xd9, 0x5e, 0x2e, 0xcd, 0x91, 0x44, 0x1f, 0x4c, 0xa3, 0x58, 0xb9, 0xcc, 0x5a, 0x85, 0x72, 0x3b, 0x71, 0x9c, 0x58, 0x15, 0x12, 0x93, 0x96, 0x7c, 0xf5, 0x70, 0x7e, 0x49, 0x8b, 0x5a, 0xb2, 0x38, 0x30, 0x6a, 0xa7, 0xbd, 0x79, 0xf4, 0xae, 0x68, 0xd4, 0x46, 0x95, 0x6e, 0xbf, 0xf6, 0x70, 0xbf, 0xaa, 0x25, 0x70, 0xb5, 0xd5, 0x6e, 0xcc, 0x13, 0x8b, 0xd6, 0xb0, 0x78, 0xf5, 0xc0, 0x91, 0x20, 0xa9, 0x32, 0x9a, 0x37, 0xb2, 0xed, 0x52, 0x21, 0x5e, 0xb2, 0x2b, 0x8d, 0x96, 0x5e, 0x4a, 0x40, 0xf9, 0x3e, 0x57, 0x57, 0x7a, 0xcb, 0xf9, 0x7d, 0x4c, 0x82, 0xe3, 0xea, 0xa2, 0x76, 0x56, 0x6a, 0xf6, 0x4a, 0xe7, 0xa3, 0x8c, 0x59, 0x25, 0x70, 0x7e, 0x3c, 0x96, 0xc8, 0x55, 0x2e, 0xd7, 0x3b, 0xad, 0xf7, 0xce, 0x72, 0xf5, 0x3b, 0x8e, 0x64, 0x74, 0x49, 0xf5, 0xec, 0x84, 0xb6, 0x8a, 0x67, 0xea, 0x2a, 0x7d, 0x7f, 0xbf, 0xe8, 0xa3, 0x51, 0xea, 0xd2, 0xee, 0x77, 0x33, 0x97, 0xe3, 0xf4, 0x69, 0xa3, 0x6c, 0x25, 0x56, 0xd9, 0xbe, 0x81, 0x93, 0xd3, 0x5e, 0xac, 0x15, 0x4d, 0xc1, 0xca, 0xc3, 0xcc, 0xee, 0xac, 0xec, 0xb8, 0x14, 0x33, 0x5b, 0x3a, 0xcc, 0xf4, 0x68, 0x94, 0x23, 0xa9, 0x76, 0x27, 0xb0, 0x71, 0x5b, 0x54, 0xc7, 0x97, 0xd2, 0x2d, 0x6e, 0x96, 0x61, 0x66, 0x91, 0x7c, 0xb8, 0xa5, 0xc5, 0x2e, 0xed, 0xc2, 0x49, 0x54, 0x4e, 0xda, 0x84, 0x3c, 0x24, 0xea, 0xa5, 0x26, 0x55, 0xec, 0x39, 0x24, 0xda, 0x71, 0x25, 0x95, 0xc8, 0xf6, 0xa4, 0xa, 0xee, 0xcc, 0x2f, 0xad, 0xe8, 0x72, 0xb5, 0x8c, 0x73, 0x24, 0xf1, 0x91, 0x6, 0x2f, 0xda, 0x25, 0x3a, 0x52, 0x2b, 0xc6, 0x1c, 0x65, 0xe4, 0xf3, 0x52, 0x22, 0x81, 0xa2, 0x92, 0xd2, 0x2d, 0xde, 0x25, 0xeb, 0x35, 0x7a, 0x3e, 0x51, 0xe1, 0xb2, 0x7f, 0x92, 0xeb, 0x5c, 0xf4, 0xe3, 0x9, 0x3c, 0x3f, 0x2f, 0xd0, 0xa8, 0x7e, 0x9c, 0x49, 0x37, 0x6e, 0x6d, 0x5b, 0xae, 0x94, 0xb5, 0xf1, 0x80, 0x23, 0x81, 0x8d, 0xe1, 0xa8, 0x70, 0x7a, 0xbb, 0x28, 0x68, 0x57, 0xcd, 0x58, 0xf2, 0xf6, 0xb8, 0xd2, 0x90, 0xa3, 0x25, 0x62, 0xa8, 0xb3, 0x12, 0x4a, 0x1b, 0x8d, 0x19, 0x5c, 0xdc, 0x6a, 0xed, 0x7a, 0x67, 0x52, 0xea, 0x2b, 0xaa, 0x89, 0xea, 0xdd, 0xe2, 0xac, 0x73, 0x3e, 0x9e, 0xc4, 0x26, 0xd9, 0x58, 0x66, 0xd9, 0x20, 0xfd, 0xfa, 0x15, 0x47, 0x72, 0x77, 0xbe, 0x2a, 0x15, 0x62, 0x85, 0x51, 0x7b, 0x5e, 0xcf, 0xf5, 0x46, 0x7d, 0x49, 0x82, 0xf7, 0xd6, 0x69, 0xb5, 0x58, 0x6d, 0xa9, 0x8d, 0xb1, 0x72, 0x76, 0x57, 0x6a, 0x1b, 0xcd, 0x66, 0x2a, 0x2b, 0xa7, 0x7, 0x24, 0xe, 0x97, 0x9a, 0x3c, 0xaf, 0x5f, 0x95, 0x2a, 0x72, 0x61, 0xd0, 0xe8, 0x18, 0xc7, 0xc7, 0x6d, 0x52, 0xd5, 0x38, 0x92, 0xfa, 0x58, 0x1a, 0xcf, 0x94, 0x3e, 0xa4, 0x4a, 0xb5, 0x98, 0x3a, 0x2f, 0x59, 0xcb, 0xab, 0x73, 0xd5, 0x6e, 0x67, 0xc7, 0x4d, 0x35, 0x96, 0x78, 0x28, 0x36, 0xc8, 0xfd, 0xa0, 0x7a, 0xdb, 0x56, 0x2b, 0xd0, 0x1a, 0x56, 0xcb, 0x3d, 0xbd, 0x5c, 0xd0, 0x9b, 0xca, 0xd5, 0xf8, 0xf2, 0x72, 0x32, 0xb9, 0xa5, 0xa5, 0xc2, 0xb2, 0x52, 0x10, 0xcc, 0x76, 0xc6, 0xd8, 0xbd, 0x58, 0x19, 0xe2, 0x69, 0x27, 0x1b, 0x4d, 0xe, 0x4f, 0x29, 0x4a, 0xdf, 0x37, 0xcc, 0x44, 0xa1, 0x5b, 0x5b, 0x64, 0x2a, 0xb9, 0x64, 0xfb, 0xb6, 0x61, 0xdd, 0xb6, 0xee, 0xba, 0x83, 0x6c, 0x6f, 0x56, 0x2e, 0x35, 0x67, 0x77, 0x85, 0x5a, 0x54, 0xed, 0xd9, 0x25, 0x75, 0x70, 0x3e, 0xce, 0xce, 0xa5, 0x11, 0x47, 0xd2, 0xab, 0x37, 0x71, 0x9a, 0xd4, 0xa6, 0x53, 0xda, 0xa8, 0x96, 0x17, 0xf3, 0x7e, 0x6c, 0x71, 0x5a, 0xa9, 0xf4, 0x4b, 0xc3, 0x54, 0x73, 0x32, 0x2a, 0xde, 0x96, 0xb, 0x97, 0x51, 0x33, 0xda, 0x92, 0xea, 0x99, 0xa9, 0x91, 0x1e, 0xc, 0x63, 0x5, 0x7b, 0x5e, 0xb8, 0x6c, 0xb4, 0x94, 0xca, 0x60, 0x31, 0x9f, 0x90, 0xd6, 0x78, 0x2a, 0x3e, 0x71, 0xee, 0x36, 0x73, 0x11, 0xad, 0xc6, 0x52, 0xf1, 0xd9, 0x49, 0xb2, 0xf1, 0x60, 0x2d, 0x6e, 0x97, 0x9a, 0x5c, 0x5b, 0xd, 0x95, 0xc, 0x84, 0xab, 0xe3, 0x7e, 0x79, 0x26, 0xe9, 0x31, 0xb, 0x76, 0xb5, 0xe8, 0xc9, 0x3, 0xbc, 0x9c, 0x4c, 0x3b, 0xed, 0xa5, 0x76, 0x76, 0xa2, 0x91, 0xa4, 0x64, 0x9f, 0xc5, 0xfb, 0x93, 0xe5, 0xec, 0x52, 0x8c, 0x49, 0xc2, 0x2a, 0xa7, 0x71, 0xeb, 0xe2, 0xb6, 0xd0, 0x6f, 0x9e, 0x27, 0x51, 0x6f, 0x82, 0x52, 0xcb, 0x6c, 0xf6, 0xc2, 0x38, 0x3d, 0x4b, 0x98, 0xa9, 0x7a, 0x75, 0xd4, 0x6a, 0xdb, 0x8d, 0x93, 0x93, 0x58, 0x4f, 0x3e, 0xcf, 0xb4, 0xda, 0xcd, 0xc4, 0x7d, 0xab, 0x56, 0x2c, 0x2c, 0xd5, 0xd8, 0xdc, 0x56, 0xb4, 0xde, 0xc5, 0x28, 0xb9, 0x14, 0xca, 0x27, 0x7d, 0x9c, 0x4d, 0x54, 0x65, 0x49, 0x3b, 0x8e, 0x17, 0xce, 0xc6, 0xa3, 0x8c, 0x34, 0x98, 0x9f, 0xd9, 0xbd, 0x31, 0x1a, 0x9e, 0xcc, 0xeb, 0x45, 0x7a, 0xa1, 0xe7, 0x72, 0xab, 0xca, 0xdd, 0x65, 0xe9, 0x38, 0x33, 0x8b, 0xdb, 0xf4, 0xf8, 0xb4, 0xa9, 0xa5, 0x32, 0xb8, 0xda, 0x8e, 0x15, 0x57, 0x67, 0x8b, 0x73, 0xbd, 0xb2, 0x6c, 0x72, 0x24, 0x57, 0x34, 0xd3, 0x55, 0xa5, 0xda, 0x14, 0x25, 0x55, 0xd4, 0xbf, 0x47, 0x57, 0x17, 0xb3, 0xc5, 0x31, 0xac, 0xc4, 0x7a, 0xd1, 0x5b, 0x2d, 0x7a, 0xae, 0x54, 0x93, 0xca, 0x54, 0xad, 0xeb, 0xf6, 0xac, 0x52, 0x96, 0xec, 0x69, 0xf6, 0xa2, 0x70, 0x3a, 0x4d, 0x65, 0xeb, 0xf3, 0xf2, 0x68, 0xd2, 0x6a, 0x74, 0x3a, 0x85, 0x66, 0xed, 0x9e, 0x23, 0x39, 0x96, 0x4a, 0xed, 0x5a, 0x45, 0x8a, 0x5f, 0xde, 0xdd, 0xd, 0xeb, 0x27, 0xcb, 0x31, 0xb9, 0x3a, 0xed, 0x8f, 0x6, 0xb7, 0xad, 0xd1, 0xe2, 0xa4, 0x19, 0xc3, 0xb1, 0x64, 0x33, 0x21, 0x9d, 0x5c, 0xe8, 0x77, 0xb5, 0xa8, 0x34, 0x2f, 0xd3, 0xe4, 0x72, 0x76, 0x59, 0x1b, 0xeb, 0xd5, 0x8b, 0xd9, 0xb9, 0x4d, 0x2d, 0xda, 0xea, 0x37, 0x72, 0xc2, 0x62, 0x5d, 0xd5, 0xab, 0xc8, 0xb0, 0xab, 0xb3, 0x96, 0xbe, 0xb0, 0x86, 0x27, 0xb1, 0x25, 0x8c, 0xf6, 0xcd, 0xa2, 0x12, 0x8b, 0xdf, 0x4d, 0xca, 0xb7, 0xe9, 0xec, 0x59, 0xad, 0x7c, 0x49, 0x1b, 0x8d, 0x31, 0x4d, 0xa3, 0x41, 0x7b, 0x54, 0x9b, 0xe8, 0x89, 0xb3, 0x66, 0xce, 0xc6, 0xd4, 0x9c, 0xb4, 0xea, 0x30, 0x55, 0x2b, 0x58, 0xc2, 0x8e, 0xbd, 0x8c, 0x8f, 0x3a, 0xb4, 0x70, 0x7e, 0x3f, 0x3d, 0x5e, 0x2d, 0xee, 0xe7, 0x17, 0xe5, 0xb4, 0x1e, 0x2b, 0xf, 0x8f, 0x61, 0xf4, 0xd8, 0x28, 0xd8, 0x69, 0x5c, 0x98, 0xe2, 0xe5, 0xea, 0xbe, 0x7f, 0x85, 0x57, 0x7a, 0x7a, 0x71, 0x3a, 0xac, 0xc4, 0x1f, 0x3a, 0x69, 0xe3, 0xb4, 0x3d, 0x9e, 0xc7, 0xe6, 0xa7, 0x17, 0xe9, 0xdb, 0x61, 0x42, 0x30, 0x5b, 0x3a, 0x47, 0x4d, 0x6b, 0xdc, 0x49, 0xde, 0x9e, 0xe9, 0xc9, 0x4a, 0x2b, 0x9b, 0xca, 0x5d, 0x8e, 0xfa, 0x77, 0xca, 0xdd, 0xf2, 0x72, 0x7a, 0xda, 0x3c, 0xbe, 0x3c, 0xad, 0x8c, 0xda, 0xe3, 0xa2, 0x5c, 0x83, 0xaa, 0x86, 0xeb, 0x9d, 0x45, 0x6f, 0x9a, 0x2b, 0x95, 0x16, 0xe5, 0x7e, 0xa3, 0x9f, 0x69, 0xa5, 0xcc, 0x53, 0xa9, 0x15, 0xe3, 0x48, 0x9a, 0x19, 0x52, 0x7a, 0x50, 0xd3, 0x95, 0x8b, 0xe3, 0x87, 0xe1, 0x38, 0x79, 0x5a, 0x9b, 0xf7, 0x3b, 0x99, 0xf3, 0x94, 0x7d, 0x5f, 0xbf, 0xb2, 0xe3, 0xb4, 0xa2, 0xd8, 0xcb, 0xd4, 0x94, 0x4c, 0xce, 0x12, 0x9d, 0x41, 0xae, 0x19, 0xbd, 0x2b, 0xcf, 0xca, 0x75, 0xc6, 0xe1, 0x67, 0x95, 0x42, 0x76, 0x10, 0xcd, 0x92, 0xf9, 0x89, 0x50, 0x5, 0x67, 0x99, 0x7e, 0xe7, 0x36, 0x41, 0xed, 0x74, 0xa3, 0x71, 0x99, 0xae, 0x45, 0xe1, 0x38, 0x11, 0xeb, 0x2f, 0x6a, 0x95, 0xd2, 0x24, 0x4a, 0xd0, 0xa9, 0x55, 0xcb, 0x5e, 0x4d, 0x9a, 0xc5, 0x51, 0x33, 0x95, 0x1a, 0x91, 0x89, 0x64, 0x5c, 0x2d, 0xef, 0xb2, 0xf8, 0xf2, 0x2c, 0x9e, 0x56, 0xb2, 0x72, 0x2d, 0x73, 0xd2, 0x6d, 0x14, 0x9, 0x47, 0x32, 0x7b, 0x90, 0x7a, 0x57, 0x39, 0x1c, 0xef, 0x27, 0xc6, 0xa3, 0xf9, 0x43, 0xe5, 0xbc, 0xd4, 0x5d, 0xe4, 0xea, 0xfd, 0x93, 0x69, 0xac, 0x5d, 0x28, 0xdd, 0x37, 0x13, 0xca, 0x50, 0xea, 0xb6, 0x8a, 0xba, 0x36, 0xd6, 0xa6, 0xf1, 0x45, 0x77, 0xd6, 0x7a, 0xa8, 0xd8, 0x67, 0xf5, 0xf8, 0x69, 0x67, 0x68, 0xa7, 0xeb, 0xcd, 0xce, 0x65, 0xa5, 0xcd, 0x91, 0x68, 0x75, 0x6d, 0x31, 0xb9, 0xbd, 0xba, 0x34, 0x68, 0x74, 0x9c, 0xed, 0xdc, 0x9e, 0x4d, 0x1f, 0xb0, 0x5c, 0x93, 0xe8, 0xbc, 0x58, 0x94, 0xc7, 0x43, 0xf5, 0x72, 0x90, 0x80, 0xc9, 0xc9, 0x49, 0x2a, 0x3e, 0xb7, 0x4a, 0xd9, 0x13, 0x7c, 0x39, 0x2d, 0x56, 0x73, 0xa7, 0xa3, 0x69, 0xaa, 0x44, 0x2f, 0xb5, 0x49, 0xb7, 0x77, 0x2f, 0x8b, 0x99, 0xb4, 0x3e, 0x80, 0xc9, 0x65, 0xf2, 0xaa, 0x59, 0xb1, 0x2b, 0x25, 0xcd, 0x3a, 0xc5, 0xa8, 0x2f, 0xb5, 0x94, 0xc6, 0x65, 0xb7, 0xb9, 0x1c, 0x9e, 0x2f, 0x12, 0x28, 0x5e, 0x54, 0xdb, 0xfd, 0xb4, 0x1c, 0x3b, 0x7d, 0xb8, 0x5c, 0x19, 0xe9, 0xca, 0xea, 0x32, 0x8a, 0xec, 0xd3, 0xac, 0x5c, 0x42, 0x9, 0x3d, 0x5a, 0xad, 0x8d, 0x7, 0x42, 0x8a, 0xe3, 0xb7, 0xb1, 0x4e, 0xe7, 0xf8, 0x7e, 0x70, 0x36, 0x39, 0x3b, 0x3d, 0x9f, 0xa4, 0xfb, 0xb9, 0x49, 0x5c, 0xeb, 0x3f, 0xdc, 0x2a, 0x35, 0x94, 0x6b, 0x5c, 0x2d, 0x2f, 0x1a, 0xd3, 0xd5, 0xfd, 0x59, 0x4e, 0xb9, 0xca, 0x9d, 0x59, 0x33, 0x12, 0x7f, 0x88, 0x4e, 0x87, 0xed, 0x5c, 0x74, 0x38, 0x6f, 0xa6, 0xe2, 0x13, 0x45, 0xbd, 0xbd, 0x55, 0x85, 0xec, 0x1c, 0x1f, 0x8f, 0xa3, 0xf, 0xb9, 0xae, 0x21, 0xab, 0xb9, 0x86, 0xe4, 0x8c, 0x7c, 0x66, 0x95, 0x3c, 0x59, 0x64, 0x95, 0xee, 0x49, 0xb3, 0xa9, 0x9b, 0x52, 0x2f, 0xda, 0xcf, 0x20, 0x52, 0x68, 0x40, 0xa9, 0x9f, 0x4b, 0xdd, 0x9f, 0xa3, 0x5c, 0xa6, 0x72, 0x5a, 0x3b, 0x1f, 0xce, 0x2e, 0x56, 0x9d, 0xba, 0x24, 0x38, 0xb6, 0x91, 0x3b, 0x57, 0xae, 0x6a, 0x46, 0x7a, 0x8c, 0xb2, 0x17, 0xd2, 0x48, 0xc9, 0xcd, 0xa2, 0xd, 0x9a, 0xbd, 0xc2, 0xc9, 0x4a, 0xb7, 0x87, 0x7, 0x3, 0x78, 0xc2, 0xa6, 0x33, 0x5c, 0x28, 0x2f, 0x95, 0xc6, 0x9, 0xba, 0xb2, 0x92, 0xb9, 0xdb, 0xea, 0x3, 0x6a, 0x1d, 0x57, 0xd5, 0xcc, 0x59, 0xbc, 0xa9, 0xe6, 0xb2, 0x17, 0x62, 0x4c, 0x52, 0xd3, 0x8e, 0x72, 0x3a, 0xaf, 0xf5, 0xcf, 0xb, 0xdd, 0xb8, 0xdd, 0xc8, 0xdc, 0xf5, 0x6f, 0xaf, 0xb2, 0x6a, 0xb7, 0x64, 0x27, 0xf1, 0x70, 0xd4, 0xa3, 0x10, 0x1d, 0x77, 0x29, 0xd4, 0x32, 0x23, 0x25, 0x7d, 0x19, 0xcb, 0xf4, 0xba, 0x52, 0x2b, 0x97, 0x4e, 0x77, 0xd5, 0xe1, 0xc9, 0x6d, 0x4c, 0xd7, 0xcd, 0xd3, 0xdb, 0x33, 0x8e, 0xa4, 0xd2, 0x4e, 0x3e, 0xa0, 0x59, 0x54, 0x93, 0x62, 0x77, 0xb9, 0xc5, 0x1d, 0x1a, 0x17, 0x73, 0xa5, 0xd8, 0x45, 0xf4, 0xa, 0x9f, 0xa6, 0x4d, 0xc3, 0x4a, 0x9f, 0x9f, 0xd7, 0xa, 0x72, 0xe7, 0xec, 0x2c, 0x9d, 0x3b, 0x89, 0xaa, 0xd, 0x65, 0x80, 0x27, 0xc9, 0xcb, 0xd2, 0x62, 0xdc, 0xaa, 0xcf, 0xea, 0x97, 0x83, 0x51, 0x1b, 0x4f, 0xe6, 0x1c, 0x49, 0x8c, 0xd4, 0x17, 0xf1, 0xbb, 0x41, 0x59, 0x9f, 0x3c, 0x3c, 0x2c, 0x8e, 0xc7, 0x53, 0x53, 0x5d, 0x18, 0xc5, 0xd3, 0xae, 0xd1, 0x1d, 0xf5, 0xa4, 0x8e, 0x9c, 0xb9, 0x20, 0x15, 0x63, 0xa5, 0xc4, 0x46, 0xb9, 0xfa, 0x3, 0x46, 0x7a, 0xf3, 0xa, 0x67, 0x7b, 0xba, 0x7d, 0x7b, 0x1e, 0x8d, 0x9e, 0x5c, 0x56, 0xb4, 0xd3, 0x64, 0xaa, 0xc2, 0x91, 0x9c, 0xe4, 0xaa, 0x95, 0x58, 0xad, 0xa3, 0x91, 0x4a, 0x4f, 0xa9, 0x5c, 0x14, 0x62, 0xe3, 0xd6, 0x68, 0xd6, 0xe9, 0xa4, 0x3a, 0x93, 0xde, 0x28, 0x56, 0xeb, 0xb7, 0xec, 0x93, 0x56, 0x21, 0x79, 0x9c, 0x22, 0xb7, 0xfd, 0xa5, 0x18, 0x74, 0x6d, 0x69, 0x8f, 0x31, 0x89, 0xc6, 0xec, 0x16, 0xd2, 0x96, 0x89, 0x9a, 0xe8, 0xce, 0xbc, 0xdb, 0xab, 0x56, 0xa9, 0x42, 0xd4, 0x72, 0x2f, 0x9a, 0xb4, 0x74, 0x7c, 0x51, 0xb5, 0x26, 0xab, 0xf8, 0xf0, 0xa1, 0x54, 0x1e, 0x1d, 0x9b, 0x99, 0x73, 0x38, 0xc9, 0xa2, 0x8e, 0x51, 0x5f, 0x65, 0x87, 0x67, 0x1a, 0xb4, 0x2a, 0x45, 0xba, 0x9a, 0x77, 0x8e, 0xb3, 0x3d, 0xb3, 0x21, 0x2f, 0x26, 0xb9, 0x91, 0x29, 0xb, 0x1, 0xbc, 0x18, 0xf6, 0xcf, 0x4f, 0xd1, 0xe2, 0x6e, 0x7c, 0x9c, 0xce, 0xd4, 0xba, 0x77, 0x39, 0x23, 0x3d, 0x19, 0x3c, 0xa4, 0xed, 0x7e, 0xf7, 0x72, 0x79, 0x75, 0xdc, 0x68, 0x5f, 0x35, 0xab, 0x97, 0xe7, 0x27, 0xe7, 0xb7, 0xf, 0x93, 0xfb, 0x8b, 0x56, 0x4c, 0x59, 0x98, 0xdd, 0xc, 0x2a, 0x66, 0x6, 0x97, 0x8d, 0xda, 0x4c, 0xab, 0x66, 0xac, 0xdc, 0x82, 0x23, 0xc9, 0xa4, 0x47, 0xcb, 0x44, 0xbf, 0x38, 0xe8, 0x8c, 0xeb, 0xb9, 0x66, 0xa9, 0x9d, 0xb8, 0x5d, 0x9d, 0x6b, 0x98, 0x96, 0xe5, 0xce, 0x71, 0x25, 0x9e, 0xa8, 0x1c, 0x9f, 0x9f, 0x8f, 0xbb, 0x1d, 0xa3, 0x14, 0x4f, 0x4b, 0x8d, 0xf8, 0x38, 0x1b, 0x23, 0xf2, 0xe5, 0xb0, 0x4b, 0xcf, 0x8e, 0xa5, 0x66, 0xfd, 0xc1, 0xb2, 0xa3, 0xc3, 0xba, 0x98, 0x1, 0x8d, 0xe4, 0x6c, 0xb5, 0xba, 0xb7, 0xa7, 0xe9, 0xe3, 0xc4, 0xc5, 0x83, 0x3e, 0xb0, 0xac, 0xcb, 0xf6, 0xa8, 0x21, 0xcf, 0x8e, 0x73, 0xf5, 0x45, 0x69, 0x6a, 0xa6, 0x9a, 0x2b, 0xa9, 0x6d, 0xf, 0x17, 0xcb, 0xe3, 0x7b, 0xa3, 0x75, 0x52, 0xb8, 0xba, 0x9d, 0x2e, 0x2f, 0x8b, 0x76, 0xa2, 0xd8, 0x30, 0xda, 0xc9, 0xca, 0x3c, 0x3b, 0x2a, 0x8, 0x1, 0x54, 0x66, 0x77, 0xfa, 0xfc, 0x7c, 0x81, 0x4a, 0x83, 0xa9, 0x5d, 0x31, 0xcf, 0x4e, 0x32, 0xd9, 0xba, 0x5a, 0x2d, 0x8e, 0xeb, 0xf3, 0xa1, 0x79, 0x9c, 0x1d, 0x14, 0xc6, 0xd, 0x7a, 0x91, 0x9c, 0x4d, 0xa7, 0x95, 0x8b, 0xfb, 0xf3, 0xe4, 0x79, 0x2c, 0xb3, 0x2a, 0xe8, 0xa7, 0xb4, 0xf2, 0x90, 0x4e, 0x8d, 0x7, 0x96, 0xaa, 0xdc, 0xd, 0x6e, 0xc5, 0x84, 0x5e, 0x5b, 0x3d, 0x50, 0xce, 0xee, 0xc5, 0xea, 0x6c, 0x91, 0x9e, 0x46, 0xe3, 0x4a, 0xae, 0x37, 0x9d, 0xc4, 0xe4, 0x71, 0xb1, 0x69, 0xe8, 0xf4, 0x78, 0x81, 0xd4, 0xf4, 0xc5, 0xf8, 0x3e, 0x71, 0x9f, 0x96, 0x63, 0x7d, 0xc3, 0x32, 0xb4, 0xe9, 0x48, 0x9a, 0xb7, 0xd4, 0x5e, 0xe6, 0x14, 0x21, 0x59, 0x6d, 0xa5, 0x4, 0x92, 0xcb, 0x68, 0xa5, 0xf8, 0xf0, 0xa0, 0x27, 0x8d, 0xd3, 0xaa, 0x11, 0xcf, 0x5c, 0xc2, 0x33, 0x9c, 0x50, 0x27, 0xe7, 0xc5, 0xfb, 0x55, 0x25, 0x19, 0xab, 0x8d, 0x6, 0x35, 0x7c, 0x75, 0x7e, 0x72, 0x76, 0x92, 0x68, 0x46, 0xb3, 0x3d, 0x1a, 0xcb, 0x5e, 0x56, 0x1b, 0xc5, 0xbb, 0xe2, 0xbd, 0x7a, 0x5b, 0x4f, 0x1a, 0x97, 0xe6, 0xc5, 0xd2, 0x51, 0xd4, 0xbd, 0xd6, 0x64, 0xb5, 0x4a, 0xa8, 0x85, 0xbb, 0xc5, 0xe9, 0x62, 0xd6, 0x92, 0xb2, 0xf1, 0x42, 0x6c, 0x86, 0xcf, 0x93, 0x27, 0xad, 0xfb, 0xa4, 0x34, 0xc1, 0xc9, 0xfb, 0xd8, 0x69, 0xa7, 0x2d, 0x49, 0xfd, 0x3a, 0xa9, 0xdb, 0xb4, 0xaf, 0x3e, 0xdc, 0x76, 0xea, 0xc9, 0x7, 0xa5, 0x59, 0xb1, 0x8c, 0xb2, 0x72, 0xd1, 0x7b, 0x80, 0x1c, 0xc9, 0xf4, 0x38, 0x35, 0x9b, 0x5b, 0xa5, 0x45, 0xb3, 0x32, 0x46, 0x9d, 0xf2, 0xe4, 0xf2, 0xfc, 0x54, 0x1b, 0xd, 0x49, 0xfb, 0xf2, 0xec, 0x42, 0x55, 0xb4, 0xcc, 0x24, 0x4b, 0x68, 0x59, 0xcb, 0xa2, 0x55, 0xb3, 0x55, 0x5f, 0xa6, 0x8e, 0x5b, 0xa5, 0x6a, 0x67, 0x4a, 0xcd, 0xab, 0xee, 0x68, 0xaa, 0xd3, 0x72, 0xe2, 0xee, 0xbe, 0x20, 0xe6, 0xe2, 0x45, 0x7f, 0xba, 0x38, 0xe9, 0xcf, 0x52, 0xe4, 0xa, 0x96, 0x71, 0x21, 0x79, 0x12, 0xb7, 0x6a, 0x28, 0x31, 0x39, 0x2b, 0x3e, 0xcc, 0xea, 0xc3, 0x55, 0xed, 0x2c, 0x6b, 0x25, 0xef, 0xd5, 0x87, 0x64, 0x32, 0x73, 0x55, 0x94, 0xc9, 0xc3, 0x22, 0xd3, 0x8c, 0x59, 0x8d, 0x8b, 0xab, 0x8b, 0x69, 0x45, 0x9a, 0xa4, 0xb4, 0xea, 0xc3, 0xd4, 0x16, 0x86, 0x5f, 0xf1, 0xae, 0x3d, 0x9e, 0x25, 0x57, 0xb1, 0xc9, 0xc5, 0x6d, 0xb6, 0xab, 0x1a, 0xd1, 0x95, 0x45, 0x6, 0xfd, 0xe2, 0x54, 0x29, 0xd6, 0xaf, 0x14, 0x7b, 0x7e, 0x5b, 0x20, 0xf5, 0x62, 0xa1, 0xd2, 0x3c, 0xed, 0xf6, 0xcf, 0x4f, 0x74, 0x75, 0x12, 0x9d, 0x2e, 0xd2, 0x9d, 0x8b, 0x5e, 0xa9, 0xb6, 0x6a, 0xdc, 0xdf, 0xd9, 0xd5, 0x5b, 0xc1, 0x6c, 0x93, 0x8a, 0x1e, 0xad, 0x64, 0xc7, 0xcb, 0xd4, 0x45, 0xff, 0xae, 0x25, 0xdf, 0x46, 0x33, 0x4b, 0xdc, 0x3f, 0x25, 0xf, 0xa6, 0x34, 0xca, 0x92, 0xd5, 0xc5, 0xad, 0x75, 0x71, 0xd9, 0x19, 0xab, 0xb8, 0xd9, 0x3b, 0xbb, 0x94, 0x2b, 0xb3, 0x52, 0x76, 0x99, 0x50, 0x9b, 0xad, 0xdb, 0xf6, 0x9d, 0x7e, 0xf2, 0x70, 0xd6, 0x29, 0xd4, 0x33, 0x94, 0x23, 0xe9, 0xc0, 0x74, 0xf1, 0xac, 0x8b, 0xa7, 0x77, 0xab, 0xcb, 0xab, 0xee, 0x65, 0x35, 0x55, 0x6d, 0x1c, 0x9f, 0x4b, 0x3, 0x75, 0x38, 0x5a, 0x5c, 0x9d, 0xe9, 0x39, 0x3a, 0x29, 0xdd, 0x25, 0x47, 0xa5, 0xfb, 0x61, 0xb1, 0x6d, 0x9d, 0x25, 0xab, 0xb5, 0x71, 0xa3, 0xa1, 0x26, 0x4e, 0xaa, 0x67, 0xcd, 0x84, 0x79, 0x8a, 0xed, 0x13, 0x33, 0x5d, 0x15, 0x94, 0x9c, 0xcb, 0xc8, 0xb2, 0xcb, 0x27, 0xed, 0x12, 0xae, 0x57, 0x47, 0xe3, 0xe1, 0xc9, 0xf1, 0x14, 0x9f, 0xc6, 0xe5, 0x4a, 0x3b, 0x1, 0xa3, 0x67, 0xc7, 0x99, 0x49, 0x2f, 0x7d, 0x9e, 0xbc, 0x2f, 0x65, 0xce, 0xcb, 0x4a, 0xf3, 0xb8, 0x78, 0xd7, 0x92, 0x72, 0xcb, 0xab, 0x74, 0x2f, 0x97, 0xad, 0x26, 0xb3, 0x97, 0xf, 0xa5, 0x46, 0xc3, 0x10, 0x66, 0xf9, 0xbc, 0x6d, 0x54, 0x26, 0xda, 0xe2, 0x14, 0xdb, 0xf3, 0x9c, 0x75, 0x72, 0x72, 0xb6, 0x2c, 0x77, 0x2f, 0xe4, 0xb3, 0xb4, 0xbd, 0xaa, 0xdd, 0x21, 0xda, 0x88, 0x5e, 0xe0, 0x25, 0x22, 0x29, 0x7d, 0x96, 0x5a, 0xc5, 0xaf, 0xba, 0xd8, 0x58, 0xca, 0x63, 0xc9, 0x2e, 0x92, 0x51, 0xe7, 0xac, 0x96, 0x96, 0xda, 0x77, 0x15, 0x61, 0x9f, 0xa4, 0x5b, 0x97, 0xbd, 0xf3, 0xb6, 0x62, 0x35, 0xec, 0xdb, 0x5b, 0x35, 0x76, 0x79, 0x35, 0xaf, 0x4a, 0xa9, 0x44, 0x7b, 0x21, 0x3f, 0x44, 0x47, 0x93, 0x93, 0xb4, 0x7a, 0x72, 0x5b, 0x46, 0xf3, 0xe5, 0x74, 0xa2, 0xe4, 0xb4, 0x64, 0x6e, 0x1c, 0xa5, 0x52, 0xa5, 0x3a, 0x30, 0xd5, 0xcc, 0x43, 0xee, 0x21, 0x3b, 0xfa, 0x6d, 0xbd, 0xa8, 0xaa, 0xb4, 0xcb, 0xc1, 0x4b, 0xaa, 0xed, 0x95, 0xe8, 0xd6, 0x42, 0x34, 0x60, 0x1d, 0xfa, 0xca, 0x65, 0xa8, 0x2, 0x2d, 0x38, 0x86, 0x14, 0x9, 0xa7, 0x0, 0x9e, 0x78, 0x8e, 0x4e, 0xe0, 0xbe, 0x1, 0x98, 0x72, 0xf7, 0xe5, 0x77, 0xee, 0xe4, 0x3d, 0xb2, 0x56, 0x6, 0x3a, 0xe2, 0x1b, 0x2a, 0x6e, 0x4d, 0xee, 0xf7, 0xe0, 0x5b, 0x1, 0x13, 0xac, 0xaa, 0xdc, 0xc9, 0x28, 0x13, 0x5d, 0x47, 0x32, 0xdf, 0x45, 0xc2, 0xfa, 0x84, 0x98, 0x9a, 0xe3, 0xa2, 0xc4, 0xba, 0xf, 0x8c, 0x2d, 0x49, 0xf9, 0x56, 0x1, 0x0, 0xc, 0x69, 0x1e, 0x60, 0x5d, 0xbc, 0x61, 0xeb, 0x61, 0xe7, 0x4f, 0xc7, 0xd7, 0xb8, 0x5e, 0x1d, 0x3f, 0xb6, 0x3e, 0x56, 0xc6, 0x4e, 0x95, 0x47, 0x56, 0xc8, 0x8f, 0xae, 0x91, 0x59, 0x3f, 0xb8, 0xb7, 0xdc, 0x36, 0x90, 0x69, 0x53, 0x64, 0x2, 0xd7, 0xed, 0x5, 0xc8, 0xc4, 0x1b, 0x10, 0xa1, 0x43, 0xd7, 0xfe, 0x26, 0x79, 0x6, 0xf5, 0x29, 0xc2, 0x96, 0xe3, 0x16, 0xf2, 0x3b, 0x76, 0xdc, 0xcd, 0x2d, 0x67, 0x2b, 0xe2, 0x11, 0xd7, 0xce, 0x61, 0xe7, 0xce, 0xa6, 0x7b, 0x67, 0xc7, 0xc1, 0xb3, 0xe3, 0xe2, 0xd9, 0xe3, 0xe4, 0x9, 0x70, 0xf3, 0x4, 0x32, 0x4e, 0x10, 0xeb, 0x6c, 0x31, 0xf, 0xf0, 0x98, 0x45, 0xb4, 0x37, 0x13, 0x3e, 0xd0, 0x58, 0x2e, 0x1e, 0x89, 0xa5, 0xb3, 0x11, 0x29, 0x12, 0x13, 0xbd, 0x33, 0x84, 0xeb, 0x33, 0x95, 0x4c, 0x38, 0xbe, 0x34, 0x77, 0x2f, 0x3e, 0xf, 0x8e, 0xd8, 0x9f, 0x47, 0xdb, 0x23, 0xea, 0xfe, 0x79, 0xe4, 0x78, 0xc7, 0x4c, 0x54, 0x76, 0xd9, 0x14, 0x1c, 0xb9, 0xce, 0x6d, 0xe7, 0xa5, 0xa, 0xb1, 0xe9, 0x7b, 0xcb, 0x7f, 0x3b, 0x3e, 0x64, 0x62, 0x41, 0x73, 0xd5, 0xe7, 0xbe, 0x43, 0x5f, 0xd, 0xf1, 0xf8, 0xda, 0xef, 0x53, 0x74, 0x6a, 0xe2, 0xa9, 0x1e, 0x58, 0x93, 0x3f, 0x3f, 0xa, 0x85, 0x42, 0x6e, 0xd3, 0x87, 0x3d, 0x36, 0x6e, 0xcd, 0xb0, 0x31, 0x23, 0x16, 0x67, 0x71, 0x87, 0x2b, 0x17, 0xf1, 0x48, 0x3a, 0x12, 0xf, 0x7, 0xec, 0x12, 0x1d, 0xe0, 0xcd, 0x99, 0x65, 0x19, 0xfd, 0x47, 0x7d, 0x38, 0x2a, 0x99, 0x36, 0xd1, 0x2, 0xa9, 0x79, 0x2e, 0x73, 0xa1, 0x2f, 0x7c, 0x63, 0x3, 0xe9, 0x16, 0x20, 0xb6, 0x5, 0x88, 0x2e, 0x36, 0x37, 0x66, 0x8, 0x8c, 0x11, 0xdf, 0x27, 0x20, 0xec, 0x33, 0x88, 0x9d, 0x16, 0x59, 0x25, 0xb6, 0xf2, 0xb, 0x5, 0xc2, 0x35, 0xea, 0xf2, 0x68, 0x84, 0x29, 0x18, 0xf1, 0xc8, 0xd9, 0x87, 0x75, 0xb4, 0xd0, 0x54, 0x5e, 0x6f, 0xba, 0xcd, 0xd1, 0x6a, 0x82, 0x55, 0xbe, 0x91, 0xea, 0x3e, 0x1a, 0xdb, 0xf2, 0x9c, 0x51, 0xb9, 0x7e, 0x22, 0xcf, 0x6c, 0x7d, 0xee, 0xf8, 0xea, 0x53, 0xf1, 0x64, 0x3c, 0x9b, 0x95, 0x9c, 0x77, 0x34, 0xe1, 0x61, 0x62, 0x43, 0xc6, 0xfd, 0xc1, 0x1e, 0x98, 0x90, 0x88, 0x39, 0x5a, 0xf9, 0x1f, 0x8a, 0xee, 0x6e, 0x3d, 0xdc, 0x6d, 0xd2, 0xd9, 0x67, 0xf1, 0xf6, 0x20, 0xf8, 0x63, 0xf8, 0x60, 0x9b, 0x28, 0xef, 0x43, 0x4f, 0x6c, 0xdd, 0x72, 0x18, 0xf1, 0x68, 0xeb, 0xf1, 0x56, 0xb, 0x32, 0x71, 0x82, 0x4f, 0x1c, 0x8f, 0xb2, 0x49, 0x88, 0x55, 0x62, 0x6c, 0xbd, 0xe3, 0x2a, 0x2b, 0x55, 0x7a, 0x83, 0xfa, 0x49, 0xbd, 0x54, 0x18, 0x54, 0x36, 0xdc, 0x64, 0x15, 0xe9, 0xa1, 0x54, 0x2a, 0x98, 0xf6, 0xb4, 0xb0, 0xac, 0x17, 0xb, 0xd3, 0xfa, 0x69, 0xa1, 0x3e, 0xa5, 0x89, 0xfe, 0xf1, 0x8c, 0xde, 0xce, 0xb4, 0x56, 0x41, 0xaa, 0x96, 0xfa, 0x77, 0xd5, 0x7e, 0x7d, 0x9c, 0x28, 0x9f, 0x55, 0x8a, 0xa5, 0xe5, 0xb0, 0xd0, 0x2a, 0x14, 0x96, 0xb5, 0x99, 0xdc, 0x6e, 0xd, 0x64, 0xb1, 0xac, 0x6d, 0xd, 0xa, 0xa9, 0xd6, 0x40, 0x5e, 0xb5, 0x1f, 0xce, 0x52, 0x17, 0xec, 0xc5, 0xad, 0x7c, 0xdf, 0x1a, 0x14, 0x12, 0xde, 0xb3, 0xdb, 0x42, 0xa1, 0x55, 0xaf, 0x97, 0xea, 0xb7, 0x85, 0x76, 0x71, 0x3a, 0xbf, 0x9b, 0xcd, 0x71, 0x35, 0xb7, 0x94, 0x8a, 0x85, 0xb3, 0xca, 0x49, 0xa1, 0xd0, 0x29, 0x89, 0xc5, 0xe4, 0x34, 0xcb, 0x2b, 0x95, 0xa6, 0x9f, 0xe, 0xbb, 0x4f, 0x87, 0xdd, 0xff, 0x1c, 0x87, 0x1d, 0x6c, 0x9f, 0xb5, 0x2a, 0xc9, 0x65, 0xed, 0x6c, 0x54, 0x3e, 0xef, 0x49, 0x9d, 0x62, 0x71, 0x54, 0x39, 0x29, 0xb5, 0x46, 0xa3, 0x56, 0xa7, 0x29, 0x55, 0xa2, 0xc3, 0xd5, 0x6d, 0x6a, 0x49, 0x51, 0x79, 0x52, 0x57, 0x33, 0x24, 0x59, 0x69, 0xd2, 0x56, 0x31, 0x5b, 0x2d, 0xc4, 0x86, 0x4a, 0x7d, 0x79, 0x36, 0x6a, 0x15, 0xa1, 0x40, 0xf2, 0x28, 0x40, 0x61, 0xc9, 0x1, 0x2a, 0xcb, 0xb3, 0x93, 0x56, 0xa1, 0x55, 0x2c, 0x4c, 0xb2, 0xcb, 0xf2, 0xd9, 0xe8, 0xb4, 0x41, 0xae, 0xea, 0xb3, 0x85, 0xdc, 0x2e, 0x9c, 0x39, 0x16, 0x64, 0xf1, 0xac, 0x50, 0x9e, 0x4e, 0xeb, 0xc5, 0x42, 0xb1, 0x9a, 0x9d, 0x74, 0x17, 0xa6, 0xd9, 0x3b, 0x46, 0xa6, 0xb1, 0x3c, 0x3b, 0xb1, 0x8b, 0xd1, 0x54, 0xfa, 0x36, 0x8e, 0xd3, 0xb4, 0x73, 0x7c, 0x47, 0x3a, 0xa7, 0x5d, 0xa3, 0xd0, 0x1a, 0x2d, 0xe7, 0xf, 0xf5, 0x92, 0x39, 0x90, 0x10, 0x32, 0x2f, 0xa, 0x70, 0xb1, 0x24, 0x2b, 0x61, 0x10, 0x4b, 0xc3, 0x42, 0xa3, 0x9d, 0x91, 0x87, 0xa8, 0x72, 0x7a, 0x79, 0x5b, 0xef, 0x65, 0x68, 0xa6, 0x44, 0xa6, 0xd5, 0x13, 0x7c, 0x7b, 0xa1, 0xc0, 0xb, 0x78, 0x46, 0x2f, 0x87, 0x65, 0xe9, 0xe1, 0xf4, 0x2e, 0x5, 0x89, 0xd5, 0x1c, 0x5d, 0x35, 0xb1, 0x56, 0x91, 0x63, 0x1d, 0xc9, 0x2e, 0x68, 0xa7, 0x15, 0x3a, 0x19, 0x9, 0x55, 0xb0, 0xc8, 0x68, 0x55, 0x29, 0x83, 0x86, 0xe9, 0x29, 0x8a, 0xc7, 0x69, 0x9f, 0xa4, 0x66, 0xf7, 0xb3, 0x73, 0x43, 0xad, 0x5a, 0xa9, 0xf8, 0x4c, 0xbf, 0xd4, 0x7, 0x52, 0x59, 0x99, 0xf6, 0x7b, 0x57, 0xc6, 0x5d, 0x5c, 0x5b, 0x4c, 0x95, 0x9c, 0x2a, 0x13, 0xa3, 0x80, 0x95, 0xd6, 0x71, 0xa3, 0xa6, 0x42, 0x48, 0x2f, 0xc5, 0x2a, 0xbd, 0x8e, 0xe4, 0x5a, 0xa3, 0x95, 0xcb, 0x35, 0x2e, 0x73, 0xe5, 0x2c, 0xd5, 0x4c, 0xa9, 0x20, 0x9f, 0xa5, 0x5b, 0xd1, 0xd1, 0x74, 0x3c, 0x89, 0xdf, 0xdd, 0x76, 0x72, 0xa3, 0x9e, 0x66, 0x14, 0xeb, 0xb7, 0xab, 0xb3, 0x8b, 0xa, 0x9c, 0xa3, 0xe4, 0x2a, 0x9a, 0x6c, 0x5e, 0x68, 0xc7, 0x89, 0xe3, 0x85, 0x94, 0x45, 0xf2, 0x34, 0x59, 0x74, 0x16, 0x4e, 0xb8, 0x55, 0x92, 0x7a, 0xcb, 0xd8, 0x99, 0xdc, 0xbd, 0x9b, 0x2e, 0x61, 0x15, 0x5e, 0xd8, 0x99, 0x58, 0xcf, 0x1a, 0xcd, 0x56, 0x83, 0x69, 0xa6, 0xaf, 0x17, 0xe6, 0xfa, 0x38, 0xa5, 0x17, 0xe5, 0x5a, 0x7d, 0xdc, 0x1c, 0x4b, 0x33, 0xa5, 0x39, 0x3d, 0x1b, 0xc0, 0xc4, 0x95, 0x62, 0x5d, 0x4e, 0xef, 0xe4, 0x59, 0x5d, 0xd8, 0x35, 0x55, 0xfb, 0x44, 0xad, 0x14, 0xb5, 0xda, 0xc9, 0x55, 0x37, 0x3d, 0x6e, 0x9e, 0x76, 0x7a, 0xbd, 0xe1, 0x99, 0x4, 0x4d, 0x9c, 0x5a, 0x1a, 0x97, 0x75, 0x3a, 0x9a, 0x98, 0xc5, 0xe4, 0x20, 0xdb, 0x5d, 0x8d, 0x7, 0xf, 0xb, 0x98, 0xe8, 0x94, 0x5a, 0x6a, 0x85, 0xb6, 0x6e, 0xed, 0x15, 0x3d, 0x31, 0x73, 0x68, 0x39, 0x7b, 0x10, 0x9a, 0x2d, 0x37, 0xac, 0x36, 0x71, 0xff, 0xac, 0x5d, 0x5e, 0x35, 0xa, 0x31, 0xe9, 0x34, 0x7b, 0xb1, 0x54, 0x1f, 0xc6, 0xa8, 0x2c, 0x15, 0xa, 0x17, 0xb9, 0x64, 0x72, 0x76, 0x91, 0x2c, 0x8f, 0xa7, 0xe9, 0xfe, 0xc5, 0x22, 0x39, 0x2d, 0x90, 0x54, 0x6c, 0x70, 0x9c, 0x2e, 0xd8, 0xf3, 0x9e, 0x62, 0x1b, 0xa9, 0x55, 0x5a, 0x9d, 0x9c, 0xa5, 0xc4, 0x3a, 0x70, 0x96, 0x6c, 0x8e, 0x49, 0xda, 0x78, 0x80, 0x85, 0x44, 0x3a, 0x57, 0xa7, 0xa7, 0x45, 0xdd, 0x5a, 0x34, 0x16, 0x18, 0xa6, 0x67, 0xc3, 0x49, 0xb4, 0x3f, 0x9d, 0xe9, 0xe3, 0x6a, 0xc6, 0x28, 0xd5, 0x2e, 0xa3, 0x85, 0xa, 0x56, 0xe5, 0xe9, 0x60, 0x1c, 0x4b, 0xd4, 0x74, 0xe5, 0x24, 0x5a, 0x3d, 0xce, 0xc0, 0xab, 0x69, 0x43, 0xa8, 0x47, 0xd, 0xe7, 0xee, 0x16, 0xed, 0x5e, 0xbf, 0x4c, 0x2b, 0xd1, 0xa2, 0x39, 0xbd, 0x82, 0x4b, 0xb2, 0x30, 0xb2, 0xb1, 0xe3, 0xdb, 0x34, 0x6c, 0x26, 0x57, 0xe9, 0xa1, 0x75, 0x39, 0xa2, 0x46, 0xcd, 0x3c, 0xee, 0xdb, 0x17, 0xb4, 0x31, 0xa2, 0xb0, 0x96, 0x31, 0xd4, 0x54, 0x8d, 0x1a, 0xed, 0xd2, 0xea, 0x34, 0xb5, 0x38, 0x17, 0xea, 0x51, 0x31, 0x4e, 0xb, 0x4b, 0x42, 0x4f, 0x8a, 0x77, 0xfd, 0x8a, 0x5e, 0x4f, 0x24, 0x12, 0xcb, 0x82, 0xad, 0x1b, 0xad, 0x91, 0x86, 0xa3, 0xb7, 0x8d, 0x5a, 0x2d, 0x7a, 0x9b, 0xbc, 0xb8, 0xbb, 0x6d, 0xea, 0xa5, 0xba, 0x6e, 0x48, 0xd1, 0x25, 0xb1, 0x1f, 0x46, 0x76, 0x32, 0xae, 0x66, 0xbb, 0xda, 0x43, 0x2a, 0x17, 0x2b, 0xf6, 0xc5, 0xc2, 0xe9, 0x14, 0x4e, 0x53, 0x92, 0x34, 0xae, 0x14, 0xef, 0xef, 0xea, 0xf1, 0x5e, 0xb3, 0xd2, 0x9c, 0xb6, 0xac, 0xe8, 0x78, 0xa8, 0xdc, 0x1a, 0xc9, 0x76, 0xbc, 0x95, 0xd1, 0xcc, 0x7b, 0xc5, 0x6c, 0x1f, 0xc7, 0x53, 0x99, 0x5c, 0x6d, 0xf0, 0x30, 0x4e, 0xd7, 0x16, 0xd8, 0xce, 0x6d, 0xae, 0x75, 0x2, 0xe6, 0xc4, 0x97, 0xed, 0x94, 0xfa, 0xcd, 0xe9, 0xd7, 0xee, 0x93, 0xa6, 0xde, 0x6c, 0x9f, 0xf4, 0xcb, 0x17, 0x50, 0xe1, 0xf1, 0x12, 0x4e, 0xf4, 0x85, 0x82, 0x29, 0x90, 0xa1, 0x3c, 0xc3, 0xfa, 0x94, 0x2d, 0x79, 0xfc, 0x21, 0x19, 0xac, 0x36, 0xaf, 0xe0, 0x60, 0x87, 0x8a, 0x62, 0x72, 0xa3, 0x56, 0xc1, 0x34, 0xcc, 0xd, 0xe8, 0x74, 0x22, 0x93, 0x13, 0x4b, 0x2a, 0xa0, 0x8c, 0xf3, 0x20, 0xfe, 0xb2, 0x75, 0x1d, 0xb7, 0x83, 0x83, 0x2, 0x39, 0xf6, 0xd8, 0xad, 0xab, 0xe5, 0x3c, 0x9e, 0x4a, 0xc4, 0x24, 0x29, 0xca, 0x21, 0x83, 0xec, 0x56, 0x29, 0x12, 0x7b, 0xb6, 0xdd, 0xfa, 0x9a, 0xf5, 0xec, 0x17, 0x60, 0x4c, 0x7b, 0xaf, 0x58, 0xd, 0x6f, 0x30, 0x8e, 0x31, 0x2d, 0xbf, 0x21, 0xeb, 0xfc, 0xec, 0x2d, 0xf6, 0x3e, 0xb2, 0x2c, 0xac, 0x4f, 0x45, 0xb0, 0x94, 0xe0, 0x37, 0x5, 0x19, 0x48, 0x57, 0x90, 0x2e, 0xaf, 0x78, 0xe0, 0x25, 0x45, 0x28, 0x28, 0x60, 0x79, 0x1d, 0x49, 0x13, 0xe5, 0xdf, 0x90, 0x6e, 0x4, 0x2d, 0x53, 0x8b, 0xf1, 0x4a, 0x94, 0x23, 0x64, 0x48, 0x18, 0xf6, 0x89, 0x6d, 0x5a, 0x33, 0x64, 0x3a, 0x31, 0xa, 0xb6, 0xc9, 0x63, 0x24, 0x23, 0x21, 0x87, 0x87, 0xbf, 0x80, 0xa1, 0xa1, 0x40, 0xb, 0xf1, 0x40, 0x30, 0xa4, 0xb8, 0xdc, 0x2e, 0x6b, 0x22, 0xdc, 0x54, 0x44, 0x4b, 0x52, 0x41, 0xe2, 0xb5, 0x6d, 0xaa, 0x0, 0x4f, 0xd8, 0xb2, 0xc3, 0x8d, 0xd6, 0xd8, 0x9, 0x19, 0x5, 0xfe, 0xb7, 0x6e, 0x84, 0x17, 0x5b, 0xe5, 0xd9, 0x8c, 0x40, 0xf1, 0xf9, 0x3c, 0x8e, 0x76, 0xdf, 0xa, 0xea, 0x9d, 0xef, 0xec, 0xf, 0xf1, 0xfb, 0x2, 0x6, 0x9d, 0x72, 0x27, 0xf, 0x6, 0x33, 0x64, 0x72, 0x3f, 0x6, 0xd7, 0x30, 0x1a, 0xc0, 0x94, 0xda, 0x28, 0xf, 0x4a, 0x50, 0xff, 0xc5, 0x2, 0xc4, 0x40, 0x82, 0x68, 0x68, 0xb0, 0x11, 0xc, 0x13, 0x5d, 0x5d, 0x1, 0xb1, 0xa6, 0xe9, 0x22, 0x53, 0xc3, 0x94, 0x62, 0xa2, 0x3, 0x5, 0xe9, 0x18, 0x29, 0x1e, 0x46, 0xe7, 0x3, 0x0, 0xcc, 0x69, 0xe7, 0x84, 0x8, 0xfc, 0x16, 0xd2, 0xc, 0x1e, 0x73, 0x5, 0x4d, 0x62, 0xeb, 0x4a, 0x4, 0x80, 0xb, 0xac, 0xaa, 0xc0, 0x44, 0xe1, 0x5, 0xa6, 0xac, 0xf6, 0xc, 0x53, 0x60, 0x98, 0x64, 0xac, 0x22, 0x2d, 0x12, 0x72, 0xd7, 0x2a, 0x1b, 0x1d, 0xa, 0x89, 0x75, 0x67, 0x90, 0xf0, 0xae, 0xe3, 0x9b, 0xe, 0x3b, 0x45, 0x4, 0x96, 0xb0, 0x0, 0xf1, 0xcb, 0xb3, 0x2b, 0xd1, 0x52, 0x24, 0xb5, 0x5f, 0xa2, 0x1f, 0x8f, 0x25, 0xe0, 0x2b, 0xe2, 0xe7, 0x91, 0xc1, 0x41, 0xde, 0x96, 0xc, 0x36, 0x3e, 0xb, 0xb7, 0xe9, 0x76, 0x67, 0x50, 0xe8, 0x8d, 0xae, 0xfb, 0xf5, 0x6a, 0xbb, 0xd2, 0xbb, 0x2e, 0x57, 0x4e, 0xa, 0xc3, 0xe6, 0xa0, 0xd0, 0xac, 0x17, 0xfa, 0x79, 0x37, 0xb6, 0x16, 0xaa, 0x18, 0x52, 0xc7, 0x5, 0xb2, 0x2f, 0x30, 0xb0, 0xcd, 0xc9, 0x5, 0xc2, 0x17, 0x10, 0x11, 0x1f, 0xf, 0xda, 0x16, 0x1, 0x6e, 0x98, 0x21, 0x63, 0x15, 0x8d, 0xb3, 0xb1, 0x4e, 0xd, 0x24, 0xe3, 0x9, 0x46, 0xa, 0x60, 0xfc, 0x25, 0xbe, 0xa6, 0x13, 0x1, 0xc8, 0xbb, 0xe7, 0x4, 0x7, 0x3a, 0x7f, 0xf3, 0x0, 0xc1, 0x97, 0x8, 0xbc, 0x17, 0xff, 0xed, 0x1e, 0x33, 0x40, 0x9a, 0xa1, 0x42, 0x26, 0xc2, 0xed, 0xce, 0xa0, 0xd2, 0x8f, 0x58, 0xf7, 0xd6, 0xdb, 0xc5, 0x98, 0x1f, 0x38, 0xff, 0x13, 0x8f, 0xa7, 0x13, 0x5b, 0xf1, 0xff, 0x49, 0xe9, 0xf3, 0xfc, 0xcf, 0xc7, 0x94, 0x50, 0x57, 0x45, 0x90, 0x22, 0xb0, 0x84, 0x58, 0x4, 0xfe, 0x52, 0xb4, 0x40, 0x26, 0x54, 0x81, 0x86, 0x75, 0xdb, 0x72, 0x42, 0x67, 0xdd, 0xb8, 0x65, 0x64, 0xa8, 0x64, 0xc5, 0xdd, 0x3f, 0x16, 0x1, 0x32, 0xd1, 0xc, 0x15, 0x59, 0x28, 0x12, 0x1a, 0xcc, 0x90, 0xe, 0x26, 0xc4, 0xb, 0x15, 0xa5, 0x16, 0x32, 0xe8, 0xa6, 0x4b, 0xc8, 0x89, 0x8d, 0xd, 0x85, 0x62, 0x11, 0x50, 0x50, 0x44, 0xd4, 0xa8, 0x83, 0xb5, 0x54, 0xd8, 0x10, 0x18, 0x8b, 0xb8, 0x7, 0x8c, 0xc6, 0x2b, 0x80, 0xee, 0x91, 0x6c, 0x5b, 0x6e, 0x38, 0xbb, 0x68, 0x82, 0xfd, 0x92, 0x89, 0xa6, 0x41, 0x5d, 0xc9, 0x87, 0x98, 0xe2, 0xb0, 0x15, 0x2, 0xb4, 0xb9, 0x82, 0x4d, 0x10, 0x36, 0x40, 0x14, 0x59, 0x72, 0x54, 0x44, 0xf9, 0x46, 0x19, 0x5a, 0x1a, 0x51, 0xa2, 0x7f, 0xfe, 0x9, 0x22, 0xe7, 0xe2, 0x80, 0xcb, 0x56, 0xc8, 0xed, 0x5f, 0x4c, 0x54, 0xd8, 0xfc, 0x25, 0x5b, 0x2a, 0x98, 0x22, 0xcb, 0xd, 0xaf, 0xfd, 0xbf, 0x8e, 0xa9, 0xa9, 0x43, 0xd, 0x51, 0x3, 0xca, 0x8, 0x30, 0x1c, 0x3d, 0xc4, 0xc7, 0x2a, 0xd2, 0xf6, 0x9e, 0xfe, 0xf5, 0x17, 0x7b, 0xe1, 0xa, 0xf, 0x38, 0x72, 0xa2, 0xac, 0x27, 0xb6, 0xaa, 0x32, 0xd0, 0x23, 0x10, 0x1, 0x7f, 0xfd, 0x15, 0x76, 0xc3, 0x62, 0x1d, 0xac, 0x4, 0xdc, 0x52, 0xa2, 0x1b, 0xd0, 0x9a, 0xfd, 0x76, 0xf4, 0x67, 0x84, 0x59, 0x9f, 0x11, 0x19, 0xfe, 0xdf, 0x88, 0x6c, 0x5a, 0x7f, 0x1d, 0x81, 0x1f, 0x3c, 0xc8, 0x30, 0x9d, 0x4, 0xe1, 0xb0, 0x82, 0x64, 0xa2, 0x20, 0xf0, 0xc3, 0x81, 0xe3, 0xfd, 0xb4, 0x10, 0x7a, 0x76, 0x17, 0xa3, 0x32, 0x64, 0xc8, 0x43, 0xa1, 0x78, 0x4, 0x54, 0x91, 0xe5, 0x8e, 0xbb, 0x8, 0xf3, 0xf4, 0x9c, 0xd1, 0x4f, 0x1c, 0xed, 0x80, 0xc1, 0x7a, 0xab, 0x61, 0xf2, 0x45, 0xce, 0x6, 0xe, 0x52, 0xad, 0xd0, 0x2b, 0x76, 0x7a, 0xd7, 0x85, 0x72, 0xab, 0xde, 0xbe, 0xee, 0x16, 0xfa, 0xfd, 0x8b, 0x4e, 0xaf, 0x1c, 0x34, 0x64, 0xff, 0x7f, 0x80, 0xe4, 0x19, 0x9, 0x85, 0x12, 0x82, 0xd7, 0xca, 0xed, 0x3e, 0x37, 0xaa, 0x54, 0x9b, 0x6f, 0x1e, 0x20, 0xdd, 0x32, 0x57, 0x7e, 0xb6, 0xe6, 0x81, 0xe3, 0x8f, 0xd, 0x20, 0xe3, 0xc9, 0x46, 0x96, 0x82, 0xba, 0xf3, 0x1d, 0x4b, 0x5e, 0x78, 0x33, 0xa8, 0x77, 0x1, 0xd1, 0x79, 0xb, 0xc2, 0x27, 0xc, 0x88, 0xc9, 0xec, 0x15, 0x36, 0xd5, 0x8b, 0xef, 0xc4, 0xcc, 0x6f, 0x1a, 0x9, 0xfd, 0xf9, 0x67, 0x98, 0xe9, 0x77, 0xb7, 0x9, 0x31, 0x81, 0xb9, 0xa7, 0x5c, 0x4, 0x1b, 0x3e, 0x4e, 0xab, 0x33, 0x87, 0xb8, 0xb4, 0xee, 0x8c, 0xa5, 0xc0, 0xc8, 0x5e, 0x8b, 0xd1, 0x7c, 0x23, 0x9a, 0x91, 0xce, 0xa9, 0xb, 0x25, 0x23, 0xa0, 0xc0, 0xad, 0x58, 0x77, 0xd0, 0x86, 0x75, 0xb0, 0xc0, 0xd0, 0x33, 0x7, 0x1f, 0x15, 0xb1, 0x50, 0x2a, 0x2, 0x9a, 0x64, 0x8a, 0x75, 0x17, 0x78, 0x89, 0xad, 0x99, 0x2b, 0xe5, 0xa5, 0x66, 0x9d, 0x33, 0x96, 0x13, 0x99, 0xaf, 0xf2, 0x7a, 0x8f, 0xa2, 0xfb, 0xd9, 0x2a, 0xf3, 0xdf, 0xaa, 0xec, 0xcc, 0xff, 0xd7, 0x33, 0xa4, 0x32, 0x83, 0x37, 0x62, 0x19, 0x6f, 0x75, 0x14, 0xf0, 0xf1, 0xf9, 0x3f, 0x16, 0x4b, 0x48, 0x3b, 0xf3, 0x7f, 0x26, 0x9e, 0xf8, 0x9c, 0xff, 0x3f, 0xa2, 0xfc, 0xf9, 0x67, 0xf4, 0x1b, 0x58, 0x60, 0x2d, 0xcf, 0xd7, 0x4c, 0x4c, 0x7, 0x58, 0x2b, 0x3, 0xfd, 0xa6, 0xb1, 0x61, 0x91, 0x67, 0x28, 0xf, 0xbe, 0x45, 0xff, 0xfa, 0x2b, 0xc4, 0x6a, 0x85, 0x2a, 0xf7, 0x6, 0x74, 0xf, 0x7c, 0xb8, 0xe7, 0x5a, 0xd8, 0xba, 0x8c, 0x1f, 0x1d, 0xf, 0x5d, 0x20, 0xb6, 0xa0, 0xd0, 0xf9, 0x1c, 0xe, 0x2d, 0x90, 0x4e, 0x88, 0x33, 0x27, 0x60, 0x8c, 0x64, 0xc8, 0x2c, 0x0, 0x4a, 0x34, 0x4, 0x1a, 0xde, 0x52, 0x51, 0xa0, 0x98, 0x60, 0xa4, 0x2a, 0x14, 0x40, 0x13, 0x89, 0x23, 0x5f, 0xe2, 0xe0, 0xf, 0x5f, 0xcf, 0xfc, 0x3a, 0x5e, 0x71, 0xf4, 0x4c, 0x4f, 0xe9, 0x50, 0x63, 0xf3, 0x10, 0xb3, 0x8d, 0xbf, 0x46, 0x42, 0xe, 0x45, 0x61, 0x67, 0x25, 0xb8, 0x56, 0x81, 0x7c, 0x2a, 0x9, 0xaf, 0x5f, 0xf2, 0xb3, 0x6f, 0xce, 0xdb, 0xa3, 0xb5, 0xee, 0x85, 0x1a, 0xea, 0x2c, 0x90, 0x69, 0x62, 0x3e, 0xb9, 0x72, 0xa2, 0x19, 0xb9, 0xec, 0x4f, 0xac, 0xf5, 0xed, 0xc9, 0x4, 0xdf, 0xb3, 0xc5, 0xbb, 0x87, 0x89, 0xe9, 0x40, 0xf6, 0xb7, 0x18, 0x85, 0x92, 0x89, 0x78, 0x17, 0xbd, 0x6, 0xd8, 0x2c, 0xb6, 0x2, 0x77, 0x36, 0x54, 0x85, 0xdd, 0xe, 0xd, 0x83, 0x77, 0xee, 0xef, 0x30, 0x24, 0xeb, 0x19, 0xd6, 0xed, 0xcc, 0xff, 0xe2, 0x8d, 0xe4, 0x7f, 0x7b, 0xe2, 0xf8, 0xb8, 0x60, 0x86, 0x89, 0x75, 0x6b, 0x2, 0x8e, 0xfe, 0x37, 0xd, 0xff, 0x6f, 0x7a, 0xb4, 0x39, 0xc7, 0x3b, 0x38, 0x9f, 0x3d, 0x94, 0xa0, 0x86, 0x54, 0xcd, 0x3d, 0x3d, 0xaa, 0x0, 0x15, 0x8e, 0x91, 0x4a, 0xc1, 0xbe, 0xae, 0x88, 0xd7, 0x2, 0xd5, 0xc, 0x99, 0xd8, 0x62, 0x4b, 0xc4, 0xd, 0x7b, 0xa3, 0xef, 0x9c, 0x47, 0xfb, 0xeb, 0xaf, 0x90, 0x29, 0x1e, 0xe5, 0x77, 0xec, 0x11, 0xf6, 0x92, 0x33, 0xac, 0x78, 0x25, 0xd2, 0x1e, 0x38, 0x2f, 0xc2, 0xeb, 0x27, 0xce, 0x91, 0x78, 0xf0, 0x83, 0xad, 0x3e, 0x55, 0x66, 0xc2, 0x1c, 0x1d, 0x1f, 0x81, 0xa3, 0xeb, 0x23, 0x6, 0xf, 0xd, 0x23, 0xf, 0x8e, 0x2, 0x27, 0x61, 0xcf, 0x98, 0x39, 0xa, 0xe8, 0x2e, 0x3f, 0xf8, 0xd5, 0x7c, 0xbc, 0x97, 0xbe, 0x3a, 0xa2, 0xab, 0x8f, 0xf6, 0xe4, 0x45, 0x94, 0xec, 0xb4, 0xe9, 0xce, 0xb0, 0xc3, 0x5e, 0x73, 0xfd, 0xa5, 0x7c, 0xc6, 0xca, 0xc6, 0x39, 0xc1, 0x5d, 0x86, 0xc8, 0x73, 0x86, 0x8, 0x9e, 0xad, 0x7f, 0xb5, 0x48, 0x5f, 0x1c, 0x43, 0xb, 0x42, 0xf6, 0x75, 0xcd, 0x17, 0x2a, 0x5d, 0xf3, 0xda, 0x1e, 0x5c, 0xdb, 0x3c, 0xb4, 0x2b, 0x9a, 0x43, 0x8a, 0xc0, 0xb7, 0xc8, 0xfa, 0xa0, 0x12, 0x80, 0xe2, 0x50, 0x63, 0x49, 0x1c, 0xc6, 0xe3, 0xa3, 0xe6, 0x3a, 0x93, 0xd6, 0x2b, 0x8e, 0xef, 0x21, 0x4a, 0x0, 0xb6, 0x80, 0xc, 0x75, 0xe7, 0x6c, 0xde, 0xe6, 0xd9, 0x47, 0xe7, 0x60, 0xa1, 0xae, 0xb8, 0x36, 0x98, 0xff, 0xf9, 0x7e, 0xd1, 0x63, 0x2d, 0x88, 0x86, 0xdb, 0x9b, 0x2, 0xa8, 0x62, 0x6a, 0x31, 0x1, 0xa4, 0x86, 0x8a, 0xad, 0x26, 0xfb, 0x71, 0x14, 0xd9, 0x3b, 0x80, 0x3b, 0x50, 0x86, 0xc9, 0x7d, 0x72, 0xe0, 0x57, 0x13, 0x51, 0x4b, 0x3c, 0xfe, 0xa, 0x8e, 0xbe, 0xf9, 0xf0, 0xcb, 0x3a, 0xab, 0x77, 0x4b, 0xb0, 0xce, 0x11, 0xb, 0xc8, 0xdd, 0xcf, 0x76, 0xc4, 0x6b, 0x6, 0xb, 0xe6, 0xc0, 0x77, 0x38, 0x51, 0x74, 0x9f, 0x4c, 0xfc, 0xbd, 0x47, 0x66, 0x64, 0x2f, 0x7, 0xfb, 0xd, 0xd1, 0xed, 0x56, 0x1d, 0xbf, 0xcd, 0x23, 0xc, 0x13, 0x40, 0xcf, 0x9e, 0x16, 0x1c, 0x69, 0xdf, 0x1c, 0x5c, 0x9f, 0x9a, 0xda, 0x70, 0x55, 0x1d, 0x81, 0x5f, 0xb1, 0x2e, 0xab, 0xb6, 0x12, 0xb8, 0xf8, 0xf8, 0xfa, 0xa4, 0x66, 0xdd, 0x50, 0x9d, 0x8, 0x33, 0x8f, 0x45, 0x93, 0x0, 0x38, 0xa2, 0x82, 0xee, 0xbc, 0xe, 0x79, 0xd5, 0xd8, 0x3c, 0xa, 0x8e, 0xdc, 0xa0, 0x23, 0x17, 0x40, 0x80, 0x1c, 0x5a, 0xa, 0xf9, 0xc2, 0x82, 0x36, 0xc4, 0xc3, 0x85, 0xdf, 0x69, 0xcc, 0x1d, 0x46, 0x4e, 0x9c, 0x8f, 0xb6, 0x3d, 0xc2, 0xb2, 0xbf, 0x73, 0x6, 0x31, 0x5f, 0xdb, 0x39, 0x3f, 0x8f, 0x89, 0x0, 0x1d, 0x3f, 0x41, 0x4f, 0xef, 0x8a, 0xe1, 0xea, 0x9b, 0x17, 0x76, 0xc5, 0x8d, 0x8, 0x7a, 0xcb, 0xee, 0x18, 0x84, 0x5a, 0x6c, 0x41, 0xf5, 0xc2, 0x2e, 0xb9, 0x24, 0xbd, 0xea, 0xb, 0xb9, 0x81, 0x4c, 0xaf, 0xeb, 0xd6, 0x4e, 0x65, 0xb7, 0x9a, 0xd7, 0x0, 0x5b, 0x58, 0xa7, 0x93, 0x48, 0x97, 0xc1, 0xf, 0x70, 0x67, 0x13, 0xb, 0xbd, 0xf0, 0x33, 0x3e, 0x5, 0xdd, 0x73, 0x7, 0xc1, 0x84, 0xcb, 0xee, 0x87, 0x8d, 0xc3, 0xeb, 0xba, 0xfd, 0x8a, 0x5e, 0xfa, 0xa3, 0xd4, 0xde, 0x92, 0x8b, 0xbd, 0x70, 0xb7, 0x97, 0x75, 0xcd, 0x4f, 0xd6, 0xab, 0xba, 0xe7, 0x8f, 0xb3, 0xfb, 0x1b, 0x49, 0xe9, 0x6, 0x5d, 0xaf, 0xe9, 0x60, 0x50, 0xb4, 0xe0, 0x5b, 0xf6, 0x53, 0xe0, 0x77, 0xa7, 0xb8, 0x17, 0xf5, 0x35, 0x88, 0xc4, 0x37, 0xe8, 0xf2, 0x46, 0xd8, 0xe3, 0x3b, 0x74, 0x59, 0x84, 0x4f, 0xbe, 0xaa, 0xcb, 0x1b, 0x24, 0xbe, 0x9a, 0x8d, 0x5, 0x2d, 0x2e, 0xe3, 0x9, 0x8f, 0xd8, 0xce, 0x2c, 0x1f, 0x30, 0x35, 0xb1, 0xe9, 0x3e, 0xff, 0x68, 0xdd, 0xd, 0x55, 0xc7, 0xaa, 0xff, 0xeb, 0xd1, 0xea, 0xc2, 0x3a, 0x39, 0x8c, 0x56, 0x4c, 0xf4, 0xac, 0xde, 0xe3, 0xa4, 0x6e, 0x9, 0x29, 0x3, 0xf8, 0x6f, 0x4a, 0x55, 0x8d, 0x28, 0xe8, 0x37, 0x27, 0xbd, 0xcd, 0xcb, 0x4, 0xe2, 0xdf, 0x76, 0xc4, 0x82, 0xa5, 0xfe, 0x4d, 0x6, 0xce, 0xc7, 0xf6, 0xff, 0xbe, 0x3, 0xb7, 0xa5, 0x3b, 0xe, 0xe, 0xdc, 0xcf, 0x76, 0xa1, 0xfd, 0xa3, 0xcb, 0x8e, 0xff, 0xd7, 0xb7, 0xb, 0xe3, 0xff, 0x3b, 0x2c, 0x6b, 0x2f, 0xce, 0xe, 0x77, 0x60, 0xff, 0x37, 0x15, 0x4b, 0x6c, 0xf9, 0x7f, 0xe3, 0x52, 0x2a, 0x9e, 0xfc, 0xf4, 0xff, 0x7e, 0x44, 0x81, 0x6, 0xf6, 0xf2, 0x3f, 0x2e, 0x62, 0xa1, 0x39, 0xd6, 0x95, 0xbc, 0x93, 0x9e, 0xab, 0x5, 0x8d, 0x90, 0x86, 0x2c, 0xe8, 0xc6, 0xc7, 0x39, 0x41, 0xed, 0xcf, 0xdb, 0xc9, 0xe3, 0x69, 0x58, 0xb8, 0x3f, 0x2b, 0x1f, 0xfa, 0xf3, 0x4f, 0xb0, 0xbd, 0xa, 0x77, 0xbd, 0x7a, 0x11, 0xf0, 0x3, 0x60, 0x5d, 0x41, 0xba, 0x5, 0x92, 0xce, 0x76, 0x18, 0xdf, 0x60, 0x26, 0x3a, 0xd2, 0xad, 0x3c, 0xf0, 0x27, 0xd5, 0x71, 0xc9, 0xe9, 0x76, 0xfa, 0x83, 0x6a, 0xaf, 0xd2, 0x3f, 0x6b, 0x5e, 0xd7, 0x3a, 0xfd, 0xc1, 0x1e, 0xca, 0x2, 0x94, 0xd5, 0xd1, 0x26, 0x70, 0xb7, 0xd3, 0x3b, 0x8, 0xbc, 0xd6, 0x60, 0x5b, 0xc0, 0xc3, 0x7e, 0xa5, 0xd7, 0x2e, 0xb4, 0x2a, 0x87, 0x10, 0x6c, 0x6a, 0xe1, 0x2d, 0x24, 0xe5, 0xc2, 0xa0, 0x50, 0x2c, 0xf4, 0xf, 0x22, 0xd9, 0x5c, 0xe, 0xb8, 0x88, 0x2a, 0xad, 0x42, 0xdd, 0x3f, 0x4, 0xae, 0xe1, 0xe3, 0x1b, 0xb3, 0x88, 0x97, 0x4e, 0x68, 0x3, 0x66, 0xdd, 0xf3, 0xbd, 0x30, 0xdc, 0xe7, 0xe7, 0x87, 0x19, 0xf6, 0x7b, 0x7, 0x40, 0x86, 0x14, 0x99, 0x1b, 0x20, 0xfd, 0x7e, 0xf3, 0x0, 0x48, 0x9f, 0xaa, 0x1b, 0x10, 0x27, 0xbd, 0x4e, 0xeb, 0x0, 0xc8, 0x89, 0x49, 0xb4, 0xd, 0x98, 0x7a, 0xb9, 0xd2, 0x1e, 0xd4, 0x7, 0xa3, 0x3, 0x70, 0x6e, 0xca, 0xa2, 0x4d, 0xd8, 0x76, 0xbf, 0x52, 0x1a, 0xf6, 0x2a, 0x87, 0x60, 0x9d, 0xd4, 0x46, 0x2e, 0xec, 0xe5, 0xe0, 0xba, 0xd2, 0x2e, 0x77, 0x3b, 0xf5, 0x36, 0x1b, 0xc6, 0xa7, 0x6c, 0x85, 0x32, 0xb0, 0x61, 0xfd, 0x7a, 0xd8, 0x6b, 0x3a, 0x0, 0x7b, 0x66, 0xef, 0x2d, 0x79, 0xb2, 0x31, 0x3, 0x3c, 0xed, 0x14, 0xfb, 0x95, 0xde, 0x79, 0xbd, 0x54, 0x79, 0x36, 0x82, 0x75, 0xfa, 0x1b, 0x86, 0xa8, 0x57, 0xa9, 0xd6, 0xfb, 0x83, 0xde, 0xe8, 0xd9, 0x68, 0xbc, 0xb3, 0x4a, 0x29, 0x49, 0xe2, 0x49, 0xb5, 0x6, 0x9d, 0x46, 0xa5, 0x7d, 0xfd, 0x52, 0xaa, 0x6c, 0x1c, 0x75, 0xa8, 0x8a, 0x5a, 0x64, 0x8e, 0x78, 0xca, 0xa6, 0x8b, 0xfa, 0xa0, 0x76, 0x2d, 0xe2, 0xae, 0x36, 0xbf, 0xc6, 0xce, 0x96, 0x39, 0xab, 0xed, 0x4, 0x68, 0x1d, 0x6c, 0x38, 0xc0, 0x65, 0xc8, 0xcd, 0x96, 0x64, 0x32, 0x99, 0x60, 0x78, 0x9a, 0x9d, 0xea, 0x75, 0xb3, 0x72, 0x5e, 0x61, 0x68, 0xb0, 0x3e, 0x21, 0xec, 0x59, 0xbd, 0x55, 0xa8, 0x56, 0xae, 0xfb, 0x83, 0x4e, 0xaf, 0x72, 0xdd, 0x2d, 0xc, 0x6a, 0x79, 0x70, 0x14, 0x3d, 0xe2, 0x51, 0x5e, 0x98, 0xae, 0xe3, 0xf2, 0x88, 0x9, 0xcd, 0x15, 0x98, 0x41, 0x79, 0x1e, 0x9, 0x1, 0x50, 0x18, 0xe, 0x6a, 0xd7, 0xad, 0x4e, 0xf9, 0x11, 0x4e, 0xda, 0x4d, 0x66, 0xe5, 0xf4, 0xa5, 0x5f, 0x69, 0x9e, 0x5c, 0x3b, 0x9f, 0xa6, 0x30, 0xa8, 0x77, 0xda, 0xfb, 0x71, 0x6c, 0x27, 0xbc, 0x72, 0x30, 0x34, 0xcb, 0x85, 0xae, 0x33, 0x16, 0x7b, 0x0, 0x55, 0x5, 0x1a, 0x11, 0xdb, 0x54, 0xfd, 0x0, 0xfd, 0x4a, 0xa1, 0x57, 0xaa, 0x5d, 0x97, 0x1f, 0x69, 0x8f, 0x83, 0xb9, 0x49, 0xb2, 0xfc, 0xb0, 0x4c, 0x6b, 0x1d, 0x86, 0x14, 0x69, 0xb3, 0xfc, 0x70, 0x27, 0xf5, 0xe6, 0xa0, 0xf2, 0x88, 0x22, 0xe1, 0x60, 0x22, 0xbd, 0xd6, 0x46, 0xdf, 0xea, 0xe5, 0x43, 0x7d, 0xc3, 0xca, 0x46, 0xdf, 0x4a, 0x9d, 0xee, 0x23, 0xdf, 0x42, 0xf4, 0x4b, 0x26, 0x6, 0xf2, 0x3, 0xd, 0xea, 0xad, 0x4a, 0x67, 0xf8, 0x88, 0x66, 0xe4, 0x60, 0x4e, 0xee, 0x2e, 0x3f, 0xe0, 0x79, 0xa5, 0x57, 0x3f, 0x19, 0x5d, 0x97, 0x2a, 0x8f, 0xa9, 0x55, 0xe, 0xbc, 0xce, 0xf3, 0xe5, 0xc0, 0xbb, 0x53, 0xc0, 0xf5, 0x60, 0xc4, 0x9, 0x76, 0xad, 0xfb, 0x3b, 0x7e, 0x12, 0xb6, 0xdb, 0xeb, 0x9c, 0x56, 0x4a, 0x83, 0xeb, 0x52, 0xaf, 0xc2, 0x39, 0xe3, 0xba, 0x57, 0xe9, 0xf, 0x7a, 0xf5, 0x92, 0xc3, 0x25, 0x68, 0x81, 0xcc, 0x15, 0xd1, 0xb9, 0x6c, 0x3b, 0x34, 0xf4, 0x2a, 0xad, 0xce, 0xa0, 0xe2, 0x92, 0x42, 0x26, 0x13, 0xf6, 0xae, 0x55, 0xb8, 0xbc, 0x3e, 0xed, 0x14, 0xaf, 0x2f, 0x3a, 0xbd, 0x46, 0xa5, 0xd7, 0xcf, 0x83, 0xa3, 0xc4, 0x5a, 0x88, 0x2b, 0x97, 0xdd, 0xba, 0xc7, 0x77, 0x9, 0x2e, 0xdd, 0xa5, 0x93, 0xea, 0xe6, 0x63, 0x9e, 0xa5, 0xac, 0xda, 0x29, 0x57, 0x8a, 0xc3, 0x6a, 0x1e, 0x1c, 0xe9, 0xc8, 0x52, 0x74, 0xfa, 0x9b, 0x3c, 0xe5, 0xf2, 0x52, 0x28, 0xb7, 0xea, 0xbd, 0x42, 0xd3, 0x61, 0xc0, 0x76, 0x41, 0x68, 0x9a, 0x7e, 0x65, 0xe0, 0xcf, 0x33, 0xc7, 0xc5, 0xbb, 0xd4, 0x2c, 0xd4, 0xb7, 0x3e, 0x3e, 0x5f, 0x65, 0x6e, 0x9, 0x37, 0xaf, 0x76, 0x5d, 0x2e, 0xbe, 0x60, 0x7a, 0xf7, 0x40, 0x9f, 0x3f, 0xb9, 0x7b, 0xa0, 0x2f, 0x9f, 0xda, 0x5d, 0x14, 0x7, 0xe7, 0xf3, 0xdd, 0xa5, 0xf5, 0x1a, 0xfc, 0xb9, 0xca, 0x54, 0x4, 0xe7, 0xa7, 0xa5, 0x34, 0xff, 0x74, 0xc3, 0x42, 0xc1, 0x3f, 0x37, 0xb9, 0x8f, 0x4a, 0xcd, 0x3a, 0x9b, 0x25, 0xcb, 0x3b, 0x8f, 0xfa, 0x95, 0x52, 0xaf, 0xe2, 0xaf, 0xb9, 0xc9, 0xcb, 0x6e, 0x2a, 0x3a, 0x6f, 0xe6, 0x60, 0x6a, 0x91, 0xa9, 0xc7, 0x6e, 0xaf, 0x73, 0x5e, 0x2f, 0x57, 0x7a, 0xd7, 0xce, 0x48, 0x4d, 0xb0, 0x8a, 0xe8, 0x8a, 0x5a, 0x48, 0x3b, 0xfa, 0x5c, 0x97, 0xfd, 0x13, 0xca, 0x93, 0xd7, 0x7f, 0x22, 0xd8, 0xef, 0x45, 0x29, 0xc2, 0xf, 0xac, 0xff, 0x62, 0xa9, 0x58, 0x7a, 0x6b, 0xfd, 0x17, 0x4b, 0x65, 0xd2, 0x9f, 0xeb, 0xbf, 0x8f, 0x28, 0x81, 0xeb, 0x3f, 0x71, 0x26, 0xfc, 0xef, 0xb8, 0xf8, 0x13, 0x49, 0x1d, 0x3a, 0x6, 0xbc, 0xb3, 0x91, 0xb7, 0x12, 0xf4, 0x65, 0xb5, 0xf5, 0x4d, 0x29, 0xde, 0xd3, 0xdd, 0x8d, 0x27, 0xde, 0x86, 0xb3, 0x6, 0xba, 0x28, 0x6f, 0x40, 0xed, 0xae, 0x80, 0x96, 0x1, 0x3b, 0x57, 0x1c, 0x41, 0x60, 0x3c, 0xea, 0x5e, 0x64, 0x1b, 0x19, 0x4e, 0xf7, 0x60, 0xf4, 0xaf, 0x4a, 0xfd, 0xf8, 0x1e, 0x99, 0xbb, 0x36, 0xdc, 0x75, 0x9b, 0x8b, 0x4, 0x57, 0xa7, 0xfb, 0x8, 0x5a, 0xaf, 0x3, 0x9c, 0xe1, 0xd9, 0x43, 0xc8, 0xb0, 0x1e, 0x4, 0x6d, 0xe3, 0xc7, 0xa0, 0x76, 0xbd, 0xfb, 0x7, 0xcc, 0x5e, 0x9e, 0x8e, 0x55, 0x24, 0x71, 0x5, 0x7f, 0xfd, 0x5, 0xb6, 0xec, 0xd1, 0xc7, 0x3e, 0x8c, 0xcf, 0x1e, 0xdd, 0xf3, 0x79, 0x80, 0x3f, 0x7a, 0x95, 0xb1, 0xde, 0x64, 0xaf, 0xa9, 0xb1, 0x61, 0x2e, 0xbc, 0x68, 0xd4, 0xff, 0xfc, 0xd3, 0x6d, 0xe9, 0x67, 0x4b, 0xf3, 0xf3, 0xcb, 0xd3, 0xf5, 0xff, 0xcb, 0x6f, 0x87, 0x38, 0xe4, 0xff, 0x8b, 0xa7, 0x92, 0x3b, 0xfe, 0xbf, 0xd4, 0xa7, 0xfe, 0xff, 0x90, 0xe2, 0xd7, 0xff, 0xd0, 0x30, 0x68, 0x74, 0x11, 0x1b, 0x23, 0xb, 0xc6, 0xdd, 0x99, 0xc0, 0x82, 0x16, 0x9a, 0xd8, 0x6a, 0xff, 0xef, 0x39, 0x1d, 0x50, 0x3, 0xc9, 0x8c, 0x1c, 0x13, 0xf1, 0x4b, 0x2, 0x68, 0x1e, 0xc4, 0x9c, 0x13, 0x6d, 0xce, 0x3a, 0xff, 0x45, 0x34, 0x52, 0xf7, 0x28, 0x95, 0x38, 0x33, 0xbd, 0xe, 0xc9, 0xb, 0x24, 0x7b, 0x23, 0x64, 0xcf, 0x47, 0x7b, 0xda, 0xa5, 0x7d, 0x2f, 0xf5, 0xc0, 0x23, 0xcc, 0x69, 0xc9, 0x37, 0xc0, 0xac, 0x3c, 0x6f, 0xac, 0xb2, 0xeb, 0xf6, 0x1e, 0x69, 0xf1, 0xd1, 0x57, 0xee, 0x70, 0x2, 0x7f, 0x72, 0x12, 0x2f, 0xed, 0x50, 0xd8, 0xf9, 0xea, 0xdb, 0x50, 0xeb, 0x13, 0x7b, 0x7b, 0x17, 0xbd, 0xfc, 0x75, 0x64, 0x7d, 0x92, 0xcf, 0xd9, 0x32, 0xda, 0x5f, 0xd5, 0x82, 0x53, 0x67, 0x35, 0xe4, 0xc3, 0xdf, 0xf5, 0x1d, 0xdb, 0x7b, 0xbc, 0xa5, 0xf5, 0x1, 0xbf, 0xd, 0x2c, 0xbe, 0xa3, 0xb5, 0x8c, 0x2b, 0xc8, 0x8, 0x6a, 0x6a, 0x20, 0x16, 0xaf, 0xe2, 0x7a, 0x78, 0x63, 0x92, 0x7f, 0x7c, 0x91, 0xbe, 0xe0, 0xd9, 0xd2, 0xbd, 0x7, 0x61, 0xe7, 0x30, 0x6b, 0xb, 0x1a, 0x3d, 0x34, 0x59, 0x3f, 0x67, 0xe5, 0xc5, 0xc2, 0xe2, 0xa2, 0x16, 0xb3, 0xee, 0x5b, 0xe3, 0xf5, 0x1d, 0x74, 0xf4, 0x7f, 0x5e, 0xb6, 0x64, 0xde, 0x68, 0x86, 0xdf, 0xd, 0x94, 0x7, 0x47, 0x59, 0x29, 0x2b, 0x1d, 0x5, 0x0, 0x9c, 0xf6, 0x3b, 0xed, 0xeb, 0xd2, 0x49, 0xd5, 0xe7, 0x2b, 0xb, 0x82, 0xe7, 0x47, 0x4b, 0xfc, 0xb3, 0x8b, 0x18, 0x30, 0xe7, 0x9f, 0xc8, 0x2d, 0xf5, 0xce, 0x70, 0xfa, 0x91, 0x37, 0x2a, 0xa3, 0xa7, 0x63, 0x9c, 0xa3, 0x95, 0x57, 0x8f, 0xad, 0xea, 0xe9, 0xd6, 0xf7, 0x11, 0xc, 0x2d, 0xb2, 0xe6, 0xb3, 0xde, 0x78, 0x6f, 0xc5, 0xb1, 0xee, 0x16, 0xb1, 0xf5, 0x4d, 0x98, 0x1d, 0x86, 0xf, 0xb, 0x6a, 0x7d, 0xd4, 0x68, 0xc, 0xa8, 0xb, 0xad, 0xd9, 0xde, 0x1e, 0x3e, 0x8a, 0xcf, 0x4f, 0xf2, 0xe3, 0xc8, 0x36, 0x6b, 0x52, 0x7b, 0x2c, 0xea, 0xad, 0x1f, 0x6f, 0x65, 0x9, 0x73, 0xec, 0x31, 0x9d, 0x58, 0x1e, 0x8b, 0x7, 0xdc, 0xb0, 0xb3, 0x66, 0xea, 0x27, 0xf4, 0x96, 0x5f, 0xff, 0x51, 0xc6, 0xeb, 0x74, 0x5e, 0xeb, 0x88, 0x89, 0x47, 0xd1, 0xf8, 0x49, 0x77, 0x72, 0x86, 0xfb, 0xbb, 0xc2, 0x9f, 0xbc, 0x58, 0x5b, 0xbb, 0x5, 0x5b, 0x48, 0xa3, 0x9b, 0xc, 0xcd, 0xf3, 0x2d, 0x79, 0xeb, 0x80, 0xd, 0x16, 0x32, 0x36, 0x6, 0x8f, 0xf5, 0x82, 0x1f, 0x4a, 0xa, 0x52, 0x6, 0xfe, 0xd3, 0xb5, 0xeb, 0x7e, 0x6e, 0x9c, 0xb9, 0xf5, 0xab, 0x93, 0x0, 0x8d, 0xbc, 0x35, 0x48, 0x8f, 0xb6, 0xe6, 0x1e, 0xd3, 0x5d, 0xb7, 0xe4, 0x1d, 0xdc, 0x7d, 0xc3, 0x56, 0x7c, 0xc7, 0x83, 0xd7, 0xd, 0xf9, 0xcf, 0xc, 0x3f, 0xa7, 0xad, 0xad, 0x78, 0xf1, 0xbd, 0x5c, 0x26, 0x18, 0xb4, 0xa4, 0x42, 0xac, 0xd, 0x5c, 0x7b, 0x93, 0x7d, 0xb0, 0xf0, 0xce, 0xcc, 0xf7, 0x28, 0x27, 0xfa, 0x67, 0xa9, 0x75, 0x6a, 0x5, 0x9a, 0x7, 0xbf, 0xef, 0x99, 0x13, 0x1c, 0xc1, 0x88, 0x38, 0x9a, 0x66, 0xd, 0xe3, 0x5b, 0x30, 0xfc, 0xb1, 0x29, 0x35, 0x4f, 0x40, 0xe3, 0xcf, 0xf6, 0xb0, 0x1e, 0x43, 0x7, 0xfe, 0x57, 0x74, 0xc7, 0xf, 0x3e, 0x3c, 0x13, 0xcf, 0xd7, 0x35, 0xa2, 0xa0, 0x6b, 0xa5, 0xfc, 0x32, 0xa7, 0x52, 0xf4, 0x68, 0xe5, 0xa7, 0x8d, 0xc5, 0x56, 0x27, 0x8e, 0xf6, 0x49, 0xf5, 0xce, 0x83, 0xad, 0x3c, 0x81, 0x0, 0x6c, 0x27, 0xa, 0xdc, 0x20, 0x6c, 0xef, 0x4a, 0x6e, 0x9b, 0x1c, 0xfc, 0x80, 0x36, 0x57, 0xa2, 0x9f, 0x81, 0x1e, 0xef, 0x5b, 0x9e, 0xbe, 0xfe, 0x5b, 0xc8, 0x2f, 0x5d, 0x0, 0x1e, 0x58, 0xff, 0x49, 0x89, 0xd4, 0x4e, 0xfc, 0x47, 0x26, 0xf6, 0x79, 0xfe, 0xff, 0x43, 0xca, 0x1e, 0xff, 0x9f, 0xb8, 0x2a, 0xe5, 0xd, 0x56, 0x7c, 0xae, 0xb2, 0xf6, 0x99, 0x63, 0x61, 0x27, 0x79, 0xa7, 0x67, 0x7f, 0x59, 0xd0, 0x9c, 0x22, 0x6b, 0xc3, 0x2c, 0xa3, 0xfe, 0x99, 0xf5, 0xa9, 0x2b, 0xae, 0x3, 0xab, 0xc5, 0x9f, 0x3d, 0xd4, 0x7f, 0xcb, 0xb2, 0x23, 0xff, 0xdc, 0x3d, 0xe6, 0x24, 0x6c, 0x7a, 0x45, 0xcc, 0x97, 0xbf, 0x1c, 0xf2, 0xff, 0xa7, 0xb7, 0xfd, 0x3f, 0xb1, 0xb4, 0xf4, 0xe9, 0xff, 0xf9, 0x98, 0x72, 0xc0, 0x37, 0xfa, 0xec, 0xf0, 0xb0, 0xa7, 0xee, 0x54, 0xbe, 0x89, 0x2b, 0x48, 0x60, 0x72, 0x9, 0x70, 0x6c, 0x8, 0xc6, 0xb1, 0x6e, 0xa, 0x55, 0x2f, 0x63, 0x19, 0x70, 0xf2, 0xbc, 0xb9, 0x69, 0xa9, 0x1d, 0xc5, 0xc3, 0x77, 0x13, 0x8c, 0x29, 0xbd, 0x53, 0xbd, 0x67, 0xc4, 0xf0, 0x5d, 0x48, 0xe7, 0x18, 0x31, 0xdc, 0xd2, 0x79, 0xd2, 0x6e, 0xee, 0x7a, 0x17, 0xd7, 0x2d, 0x5f, 0x40, 0xdb, 0xd6, 0xc6, 0xc8, 0x4, 0x64, 0x2, 0x90, 0x8a, 0x34, 0xa4, 0x5b, 0x14, 0xcc, 0x91, 0x61, 0x79, 0x47, 0x11, 0xa1, 0x3c, 0x43, 0x1b, 0x0, 0xe2, 0x63, 0x0, 0x5b, 0x57, 0xf1, 0x1c, 0xa9, 0xfc, 0xc6, 0x3d, 0x91, 0xf, 0x1a, 0xfc, 0x8a, 0x22, 0xd3, 0x8, 0xf0, 0xd2, 0x6d, 0xd0, 0xaf, 0xfc, 0x9c, 0x30, 0xc7, 0xc0, 0xf3, 0x64, 0x11, 0x53, 0x41, 0x26, 0xbf, 0x3b, 0x11, 0x2e, 0x10, 0x30, 0x4c, 0xb4, 0x60, 0x43, 0xa7, 0x23, 0xa4, 0xa8, 0x88, 0x52, 0xc0, 0xd3, 0x47, 0x59, 0x26, 0x36, 0xa8, 0x38, 0x53, 0x8c, 0xbc, 0x31, 0x89, 0xf8, 0x8, 0xe0, 0xe8, 0x9c, 0x9c, 0x64, 0xe9, 0x44, 0x36, 0x19, 0x72, 0x6d, 0x6d, 0x3, 0xfb, 0xb3, 0x4e, 0x17, 0xba, 0x75, 0xe7, 0x30, 0x20, 0xd7, 0xe7, 0x1b, 0xb, 0xef, 0x3c, 0x48, 0x4b, 0xe9, 0xf5, 0xf2, 0x7a, 0x86, 0xa0, 0x6a, 0xcd, 0xbc, 0x17, 0xb1, 0x90, 0xf, 0x4d, 0x19, 0x41, 0x45, 0xc5, 0x3a, 0x2, 0x63, 0x34, 0x21, 0x26, 0x2, 0x50, 0xe7, 0x98, 0x1d, 0x63, 0x52, 0x5c, 0x15, 0x6b, 0x22, 0x6a, 0x10, 0x5d, 0x11, 0xcb, 0x19, 0x8, 0x52, 0x52, 0x62, 0xfd, 0x9, 0xdd, 0x2b, 0xd4, 0x12, 0x92, 0x44, 0x9d, 0xa7, 0x36, 0x4f, 0x1f, 0x66, 0xae, 0x89, 0xe5, 0xe7, 0xa, 0x16, 0x50, 0xcd, 0x83, 0x58, 0x7c, 0x16, 0xf2, 0x16, 0x70, 0x16, 0x9e, 0x60, 0x7f, 0x35, 0x68, 0xb1, 0xaf, 0x6b, 0xd1, 0x3c, 0x48, 0xf8, 0x8c, 0x5a, 0x5e, 0x6f, 0xb5, 0x46, 0x11, 0x9f, 0xad, 0xfb, 0x65, 0x59, 0x86, 0x9f, 0x51, 0x90, 0xae, 0x18, 0x4, 0x33, 0xce, 0x7c, 0x59, 0x14, 0x94, 0x20, 0xc9, 0xb9, 0x5c, 0x37, 0xea, 0xa4, 0x9d, 0x7e, 0xdf, 0xdd, 0x85, 0x47, 0xf5, 0xbf, 0x62, 0xa8, 0x6f, 0x31, 0x1, 0x1c, 0xd0, 0xff, 0xf1, 0x0, 0xfd, 0x1f, 0xff, 0xcc, 0xff, 0xf4, 0x31, 0xe5, 0x19, 0xfa, 0x1f, 0xdd, 0x5b, 0x48, 0xa7, 0x9c, 0x35, 0xc5, 0x26, 0x81, 0x3b, 0x1f, 0x94, 0xbd, 0xbc, 0x50, 0x7f, 0x8f, 0x9, 0x61, 0xdf, 0xae, 0xc0, 0xfb, 0x79, 0xf5, 0xdd, 0x1e, 0x7c, 0x84, 0x3f, 0xdf, 0x6d, 0xeb, 0xa9, 0xee, 0xfa, 0x75, 0x7d, 0xb0, 0x76, 0xd4, 0xef, 0x84, 0x5e, 0x1d, 0xf0, 0xd0, 0xfb, 0x2b, 0x9, 0xdf, 0xfc, 0x7e, 0xd7, 0xfc, 0x1e, 0xb0, 0xd, 0x9f, 0xfc, 0x5a, 0xe7, 0x9a, 0x53, 0x9a, 0x7, 0xbf, 0x1f, 0x85, 0xbd, 0x6b, 0x7b, 0x2d, 0x95, 0x1e, 0x7d, 0x7, 0x47, 0x8e, 0xc7, 0x87, 0xfd, 0xc9, 0x5d, 0xa1, 0x8e, 0x5e, 0x5a, 0xcf, 0xf1, 0x47, 0x7f, 0x3c, 0xcd, 0xaf, 0x2f, 0x68, 0x38, 0xe8, 0xd1, 0x3f, 0xe4, 0x2f, 0xde, 0x98, 0xd0, 0x1e, 0xf7, 0x17, 0x3b, 0xc6, 0xf3, 0x21, 0x4f, 0xf1, 0x4e, 0x8f, 0x82, 0xdc, 0xbb, 0xbb, 0xaf, 0xb7, 0xdc, 0xbc, 0x8f, 0x36, 0xea, 0xed, 0x49, 0xf8, 0x67, 0xa9, 0xa7, 0xaf, 0xe4, 0x7c, 0x17, 0x1f, 0x38, 0xdf, 0x7a, 0xdb, 0xc7, 0xea, 0x79, 0x59, 0x83, 0xbb, 0x1, 0x3c, 0x4f, 0xeb, 0xf6, 0xfb, 0x1d, 0xef, 0xa4, 0xf8, 0x4c, 0xef, 0xef, 0x6b, 0x15, 0xed, 0xbc, 0x9f, 0x97, 0x55, 0xe0, 0x7f, 0x43, 0xff, 0xea, 0x4f, 0x9c, 0xff, 0x5f, 0xe1, 0xf3, 0xf1, 0x97, 0x43, 0xfe, 0x9f, 0x74, 0x3a, 0xbd, 0x33, 0xff, 0xa7, 0x3e, 0xcf, 0xff, 0x7c, 0x48, 0x39, 0x30, 0xff, 0x7f, 0x11, 0x7a, 0x85, 0x5f, 0xc3, 0x2, 0x30, 0xd5, 0x7f, 0xb1, 0x36, 0x92, 0xf6, 0x82, 0x15, 0xb2, 0x22, 0x40, 0x64, 0x27, 0x92, 0x79, 0x62, 0x24, 0xa, 0x20, 0xf0, 0x6e, 0xea, 0x5, 0x3c, 0xc7, 0xa9, 0x5, 0xb0, 0xc5, 0xd3, 0xd7, 0x62, 0x5d, 0x5c, 0x83, 0xaf, 0x93, 0x65, 0x24, 0xb4, 0x37, 0x9d, 0xf0, 0xc6, 0xfd, 0x3b, 0x51, 0x9e, 0x56, 0x97, 0x46, 0x13, 0xf1, 0x94, 0x14, 0xbc, 0x18, 0x7d, 0x9d, 0xaf, 0xca, 0xd3, 0x70, 0x2f, 0xb0, 0x3d, 0x1e, 0x71, 0x6d, 0x39, 0x93, 0xc5, 0x9b, 0x79, 0xb1, 0xc4, 0x1c, 0xfe, 0x1e, 0x9a, 0x60, 0x47, 0xfe, 0xdd, 0x15, 0xa1, 0xf7, 0x87, 0x13, 0xf9, 0xf9, 0xa, 0x45, 0x70, 0x48, 0xfe, 0x93, 0x49, 0x69, 0xdb, 0xff, 0x2b, 0xa5, 0xa5, 0x4f, 0xf9, 0xff, 0x88, 0xf2, 0xcc, 0x33, 0xf9, 0x6f, 0x1f, 0x2e, 0xea, 0x36, 0xf7, 0x42, 0x21, 0xc, 0xc, 0x8, 0x75, 0xe3, 0x28, 0x5f, 0x1c, 0xcf, 0xf7, 0x3f, 0x67, 0xcf, 0xe9, 0x29, 0xf2, 0xff, 0xf2, 0xc8, 0x3f, 0x51, 0xe, 0xc8, 0x7f, 0x32, 0x9e, 0x8a, 0x6d, 0xcd, 0xff, 0x99, 0xf8, 0xe7, 0xf9, 0xdf, 0x8f, 0x29, 0xaf, 0x90, 0xff, 0x77, 0x9, 0x17, 0x7c, 0xa5, 0x3a, 0xd8, 0x9a, 0x37, 0xbd, 0x3c, 0x5a, 0x6f, 0x14, 0x28, 0xe8, 0xa7, 0xee, 0x3d, 0xfd, 0x9, 0xbe, 0xfc, 0x5f, 0x1f, 0xe1, 0x52, 0xd8, 0xb8, 0x85, 0xf0, 0x69, 0x5e, 0x85, 0xd, 0x10, 0x10, 0xe8, 0x58, 0xd8, 0xcd, 0xa8, 0x74, 0xc0, 0xc9, 0xb0, 0xf, 0xe0, 0x19, 0xe, 0x87, 0x7d, 0x28, 0x82, 0x9d, 0xf, 0x8f, 0xfa, 0xd, 0x76, 0x51, 0x3d, 0x3f, 0x2a, 0xf0, 0x91, 0xe0, 0xbd, 0x97, 0xa, 0x85, 0xdf, 0x9, 0xb0, 0xcf, 0x1, 0xe1, 0xa9, 0x6e, 0xf6, 0xc7, 0x1e, 0xf, 0xc4, 0x2, 0x9a, 0x51, 0x15, 0x8f, 0xa3, 0xeb, 0x93, 0x86, 0x51, 0x5f, 0xed, 0xe7, 0x6, 0x8f, 0xed, 0x71, 0x4a, 0x1c, 0x6d, 0x50, 0x72, 0xf4, 0x94, 0x40, 0xb2, 0xf0, 0xbe, 0x85, 0xf5, 0xee, 0xf7, 0x78, 0x7f, 0x67, 0xc1, 0x6e, 0x9b, 0xef, 0xe7, 0x38, 0xd8, 0x6d, 0xeb, 0x1f, 0x11, 0xa4, 0x15, 0xfc, 0x89, 0x5f, 0xa8, 0x97, 0x9e, 0x14, 0xda, 0xb5, 0x3b, 0x50, 0x6e, 0x14, 0x11, 0x4f, 0x13, 0xff, 0x9c, 0xf0, 0xae, 0x3, 0xa8, 0x9e, 0x15, 0xe2, 0xf5, 0xc, 0x5c, 0xef, 0x13, 0xe6, 0xf5, 0xbc, 0xce, 0x7c, 0x48, 0xa8, 0xd7, 0x21, 0x92, 0x1e, 0xd, 0xf7, 0xfa, 0xc, 0xfe, 0xfa, 0x80, 0xf2, 0x14, 0xfb, 0xff, 0xb5, 0x5e, 0xc0, 0x43, 0xeb, 0xff, 0x94, 0x24, 0x6d, 0xdb, 0xff, 0x89, 0xf4, 0x67, 0xfe, 0xf7, 0xf, 0x29, 0x6f, 0xb3, 0xfe, 0x7f, 0x8d, 0xb, 0xee, 0x95, 0x16, 0xff, 0x23, 0x5e, 0xb8, 0x54, 0x32, 0x11, 0x7f, 0x4b, 0x2f, 0x9c, 0x67, 0xf3, 0xfa, 0xf4, 0xd2, 0xcf, 0xfe, 0x7e, 0xaf, 0x2d, 0x3b, 0xf2, 0xef, 0x5c, 0x56, 0xe3, 0xfe, 0xfb, 0x1, 0xfe, 0xff, 0x44, 0x2c, 0xb1, 0x2d, 0xff, 0xe9, 0x64, 0xec, 0xd3, 0xff, 0xf7, 0x21, 0x65, 0xd3, 0xff, 0xef, 0x7e, 0x74, 0xd7, 0x22, 0x7c, 0x72, 0x8, 0x80, 0x73, 0xcd, 0xcc, 0xb, 0x55, 0x80, 0xd3, 0x6c, 0xd8, 0xc6, 0x4e, 0xda, 0xee, 0x17, 0xaf, 0xfe, 0xa1, 0xce, 0x10, 0xec, 0x5a, 0xc7, 0x5b, 0xfd, 0xf3, 0x55, 0xb, 0x54, 0x27, 0x62, 0x54, 0xfc, 0xab, 0x1f, 0x77, 0x7b, 0xda, 0x49, 0xbb, 0xe3, 0xd8, 0xff, 0x96, 0xea, 0xd8, 0xc6, 0xfc, 0xca, 0x1c, 0x57, 0xfb, 0x1c, 0x1d, 0x4a, 0x1, 0xe5, 0x56, 0x3a, 0x78, 0x8b, 0x8f, 0xa8, 0xfc, 0xdc, 0x3, 0x32, 0x4e, 0x2f, 0xfd, 0xa1, 0x43, 0x0, 0x98, 0xb6, 0x8a, 0x7c, 0xc4, 0xe6, 0x9f, 0x42, 0xa5, 0x3f, 0xbe, 0xc9, 0x80, 0xd6, 0xcc, 0xb7, 0xbe, 0x13, 0xbb, 0xba, 0x51, 0xcf, 0xfe, 0x1b, 0x43, 0x79, 0x8e, 0x74, 0x65, 0xf3, 0x5c, 0x8f, 0xcf, 0xbb, 0xf2, 0xa4, 0x40, 0xa8, 0x5d, 0xe0, 0xee, 0x3a, 0x52, 0xd8, 0x4f, 0xf7, 0x13, 0x7, 0xee, 0x9d, 0xe9, 0xdf, 0x93, 0x55, 0x6a, 0x5f, 0x2f, 0x92, 0xc9, 0x64, 0x22, 0x14, 0xa, 0x87, 0xc3, 0x1f, 0x29, 0x52, 0x5e, 0xaa, 0xe7, 0x9f, 0x26, 0x51, 0x7c, 0xa9, 0x38, 0xc5, 0xfa, 0xbd, 0x57, 0x77, 0x7d, 0x61, 0x68, 0x4, 0x93, 0xa8, 0x89, 0x96, 0x26, 0xb6, 0x50, 0x58, 0x44, 0x81, 0xe7, 0x41, 0xd4, 0x25, 0xd9, 0x30, 0xc9, 0xfd, 0x2a, 0xba, 0x10, 0xf7, 0x8f, 0xbf, 0x14, 0xf6, 0x83, 0xe4, 0xf9, 0x6f, 0x2c, 0xa2, 0xce, 0x0, 0x7e, 0x84, 0x90, 0x86, 0xd6, 0xdd, 0xf9, 0xd9, 0xf3, 0xda, 0x67, 0x79, 0x5a, 0xd9, 0x6b, 0xff, 0xbd, 0x7a, 0xd7, 0x77, 0x5d, 0xe, 0xd8, 0x7f, 0xb1, 0x58, 0x7c, 0xc7, 0xfe, 0x8b, 0xc7, 0x3f, 0xed, 0xbf, 0xf, 0x29, 0x4f, 0xd3, 0x8c, 0x9b, 0x56, 0xa2, 0x7b, 0x6d, 0x6d, 0x69, 0x7d, 0x89, 0xe, 0x75, 0x6a, 0xfd, 0x2f, 0x19, 0x82, 0xfc, 0x6f, 0x60, 0x8a, 0xf4, 0x52, 0xc1, 0x55, 0x21, 0x61, 0x19, 0x1e, 0x81, 0x44, 0x3a, 0x25, 0x79, 0x75, 0x90, 0x69, 0x39, 0xb5, 0x78, 0xb2, 0x67, 0x85, 0x27, 0xc2, 0xdb, 0xb9, 0xa1, 0x65, 0xfb, 0x6, 0x9d, 0xc8, 0x57, 0xa0, 0x63, 0x95, 0xff, 0xc7, 0xb1, 0xb1, 0xb6, 0xde, 0x65, 0x4b, 0xda, 0x53, 0xce, 0xaf, 0xd8, 0x91, 0xde, 0x9c, 0xa7, 0x2c, 0x95, 0x7a, 0x9b, 0xd3, 0x96, 0x4a, 0x23, 0xb2, 0x7b, 0x1, 0x95, 0x33, 0xa2, 0xe2, 0x5a, 0x5f, 0xf0, 0xc3, 0xbb, 0x95, 0x8b, 0x8f, 0x51, 0x84, 0x8f, 0x4b, 0x70, 0x86, 0x1e, 0x86, 0x65, 0xbe, 0x95, 0xf1, 0x48, 0x5c, 0x8, 0xbc, 0x83, 0x65, 0x7f, 0xa, 0x24, 0x71, 0x31, 0xe8, 0x66, 0xc4, 0x26, 0xdc, 0x26, 0x4, 0xee, 0x27, 0x63, 0x3d, 0x79, 0x7d, 0xea, 0xfd, 0x7f, 0x68, 0xd9, 0xd1, 0xff, 0xeb, 0x14, 0x51, 0xbe, 0x3f, 0x5f, 0x77, 0x12, 0xec, 0xa0, 0xfe, 0xdf, 0x3d, 0xff, 0x19, 0x4b, 0xc5, 0x3f, 0xf5, 0xff, 0x47, 0x94, 0x77, 0xc9, 0xff, 0xbd, 0x99, 0x6e, 0xf8, 0x5, 0x3a, 0x74, 0xfb, 0x48, 0x17, 0x3f, 0xd1, 0x75, 0xcc, 0x2d, 0x4f, 0xc3, 0x24, 0x16, 0x91, 0x89, 0xea, 0x9c, 0xb1, 0x11, 0x56, 0xb0, 0xb1, 0x99, 0xd2, 0x63, 0x49, 0xcc, 0x39, 0x32, 0xaf, 0xd, 0x42, 0x54, 0xd7, 0xd6, 0x15, 0x8f, 0xe8, 0xbe, 0x6c, 0x68, 0x1a, 0xbc, 0xbf, 0x10, 0x35, 0xd6, 0xb, 0x38, 0xd7, 0x5a, 0x6, 0x47, 0x26, 0x52, 0x30, 0x3d, 0xf2, 0xb6, 0x43, 0x14, 0x4c, 0x37, 0x70, 0xbb, 0xcf, 0x6c, 0x53, 0x75, 0x6c, 0xf6, 0xad, 0x8b, 0xfa, 0xc2, 0xfc, 0x7d, 0x58, 0x83, 0xd4, 0x42, 0xa6, 0x7f, 0xc7, 0x9b, 0x3f, 0x8f, 0x88, 0xe7, 0xe2, 0xbe, 0x2b, 0xff, 0xe1, 0x31, 0xef, 0x9c, 0x57, 0xde, 0x1d, 0xb0, 0xeb, 0x5b, 0x32, 0xbe, 0x76, 0x68, 0xbe, 0xf6, 0x5e, 0xb, 0x8, 0x95, 0x4c, 0xa7, 0xeb, 0xd3, 0x4c, 0xc2, 0xf8, 0x3f, 0x12, 0xdb, 0xbc, 0x64, 0xca, 0x65, 0xd9, 0xdb, 0x9c, 0x43, 0xb, 0xc4, 0x28, 0xad, 0xb7, 0x4f, 0x3a, 0xee, 0x33, 0x68, 0xca, 0x33, 0xbc, 0x40, 0xd7, 0x6, 0x32, 0x31, 0x51, 0xf2, 0x20, 0x96, 0x4, 0x5f, 0x14, 0xb8, 0x12, 0x47, 0xaa, 0xf8, 0x21, 0xda, 0x6b, 0x71, 0xd8, 0xeb, 0x59, 0x67, 0x9b, 0x36, 0x8e, 0x2, 0xff, 0x6c, 0x56, 0xff, 0x2c, 0x1, 0xe5, 0x89, 0xfa, 0xff, 0x55, 0x27, 0xc1, 0xe, 0xf9, 0x7f, 0xe3, 0xb1, 0x9d, 0xf8, 0xcf, 0x44, 0xfc, 0xf3, 0xfc, 0xef, 0x87, 0x94, 0xd7, 0x1f, 0xf0, 0xfa, 0x90, 0x9, 0x61, 0x6b, 0x27, 0x66, 0x8d, 0xef, 0x27, 0x1c, 0xf6, 0xf2, 0x35, 0xfe, 0x31, 0xe1, 0x59, 0x1b, 0xd, 0x3e, 0x35, 0x40, 0x6b, 0xb, 0x8, 0x4, 0x86, 0x68, 0xf9, 0x66, 0xc0, 0x3, 0xb1, 0x59, 0x3b, 0x35, 0x9f, 0x11, 0x94, 0xb5, 0x3, 0xfb, 0x82, 0x68, 0x2c, 0x1f, 0x8e, 0x97, 0x24, 0x67, 0x7b, 0x75, 0x6, 0xb5, 0x4d, 0xe, 0xf6, 0xda, 0x9, 0x4a, 0xa0, 0xe6, 0x5d, 0x26, 0xb0, 0xd1, 0x94, 0x93, 0xb3, 0x4c, 0x41, 0x63, 0x7b, 0x1a, 0x0, 0xe4, 0x64, 0x55, 0xf, 0x2, 0x59, 0xa7, 0x59, 0xf7, 0xde, 0xbe, 0x61, 0x72, 0x33, 0xbf, 0x91, 0x7f, 0xe8, 0xc4, 0x9a, 0x6f, 0x6e, 0x58, 0x5b, 0x66, 0x7e, 0x7f, 0xe0, 0xf6, 0xa9, 0x35, 0xdf, 0x5b, 0x5f, 0x7b, 0x61, 0x95, 0x4c, 0xe9, 0x63, 0x61, 0x69, 0x8e, 0xbd, 0xf2, 0x78, 0x6c, 0xd9, 0x7e, 0xc2, 0x5f, 0x79, 0xea, 0x6d, 0xf7, 0x53, 0xef, 0xa5, 0x7d, 0x27, 0x8e, 0x6d, 0x27, 0xaa, 0xcb, 0xc7, 0xb7, 0xef, 0x1f, 0xae, 0xe6, 0x6b, 0xec, 0xfd, 0xe2, 0xd4, 0x7c, 0x8d, 0xbc, 0x61, 0x80, 0xda, 0xcf, 0x9e, 0x7, 0xff, 0xa7, 0x96, 0x27, 0xda, 0x7f, 0xaf, 0xc8, 0xfe, 0xfe, 0x84, 0xf8, 0x9f, 0x78, 0x66, 0x3b, 0xff, 0x7b, 0x3c, 0xfe, 0x79, 0xfe, 0xff, 0x43, 0xca, 0xdb, 0x7b, 0x4f, 0x5f, 0x6d, 0xeb, 0xbd, 0x61, 0x8e, 0xf7, 0x9f, 0x96, 0xd, 0xfd, 0x67, 0x7f, 0xd6, 0x27, 0x97, 0xa7, 0xca, 0xff, 0x6b, 0x62, 0x0, 0xf, 0xc9, 0x7f, 0x32, 0xb1, 0x2d, 0xff, 0x52, 0xe2, 0xd3, 0xff, 0xf7, 0x31, 0xe5, 0x1d, 0x2, 0xfa, 0x5e, 0xad, 0x0, 0x7e, 0x76, 0xce, 0x38, 0xdf, 0xd2, 0xe9, 0x67, 0x7f, 0x9e, 0x77, 0x2f, 0x3b, 0xf2, 0x2f, 0x82, 0x5a, 0x9c, 0x7f, 0xde, 0x26, 0x3, 0xdc, 0x21, 0xff, 0x4f, 0x2a, 0xbe, 0x73, 0xfe, 0x3f, 0x15, 0xff, 0x94, 0xff, 0xf, 0x29, 0x9b, 0x3b, 0xbb, 0x3b, 0x97, 0xec, 0xbd, 0x4b, 0x2, 0x38, 0xd1, 0xca, 0x9b, 0x38, 0x82, 0x1c, 0x54, 0x2e, 0x9, 0xfb, 0x76, 0xa0, 0x5d, 0x6e, 0xf6, 0x6d, 0x44, 0x3b, 0x95, 0x83, 0xb7, 0xa2, 0xd7, 0x57, 0x8e, 0xbb, 0xa0, 0xee, 0x8d, 0xe3, 0x3b, 0xbb, 0xd4, 0xbe, 0xbe, 0x7d, 0xd, 0xde, 0xa0, 0x6, 0x60, 0x3, 0x47, 0xd8, 0xdd, 0x75, 0xfd, 0x11, 0xda, 0xbd, 0xde, 0x50, 0x54, 0x71, 0x76, 0x61, 0x83, 0xb6, 0x61, 0xc1, 0xce, 0x78, 0x6c, 0x20, 0x3f, 0x88, 0xf9, 0xf1, 0x9d, 0xe6, 0xc7, 0x30, 0xf3, 0xdd, 0xe6, 0xc7, 0x31, 0xef, 0xdb, 0x7d, 0xde, 0xc1, 0xbc, 0x91, 0xd4, 0x39, 0xe2, 0x1e, 0x41, 0xe3, 0x39, 0xe0, 0xdd, 0xd4, 0x7d, 0x7f, 0x3a, 0x8a, 0xfe, 0xc8, 0xd9, 0x3e, 0xc8, 0x7b, 0x4f, 0x80, 0xd8, 0x81, 0xb8, 0x86, 0x8a, 0xc2, 0x1e, 0x1f, 0x79, 0xb7, 0x38, 0xb2, 0xf2, 0xd7, 0x77, 0x17, 0xce, 0x32, 0x6d, 0x6a, 0xb9, 0x9b, 0x25, 0x9b, 0xe0, 0xcc, 0xc2, 0x3c, 0xe2, 0xfb, 0x3a, 0x1a, 0xb1, 0xd0, 0xd1, 0x77, 0x1f, 0x62, 0x42, 0x2d, 0xfe, 0x35, 0x9f, 0x36, 0xd5, 0x6d, 0x32, 0x87, 0xf, 0xf, 0xbf, 0x53, 0x2e, 0xf, 0x8e, 0x32, 0xd9, 0x5c, 0xce, 0xff, 0xdc, 0x52, 0xe9, 0xb5, 0xc, 0xaf, 0x27, 0x58, 0xe5, 0x4d, 0x44, 0xa2, 0x41, 0xac, 0xe1, 0x7, 0x98, 0xa3, 0xd5, 0x35, 0x54, 0xa7, 0xc4, 0xc4, 0xd6, 0x4c, 0x63, 0x20, 0x48, 0x56, 0x28, 0xdc, 0xed, 0xad, 0x4a, 0xa6, 0x53, 0xac, 0x4f, 0x37, 0xfb, 0xc9, 0x37, 0x7a, 0x18, 0x10, 0xf7, 0x3a, 0xed, 0x2, 0x39, 0x47, 0x76, 0x36, 0x81, 0x9c, 0x6d, 0xaf, 0x23, 0xdf, 0x2d, 0x84, 0x7e, 0x82, 0x94, 0xf1, 0xb5, 0x6d, 0xaa, 0xfb, 0xc6, 0x67, 0xcf, 0x4d, 0xef, 0xfe, 0xa4, 0x88, 0xeb, 0xe6, 0xf9, 0xd5, 0x3f, 0xfe, 0xb6, 0x7d, 0x9f, 0x46, 0x5c, 0x8, 0xfa, 0xdd, 0xff, 0xca, 0xc9, 0xcc, 0xb8, 0x5, 0xc1, 0x5f, 0x99, 0x8, 0xaa, 0x7c, 0x74, 0x7c, 0x97, 0xb0, 0xee, 0xd0, 0xe6, 0x46, 0xd0, 0xd, 0x7b, 0x4d, 0xe7, 0x36, 0xf2, 0xcd, 0xdb, 0x47, 0xbf, 0x6f, 0x63, 0x5d, 0xf3, 0xce, 0xa6, 0x1e, 0xd9, 0xad, 0xc9, 0xb3, 0x94, 0x98, 0xbe, 0x8a, 0x1c, 0x63, 0xd8, 0x79, 0xbc, 0x53, 0xdd, 0x24, 0xc4, 0x62, 0xc2, 0x31, 0xb6, 0x75, 0x45, 0x30, 0x42, 0x94, 0x3d, 0xe2, 0xdf, 0xde, 0x57, 0xd7, 0xf5, 0xa8, 0x88, 0x7f, 0xb9, 0xdc, 0x38, 0x6c, 0xf2, 0x52, 0xb9, 0x99, 0x9a, 0x86, 0xbc, 0x96, 0x9b, 0x40, 0xe6, 0x44, 0xa6, 0xb5, 0x8f, 0x3d, 0xb7, 0x79, 0x93, 0xd5, 0x67, 0xfc, 0xb9, 0xaf, 0xfa, 0x1c, 0xad, 0xfe, 0x31, 0x8c, 0xea, 0xa8, 0x77, 0xce, 0xa8, 0x7e, 0x24, 0x42, 0x97, 0x5d, 0x43, 0x15, 0x43, 0x2a, 0x8, 0xe4, 0xf, 0xc4, 0xef, 0xad, 0xef, 0xf3, 0x4f, 0xbc, 0x7, 0xea, 0x80, 0xfd, 0xe7, 0xa4, 0x69, 0x7f, 0xd7, 0xfc, 0xf, 0x89, 0x78, 0x7c, 0x27, 0xff, 0x43, 0x32, 0x1e, 0xfb, 0xb4, 0xff, 0x3e, 0xa2, 0x3c, 0xc7, 0xfe, 0x7b, 0xb7, 0x4, 0x90, 0x1b, 0xcc, 0xf6, 0x86, 0x76, 0x61, 0xf8, 0xc0, 0x35, 0x51, 0xef, 0xb7, 0x47, 0xb8, 0xdd, 0xa3, 0x8f, 0xd8, 0x26, 0xdc, 0x6e, 0xf3, 0xa9, 0x3b, 0x85, 0xbb, 0x70, 0x20, 0x70, 0xb3, 0xd0, 0x35, 0xf7, 0x9e, 0x74, 0xa5, 0x53, 0x50, 0xe5, 0x67, 0x6c, 0x19, 0x6, 0x81, 0xbf, 0x60, 0xd7, 0x70, 0x13, 0xcd, 0x53, 0x36, 0xe, 0x77, 0x37, 0xc8, 0x5a, 0xf5, 0xaa, 0xb8, 0x0, 0xb9, 0xbf, 0x7d, 0x11, 0x91, 0xb3, 0x3f, 0xa7, 0xe1, 0xa9, 0xb3, 0xdf, 0x11, 0x75, 0xee, 0x4d, 0x58, 0xe7, 0x54, 0xd8, 0xc1, 0x56, 0x2e, 0x5e, 0xf, 0x7b, 0xcd, 0x5d, 0x24, 0xcf, 0x34, 0xa6, 0x9e, 0xb8, 0xb9, 0xe7, 0xae, 0x7b, 0xe, 0x6d, 0xec, 0x79, 0x2b, 0xb2, 0x4d, 0x70, 0x66, 0x94, 0x84, 0xe5, 0x75, 0x94, 0xef, 0x1e, 0xc, 0xae, 0xed, 0x12, 0xb4, 0x13, 0xb8, 0xf5, 0x6e, 0xcf, 0x5e, 0x5e, 0x30, 0x9d, 0xaf, 0xdc, 0xc7, 0x5b, 0x1f, 0x25, 0x7b, 0x52, 0x9f, 0x5e, 0x7f, 0x31, 0x91, 0xff, 0xac, 0x4d, 0xc0, 0xc6, 0x99, 0xc3, 0x8c, 0xef, 0xbf, 0xd, 0xe8, 0x34, 0xf4, 0x7e, 0x5b, 0x80, 0x4e, 0x3, 0xff, 0x98, 0x24, 0x97, 0x9f, 0x65, 0x6f, 0x39, 0x64, 0xff, 0x9, 0x4b, 0xfe, 0x5d, 0xed, 0xbf, 0x78, 0x72, 0x3b, 0xfe, 0x2b, 0x96, 0x49, 0x4a, 0x9f, 0xf6, 0xdf, 0x87, 0x94, 0xbf, 0x95, 0xfd, 0xc7, 0x99, 0xed, 0x4d, 0xed, 0x3f, 0x81, 0xf1, 0xe7, 0xd9, 0x7f, 0x6e, 0x8f, 0x3e, 0xd2, 0xfe, 0x73, 0xdb, 0x7c, 0xae, 0xfd, 0xb7, 0x86, 0x3, 0x8f, 0xda, 0x7f, 0x42, 0x25, 0x3c, 0xd1, 0xfe, 0xf3, 0x57, 0x7e, 0x81, 0xfd, 0xe7, 0x7, 0x7f, 0x85, 0xfd, 0x27, 0xd0, 0x7c, 0x80, 0xfd, 0xc7, 0x1b, 0x7a, 0x2f, 0xfb, 0xcf, 0xe7, 0xa3, 0xd8, 0xc1, 0xdb, 0xee, 0xc, 0xa, 0xbd, 0xd1, 0x75, 0xbf, 0x5e, 0x6d, 0x57, 0x7a, 0xd7, 0xe5, 0xca, 0x49, 0x61, 0xd8, 0x1c, 0x14, 0x9a, 0xf5, 0x42, 0x3f, 0xb0, 0xad, 0xe0, 0x1, 0x42, 0xfa, 0x22, 0xb2, 0x1f, 0xd1, 0x3b, 0x9b, 0x9d, 0x3f, 0xc3, 0x34, 0xfc, 0x34, 0x3b, 0xe, 0xcd, 0xff, 0x6f, 0x91, 0x1, 0xfc, 0xc0, 0xfc, 0x1f, 0x8b, 0x49, 0xdb, 0xfe, 0x9f, 0x74, 0xe6, 0x73, 0xfe, 0xff, 0x98, 0xf2, 0xa2, 0xfd, 0xbf, 0xfd, 0xf1, 0x1, 0xcf, 0x4b, 0x93, 0xf0, 0x16, 0xb1, 0x1, 0x6e, 0x64, 0x0, 0xcf, 0xae, 0xf0, 0x86, 0x41, 0x0, 0x9b, 0x5e, 0x91, 0x9d, 0xb4, 0xd, 0x2f, 0x1c, 0x8b, 0xb7, 0x37, 0x7c, 0xf6, 0xe, 0x48, 0x26, 0x9b, 0xcb, 0xbd, 0xc7, 0x80, 0x8, 0x5a, 0x3f, 0xf, 0x7c, 0xfe, 0x7b, 0x94, 0x1d, 0xfd, 0xef, 0x3a, 0x31, 0xbc, 0x3f, 0x5e, 0x1f, 0x3, 0x72, 0x70, 0xfd, 0x97, 0xce, 0xec, 0xac, 0xff, 0x62, 0xa9, 0x4f, 0xfd, 0xff, 0x11, 0xe5, 0x5d, 0xce, 0x7f, 0xbe, 0x32, 0xe9, 0xcc, 0x23, 0xa7, 0x3f, 0x17, 0x2e, 0xad, 0x52, 0x24, 0xc6, 0x1f, 0xa8, 0x64, 0xea, 0xad, 0xa2, 0xc4, 0xb1, 0xc6, 0x8d, 0xe3, 0x95, 0x82, 0x8e, 0x88, 0x4a, 0xa6, 0x4d, 0xf6, 0x76, 0x6d, 0xc2, 0x4e, 0x30, 0x52, 0x15, 0x9f, 0xe9, 0xea, 0xec, 0xf5, 0xe6, 0x81, 0xb, 0x23, 0x16, 0x50, 0x4e, 0x4e, 0x4b, 0xa7, 0xda, 0x56, 0xf6, 0x50, 0xf, 0x3d, 0x19, 0xdf, 0x22, 0xd9, 0xea, 0x8b, 0xca, 0x8e, 0x3b, 0x6b, 0x6b, 0x25, 0xb2, 0xa7, 0x6e, 0xd0, 0x32, 0x72, 0x37, 0xef, 0xe7, 0x4, 0xab, 0x88, 0xae, 0xa8, 0x85, 0x7c, 0x67, 0x5a, 0x4c, 0x42, 0x2c, 0x5, 0x9b, 0x5c, 0xb7, 0xaf, 0x7c, 0xf9, 0x7c, 0x37, 0xe8, 0xf, 0xc8, 0xe6, 0xc9, 0x6f, 0xf7, 0x5b, 0xa3, 0x51, 0xe1, 0xa, 0x99, 0x58, 0x9f, 0x90, 0x3c, 0xc0, 0xba, 0x86, 0x34, 0xe2, 0x41, 0x6a, 0x10, 0xeb, 0x16, 0xd2, 0xa1, 0x2e, 0xfb, 0xaa, 0xdb, 0x86, 0x4a, 0xa0, 0x62, 0xd8, 0xe6, 0x14, 0xeb, 0xd3, 0xcd, 0xab, 0xef, 0xb8, 0xc9, 0x90, 0x7, 0x13, 0xa8, 0x7a, 0x59, 0xa1, 0x15, 0xa4, 0x22, 0xcb, 0x7, 0xed, 0x55, 0xb2, 0x4c, 0x1b, 0x85, 0x1e, 0x1b, 0x51, 0x7e, 0x30, 0xd6, 0x25, 0x9a, 0xff, 0x8, 0x5a, 0xde, 0x6d, 0x55, 0xdf, 0x1d, 0xcc, 0xad, 0xde, 0xfb, 0x93, 0xe5, 0x40, 0x45, 0x31, 0xf3, 0x20, 0x9f, 0x92, 0x24, 0x37, 0xa4, 0xf0, 0xb, 0xa0, 0xc8, 0x2, 0xb, 0xc, 0xd9, 0x3a, 0x10, 0x9b, 0x44, 0xd7, 0x18, 0xae, 0x5, 0x34, 0x31, 0xa3, 0xda, 0x57, 0x89, 0xfb, 0x89, 0x81, 0xa1, 0x42, 0x19, 0xcd, 0x88, 0xaa, 0x78, 0x8b, 0x66, 0xbe, 0x79, 0xed, 0xbb, 0x4e, 0x90, 0xb7, 0xa0, 0x12, 0x19, 0xaa, 0x3c, 0x9d, 0x4f, 0x4a, 0x92, 0x4, 0xcf, 0x42, 0xdb, 0x9a, 0xe5, 0x3d, 0x77, 0xe9, 0x1c, 0xe9, 0xbe, 0x9b, 0xa, 0x79, 0xc4, 0x40, 0x1e, 0x4, 0x84, 0x11, 0xf8, 0x56, 0xbc, 0x50, 0xd5, 0x5e, 0x19, 0xeb, 0xb0, 0xc1, 0x46, 0xeb, 0x50, 0x4, 0x67, 0x5d, 0xb6, 0x9e, 0x3, 0xb6, 0x3d, 0xfb, 0x9e, 0x98, 0x38, 0x4, 0x7a, 0xdc, 0xc6, 0x6b, 0x6c, 0xdc, 0x5f, 0xe8, 0x76, 0xca, 0xbd, 0x14, 0x31, 0x60, 0x9d, 0x28, 0x90, 0xf8, 0xb8, 0x48, 0xc1, 0x34, 0x80, 0x8d, 0x38, 0xe3, 0x99, 0x6a, 0x1e, 0xbc, 0xfe, 0x52, 0x45, 0x1f, 0x46, 0xff, 0xcd, 0x91, 0x92, 0xb6, 0xf1, 0x66, 0x66, 0x22, 0xca, 0xbe, 0x6c, 0x1e, 0xa4, 0x7c, 0x8f, 0xc7, 0x50, 0x9e, 0x93, 0xc9, 0x24, 0xf, 0x62, 0xf4, 0xdf, 0x67, 0xb9, 0xf8, 0x84, 0xf9, 0xff, 0x67, 0xdc, 0xff, 0x12, 0xcf, 0x7c, 0xe6, 0xff, 0xf9, 0x90, 0xf2, 0xf6, 0xe7, 0x3f, 0x5e, 0x39, 0xf9, 0x7, 0x9e, 0xfe, 0x60, 0x82, 0xdf, 0x77, 0xf4, 0x6e, 0xd0, 0xfc, 0xbe, 0x7e, 0xbf, 0xe7, 0x8, 0x87, 0xab, 0xc8, 0x82, 0xc1, 0xd9, 0xdb, 0x52, 0x70, 0x8e, 0x9b, 0x9f, 0xfd, 0x7d, 0xde, 0xbb, 0x3c, 0x45, 0xfe, 0xdf, 0xf9, 0xfe, 0x97, 0x54, 0x3c, 0xb5, 0x1d, 0xff, 0x9d, 0x49, 0x26, 0x3f, 0xe3, 0xbf, 0x3f, 0xa4, 0xbc, 0xfb, 0x85, 0x2e, 0xaf, 0x4e, 0x40, 0xb9, 0xe1, 0x84, 0xf0, 0xec, 0x8d, 0x37, 0xba, 0xd0, 0xc5, 0x4f, 0xdd, 0x7b, 0xee, 0x7, 0xf9, 0xac, 0xf2, 0x8f, 0xd8, 0xa, 0xda, 0x5c, 0xc4, 0x3c, 0x69, 0x17, 0x68, 0x6b, 0xdd, 0x10, 0xb4, 0x1, 0xe4, 0xe9, 0xcb, 0x3, 0x7b, 0x3f, 0x5b, 0xf5, 0x9e, 0xb1, 0xed, 0xb3, 0x5, 0xf9, 0x82, 0x1d, 0x1f, 0xdf, 0x92, 0xe0, 0xc0, 0x66, 0x8f, 0x7b, 0xf3, 0x2c, 0xf7, 0xf0, 0x79, 0xd7, 0xcc, 0x7a, 0xea, 0x6f, 0xbd, 0xf8, 0xf4, 0x5d, 0x34, 0xbb, 0xe7, 0xc4, 0x7f, 0xaf, 0x52, 0xad, 0xf7, 0x7, 0xbd, 0xd1, 0x75, 0x6d, 0x30, 0xe8, 0x3a, 0x67, 0x5, 0x7d, 0xd5, 0x9c, 0x8d, 0x97, 0xed, 0xab, 0x61, 0x0, 0xf0, 0x1d, 0x6b, 0xc, 0xb8, 0x1f, 0xe6, 0xa5, 0x52, 0xb6, 0x59, 0x78, 0xd8, 0xfe, 0x7a, 0x7e, 0xf4, 0xde, 0x1f, 0x4a, 0x21, 0xe0, 0x5b, 0x18, 0x5, 0xbf, 0x8d, 0xed, 0xd9, 0xc, 0x72, 0x96, 0x74, 0x50, 0x57, 0xc0, 0xaf, 0xfe, 0xbc, 0x82, 0xc1, 0x2b, 0xe0, 0xaf, 0x4f, 0xbb, 0x5f, 0x66, 0x97, 0x4f, 0x9f, 0x72, 0xb7, 0x8d, 0xc7, 0xd3, 0x3b, 0xab, 0xe0, 0x0, 0x7c, 0x4f, 0xc, 0x76, 0x7a, 0x7c, 0x7d, 0xb4, 0x3f, 0xf2, 0x29, 0xa0, 0xc5, 0x83, 0xfb, 0x63, 0x1, 0xcc, 0x18, 0xd4, 0xd2, 0xce, 0xdb, 0x3d, 0x5b, 0x69, 0x87, 0xbb, 0xfa, 0xd6, 0x31, 0x50, 0x87, 0x3b, 0xfd, 0xca, 0x8d, 0xbc, 0x3, 0x21, 0x57, 0x1e, 0xcf, 0xbd, 0x7f, 0xd0, 0x95, 0xd7, 0xd4, 0xfb, 0x85, 0x5d, 0x79, 0x4d, 0xbc, 0xc7, 0xc5, 0x40, 0x6f, 0x27, 0xaf, 0xcf, 0xbd, 0x41, 0x28, 0x48, 0xa2, 0xdf, 0xf3, 0xfe, 0x20, 0xaf, 0x63, 0x2f, 0xbe, 0x36, 0x28, 0x18, 0xc3, 0xb3, 0x6e, 0xb, 0x3a, 0x8c, 0xe2, 0x7d, 0x2e, 0x9, 0x7a, 0x12, 0xe9, 0x1f, 0x72, 0x37, 0xd0, 0x1e, 0x4a, 0x1e, 0xbd, 0x12, 0xe8, 0x67, 0x5b, 0xcb, 0xff, 0x7e, 0xe5, 0x29, 0xeb, 0xbf, 0xf7, 0xbe, 0xff, 0x27, 0x29, 0xa5, 0xb6, 0xd7, 0x7f, 0xa9, 0xcc, 0x67, 0xfe, 0x8f, 0xf, 0x29, 0xef, 0x70, 0xfe, 0xff, 0x95, 0x2b, 0xbe, 0xc7, 0x2e, 0xf4, 0x11, 0x36, 0xe9, 0x9b, 0x6d, 0x69, 0xbb, 0x94, 0xfe, 0xec, 0x8f, 0xf0, 0x13, 0xcb, 0x8e, 0xfc, 0xdb, 0x38, 0x6a, 0xe3, 0xb7, 0x39, 0xf7, 0xef, 0x96, 0xc3, 0xf2, 0xbf, 0x1d, 0xff, 0x93, 0xcc, 0xc4, 0x3e, 0xe3, 0x7f, 0x3e, 0xa4, 0xbc, 0xcb, 0xfe, 0xaf, 0x8d, 0x5f, 0xb9, 0xf3, 0xb, 0xd, 0x23, 0xc2, 0x56, 0x6, 0xde, 0xbe, 0x2f, 0x34, 0xc, 0x86, 0x1f, 0xfc, 0x6, 0x6a, 0xeb, 0x3d, 0x23, 0xd3, 0xd6, 0x35, 0x66, 0xaf, 0xfd, 0x6, 0xc, 0x93, 0x28, 0xfc, 0x91, 0x30, 0x44, 0xa7, 0xf, 0xd8, 0x0, 0xbf, 0x89, 0x5d, 0x46, 0xfe, 0xf8, 0x77, 0x56, 0xe1, 0xf, 0x6f, 0xf, 0x90, 0x67, 0xda, 0xfd, 0x4d, 0xe4, 0xd, 0xf9, 0xd9, 0x1f, 0xe0, 0x27, 0x97, 0x3d, 0xf2, 0xff, 0xaa, 0x7c, 0xaf, 0xdb, 0xe5, 0x90, 0xff, 0x37, 0x95, 0x8c, 0x6f, 0xc9, 0x7f, 0x2a, 0x26, 0x7d, 0xde, 0xff, 0xfd, 0x21, 0xe5, 0x63, 0xf2, 0xbf, 0xbe, 0x58, 0x21, 0x6c, 0x4d, 0xd8, 0x36, 0xde, 0xe3, 0xf8, 0x7d, 0xb1, 0x53, 0x75, 0x9f, 0xb9, 0xb0, 0xcf, 0xb3, 0xea, 0xdc, 0x7f, 0xf3, 0x34, 0x9f, 0xaa, 0xef, 0xb2, 0x9c, 0x5d, 0x6f, 0xaa, 0x8d, 0xf, 0xf9, 0x51, 0xbd, 0x1a, 0xcf, 0xf0, 0xa0, 0x7a, 0x30, 0xc1, 0xbe, 0xd3, 0x3d, 0xe, 0x4c, 0x2f, 0xc3, 0xd9, 0xc7, 0x7a, 0x2d, 0x5, 0x5b, 0x6c, 0x16, 0xee, 0xaf, 0xa4, 0x9b, 0xbe, 0xca, 0x35, 0xa1, 0x3b, 0x9, 0xdd, 0xfe, 0x26, 0x4, 0xaf, 0x53, 0x46, 0xf5, 0xf7, 0x91, 0xfe, 0xbc, 0xc, 0xaf, 0xaf, 0x4c, 0x27, 0x5b, 0xea, 0xb4, 0x4f, 0xea, 0xd5, 0xed, 0x3, 0xb, 0x1e, 0x18, 0xf7, 0x2e, 0xda, 0x38, 0xea, 0xce, 0xb3, 0x1, 0x18, 0x2a, 0xed, 0x42, 0xb1, 0x59, 0xb9, 0xae, 0x15, 0x7a, 0xc5, 0x4e, 0xef, 0xba, 0x5f, 0x2a, 0xb4, 0xaf, 0x3b, 0xed, 0xeb, 0xee, 0xb0, 0x1f, 0x88, 0xf0, 0x28, 0x76, 0x14, 0x80, 0xa3, 0x50, 0x6e, 0xd5, 0xdb, 0xec, 0x7b, 0x55, 0x7a, 0x5b, 0xa7, 0x1e, 0xd6, 0x80, 0x2f, 0x4c, 0x26, 0xef, 0xa2, 0x79, 0xc3, 0x4c, 0xb8, 0xcc, 0xec, 0x3d, 0xe4, 0x91, 0xd, 0x1e, 0x33, 0xcf, 0x13, 0xbb, 0xf3, 0xce, 0x87, 0xdb, 0x49, 0xa1, 0x19, 0x9e, 0xa3, 0xd5, 0xe3, 0xd, 0x6c, 0x56, 0xf0, 0x70, 0xfb, 0x1f, 0x7, 0xa0, 0x35, 0x4c, 0xbc, 0x80, 0x16, 0x3a, 0x8c, 0xde, 0xa9, 0x78, 0x3d, 0x47, 0xab, 0x88, 0x81, 0xb4, 0xa0, 0xa6, 0xf6, 0x55, 0xd9, 0xe7, 0x2a, 0xdc, 0xbe, 0x32, 0xe8, 0xeb, 0xde, 0xb, 0x25, 0x3, 0x1c, 0xf0, 0x32, 0xc, 0x2b, 0x64, 0xa9, 0xab, 0x4, 0x2a, 0x8f, 0x13, 0x2e, 0xc3, 0xa8, 0xc8, 0x8f, 0x13, 0xe8, 0x3, 0xdf, 0x7c, 0xf3, 0x88, 0xc3, 0xdf, 0xa0, 0xf2, 0xe3, 0xed, 0xf0, 0x8, 0xa9, 0xc7, 0x3d, 0xe9, 0xbb, 0xac, 0xf2, 0x4a, 0x3f, 0xf6, 0x5a, 0xb1, 0x1c, 0xe0, 0x98, 0xd7, 0x7b, 0xe8, 0x37, 0x75, 0x18, 0xb6, 0x90, 0x46, 0x37, 0x55, 0x62, 0xd8, 0xaf, 0x83, 0x1b, 0x1b, 0xec, 0x4, 0xbc, 0x9b, 0x25, 0xd6, 0x34, 0x3d, 0x91, 0x17, 0x3f, 0x8e, 0xf0, 0xfd, 0xc, 0xbe, 0x26, 0x3f, 0xb8, 0xce, 0x1b, 0xf2, 0xf7, 0x63, 0xdc, 0xfd, 0xfa, 0xa1, 0xf0, 0xdd, 0x50, 0x75, 0x78, 0x3c, 0x76, 0x84, 0x66, 0x3d, 0xc, 0x1b, 0xaf, 0x76, 0x64, 0x26, 0x48, 0x62, 0xe, 0x67, 0xbc, 0xb6, 0xf1, 0x7, 0xec, 0xb6, 0xd8, 0xf8, 0x1d, 0xf7, 0x59, 0x6c, 0xfc, 0x3f, 0x27, 0xb3, 0xf5, 0x9e, 0xf5, 0xdf, 0xab, 0xf2, 0x3d, 0x6f, 0x97, 0x43, 0xfe, 0x9f, 0x74, 0x6c, 0x3b, 0xfe, 0x27, 0x95, 0xc9, 0x7c, 0xc6, 0xff, 0x7c, 0x48, 0x79, 0xfb, 0xf8, 0xbf, 0x17, 0xaf, 0xf5, 0xde, 0x30, 0xef, 0x33, 0xdd, 0x8d, 0x15, 0x7c, 0x34, 0x5d, 0x73, 0x8, 0x6c, 0xcf, 0x8, 0xdb, 0xb0, 0xce, 0xeb, 0x6, 0x5a, 0x75, 0x91, 0xb6, 0x7, 0xc5, 0xf6, 0x22, 0xe0, 0xf9, 0xc9, 0xa6, 0x3f, 0xfe, 0xfb, 0xef, 0x93, 0xff, 0xb7, 0x38, 0xf7, 0xe9, 0x96, 0x83, 0xfe, 0xdf, 0xd8, 0xf6, 0xf9, 0x9f, 0x54, 0x2c, 0xf6, 0xe9, 0xff, 0xf9, 0x90, 0xf2, 0xe, 0xfb, 0x3f, 0x2f, 0x56, 0x0, 0x3f, 0x3b, 0xef, 0xb3, 0x8d, 0xff, 0xe6, 0x93, 0xf5, 0x3b, 0x14, 0x47, 0xfe, 0x23, 0x33, 0xa4, 0x6a, 0x78, 0xaa, 0x13, 0x13, 0xbd, 0x7d, 0x1b, 0x87, 0xe4, 0x3f, 0x15, 0xdb, 0xde, 0xff, 0x8d, 0xa7, 0x92, 0x9f, 0xf1, 0xff, 0x1f, 0x52, 0xbe, 0x80, 0x2e, 0xb4, 0x2c, 0x64, 0xea, 0x14, 0x58, 0x4, 0x8, 0xe, 0x0, 0xcb, 0x19, 0xd2, 0xc1, 0xd8, 0xc6, 0xaa, 0x82, 0xf5, 0x29, 0x30, 0xa0, 0x3c, 0x87, 0x53, 0x44, 0x23, 0xa1, 0x2f, 0x60, 0x30, 0xc3, 0x14, 0x50, 0x9b, 0xef, 0xa0, 0x50, 0x40, 0x67, 0x48, 0x55, 0xc1, 0x54, 0x25, 0x63, 0x11, 0x2e, 0x8b, 0xf5, 0xe9, 0x77, 0x60, 0x22, 0x15, 0x5a, 0x78, 0x81, 0xf8, 0x12, 0xc3, 0xf7, 0x1c, 0xea, 0x4a, 0xe8, 0xb, 0xd0, 0xd1, 0x94, 0x5b, 0xd0, 0xe0, 0x57, 0xc3, 0x44, 0x13, 0x7c, 0x8f, 0x14, 0x61, 0x7b, 0xff, 0xff, 0xbe, 0x46, 0x40, 0x47, 0x57, 0x57, 0x80, 0xe8, 0x1c, 0x92, 0x91, 0x4, 0xc, 0x64, 0x2, 0x15, 0xeb, 0x28, 0x12, 0x8a, 0x94, 0xfb, 0xd7, 0x7d, 0x8b, 0x98, 0x28, 0xf4, 0x5, 0x88, 0x3b, 0x81, 0xc1, 0x79, 0xa9, 0xf, 0x14, 0x6c, 0xd2, 0x50, 0x64, 0x8a, 0xad, 0x28, 0xff, 0xbf, 0x20, 0x3f, 0x14, 0x19, 0x3f, 0x98, 0x51, 0xfe, 0x7f, 0xf7, 0xc1, 0x6c, 0x1a, 0x65, 0xff, 0x73, 0x7f, 0xd2, 0x85, 0x1e, 0x5d, 0x23, 0x1a, 0x43, 0x79, 0x6e, 0x1b, 0xe2, 0x90, 0x5d, 0xe8, 0x5b, 0x84, 0x2e, 0x8d, 0xd0, 0xb7, 0xc8, 0x18, 0xce, 0x43, 0xdf, 0x22, 0x96, 0x66, 0x84, 0xbe, 0xfd, 0xbf, 0xd0, 0x17, 0x70, 0xe, 0x4d, 0x4c, 0x6c, 0xa, 0xea, 0xe5, 0xa, 0xd, 0x45, 0xc, 0x93, 0xdc, 0x22, 0xd9, 0xa, 0x45, 0xb0, 0x82, 0x60, 0x54, 0xd4, 0x33, 0xc9, 0xed, 0x3f, 0x51, 0x7b, 0x38, 0xf2, 0x5f, 0xea, 0xb4, 0x7, 0xbd, 0x7a, 0x71, 0x38, 0xa8, 0xb7, 0xab, 0x11, 0x4d, 0x79, 0xdb, 0x36, 0xe, 0xc8, 0x7f, 0x4c, 0xca, 0x6c, 0xcf, 0xff, 0xf1, 0x74, 0x32, 0xf1, 0x29, 0xff, 0x1f, 0x51, 0x98, 0x14, 0xe8, 0x96, 0x89, 0xc7, 0xb6, 0xc5, 0x64, 0xdd, 0x22, 0xa0, 0x86, 0x54, 0xd, 0x94, 0x66, 0xd0, 0xb4, 0xc0, 0x84, 0x98, 0xee, 0x7e, 0x6b, 0xa8, 0xcb, 0x6f, 0x34, 0x5, 0x13, 0xa2, 0xaa, 0x64, 0x9, 0x7e, 0x17, 0x8f, 0xb9, 0xb7, 0xd5, 0x3, 0x9e, 0xda, 0x58, 0x41, 0x7f, 0xfc, 0xea, 0x9e, 0x4b, 0x9c, 0x62, 0x6b, 0x66, 0x8f, 0x23, 0x32, 0xd1, 0xa2, 0xb, 0x6d, 0x9, 0x4d, 0x14, 0x75, 0x98, 0x6d, 0xac, 0x92, 0x71, 0x54, 0x5c, 0x7a, 0xba, 0xcd, 0x78, 0x5f, 0x19, 0x5, 0x2a, 0x82, 0xa6, 0xe, 0x66, 0x64, 0xc9, 0x7e, 0x68, 0x70, 0x8e, 0x80, 0x4c, 0x14, 0xe4, 0x6b, 0x8b, 0xe8, 0x91, 0x50, 0xc8, 0x47, 0x39, 0x32, 0x69, 0x28, 0x34, 0x98, 0x41, 0x7d, 0x4e, 0xc1, 0x2, 0x99, 0x2b, 0xa0, 0xd9, 0xf2, 0x8c, 0x1, 0x43, 0x55, 0x5d, 0x83, 0x21, 0x93, 0x82, 0xe5, 0x8c, 0x0, 0x6a, 0x8f, 0x35, 0x6c, 0x59, 0x48, 0x1, 0x86, 0xad, 0xaa, 0x5e, 0xbc, 0xda, 0xde, 0xbe, 0x47, 0x42, 0xa1, 0x30, 0xf8, 0xbd, 0xb, 0x6d, 0x15, 0x94, 0x1e, 0xa0, 0x39, 0x27, 0x4b, 0x3a, 0xc7, 0xe0, 0x5f, 0x6, 0xb4, 0x55, 0xf9, 0x1, 0x9a, 0x81, 0x1d, 0x76, 0x5f, 0x7e, 0x65, 0xa0, 0x4d, 0x5b, 0x86, 0xa0, 0xae, 0xeb, 0x44, 0x46, 0xba, 0x85, 0x41, 0xb, 0x9b, 0x26, 0x6, 0xff, 0x52, 0x6d, 0x19, 0x62, 0x2d, 0x10, 0x5a, 0xbc, 0xe2, 0xb0, 0x7d, 0xb, 0x2d, 0x90, 0xe, 0xa, 0xa6, 0x4e, 0x2c, 0xb, 0xfc, 0xab, 0x60, 0xca, 0x16, 0x96, 0xfb, 0x3a, 0x59, 0x6a, 0x50, 0xf, 0x84, 0xdd, 0xa8, 0xc1, 0x51, 0x14, 0x54, 0x74, 0xf, 0x5a, 0xe0, 0x5f, 0x8a, 0x9, 0x91, 0x49, 0x82, 0xa1, 0x9c, 0x77, 0xa2, 0x49, 0xa8, 0x4f, 0x4f, 0x6d, 0x1d, 0x8c, 0xec, 0xe0, 0xba, 0x23, 0x5b, 0x77, 0xaa, 0x7c, 0xfd, 0x27, 0x6a, 0xbc, 0xcf, 0xe2, 0x2f, 0x8e, 0x48, 0xf6, 0x2a, 0x85, 0x72, 0xab, 0xf2, 0xe6, 0x9a, 0x5f, 0x94, 0xc7, 0xf5, 0x7f, 0x22, 0x96, 0x49, 0x24, 0xb7, 0xf4, 0x7f, 0x2c, 0x9d, 0xf9, 0x5c, 0xff, 0x7d, 0x48, 0xf9, 0xb2, 0x4f, 0xdf, 0x7f, 0xf9, 0x2, 0xea, 0xba, 0x65, 0x12, 0xc5, 0x96, 0x99, 0xc2, 0x65, 0xfa, 0x15, 0x53, 0xf0, 0x3b, 0xab, 0x1d, 0xa8, 0x15, 0xe6, 0xf6, 0x18, 0x99, 0x3a, 0xb2, 0x10, 0x8d, 0xb2, 0xb5, 0xc4, 0x57, 0x20, 0x73, 0x94, 0x58, 0xa7, 0x16, 0x54, 0x55, 0xea, 0xce, 0x18, 0x2, 0x36, 0x1f, 0x75, 0xa6, 0x84, 0x88, 0x83, 0x1, 0x13, 0x77, 0x72, 0xf8, 0xa, 0xb0, 0xe, 0x20, 0x68, 0x78, 0xe8, 0x80, 0xac, 0xda, 0xfc, 0x7e, 0x6c, 0x50, 0xb2, 0x4d, 0x13, 0xe9, 0x96, 0xba, 0x2, 0x16, 0xa3, 0x45, 0x34, 0xe0, 0x19, 0xa3, 0xce, 0x8c, 0xb4, 0x88, 0x45, 0x92, 0x11, 0x89, 0x59, 0xa1, 0xfc, 0x6, 0x6e, 0x70, 0x81, 0x54, 0x99, 0x68, 0x88, 0x69, 0xf7, 0xdf, 0xd7, 0x53, 0xc1, 0x1f, 0xbf, 0x6, 0x4d, 0x3c, 0xfb, 0xd4, 0xff, 0x97, 0x2f, 0xa0, 0x6b, 0x22, 0x36, 0x51, 0x60, 0x8a, 0x2d, 0x44, 0xd9, 0x84, 0xb0, 0x4b, 0x20, 0x88, 0x45, 0xb2, 0xc7, 0xc2, 0x9a, 0x2d, 0x22, 0xb, 0x82, 0x42, 0xb7, 0x4e, 0xdd, 0x94, 0x7, 0x9b, 0x0, 0x75, 0xe1, 0xbe, 0x17, 0x13, 0x18, 0x51, 0x55, 0x64, 0x2, 0xec, 0xaf, 0xca, 0xc6, 0x52, 0xb6, 0x54, 0x50, 0x6a, 0xd6, 0x39, 0xd2, 0x50, 0xd8, 0x21, 0xad, 0x59, 0x7, 0xf1, 0x48, 0x36, 0x22, 0x1d, 0x73, 0x9a, 0x1a, 0x3a, 0x59, 0xea, 0xa0, 0x4e, 0xa9, 0x2d, 0x48, 0x1a, 0xac, 0x47, 0x45, 0x21, 0x88, 0xea, 0xbf, 0x58, 0xfc, 0xda, 0x73, 0x41, 0x92, 0xaf, 0x79, 0xbe, 0x99, 0x81, 0xad, 0x15, 0xb0, 0xd, 0x85, 0xad, 0xe3, 0x9d, 0xb1, 0x62, 0x4d, 0x45, 0x72, 0xc7, 0x7c, 0xfb, 0x23, 0x16, 0xc9, 0x45, 0x92, 0xc7, 0x11, 0xd0, 0x43, 0x13, 0x64, 0xf2, 0xb1, 0xe3, 0x49, 0x8, 0x40, 0x32, 0x99, 0x4b, 0x3f, 0x61, 0x86, 0xe7, 0x95, 0x69, 0x94, 0xd5, 0xfe, 0x2a, 0xc6, 0xaf, 0x8f, 0x2c, 0xdb, 0x8, 0xfc, 0xae, 0xa1, 0xd0, 0x88, 0xd8, 0x40, 0x86, 0x3a, 0xb0, 0x29, 0x2, 0x50, 0x5f, 0x1, 0x8b, 0x10, 0x95, 0x4f, 0xc7, 0xd4, 0x5, 0xca, 0xae, 0xb9, 0x20, 0x54, 0xd7, 0xc5, 0xe7, 0xe7, 0xd6, 0xc6, 0x77, 0xb0, 0x44, 0x1c, 0xee, 0x77, 0xd, 0xeb, 0x98, 0x8d, 0xdb, 0x21, 0xe6, 0x74, 0xeb, 0x7d, 0x5, 0x52, 0x24, 0x9e, 0x8a, 0x48, 0x7b, 0xda, 0x1, 0x90, 0x2, 0x6b, 0x86, 0x80, 0x82, 0x16, 0x51, 0xb, 0x51, 0xb, 0x20, 0x7d, 0x11, 0x9, 0xdd, 0xdc, 0xdc, 0x8c, 0x21, 0x9d, 0x85, 0xbe, 0x80, 0xbe, 0xc5, 0x86, 0xd9, 0x45, 0x16, 0x72, 0xff, 0x0, 0x94, 0x3f, 0xf, 0x87, 0x17, 0x5a, 0x58, 0x31, 0xf1, 0x2, 0x99, 0xbf, 0xe9, 0x44, 0x67, 0xab, 0x96, 0xa, 0xff, 0xba, 0x1, 0x9f, 0x7e, 0xd, 0xb, 0x15, 0x85, 0xe8, 0x2e, 0x1f, 0x0, 0x67, 0x8f, 0x87, 0xb5, 0x29, 0x84, 0x91, 0xb, 0x12, 0x37, 0xd2, 0x66, 0x48, 0x70, 0x69, 0x28, 0x74, 0x82, 0x4d, 0xea, 0x9, 0x99, 0x90, 0x4e, 0xc6, 0x25, 0x4f, 0x91, 0xd0, 0x2f, 0xe, 0xd4, 0xd7, 0xef, 0xc, 0xa1, 0xe, 0xb0, 0x8e, 0x2d, 0xc, 0x55, 0xfc, 0x80, 0x38, 0xb3, 0xad, 0x3b, 0xcb, 0x2a, 0xf3, 0xb7, 0x9c, 0x94, 0xb2, 0xb3, 0x9b, 0xe5, 0xca, 0x1b, 0x7f, 0x2b, 0x98, 0x8e, 0x59, 0x69, 0x6b, 0xb0, 0x29, 0xb6, 0x80, 0xac, 0xb2, 0xe5, 0xdc, 0x21, 0x76, 0x9, 0xc9, 0x8a, 0x6b, 0xa5, 0x71, 0xca, 0xdc, 0xc7, 0x1b, 0xcd, 0xb9, 0x19, 0x2d, 0x80, 0x82, 0xc, 0x24, 0x7c, 0x38, 0xbc, 0x55, 0xca, 0xd, 0x38, 0x6c, 0x22, 0x5, 0x8c, 0x57, 0x2e, 0x51, 0xfc, 0xcd, 0x56, 0xf, 0x5c, 0x38, 0xd9, 0xe5, 0x7b, 0x67, 0x64, 0x19, 0x6f, 0xca, 0xb6, 0x89, 0x80, 0xbb, 0xbb, 0x7, 0x5a, 0x44, 0x41, 0xa1, 0x50, 0x71, 0xe5, 0xdd, 0x64, 0xe3, 0x53, 0x37, 0x4b, 0xcc, 0x56, 0xbc, 0x48, 0x47, 0x26, 0x93, 0x1c, 0xc8, 0x4f, 0x79, 0x81, 0x52, 0x81, 0xb, 0x4d, 0xbf, 0xdf, 0x4, 0xbe, 0x53, 0x55, 0x5c, 0x87, 0xac, 0x88, 0xbd, 0x56, 0x24, 0x2e, 0xa3, 0x43, 0x95, 0x12, 0xce, 0xb5, 0xfc, 0x2d, 0x13, 0xe2, 0x52, 0x41, 0xdc, 0xed, 0xa1, 0xf8, 0x11, 0xe4, 0x43, 0x21, 0x62, 0x20, 0x5d, 0x84, 0x4a, 0x88, 0xbd, 0x98, 0xef, 0x3c, 0x47, 0x9, 0x63, 0x0, 0xfe, 0x10, 0x90, 0x9, 0xf8, 0xc5, 0x1d, 0x99, 0x32, 0xd1, 0x20, 0xd6, 0x7f, 0x61, 0xc, 0xed, 0x6b, 0x15, 0x9c, 0x9c, 0x95, 0xdb, 0x62, 0xf5, 0xcd, 0xd3, 0x9b, 0x78, 0x60, 0xe2, 0x26, 0xa0, 0x5f, 0xbe, 0xf3, 0xbf, 0x1a, 0x68, 0xc5, 0xfe, 0xe2, 0xf7, 0xe, 0xfd, 0x12, 0x1, 0x83, 0x19, 0x33, 0xb7, 0xf9, 0xea, 0x98, 0x87, 0x5d, 0x92, 0x9, 0x6f, 0xd3, 0xdf, 0x39, 0xcd, 0xa6, 0x96, 0x58, 0xdd, 0xef, 0xb4, 0x16, 0x9, 0x85, 0x1c, 0x6e, 0xe5, 0x50, 0xbb, 0x8c, 0xc2, 0x15, 0x12, 0xf4, 0xf4, 0xe, 0x6f, 0xe2, 0x46, 0x5b, 0x85, 0x9d, 0x7, 0x37, 0xf9, 0x6d, 0xde, 0x13, 0xc8, 0x22, 0x20, 0x1c, 0xe6, 0x1, 0x2f, 0x20, 0x1c, 0xe6, 0x30, 0x6b, 0x10, 0x10, 0xe, 0xb3, 0xde, 0x6d, 0xe, 0xc5, 0x6f, 0xae, 0xf, 0x70, 0x15, 0x51, 0xf8, 0x3, 0xfe, 0xc5, 0xbf, 0x7d, 0x6b, 0xb1, 0x25, 0x5, 0xb5, 0x4d, 0xf4, 0xed, 0x1b, 0xb8, 0xd9, 0xae, 0x73, 0xc3, 0xcf, 0xb4, 0xa8, 0xb, 0xc4, 0x35, 0x10, 0xa3, 0x9f, 0x69, 0x85, 0x0, 0x8d, 0x5d, 0xef, 0x2, 0xa2, 0xf3, 0xa, 0x1a, 0x94, 0x67, 0x58, 0x47, 0x6c, 0x75, 0x81, 0x4c, 0xfe, 0x51, 0x81, 0x69, 0xeb, 0x40, 0x21, 0xf2, 0x1c, 0x99, 0x80, 0x98, 0xce, 0x51, 0x24, 0x77, 0x20, 0x86, 0x75, 0x1f, 0x27, 0x28, 0x4a, 0x10, 0x9, 0x8c, 0x9b, 0xea, 0x5d, 0xa0, 0x41, 0xc3, 0x60, 0x2, 0x8f, 0x45, 0x3b, 0xe5, 0x76, 0xdf, 0xb9, 0x39, 0xe9, 0x3b, 0xc3, 0x8a, 0x75, 0x11, 0xb2, 0x30, 0x23, 0xd4, 0xa2, 0xfc, 0x9, 0x63, 0x29, 0x56, 0x91, 0x7d, 0x4, 0xf, 0xed, 0x7f, 0xd6, 0xbb, 0xff, 0x15, 0xb9, 0xc7, 0x46, 0x4, 0x93, 0x9b, 0x48, 0x28, 0x74, 0x22, 0x16, 0x70, 0xac, 0xda, 0x4d, 0xbb, 0x33, 0xa8, 0xf4, 0x6f, 0xd8, 0x7c, 0xc0, 0x5d, 0x32, 0x4e, 0x33, 0xec, 0xc3, 0x33, 0x2, 0x88, 0x6d, 0x19, 0xb6, 0xc5, 0x46, 0x61, 0x8a, 0x2c, 0x97, 0x78, 0x1e, 0x84, 0x3, 0xc, 0x48, 0xe9, 0x92, 0x98, 0xa, 0x27, 0xf4, 0xdb, 0x37, 0xd6, 0xb, 0xe7, 0xbd, 0x2b, 0xf, 0x58, 0xb7, 0x88, 0x3b, 0x2, 0xfc, 0x26, 0xa6, 0x4d, 0xd6, 0xa6, 0xdf, 0xbe, 0x31, 0x2e, 0x99, 0xf0, 0xc1, 0x82, 0x26, 0xd3, 0xe1, 0xac, 0xa3, 0x50, 0x5f, 0xcb, 0xb9, 0xb3, 0x5b, 0x2, 0x54, 0x3c, 0x47, 0xe0, 0x77, 0x6, 0x1b, 0xd6, 0xa0, 0xe, 0xa7, 0x28, 0x78, 0xc5, 0x75, 0x8b, 0x2c, 0x6a, 0x41, 0x79, 0x1e, 0xf5, 0xd7, 0xfc, 0xca, 0x65, 0xd0, 0x11, 0x57, 0x57, 0x75, 0xe, 0x9a, 0xfd, 0xd, 0x52, 0xbe, 0x87, 0x18, 0x11, 0x5c, 0xb0, 0x97, 0x50, 0xe7, 0x1d, 0x76, 0xf2, 0xd0, 0xec, 0x30, 0xbd, 0x8b, 0x89, 0xe8, 0x4c, 0xd9, 0x70, 0xe6, 0x1c, 0xaf, 0x98, 0x48, 0x7a, 0xc8, 0x85, 0x7c, 0xdd, 0xb8, 0x1a, 0xa2, 0xe4, 0x6b, 0xe7, 0x86, 0x61, 0xbe, 0xe6, 0x89, 0x6d, 0xae, 0xb9, 0x88, 0x89, 0xe1, 0x76, 0xf4, 0xfc, 0x2f, 0x0, 0xea, 0x3a, 0xb1, 0x9c, 0x3d, 0x67, 0x4e, 0xd, 0x9b, 0x4e, 0x64, 0xa8, 0xeb, 0x48, 0x9, 0x8d, 0x57, 0xe0, 0xda, 0xdf, 0xb1, 0x6b, 0x3e, 0xf2, 0xc, 0x1c, 0x1a, 0x86, 0x49, 0xc, 0x13, 0x33, 0xf2, 0x9c, 0x8d, 0x25, 0x47, 0x47, 0x59, 0x40, 0x36, 0x11, 0x64, 0x3, 0xcf, 0xea, 0xa, 0x9d, 0xc7, 0x95, 0x24, 0x3, 0x73, 0xf7, 0xa2, 0xf8, 0x47, 0x70, 0x6, 0x5f, 0xd6, 0xd0, 0x2f, 0x74, 0x73, 0x68, 0x80, 0x42, 0x80, 0x4e, 0xb8, 0x39, 0xc4, 0x10, 0xb2, 0x25, 0xb5, 0x22, 0xda, 0x15, 0x8e, 0x0, 0xf1, 0xd1, 0x5c, 0xba, 0x81, 0x45, 0x42, 0x5c, 0x19, 0xb8, 0x61, 0x11, 0x21, 0x26, 0x71, 0x4c, 0x6f, 0x85, 0x9c, 0x27, 0x3c, 0xb8, 0x7b, 0xdd, 0x4f, 0xe1, 0xe4, 0x5f, 0x4f, 0x4c, 0xcc, 0xe, 0xb4, 0x54, 0x1a, 0x66, 0xa4, 0xe4, 0xf9, 0x15, 0x5e, 0xe8, 0x88, 0xb, 0x6d, 0x68, 0xe0, 0x63, 0x4c, 0x85, 0x87, 0xa3, 0x7a, 0xf2, 0xe4, 0x48, 0x61, 0x80, 0x41, 0xe6, 0x30, 0xb4, 0xab, 0xc6, 0x45, 0x8c, 0x8e, 0x2d, 0x3e, 0x60, 0x84, 0xa3, 0xfc, 0x7d, 0xe3, 0xd9, 0x1f, 0xbf, 0x7e, 0xd9, 0xf8, 0xfd, 0xd5, 0x13, 0xd, 0x15, 0x73, 0xf, 0xc1, 0xc, 0x1, 0x3, 0x9a, 0x50, 0x43, 0xdc, 0x99, 0x60, 0xcd, 0xa0, 0xc5, 0xa5, 0x78, 0x8c, 0x3c, 0xdc, 0x48, 0x61, 0xad, 0xfa, 0x34, 0x36, 0x93, 0xcb, 0x5, 0x86, 0xe0, 0x17, 0xae, 0xa1, 0x7e, 0x11, 0xf0, 0x14, 0x28, 0xb6, 0x29, 0x24, 0x9b, 0xab, 0x36, 0x87, 0xa2, 0xd0, 0x7f, 0x81, 0x6f, 0xdf, 0x6, 0xd8, 0xf8, 0xf6, 0x2d, 0xf, 0x9a, 0x98, 0x5a, 0xdc, 0x7f, 0xe1, 0x68, 0x38, 0xea, 0x7c, 0xa5, 0x1b, 0xce, 0x75, 0x8c, 0x9e, 0x9b, 0x50, 0x88, 0x4f, 0x5f, 0x75, 0x27, 0x3a, 0x65, 0x7b, 0x2, 0xab, 0x4f, 0x3c, 0xce, 0x74, 0x46, 0xca, 0xab, 0x0, 0x29, 0x70, 0x63, 0x5a, 0xdc, 0xe3, 0x3a, 0x18, 0x51, 0x2e, 0x2c, 0x42, 0x66, 0x85, 0x4c, 0x28, 0x84, 0xd9, 0x90, 0x3a, 0x42, 0x8a, 0xd0, 0x2, 0xce, 0xac, 0xd7, 0x3b, 0x30, 0xe7, 0xf9, 0x94, 0xbe, 0xf3, 0xd9, 0xc5, 0xf4, 0xce, 0xf5, 0xe6, 0xdf, 0x76, 0x72, 0xf8, 0xbe, 0x1d, 0xe5, 0xf3, 0x1b, 0x3f, 0x3f, 0xf0, 0x39, 0x65, 0x84, 0xb8, 0xaa, 0xe2, 0xcd, 0x1e, 0x5, 0x70, 0xcd, 0x51, 0x1e, 0xfc, 0x7e, 0xb4, 0x4d, 0xcf, 0xd1, 0x1f, 0x37, 0x9e, 0xf4, 0x89, 0x3e, 0x29, 0x10, 0x31, 0x73, 0x42, 0xc8, 0x9, 0x77, 0xb6, 0x73, 0x92, 0x4d, 0x24, 0x2c, 0x66, 0xa7, 0xd6, 0x5a, 0x2d, 0x7d, 0xf9, 0x2, 0x86, 0x3a, 0xe, 0x34, 0x7a, 0x7, 0x4, 0xd8, 0xee, 0xab, 0xa8, 0xc8, 0x31, 0x27, 0xe6, 0x33, 0x1f, 0x97, 0x38, 0x4a, 0x42, 0x43, 0xba, 0x95, 0xf, 0x6d, 0x1b, 0x82, 0x1c, 0x62, 0x5d, 0x79, 0x57, 0xc3, 0x98, 0x48, 0x23, 0xec, 0x73, 0xba, 0x5c, 0xea, 0xd7, 0x2d, 0xee, 0x7e, 0x21, 0x5, 0x90, 0x52, 0x22, 0x63, 0xae, 0x55, 0x39, 0xd3, 0xf2, 0xd9, 0x82, 0xf3, 0xb0, 0xd0, 0x52, 0x2a, 0x7, 0x60, 0x4f, 0xdd, 0xd5, 0x28, 0xef, 0x56, 0xc9, 0xaf, 0x62, 0x44, 0xbb, 0x6b, 0x65, 0x6a, 0xb1, 0x79, 0x87, 0xfa, 0xf4, 0x8d, 0xa7, 0x90, 0xd8, 0x7c, 0xe4, 0x53, 0x3e, 0x8e, 0x49, 0xe6, 0x37, 0x78, 0xbd, 0x39, 0xc1, 0x55, 0x78, 0x8e, 0x1a, 0xa, 0x85, 0x7e, 0x80, 0xae, 0xb, 0x9, 0x76, 0xca, 0xf, 0x50, 0x46, 0x54, 0x36, 0x31, 0xbf, 0x5e, 0x6f, 0xf7, 0xf5, 0xba, 0x92, 0x40, 0xba, 0xf3, 0x26, 0xf4, 0x3, 0x84, 0x83, 0x8b, 0x0, 0xdc, 0xf3, 0xd2, 0x5f, 0x6f, 0x3f, 0x6, 0x86, 0xfd, 0xdb, 0x37, 0xd1, 0xcb, 0x6f, 0xdf, 0xf8, 0x4f, 0x87, 0x4b, 0xeb, 0x1a, 0x9c, 0xa2, 0x1, 0x9c, 0xde, 0x38, 0xf4, 0xb1, 0x71, 0xb4, 0xe0, 0xd4, 0xb7, 0x72, 0x77, 0x99, 0x8a, 0x7, 0xc4, 0x53, 0xf0, 0x3, 0xdc, 0x8, 0xe7, 0xc0, 0x8d, 0x40, 0xb3, 0xa9, 0xf, 0x6e, 0xbc, 0x8e, 0x3a, 0xc0, 0x7c, 0x16, 0x65, 0x42, 0x49, 0x74, 0xe0, 0x19, 0x1c, 0xdb, 0x40, 0xd1, 0xaf, 0x6c, 0x8d, 0xcc, 0xd8, 0x6, 0xb1, 0x39, 0x96, 0x6b, 0xe8, 0x3d, 0xc2, 0xcf, 0x25, 0xc, 0xd2, 0x9d, 0x76, 0xb9, 0x18, 0x6a, 0xae, 0x7e, 0x11, 0x6b, 0xd, 0x5e, 0xf7, 0xc9, 0x4a, 0x25, 0x2, 0x7e, 0x4, 0x69, 0x4, 0xde, 0xc9, 0x6d, 0xa5, 0xe6, 0x8e, 0x96, 0x98, 0x1c, 0x38, 0x6a, 0xd3, 0x46, 0xdf, 0xc1, 0xae, 0xba, 0xe7, 0xaf, 0xf9, 0x1a, 0x21, 0x2a, 0x16, 0x8, 0x51, 0xbe, 0x3a, 0xf8, 0xe, 0xc6, 0xb6, 0x25, 0xac, 0x7f, 0x9f, 0xd9, 0xe7, 0x76, 0x6e, 0xdf, 0xac, 0xc2, 0xad, 0x2, 0xe7, 0x73, 0xc8, 0x2a, 0x46, 0xba, 0xc5, 0x89, 0xe6, 0xc6, 0x90, 0x43, 0x69, 0xb0, 0xc9, 0x4, 0x7e, 0x80, 0xbe, 0xa0, 0x93, 0xd7, 0x5, 0x78, 0xb2, 0x6d, 0xbf, 0x79, 0xb6, 0x92, 0x30, 0x8d, 0xb8, 0x85, 0x13, 0x64, 0x47, 0xfe, 0x0, 0x37, 0xac, 0xab, 0x4e, 0x6b, 0xa2, 0x67, 0x37, 0x3b, 0x5c, 0xbe, 0x8d, 0xdc, 0x12, 0xb, 0xb5, 0x35, 0x53, 0xfd, 0x42, 0xc5, 0x7a, 0xd6, 0xcb, 0x6b, 0x18, 0x1, 0x75, 0x8b, 0x82, 0x52, 0xdb, 0xbf, 0x22, 0xda, 0xfe, 0xc6, 0xac, 0xb3, 0xd0, 0xb6, 0x48, 0xd8, 0xed, 0xa4, 0xe2, 0x51, 0xd1, 0x40, 0xab, 0x60, 0x2a, 0xe6, 0x68, 0xf5, 0x84, 0xd6, 0xf7, 0x20, 0xe6, 0x9f, 0x6a, 0x1b, 0x2f, 0xf8, 0xc1, 0x66, 0x6d, 0x7e, 0xd5, 0x2b, 0xc3, 0x48, 0x91, 0x3a, 0x71, 0x57, 0x9d, 0x6e, 0xb7, 0xf7, 0xa0, 0xb, 0xc8, 0xf5, 0x70, 0x3, 0x7e, 0xb8, 0x9e, 0xa, 0xef, 0xad, 0x5, 0x14, 0x68, 0x41, 0xf7, 0xb4, 0xff, 0xf6, 0x7, 0xf6, 0x82, 0xb6, 0x6e, 0x1c, 0x69, 0x75, 0xcc, 0x55, 0xd6, 0x51, 0x9b, 0x22, 0x85, 0x13, 0x85, 0x74, 0xd9, 0x5c, 0x71, 0x4d, 0x14, 0x1, 0x2d, 0x36, 0xa0, 0x63, 0xb6, 0xca, 0x66, 0x9c, 0xa4, 0x4f, 0x99, 0xce, 0x8b, 0xa5, 0xb9, 0xae, 0xa3, 0x9c, 0x7d, 0x74, 0x62, 0x85, 0x61, 0xd8, 0xe1, 0xb8, 0x39, 0x47, 0xcc, 0xd5, 0x45, 0x61, 0x7d, 0x52, 0xc0, 0xd5, 0x19, 0xbe, 0xc3, 0x3, 0x3b, 0xc7, 0x6e, 0x18, 0x3d, 0xbd, 0xf5, 0x21, 0x9c, 0x89, 0xbb, 0xcc, 0x11, 0xd5, 0x85, 0xfa, 0xe0, 0xda, 0xc3, 0xef, 0xb4, 0xf0, 0x1f, 0x47, 0xb8, 0xd9, 0xd7, 0x86, 0xc5, 0xd4, 0xd3, 0xf, 0x30, 0x70, 0xd4, 0x52, 0x30, 0x56, 0xbf, 0x4e, 0xda, 0x45, 0xb1, 0x3e, 0xc7, 0xc3, 0x30, 0x75, 0x6d, 0x55, 0x5, 0xce, 0xb1, 0x9e, 0xbd, 0x18, 0xeb, 0x93, 0x36, 0xb1, 0xba, 0x26, 0xa2, 0x48, 0xb7, 0x2, 0xf0, 0x22, 0xd, 0x62, 0xb5, 0x46, 0xa8, 0xc5, 0x3f, 0x21, 0xfb, 0xe1, 0x98, 0x8, 0xc, 0x96, 0x6a, 0x96, 0x11, 0xd1, 0x56, 0x42, 0x87, 0xb0, 0x85, 0xd5, 0x3e, 0x4, 0x5d, 0x62, 0xfa, 0x10, 0xf0, 0x13, 0x95, 0x3f, 0xc0, 0x4d, 0x3c, 0xb5, 0xaf, 0xfe, 0x90, 0xf2, 0x71, 0x72, 0xea, 0xdb, 0x94, 0x89, 0x87, 0xc6, 0xc9, 0xa5, 0x50, 0x33, 0x54, 0x74, 0xcd, 0x21, 0xfe, 0xf5, 0x94, 0xa6, 0xfb, 0x54, 0xdd, 0xc0, 0x44, 0x99, 0x15, 0xfa, 0xdf, 0xdb, 0xec, 0xb6, 0x3, 0x76, 0x62, 0x72, 0x94, 0x80, 0x32, 0x55, 0x2d, 0x80, 0x27, 0x26, 0xd1, 0x78, 0x9e, 0x56, 0xa6, 0x53, 0x5d, 0x18, 0xf0, 0x9f, 0x7b, 0x29, 0xfa, 0xaf, 0x7d, 0xb8, 0xeb, 0xa, 0xd2, 0x2d, 0x6c, 0xf1, 0x6f, 0xf4, 0x3, 0x1c, 0x1d, 0xed, 0x56, 0x13, 0xec, 0xe9, 0x7e, 0x30, 0x36, 0xda, 0x73, 0x1e, 0xbe, 0xb8, 0x87, 0x89, 0x3, 0x6, 0x7c, 0xc9, 0x65, 0xce, 0x5b, 0x7a, 0x73, 0x69, 0xe1, 0xbd, 0xd8, 0x46, 0xe2, 0x56, 0x9, 0xc0, 0xc4, 0xff, 0xee, 0xae, 0xdf, 0x6f, 0xa2, 0x13, 0xdd, 0x67, 0xdf, 0x86, 0xe1, 0x14, 0x1a, 0x27, 0x16, 0x4f, 0x24, 0x83, 0x3e, 0x2a, 0xb4, 0xad, 0x19, 0xeb, 0xb3, 0x48, 0xac, 0xca, 0x56, 0x1c, 0x37, 0x42, 0x79, 0xf8, 0x9e, 0x2, 0x7e, 0x3c, 0xd7, 0x37, 0x21, 0xff, 0xa, 0x6e, 0x94, 0xf1, 0x35, 0xab, 0x74, 0xc3, 0x1f, 0xf3, 0xe4, 0xb8, 0xc0, 0xbd, 0xe7, 0xe4, 0x3b, 0xb8, 0x51, 0x15, 0x68, 0xf8, 0xde, 0x37, 0xcb, 0x85, 0xee, 0x77, 0x80, 0x2c, 0x39, 0x12, 0x89, 0x7c, 0x5, 0xbf, 0x97, 0x89, 0x4c, 0x9f, 0xb9, 0xbf, 0xac, 0x10, 0x99, 0x46, 0x59, 0x8f, 0xae, 0xb9, 0xbf, 0x38, 0xa2, 0x29, 0x5f, 0xd8, 0xaf, 0x30, 0x94, 0x65, 0x62, 0xeb, 0xd6, 0x57, 0xd6, 0x51, 0x8f, 0xa4, 0x9d, 0x4e, 0x32, 0x15, 0xe9, 0xcc, 0x9c, 0xbc, 0x43, 0xac, 0x8b, 0x5, 0x66, 0xaa, 0x51, 0x3e, 0x4a, 0x7c, 0x6a, 0x16, 0x33, 0x1d, 0x32, 0x9d, 0x95, 0xb5, 0x46, 0x11, 0x9b, 0xb4, 0xbf, 0x3, 0x62, 0xcd, 0x90, 0xb9, 0xc4, 0x14, 0x1, 0xa2, 0xab, 0x2b, 0x31, 0xb6, 0x2, 0x11, 0x31, 0xa9, 0x67, 0xc9, 0xb, 0x34, 0x3f, 0xc0, 0xd, 0xd1, 0x3, 0xda, 0x67, 0xc3, 0x11, 0xb1, 0x4d, 0xce, 0xee, 0x6c, 0x2c, 0x5c, 0x41, 0x1d, 0xf6, 0x9a, 0x7c, 0x7c, 0xfc, 0xe3, 0xb5, 0x35, 0xf4, 0x3f, 0xc4, 0x4b, 0x36, 0x50, 0xfc, 0xdf, 0x7d, 0x8a, 0x8a, 0x37, 0x41, 0x11, 0x34, 0xe5, 0x59, 0xb9, 0xed, 0xb5, 0xd3, 0xe7, 0xf, 0x40, 0xb9, 0xcd, 0xf0, 0xec, 0x83, 0x62, 0xdf, 0xcc, 0x7, 0x53, 0x64, 0x4b, 0xad, 0xc7, 0x21, 0x26, 0x58, 0xb5, 0x84, 0x1a, 0xe0, 0x10, 0x27, 0xfc, 0x27, 0x3, 0xf8, 0x55, 0x24, 0x19, 0xe2, 0x9, 0x68, 0x7e, 0x63, 0xf3, 0x9, 0xd1, 0xbf, 0xee, 0x1d, 0xf, 0xac, 0x78, 0x18, 0x86, 0xf5, 0x32, 0x3, 0x17, 0x8f, 0x82, 0xbb, 0x26, 0x13, 0x3, 0xad, 0xfb, 0xc5, 0x7e, 0x71, 0x45, 0xb5, 0xaf, 0xbe, 0x93, 0x16, 0xd8, 0x83, 0x18, 0x88, 0xdf, 0xc, 0x26, 0x48, 0xc, 0x38, 0xcc, 0x2, 0x99, 0x78, 0xb2, 0x62, 0x13, 0xab, 0x7, 0x76, 0xce, 0x1f, 0x81, 0xda, 0x60, 0xd0, 0xed, 0x3, 0x9f, 0x49, 0xc3, 0xf0, 0xc, 0xd6, 0x66, 0x88, 0x1f, 0x95, 0x97, 0x4b, 0x87, 0x21, 0xf9, 0xdd, 0xfb, 0xb5, 0x66, 0xf8, 0x4d, 0x3f, 0x9, 0xe7, 0x6c, 0x99, 0xe8, 0x32, 0x32, 0x2c, 0x1a, 0xdd, 0xf0, 0x5b, 0x44, 0x85, 0x29, 0x14, 0x66, 0x6b, 0x15, 0xdb, 0x62, 0xc6, 0x98, 0x83, 0x2b, 0xec, 0x9d, 0x8e, 0x8b, 0x7e, 0x75, 0xe2, 0x23, 0x88, 0xb7, 0x6c, 0xf7, 0xde, 0x71, 0x4b, 0xc1, 0xd6, 0x15, 0x34, 0xc1, 0xba, 0x6b, 0x1, 0xf8, 0x9, 0x75, 0xe, 0x45, 0x31, 0x32, 0xf9, 0xbc, 0x6d, 0x11, 0xc7, 0xd3, 0x4, 0xba, 0xe7, 0x25, 0xca, 0x4c, 0x34, 0x9f, 0xbd, 0xe0, 0xdb, 0xd9, 0x2, 0xbf, 0x52, 0x84, 0xf8, 0x82, 0xdd, 0x14, 0x9b, 0x8a, 0x74, 0xcb, 0x57, 0xf2, 0x95, 0x6b, 0x67, 0xb4, 0xe1, 0x3e, 0xd9, 0x69, 0xdd, 0x7f, 0x8c, 0x84, 0x91, 0xd0, 0x66, 0x3a, 0x46, 0x84, 0x94, 0xf2, 0x6e, 0x18, 0x44, 0x61, 0x2b, 0x33, 0x3c, 0x15, 0xf9, 0xbb, 0x7f, 0x80, 0x9b, 0x3f, 0xff, 0xa, 0x18, 0x6d, 0xdf, 0x61, 0xe, 0x3e, 0x45, 0xfb, 0xe, 0x7c, 0x4, 0x63, 0xf9, 0xfd, 0x8f, 0x20, 0x2d, 0xe8, 0x1c, 0x39, 0x71, 0x9, 0x89, 0x76, 0x19, 0x9c, 0x78, 0x88, 0xc5, 0x9a, 0xc3, 0x6d, 0xfd, 0xdb, 0xb7, 0x53, 0x2f, 0x4, 0xdc, 0xb5, 0x4a, 0x7c, 0x41, 0xe1, 0x4f, 0x30, 0x4a, 0xd6, 0xb5, 0xf7, 0xd9, 0x24, 0xeb, 0x1a, 0x37, 0x7b, 0x1a, 0xd8, 0xb2, 0x48, 0x2, 0x51, 0xfa, 0xd, 0x92, 0x1d, 0x4, 0x8f, 0xdb, 0x23, 0x41, 0xf8, 0x2, 0xcc, 0x11, 0x1f, 0x56, 0x67, 0x5e, 0xf4, 0x1, 0x3e, 0x3e, 0x2f, 0xee, 0xc4, 0xd1, 0x6f, 0x41, 0x7b, 0xc1, 0xf5, 0x9b, 0x8, 0xbc, 0xba, 0x5b, 0x38, 0xfe, 0xd6, 0x42, 0xe7, 0xa3, 0xf3, 0x15, 0x5c, 0xef, 0xc3, 0xf2, 0x72, 0xa6, 0xf7, 0x21, 0x79, 0x16, 0xcf, 0xf, 0xeb, 0x2e, 0xaf, 0x7, 0x9c, 0x77, 0xf, 0xe0, 0x71, 0x1b, 0xef, 0xe3, 0x6d, 0x1b, 0xdf, 0x6c, 0x21, 0xda, 0xe2, 0xe5, 0xd, 0x50, 0x3f, 0xf, 0x7, 0x9c, 0x89, 0xf, 0xe2, 0x5d, 0x3f, 0x7c, 0x0, 0xcf, 0xda, 0xd8, 0xe5, 0x55, 0x1b, 0x1f, 0xe0, 0x51, 0xef, 0xac, 0x89, 0x53, 0xfb, 0x29, 0x3c, 0xb9, 0x7d, 0xc6, 0xc4, 0x1, 0x75, 0x9e, 0x39, 0xd, 0x6, 0x2a, 0x47, 0x1b, 0xff, 0xbd, 0xb9, 0x78, 0xeb, 0x40, 0xe0, 0x33, 0xb9, 0x77, 0xf3, 0x30, 0xde, 0x33, 0xb9, 0xd6, 0x77, 0x4c, 0xf0, 0x49, 0xdc, 0xda, 0x5a, 0xf5, 0xcf, 0x9a, 0x2e, 0xc3, 0x6a, 0x2b, 0x7a, 0xa7, 0x3e, 0x85, 0x67, 0x79, 0xc5, 0x7d, 0x6c, 0xcb, 0x5f, 0xde, 0xec, 0x62, 0xdc, 0x62, 0xde, 0x6d, 0x1c, 0x7e, 0xfe, 0xf5, 0x83, 0x3d, 0xce, 0xc2, 0x5b, 0x58, 0x2, 0xb8, 0x58, 0xe0, 0x9a, 0x39, 0xab, 0x3f, 0xde, 0x5f, 0xd0, 0xf7, 0x56, 0x7f, 0xff, 0x6f, 0xa3, 0x96, 0xe1, 0x2c, 0xf1, 0x44, 0xad, 0xae, 0xb3, 0xc4, 0x4b, 0x24, 0xa4, 0xf4, 0x46, 0x35, 0xdb, 0x59, 0xd9, 0x89, 0x6a, 0x43, 0xdf, 0xca, 0xce, 0x24, 0x64, 0xb3, 0x5d, 0xb6, 0xda, 0xf0, 0x61, 0x74, 0xd7, 0x1e, 0xac, 0xaa, 0xe7, 0x9f, 0xf2, 0x55, 0x77, 0x17, 0x6, 0x6b, 0x90, 0xb2, 0xf3, 0x64, 0x2f, 0xc8, 0xdf, 0x5a, 0x12, 0x4, 0x89, 0x1f, 0x6f, 0x3e, 0x89, 0x76, 0x5f, 0x21, 0x84, 0x2, 0xc1, 0xcb, 0xe5, 0x50, 0xc0, 0x3f, 0x4b, 0x14, 0x5d, 0x87, 0xa5, 0x2b, 0x8d, 0x7b, 0x93, 0x4f, 0x7, 0x8, 0xa4, 0x5b, 0x77, 0x47, 0x26, 0xbd, 0x9c, 0x86, 0xc6, 0x8c, 0x58, 0xde, 0x8a, 0x6b, 0x37, 0x5f, 0xb5, 0x5f, 0x32, 0x3, 0x90, 0xc5, 0x23, 0xe9, 0x48, 0x3c, 0xbc, 0x21, 0xa3, 0x7b, 0x33, 0x57, 0x7, 0x89, 0xe9, 0x2e, 0xca, 0x0, 0x49, 0xdd, 0xbe, 0x9d, 0x80, 0x21, 0xf2, 0x0, 0xf9, 0x36, 0xbb, 0xe3, 0xb0, 0xb, 0xfc, 0xe4, 0x1, 0x57, 0x23, 0x6c, 0x20, 0x78, 0xca, 0x94, 0xb4, 0x9b, 0x4b, 0xfb, 0xef, 0x28, 0x54, 0xdb, 0x79, 0x44, 0x3f, 0x50, 0xae, 0x2, 0xd3, 0xb, 0x3f, 0x53, 0xb4, 0x82, 0x92, 0xfa, 0x3e, 0x53, 0xba, 0x76, 0x52, 0xf, 0x3f, 0x49, 0xc0, 0x4a, 0x2a, 0xc4, 0x9e, 0x7b, 0x54, 0x66, 0x3f, 0xfc, 0xae, 0x5d, 0x27, 0x5c, 0x8d, 0x57, 0xfa, 0xef, 0x2d, 0xdf, 0xb9, 0xa8, 0xfc, 0x4, 0x39, 0xe4, 0x15, 0x77, 0x84, 0x90, 0x3f, 0xdd, 0x94, 0x40, 0x3f, 0xc6, 0x2d, 0xf1, 0xdb, 0xc6, 0x11, 0x8f, 0x48, 0x91, 0x98, 0x2b, 0x7b, 0x6b, 0xd8, 0xbf, 0x29, 0x8b, 0xae, 0x9, 0x7c, 0x5, 0x8b, 0x8, 0x4, 0x2f, 0xe7, 0xf, 0x1, 0xff, 0xc, 0xe6, 0xb8, 0x59, 0x5f, 0x60, 0xcc, 0x6a, 0x77, 0x16, 0xc8, 0x34, 0xb1, 0x82, 0xdc, 0x86, 0xdc, 0x77, 0xce, 0xae, 0xe3, 0xef, 0x3e, 0xb9, 0x38, 0x14, 0x78, 0x28, 0x42, 0xf6, 0x84, 0x87, 0x6e, 0x12, 0xcf, 0x25, 0xb2, 0x30, 0x99, 0x46, 0x9, 0x88, 0xb2, 0x28, 0x9e, 0x8a, 0xc5, 0x91, 0x82, 0x62, 0xb1, 0x64, 0x3c, 0x99, 0x4e, 0x49, 0x52, 0x52, 0xca, 0x25, 0xe5, 0x84, 0x9c, 0x48, 0x24, 0xa2, 0x94, 0x6f, 0x8a, 0xfa, 0x6e, 0x55, 0x8e, 0x3e, 0x2e, 0x9c, 0x9c, 0xbd, 0xdb, 0xfc, 0xba, 0x4a, 0x97, 0xbf, 0x37, 0x2f, 0x36, 0xf5, 0x31, 0xb8, 0xa8, 0xb6, 0xcd, 0xe1, 0xee, 0xb5, 0xc8, 0x4f, 0xde, 0x2f, 0x10, 0x0, 0x60, 0xcf, 0x8e, 0xc1, 0xc6, 0x5d, 0xa2, 0x9b, 0x9c, 0x1f, 0xd4, 0xd2, 0x96, 0x4, 0xec, 0xc3, 0x2d, 0x45, 0x52, 0x9b, 0x92, 0xf0, 0xf8, 0x5, 0xdd, 0x8f, 0x50, 0xcd, 0x1, 0xf6, 0x52, 0xcd, 0xdf, 0x6, 0x53, 0xbd, 0x75, 0xbb, 0x77, 0x10, 0xd5, 0xdb, 0xb8, 0xf7, 0x51, 0xad, 0x8c, 0x9f, 0x41, 0xb1, 0x6b, 0x18, 0xee, 0xd0, 0xac, 0x41, 0x13, 0x43, 0x65, 0x1c, 0x48, 0xad, 0xd7, 0x42, 0x30, 0xa5, 0x1, 0x38, 0x83, 0x88, 0xf4, 0x39, 0xd7, 0xf9, 0x26, 0x96, 0xe7, 0x40, 0x27, 0x13, 0xc7, 0x99, 0x1b, 0x84, 0xf3, 0x7, 0xe8, 0x1b, 0x48, 0xc6, 0x93, 0xd5, 0x3a, 0xe4, 0xd3, 0x3, 0xf4, 0xd3, 0xf8, 0xa, 0x2d, 0xe1, 0x60, 0x78, 0xb9, 0x9a, 0x70, 0x10, 0x3c, 0x6b, 0x12, 0x71, 0xf6, 0xa3, 0x5d, 0x31, 0xdb, 0x4a, 0x87, 0xe3, 0x93, 0x33, 0x37, 0x10, 0x48, 0xf8, 0x79, 0xc5, 0xf6, 0x9d, 0x2b, 0x70, 0x21, 0x77, 0x6c, 0x10, 0x94, 0x67, 0xeb, 0x18, 0x7, 0x67, 0x3b, 0x9d, 0x47, 0x76, 0x88, 0x8, 0x9e, 0x39, 0x5a, 0xfd, 0xc6, 0xe5, 0xfc, 0xf7, 0xef, 0xde, 0x9f, 0x7f, 0xdc, 0x0, 0x68, 0x4e, 0x6d, 0xde, 0x1b, 0x8b, 0x38, 0xe1, 0x51, 0x4e, 0x70, 0xc8, 0x4d, 0x4, 0x9c, 0x10, 0x13, 0xa0, 0x7b, 0xbe, 0x9f, 0xb3, 0x1d, 0x7, 0xe2, 0x8f, 0x1c, 0x7a, 0x4e, 0xc4, 0x90, 0x2f, 0x3a, 0x46, 0x44, 0x8e, 0x14, 0x54, 0x5e, 0xcd, 0xc2, 0xb, 0xa4, 0xae, 0xbe, 0x3, 0x8, 0x46, 0x85, 0x56, 0x53, 0x84, 0xb8, 0xf0, 0x10, 0x31, 0xca, 0xbb, 0x87, 0x9d, 0x58, 0x10, 0xa1, 0xa8, 0xbc, 0x40, 0x29, 0x5f, 0x48, 0x87, 0x13, 0x4a, 0x66, 0x98, 0x64, 0x81, 0x15, 0xa4, 0x80, 0xe5, 0xc, 0xab, 0x8, 0x6c, 0xc5, 0xc0, 0x88, 0xd0, 0x66, 0x7f, 0xbf, 0xbe, 0x3f, 0xa7, 0x5f, 0x13, 0x10, 0x35, 0xa0, 0x35, 0x8b, 0x5a, 0xc4, 0xaf, 0x44, 0x45, 0x3f, 0x7c, 0xd1, 0x67, 0xfe, 0x90, 0x7c, 0x7f, 0x44, 0xc9, 0xa6, 0xae, 0xf7, 0xab, 0x61, 0x71, 0x4a, 0x62, 0x6d, 0x60, 0x85, 0x42, 0xce, 0x9e, 0xf, 0xb5, 0x88, 0xe9, 0x74, 0x9d, 0xef, 0xe, 0x43, 0x5d, 0xd9, 0x8c, 0xc4, 0xe3, 0xe6, 0x96, 0x9b, 0x57, 0xc8, 0xcd, 0xb8, 0x15, 0xf1, 0x48, 0x90, 0x67, 0x50, 0x9f, 0x22, 0xdf, 0xd0, 0x9, 0x3, 0xcc, 0x22, 0x3b, 0xdb, 0xcf, 0xb2, 0x8, 0x29, 0xe2, 0xc7, 0x8, 0xd6, 0xa4, 0x58, 0xe7, 0xeb, 0xfb, 0x8, 0xf8, 0xd8, 0x20, 0xa8, 0x88, 0x50, 0xbb, 0xa3, 0xc2, 0x4e, 0xbb, 0xcc, 0x2c, 0x9c, 0xf0, 0x98, 0x7a, 0x37, 0x76, 0x92, 0x9f, 0x85, 0x86, 0x80, 0x9, 0x1, 0xa6, 0x8e, 0xe8, 0x8, 0xe3, 0x12, 0x72, 0xf1, 0xe0, 0xa1, 0xd5, 0x0, 0xdd, 0xf3, 0x80, 0x1d, 0x48, 0x81, 0x4a, 0xf4, 0xa9, 0x38, 0x3c, 0x0, 0x2d, 0x17, 0xca, 0xb4, 0x75, 0x9d, 0xef, 0x5f, 0xeb, 0xe2, 0x31, 0x93, 0xf2, 0x8, 0xb8, 0xd8, 0xc0, 0x2c, 0x42, 0x8e, 0x14, 0x67, 0x4b, 0x92, 0x57, 0x11, 0x7b, 0x72, 0xfa, 0xa, 0x98, 0x8, 0x52, 0xa2, 0x7f, 0x5f, 0x8f, 0xa1, 0x13, 0x5a, 0xe5, 0x11, 0x8f, 0xa9, 0x13, 0x6e, 0xc4, 0xf7, 0xf1, 0x10, 0x9b, 0x4a, 0x8e, 0xbe, 0x3d, 0xe9, 0x70, 0x9e, 0xb3, 0x65, 0xe6, 0xc4, 0xd0, 0x33, 0x29, 0xa2, 0x11, 0x95, 0xc8, 0xf3, 0xb7, 0x3c, 0x63, 0x74, 0xe8, 0xfc, 0x7f, 0x22, 0x95, 0xd8, 0xce, 0xff, 0x2c, 0x49, 0x9f, 0xe7, 0x7f, 0x3f, 0xa4, 0x78, 0xa7, 0x22, 0x30, 0xa2, 0xf9, 0xd0, 0xfa, 0xb6, 0x16, 0x5, 0x53, 0x91, 0x64, 0xd5, 0x99, 0x7d, 0xf3, 0x60, 0xd7, 0x90, 0xe, 0xb, 0x6b, 0xce, 0xbd, 0x3d, 0x22, 0x32, 0x25, 0x64, 0xaa, 0x22, 0x68, 0x60, 0xca, 0xcc, 0xbe, 0x90, 0xef, 0xb6, 0xde, 0x44, 0x24, 0x1e, 0x49, 0x85, 0x14, 0x3c, 0x45, 0xd4, 0xca, 0x3, 0x3a, 0x83, 0xf1, 0x54, 0x3a, 0x8f, 0xa4, 0x49, 0x2c, 0x1b, 0x4f, 0x66, 0xa4, 0x44, 0x16, 0x41, 0x38, 0xce, 0x2a, 0x59, 0x94, 0xcd, 0xa2, 0xf1, 0x38, 0x9, 0xc7, 0x93, 0x24, 0x82, 0xa9, 0x5c, 0x26, 0x93, 0x85, 0xd9, 0x74, 0x2a, 0x99, 0x8a, 0x67, 0x73, 0xa9, 0x4c, 0x2e, 0x95, 0x49, 0x25, 0x92, 0x50, 0x99, 0xa4, 0x13, 0x89, 0x4c, 0x2e, 0x96, 0x1a, 0x87, 0xbc, 0x20, 0x95, 0x3c, 0x88, 0x4b, 0xb1, 0x6c, 0x58, 0x4a, 0x85, 0x63, 0xe9, 0x41, 0x2c, 0x96, 0x4f, 0x24, 0xf3, 0x52, 0x2e, 0x22, 0x65, 0xa4, 0x4c, 0x2e, 0x97, 0xcd, 0xa6, 0x8f, 0xa5, 0x6c, 0x5e, 0xfa, 0x1f, 0x9f, 0xe9, 0x39, 0xb8, 0x4, 0xc9, 0xff, 0xdb, 0x65, 0x7e, 0x17, 0xe5, 0x90, 0xfc, 0xc7, 0x52, 0xdb, 0xe7, 0x3f, 0x93, 0xd2, 0x67, 0xfe, 0x9f, 0x8f, 0x29, 0x8f, 0xcb, 0xff, 0x96, 0x0, 0xbf, 0x52, 0x21, 0xfc, 0xec, 0xbe, 0x7e, 0x96, 0xcf, 0xf2, 0x59, 0x3e, 0xcb, 0x67, 0x59, 0x97, 0xff, 0x2f, 0x0, 0x0, 0xff, 0xff, 0xb9, 0xe, 0xe4, 0x88, 0x0, 0x6c, 0x1, 0x0} diff --git a/src/testing/clients/dumb_core_client.go b/src/testing/clients/dumb_core_client.go index 28b40721cbee..8780c0e4a26d 100644 --- a/src/testing/clients/dumb_core_client.go +++ b/src/testing/clients/dumb_core_client.go @@ -15,7 +15,6 @@ package clients import ( - "github.com/goharbor/harbor/src/chartserver" modelsv2 "github.com/goharbor/harbor/src/controller/artifact" ) @@ -36,19 +35,5 @@ func (d *DumbCoreClient) DeleteArtifact(project, repository, digest string) erro // DeleteArtifactRepository ... func (d *DumbCoreClient) DeleteArtifactRepository(project, repository string) error { return nil -} - -// ListAllCharts ... -func (d *DumbCoreClient) ListAllCharts(project, repository string) ([]*chartserver.ChartVersion, error) { - return nil, nil -} -// DeleteChart ... -func (d *DumbCoreClient) DeleteChart(project, repository, version string) error { - return nil -} - -// DeleteChartRepository ... -func (d *DumbCoreClient) DeleteChartRepository(project, repository string) error { - return nil } diff --git a/src/testing/controller/chartmuseum/controller.go b/src/testing/controller/chartmuseum/controller.go deleted file mode 100644 index f75375bd198e..000000000000 --- a/src/testing/controller/chartmuseum/controller.go +++ /dev/null @@ -1,56 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package chartmuseum - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// Controller is an autogenerated mock type for the Controller type -type Controller struct { - mock.Mock -} - -// Count provides a mock function with given fields: ctx, projectID -func (_m *Controller) Count(ctx context.Context, projectID int64) (int64, error) { - ret := _m.Called(ctx, projectID) - - var r0 int64 - if rf, ok := ret.Get(0).(func(context.Context, int64) int64); ok { - r0 = rf(ctx, projectID) - } else { - r0 = ret.Get(0).(int64) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { - r1 = rf(ctx, projectID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Exist provides a mock function with given fields: ctx, projectID, chartName, version -func (_m *Controller) Exist(ctx context.Context, projectID int64, chartName string, version string) (bool, error) { - ret := _m.Called(ctx, projectID, chartName, version) - - var r0 bool - if rf, ok := ret.Get(0).(func(context.Context, int64, string, string) bool); ok { - r0 = rf(ctx, projectID, chartName, version) - } else { - r0 = ret.Get(0).(bool) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64, string, string) error); ok { - r1 = rf(ctx, projectID, chartName, version) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/src/testing/pkg/chart/operator.go b/src/testing/pkg/chart/operator.go deleted file mode 100644 index eecaab4ef09f..000000000000 --- a/src/testing/pkg/chart/operator.go +++ /dev/null @@ -1,33 +0,0 @@ -package chart - -import ( - "github.com/stretchr/testify/mock" - helm_chart "helm.sh/helm/v3/pkg/chart" - - chartserver "github.com/goharbor/harbor/src/pkg/chart" -) - -// FakeOpertaor ... -type FakeOpertaor struct { - mock.Mock -} - -// GetDetails ... -func (f *FakeOpertaor) GetDetails(content []byte) (*chartserver.VersionDetails, error) { - args := f.Called() - var chartDetails *chartserver.VersionDetails - if args.Get(0) != nil { - chartDetails = args.Get(0).(*chartserver.VersionDetails) - } - return chartDetails, args.Error(1) -} - -// GetData ... -func (f *FakeOpertaor) GetData(content []byte) (*helm_chart.Chart, error) { - args := f.Called() - var chartData *helm_chart.Chart - if args.Get(0) != nil { - chartData = args.Get(0).(*helm_chart.Chart) - } - return chartData, args.Error(1) -} diff --git a/tests/apitests/python/library/base.py b/tests/apitests/python/library/base.py index 16dbc1c0d4fd..acae0189c69c 100644 --- a/tests/apitests/python/library/base.py +++ b/tests/apitests/python/library/base.py @@ -55,7 +55,6 @@ def _create_client(server, credential, debug, api_type="products"): cfg.auth_settings = types.MethodType(lambda self: {}, cfg) return { - "chart": client.ChartRepositoryApi(client.ApiClient(cfg)), "products": swagger_client.ProductsApi(swagger_client.ApiClient(cfg)), "projectv2":v2_swagger_client.ProjectApi(v2_swagger_client.ApiClient(cfg)), "artifact": v2_swagger_client.ArtifactApi(v2_swagger_client.ApiClient(cfg)), diff --git a/tests/apitests/python/library/chart.py b/tests/apitests/python/library/chart.py deleted file mode 100644 index b774a2b44a91..000000000000 --- a/tests/apitests/python/library/chart.py +++ /dev/null @@ -1,46 +0,0 @@ -import base -from client.rest import ApiException - -class Chart(base.Base, object): - def __init__(self): - super(Chart,self).__init__(api_type = "chart") - - def upload_chart(self, repository, chart, prov = None, expect_status_code = 201, **kwargs): - client = self._get_client(**kwargs) - try: - _, status_code, _ = client.chartrepo_repo_charts_post_with_http_info(repository, chart) - except ApiException as e: - base._assert_status_code(expect_status_code, e.status) - else: - base._assert_status_code(expect_status_code, status_code) - base._assert_status_code(201, status_code) - - def get_charts(self, repository, expect_status_code = 200, **kwargs): - client = self._get_client(**kwargs) - try: - body, status_code, _ = client.chartrepo_repo_charts_get_with_http_info(repository) - except ApiException as e: - base._assert_status_code(expect_status_code, e.status) - return [] - else: - base._assert_status_code(expect_status_code, status_code) - base._assert_status_code(200, status_code) - return body - - def chart_should_exist(self, repository, chart_name, expect_status_code = 200, **kwargs): - charts_data = self.get_charts(repository, expect_status_code = expect_status_code, **kwargs) - for chart in charts_data: - if chart.name == chart_name: - return True - if expect_status_code == 200: - raise Exception(r"Chart {} does not exist in project {}.".format(chart_name, repository)) - - def delete_chart_with_version(self, repository, chart_name, version, expect_status_code = 200, **kwargs): - client = self._get_client(**kwargs) - try: - _, status_code, _ = client.chartrepo_repo_charts_name_version_delete_with_http_info(repository, chart_name, version) - except ApiException as e: - base._assert_status_code(expect_status_code, e.status) - else: - base._assert_status_code(expect_status_code, status_code) - base._assert_status_code(200, status_code) \ No newline at end of file diff --git a/tests/apitests/python/library/webhook.py b/tests/apitests/python/library/webhook.py index 6f4093fead3b..e19eebf24f77 100644 --- a/tests/apitests/python/library/webhook.py +++ b/tests/apitests/python/library/webhook.py @@ -12,9 +12,7 @@ def __init__(self): def create_webhook(self, project_id, targets, event_types = ["DELETE_ARTIFACT", "PULL_ARTIFACT", "PUSH_ARTIFACT", - "DELETE_CHART", - "DOWNLOAD_CHART", - "UPLOAD_CHART","QUOTA_EXCEED", + "QUOTA_EXCEED", "QUOTA_WARNING","SCANNING_FAILED", "TAG_RETENTION"], name = None, desc = None, enabled = True, diff --git a/tests/apitests/python/test_list_helm_charts.py b/tests/apitests/python/test_list_helm_charts.py deleted file mode 100644 index 6876e3801420..000000000000 --- a/tests/apitests/python/test_list_helm_charts.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import absolute_import - -import unittest - -from testutils import ADMIN_CLIENT, CHART_API_CLIENT, suppress_urllib3_warning -from testutils import TEARDOWN -import base -from library.user import User -from library.project import Project -from library.chart import Chart - -class TestProjects(unittest.TestCase): - @suppress_urllib3_warning - def setUp(self): - self.chart= Chart() - self.project= Project() - self.user= User() - - @unittest.skipIf(TEARDOWN == False, "Test data won't be erased.") - def tearDown(self): - #1. Delete chart file; - self.chart.delete_chart_with_version(TestProjects.project_chart_name, TestProjects.CHART_NAME, TestProjects.VERSION, **CHART_API_CLIENT) - - #2. Delete project(PA); - self.project.delete_project(TestProjects.project_chart_id, **TestProjects.USER_CHART_CLIENT) - - #3. Delete user(UA); - self.user.delete_user(TestProjects.user_chart_id, **ADMIN_CLIENT) - - def testListHelmCharts(self): - """ - Test case: - List Helm Charts - Test step and expected result: - 1. Create a new user(UA); - 2. Create a new project(PA) by user(UA); - 3. Upload a chart file to project(PA); - 4. Chart file should be exist in project(PA). - Tear down: - 1. Delete chart file; - 2. Delete project(PA); - 3. Delete user(UA). - """ - url = ADMIN_CLIENT["endpoint"] - chart_api_url = CHART_API_CLIENT['endpoint'] - user_chart_password = 'Aa123456' - TestProjects.CHART_NAME = 'mariadb' - TestProjects.VERSION = '4.3.1' - - base.run_command( ["curl", r"-o", "./tests/apitests/python/mariadb-4.3.1.tgz", "https://storage.googleapis.com/harbor-builds/bin/charts/mariadb-4.3.1.tgz"]) - #1. Create a new user(UA); - TestProjects.user_chart_id, user_chart_name = self.user.create_user(user_password = user_chart_password, **ADMIN_CLIENT) - - TestProjects.USER_CHART_CLIENT=dict(endpoint = url, username = user_chart_name, password = user_chart_password) - - TestProjects.API_CHART_CLIENT=dict(endpoint = chart_api_url, username = user_chart_name, password = user_chart_password) - - #2. Create a new project(PA) by user(UA); - TestProjects.project_chart_id, TestProjects.project_chart_name = self.project.create_project(metadata = {"public": "false"}, **TestProjects.USER_CHART_CLIENT) - - #3. Upload a chart file to project(PA); - self.chart.upload_chart(TestProjects.project_chart_name, r'./tests/apitests/python/mariadb-{}.tgz'.format(TestProjects.VERSION), **TestProjects.API_CHART_CLIENT) - - #4. Chart file should be exist in project(PA). - self.chart.chart_should_exist(TestProjects.project_chart_name, TestProjects.CHART_NAME, **TestProjects.API_CHART_CLIENT) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/apitests/python/test_push_chart_by_helm2_helm3_with_robot_Account.py b/tests/apitests/python/test_push_chart_by_helm2_helm3_with_robot_Account.py index 5b3ac01dfb6b..174ddb64dd5d 100644 --- a/tests/apitests/python/test_push_chart_by_helm2_helm3_with_robot_Account.py +++ b/tests/apitests/python/test_push_chart_by_helm2_helm3_with_robot_Account.py @@ -11,7 +11,6 @@ from library.robot import Robot from library.project import Project from library.user import User -from library.chart import Chart class TestProjects(unittest.TestCase): @suppress_urllib3_warning @@ -52,7 +51,7 @@ def testPushChartToChartRepoByHelm2WithRobotAccount(self): #1. Create user(UA); TestProjects.user_id, user_name = self.user.create_user(user_password = self.user_push_chart_password, **ADMIN_CLIENT) TestProjects.USER_CLIENT=dict(endpoint = self.url, username = user_name, password = self.user_push_chart_password) - TestProjects.API_CHART_CLIENT=dict(endpoint = self.chart_api_url, username = user_name, password = self.user_push_chart_password) + #2. Create private project(PA) with user(UA); TestProjects.project_id, TestProjects.project_name = self.project.create_project(metadata = {"public": "false"}, **TestProjects.USER_CLIENT) @@ -60,14 +59,8 @@ def testPushChartToChartRepoByHelm2WithRobotAccount(self): #3. Create a new robot account(RA) with full priviliges in project(PA) with user(UA); robot_id, robot_account = self.robot.create_project_robot(TestProjects.project_name, 30 ,**TestProjects.USER_CLIENT) - #4. Push chart to project(PA) by Helm2 CLI with robot account(RA);" - library.helm.helm2_add_repo(self.chart_repo_name, "https://"+harbor_server, TestProjects.project_name, robot_account.name, robot_account.secret) - library.helm.helm2_push(self.chart_repo_name, self.chart_file, TestProjects.project_name, robot_account.name, robot_account.secret) - #5. Get chart repositry from project(PA) successfully; - self.chart.chart_should_exist(TestProjects.project_name, self.CHART_NAME, **TestProjects.API_CHART_CLIENT) - - #6. Push chart to project(PA) by Helm3 CLI with robot account(RA); + #4. Push chart to project(PA) by Helm3 CLI with robot account(RA); chart_cli_ret = library.helm.helm_chart_push_to_harbor(self.chart_file, self.archive, harbor_server, TestProjects.project_name, self.repo_name, self.verion, robot_account.name, robot_account.secret) diff --git a/tests/apitests/python/test_robot_account.py b/tests/apitests/python/test_robot_account.py index e03472e04035..74b1f6ce5ace 100644 --- a/tests/apitests/python/test_robot_account.py +++ b/tests/apitests/python/test_robot_account.py @@ -3,7 +3,7 @@ import sys import unittest -from testutils import ADMIN_CLIENT, CHART_API_CLIENT, TEARDOWN, harbor_server, harbor_url, suppress_urllib3_warning +from testutils import ADMIN_CLIENT, TEARDOWN, harbor_server, harbor_url, suppress_urllib3_warning from testutils import created_user, created_project from library.user import User from library.project import Project @@ -15,7 +15,6 @@ from library.base import _assert_status_code from library.scan import Scan from library.label import Label -from library.chart import Chart import library.helm import base import v2_swagger_client @@ -29,7 +28,6 @@ def setUp(self): self.robot = Robot() self.scan = Scan() self.label = Label() - self.chart= Chart() TestRobotAccount.url = ADMIN_CLIENT["endpoint"] TestRobotAccount.user_ra_password = "Aa123456" @@ -193,9 +191,6 @@ def test_02_SystemlevelRobotAccount(self): 21. Verify the system robot account has no the corresponding right; """ #1. Define a number of access lists; - CHART_FILE_LIST = [dict(name = 'prometheus', version='7.0.2'), dict(name = 'harbor', version='0.2.0')] - for i in range(2): - base.run_command( ["curl", r"-o", "./tests/apitests/python/{}-{}.tgz".format(CHART_FILE_LIST[i]["name"], CHART_FILE_LIST[i]["version"]), "https://storage.googleapis.com/harbor-builds/helm-chart-test-files/{}-{}.tgz".format(CHART_FILE_LIST[i]["name"], CHART_FILE_LIST[i]["version"])]) # In this priviledge check list, make sure that each of lines and rows must # contains both True and False value. @@ -223,7 +218,6 @@ def test_02_SystemlevelRobotAccount(self): system_robot_account_id, system_robot_account = self.robot.create_system_robot(robot_account_Permissions_list, 300) print("system_robot_account:", system_robot_account) SYSTEM_RA_CLIENT = dict(endpoint = TestRobotAccount.url, username = system_robot_account.name, password = system_robot_account.secret) - SYSTEM_RA_CHART_CLIENT = dict(endpoint = CHART_API_CLIENT["endpoint"], username = SYSTEM_RA_CLIENT["username"], password = SYSTEM_RA_CLIENT["password"]) #4. Verify the system robot account has all the corresponding rights; for project_access in project_access_list: @@ -245,23 +239,6 @@ def test_02_SystemlevelRobotAccount(self): else: self.artifact.delete_artifact(project_access["project_name"], repo_name.split('/')[1], tag_for_del, expect_status_code = 403, **SYSTEM_RA_CLIENT) - #Prepare for chart read and delete - self.chart.upload_chart(project_access["project_name"], r'./tests/apitests/python/{}-{}.tgz'.format(CHART_FILE_LIST[1]["name"], CHART_FILE_LIST[1]["version"]), **CHART_API_CLIENT) - if project_access["check_list"][3]: #---helm-chart:read--- - library.helm.helm2_fetch_chart_file("chart_repo_" + base._random_name("repo"), harbor_url, project_access["project_name"], SYSTEM_RA_CLIENT["username"], SYSTEM_RA_CLIENT["password"], CHART_FILE_LIST[1]["name"]) - else: - library.helm.helm2_fetch_chart_file("chart_repo_" + base._random_name("repo"), harbor_url, project_access["project_name"], SYSTEM_RA_CLIENT["username"], SYSTEM_RA_CLIENT["password"], CHART_FILE_LIST[1]["name"], expected_add_repo_error_message = "403 Forbidden") - - if project_access["check_list"][4]: #---helm-chart-version:create--- - self.chart.upload_chart(project_access["project_name"], r'./tests/apitests/python/{}-{}.tgz'.format(CHART_FILE_LIST[0]["name"], CHART_FILE_LIST[0]["version"]), **SYSTEM_RA_CHART_CLIENT) - else: - self.chart.upload_chart(project_access["project_name"], r'./tests/apitests/python/{}-{}.tgz'.format(CHART_FILE_LIST[0]["name"], CHART_FILE_LIST[0]["version"]), expect_status_code = 403, **SYSTEM_RA_CHART_CLIENT) - - if project_access["check_list"][5]: #---helm-chart-version:delete--- - self.chart.delete_chart_with_version(project_access["project_name"], CHART_FILE_LIST[1]["name"], CHART_FILE_LIST[1]["version"], **SYSTEM_RA_CHART_CLIENT) - else: - self.chart.delete_chart_with_version(project_access["project_name"], CHART_FILE_LIST[1]["name"], CHART_FILE_LIST[1]["version"], expect_status_code = 403, **SYSTEM_RA_CHART_CLIENT) - repo_name, tag = push_self_build_image_to_project(project_access["project_name"], harbor_server, ADMIN_CLIENT["username"], ADMIN_CLIENT["password"], "test_create_tag", "latest_1") self.artifact.create_tag(project_access["project_name"], repo_name.split('/')[1], tag, "for_delete", **ADMIN_CLIENT) if project_access["check_list"][6]: #---tag:create--- diff --git a/tests/ci/api_common_install.sh b/tests/ci/api_common_install.sh index 604e5e12ad94..8684ed4f957d 100755 --- a/tests/ci/api_common_install.sh +++ b/tests/ci/api_common_install.sh @@ -55,7 +55,7 @@ then sed "s/# github_token: xxx/github_token: $GITHUB_TOKEN/" -i make/harbor.yml fi -sudo make compile build prepare COMPILETAG=compile_golangimage GOBUILDTAGS="include_oss include_gcs" NOTARYFLAG=true TRIVYFLAG=true CHARTFLAG=true GEN_TLS=true PULL_BASE_FROM_DOCKERHUB=false +sudo make compile build prepare COMPILETAG=compile_golangimage GOBUILDTAGS="include_oss include_gcs" NOTARYFLAG=true TRIVYFLAG=true GEN_TLS=true PULL_BASE_FROM_DOCKERHUB=false # set the debugging env echo "GC_TIME_WINDOW_HOURS=0" | sudo tee -a ./make/common/config/core/env diff --git a/tests/ci/api_run.sh b/tests/ci/api_run.sh index 83f60ffd8bda..e152ff8e5570 100755 --- a/tests/ci/api_run.sh +++ b/tests/ci/api_run.sh @@ -21,7 +21,7 @@ set +e docker ps # run db auth api cases if [ "$1" = 'DB' ]; then - docker run -i --privileged -v $DIR/../../:/drone -v $DIR/../:/ca -w /drone $E2E_IMAGE robot --exclude proxy_cache -v DOCKER_USER:${DOCKER_USER} -v DOCKER_PWD:${DOCKER_PWD} -v ip:$2 -v ip1: -v http_get_ca:false -v HARBOR_PASSWORD:Harbor12345 /drone/tests/robot-cases/Group1-Nightly/Setup.robot /drone/tests/robot-cases/Group0-BAT/API_DB.robot + docker run -i --privileged -v $DIR/../../:/drone -v $DIR/../:/ca -w /drone $E2E_IMAGE robot --exclude proxy_cache --exclude push_chart --exclude push_chart_by_Helm3.7 -v DOCKER_USER:${DOCKER_USER} -v DOCKER_PWD:${DOCKER_PWD} -v ip:$2 -v ip1: -v http_get_ca:false -v HARBOR_PASSWORD:Harbor12345 /drone/tests/robot-cases/Group1-Nightly/Setup.robot /drone/tests/robot-cases/Group0-BAT/API_DB.robot elif [ "$1" = 'PROXY_CACHE' ]; then docker run -i --privileged -v $DIR/../../:/drone -v $DIR/../:/ca -w /drone $E2E_IMAGE robot --include setup --include proxy_cache -v DOCKER_USER:${DOCKER_USER} -v DOCKER_PWD:${DOCKER_PWD} -v ip:$2 -v ip1: -v http_get_ca:false -v HARBOR_PASSWORD:Harbor12345 /drone/tests/robot-cases/Group1-Nightly/Setup.robot /drone/tests/robot-cases/Group0-BAT/API_DB.robot elif [ "$1" = 'LDAP' ]; then diff --git a/tests/ci/distro_installer.sh b/tests/ci/distro_installer.sh index 982c872bdb64..57cf26fccf53 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.19.4 COMPILETAG=compile_golangimage NOTARYFLAG=true CHARTFLAG=true 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.19.4 COMPILETAG=compile_golangimage NOTARYFLAG=true CHARTFLAG=true 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.19.4 COMPILETAG=compile_golangimage NOTARYFLAG=true 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.19.4 COMPILETAG=compile_golangimage NOTARYFLAG=true TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false diff --git a/tests/resources/Harbor-Pages/Project-Helmcharts.robot b/tests/resources/Harbor-Pages/Project-Helmcharts.robot deleted file mode 100644 index a3d468dc512e..000000000000 --- a/tests/resources/Harbor-Pages/Project-Helmcharts.robot +++ /dev/null @@ -1,60 +0,0 @@ -*** Settings *** -Documentation This resource provides any keywords related to the Harbor private registry appliance -Resource ../../resources/Util.robot - -*** Keywords *** - -Switch To Project Charts - Retry Element Click ${project_chart_tabpage} - Retry Wait Until Page Contains Element ${project_chart_list} - -Upload Chart files - ${current_dir}= Run pwd - Run wget ${harbor_chart_file_url} - Run wget ${harbor_chart_prov_file_url} - Run wget ${prometheus_chart_file_url} - - Retry Double Keywords When Error Retry Element Click xpath=${upload_chart_button} Retry Wait Until Page Contains Element xpath=${upload_action_button} - ${prometheus_file_path} Set Variable ${current_dir}/${prometheus_chart_filename} - Choose File xpath=${chart_file_browse} ${prometheus_file_path} - Retry Double Keywords When Error Retry Element Click xpath=${upload_action_button} Retry Wait Until Page Not Contains Element xpath=${upload_action_button} - Retry Double Keywords When Error Retry Element Click xpath=${upload_chart_button} Retry Wait Until Page Contains Element xpath=${upload_action_button} - Retry Wait Until Page Contains ${prometheus_chart_name} - ${harbor_file_path} Set Variable ${current_dir}/${harbor_chart_filename} - ${harbor_prov_file_path} Set Variable ${current_dir}/${harbor_chart_prov_filename} - Choose File xpath=${chart_file_browse} ${harbor_file_path} - Choose File xpath=${chart_prov_browse} ${harbor_prov_file_path} - Retry Double Keywords When Error Retry Element Click xpath=${upload_action_button} Retry Wait Until Page Not Contains Element xpath=${upload_action_button} - Retry Wait Until Page Contains ${harbor_chart_name} - -Go Into Chart Version - [Arguments] ${chart_name} - Retry Element Click xpath=//hbr-helm-chart//a[contains(., '${chart_name}')] - Sleep 3 - -Go Into Chart Detail - [Arguments] ${version_name} - Retry Element Click xpath=//hbr-helm-chart-version//a[contains(., '${version_name}')] - Retry Wait Until Page Contains Element ${chart_detail} - -Download Chart File - [Arguments] ${chart_name} ${chart_filename} - Switch To Project Charts - ${out} Run Keyword And Ignore Error OperatingSystem.File Should Not Exist ${download_directory}/${chart_filename} - Run Keyword If '${out[0]}'=='FAIL' Run rm -rf ${download_directory}/${chart_filename} - Retry File Should Not Exist ${download_directory}/${chart_filename} - Retry Element Click //clr-dg-row[contains(.,'${chart_name}')]//label - Retry Double Keywords When Error Retry Element Click ${download_chart_button} Retry File Should Exist ${download_directory}/${chart_filename} - Retry Element Click //clr-dg-row[contains(.,'${chart_name}')]//label - -Multi-delete Chart Files - [Arguments] @{obj} - Switch To Project Charts - FOR ${obj} IN @{obj} - Retry Element Click //clr-dg-row[contains(.,'${obj}')]//label - END - #Retry Element Click xpath=${version_checkbox} - Retry Double Keywords When Error Retry Element Click xpath=${version_delete} Retry Wait Until Page Contains Element ${version_confirm_delete} - Retry Double Keywords When Error Retry Element Click ${version_confirm_delete} Retry Wait Until Page Not Contains Element xpath=${version_confirm_delete} - Retry Wait Element xpath=//clr-dg-placeholder[contains(.,\"We couldn\'t find any charts!\")] - diff --git a/tests/resources/Harbor-Pages/Project-Helmcharts_Elements.robot b/tests/resources/Harbor-Pages/Project-Helmcharts_Elements.robot deleted file mode 100644 index a512c591ea59..000000000000 --- a/tests/resources/Harbor-Pages/Project-Helmcharts_Elements.robot +++ /dev/null @@ -1,44 +0,0 @@ -*** Settings *** -Documentation This resource provides any keywords related to the Harbor private registry appliance - -*** Variables *** -${project_chart_tabpage} xpath=//project-detail//a[contains(.,'Charts')] -${project_chart_list} xpath=//hbr-helm-chart -${upload_chart_button} //*[@id='helm-chart-upload'] -${download_chart_button} //clr-dg-action-bar/button[contains(.,'Download')] -${chart_file_browse} //*[@id='chart'] -${chart_prov_browse} //*[@id='prov'] -${upload_action_button} //*[@id='upload-chart'] - -${harbor_chart_name} harbor -${harbor_chart_filename} harbor-0.2.0.tgz -${harbor_chart_version} 0.2.0 -${harbor_chart_prov_filename} harbor-0.2.0.tgz.prov -${harbor_chart_file_url} https://storage.googleapis.com/harbor-builds/helm-chart-test-files/harbor-0.2.0.tgz -${harbor_chart_prov_file_url} https://storage.googleapis.com/harbor-builds/helm-chart-test-files/harbor-0.2.0.tgz.prov - -${harbor_helm_name} harbor-helm-1.7.3 -${harbor_helm_filename} harbor-helm-1.7.3.tar.gz -${harbor_helm_version} 1.7.3 -${harbor_helm_package} harbor-1.7.3.tgz - -${prometheus_chart_name} prometheus -${prometheus_chart_filename} prometheus-7.0.2.tgz -${prometheus_chart_version} 7.0.2 -${prometheus_chart_file_url} https://storage.googleapis.com/harbor-builds/helm-chart-test-files/prometheus-7.0.2.tgz -${prometheus_version} //hbr-helm-chart//a[contains(.,'prometheus')] - -${chart_detail} //hbr-chart-detail -${summary_markdown} //*[@id='summary-content']//div[contains(@class,'md-div')] -${summary_container} //*[@id='summary-content']//div[contains(@class,'summary-container')] -${detail_dependency} //*[@id='depend-link'] -${dependency_content} //*[@id='depend-content']/hbr-chart-detail-dependency -${detail_value} //*[@id='value-link'] -${value_content} //*[@id='value-content']/hbr-chart-detail-value - -${version_bread_crumbs} //project-chart-detail//a[contains(.,'Versions')] -${version_checkbox} //clr-dg-row//div[contains(@class,'clr-checkbox-wrapper')]/label -${version_delete} //clr-dg-action-bar/button[contains(.,'DELETE')] -${version_confirm_delete} //clr-modal//button[contains(.,'DELETE')] - -${helmchart_content} //project-detail/project-list-charts/hbr-helm-chart diff --git a/tests/resources/Harbor-Pages/Vulnerability.robot b/tests/resources/Harbor-Pages/Vulnerability.robot index 3539e517d557..d282d4d65329 100644 --- a/tests/resources/Harbor-Pages/Vulnerability.robot +++ b/tests/resources/Harbor-Pages/Vulnerability.robot @@ -44,12 +44,6 @@ Set Vulnerabilty Serverity Scan Is Disabled Retry Wait Until Page Contains Element //button[@id='scan-btn' and @disabled=''] -Move To Summary Chart - Sleep 2 - Wait Until Element Is Visible //hbr-vulnerability-bar//hbr-result-tip-histogram - Mouse Over //hbr-result-tip-histogram - Sleep 1 - Scan Repo #use fail for image can not scan, otherwise use success [Arguments] ${tagname} ${status} diff --git a/tests/resources/Harbor-Util.robot b/tests/resources/Harbor-Util.robot index 7cbab2b7fe51..c0fd33707289 100644 --- a/tests/resources/Harbor-Util.robot +++ b/tests/resources/Harbor-Util.robot @@ -38,35 +38,35 @@ Install Harbor to Test Server Generate Certificate Authority For Chrome Up Harbor - [Arguments] ${with_notary}=true ${with_chartmuseum}=true - ${rc} ${output}= Run And Return Rc And Output make start -e NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} + [Arguments] ${with_notary}=true + ${rc} ${output}= Run And Return Rc And Output make start -e NOTARYFLAG=${with_notary} Log ${rc} Log ${output} Should Be Equal As Integers ${rc} 0 Down Harbor - [Arguments] ${with_notary}=true ${with_chartmuseum}=true - ${rc} ${output}= Run And Return Rc And Output echo "Y" | make down -e NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} + [Arguments] ${with_notary}=true + ${rc} ${output}= Run And Return Rc And Output echo "Y" | make down -e NOTARYFLAG=${with_notary} Log ${rc} Log ${output} Should Be Equal As Integers ${rc} 0 Package Harbor Offline - [Arguments] ${with_notary}=true ${with_chartmuseum}=true ${with_trivy}=true + [Arguments] ${with_notary}=true ${with_trivy}=true Log To Console \nStart Docker Daemon Start Docker Daemon Locally - Log To Console make package_offline GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=%{Harbor_Build_Base_Tag} NPM_REGISTRY=%{NPM_REGISTRY} VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} TRIVYFLAG=${with_trivy} HTTPPROXY= - ${rc} ${output}= Run And Return Rc And Output make package_offline GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=%{Harbor_Build_Base_Tag} NPM_REGISTRY=%{NPM_REGISTRY} VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} TRIVYFLAG=${with_trivy} HTTPPROXY= + Log To Console make package_offline GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=%{Harbor_Build_Base_Tag} NPM_REGISTRY=%{NPM_REGISTRY} VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} TRIVYFLAG=${with_trivy} HTTPPROXY= + ${rc} ${output}= Run And Return Rc And Output make package_offline GOBUILDTAGS="include_oss include_gcs" BASEIMAGETAG=%{Harbor_Build_Base_Tag} NPM_REGISTRY=%{NPM_REGISTRY} VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} TRIVYFLAG=${with_trivy} HTTPPROXY= Log To Console ${rc} Log To Console ${output} Should Be Equal As Integers ${rc} 0 Package Harbor Online - [Arguments] ${with_notary}=true ${with_chartmuseum}=true ${with_trivy}=true + [Arguments] ${with_notary}=true ${with_trivy}=true Log To Console \nStart Docker Daemon Start Docker Daemon Locally - Log To Console \nmake package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} TRIVYFLAG=${with_trivy} HTTPPROXY= - ${rc} ${output}= Run And Return Rc And Output make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} TRIVYFLAG=${with_trivy} HTTPPROXY= + Log To Console \nmake package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} TRIVYFLAG=${with_trivy} HTTPPROXY= + ${rc} ${output}= Run And Return Rc And Output make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=%{Harbor_Assets_Version} PKGVERSIONTAG=%{Harbor_Package_Version} NOTARYFLAG=${with_notary} TRIVYFLAG=${with_trivy} HTTPPROXY= Log ${rc} Log ${output} Should Be Equal As Integers ${rc} 0 @@ -118,8 +118,8 @@ Notary Key Rotate Should Be Equal As Integers ${rc} 0 Prepare - [Arguments] ${with_notary}=true ${with_chartmuseum}=true - ${rc} ${output}= Run And Return Rc And Output make prepare -e NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} + [Arguments] ${with_notary}=true + ${rc} ${output}= Run And Return Rc And Output make prepare -e NOTARYFLAG=${with_notary} Log ${rc} Log ${output} Should Be Equal As Integers ${rc} 0 @@ -152,8 +152,8 @@ Prepare Cert Should Be Equal As Integers ${rc} 0 Compile and Up Harbor With Source Code - [Arguments] ${with_notary}=true ${with_chartmuseum}=true - ${rc} ${output}= Run And Return Rc And Output make install swagger_client NOTARYFLAG=${with_notary} CHARTFLAG=${with_chartmuseum} HTTPPROXY= + [Arguments] ${with_notary}=true + ${rc} ${output}= Run And Return Rc And Output make install swagger_client NOTARYFLAG=${with_notary} HTTPPROXY= Log ${output} Should Be Equal As Integers ${rc} 0 Sleep 20 diff --git a/tests/resources/Helm-Util.robot b/tests/resources/Helm-Util.robot index bff2ff4eef6d..265a8ac5de2d 100644 --- a/tests/resources/Helm-Util.robot +++ b/tests/resources/Helm-Util.robot @@ -23,23 +23,6 @@ Prepare Helm Plugin Wait Unitl Command Success helm plugin install https://github.com/chartmuseum/helm-push Wait Unitl Command Success helm3 plugin install https://github.com/chartmuseum/helm-push -Helm Repo Add - [Arguments] ${harbor_url} ${user} ${pwd} ${project_name}=library ${helm_repo_name}=myrepo - ${rc} ${output}= Run And Return Rc And Output helm repo remove ${project_name} - Log To Console ${output} - Wait Unitl Command Success helm repo add --username=${user} --password=${pwd} ${helm_repo_name} ${harbor_url}/chartrepo/${project_name} - -Helm Repo Push - [Arguments] ${user} ${pwd} ${chart_filename} ${helm_repo_name}=myrepo ${helm_cmd}=helm - ${current_dir}= Run pwd - Run cd ${current_dir} - Run wget ${harbor_chart_file_url} - Wait Unitl Command Success ${helm_cmd} cm-push --username=${user} --password=${pwd} ${chart_filename} ${helm_repo_name} - -Helm Chart Push - [Arguments] ${ip} ${user} ${pwd} ${chart_file} ${archive} ${project} ${repo_name} ${verion} - Wait Unitl Command Success ./tests/robot-cases/Group0-Util/helm_push_chart.sh ${ip} ${user} ${pwd} ${chart_file} ${archive} ${project} ${repo_name} ${verion} - Helm3.7 Registry Login [Arguments] ${ip} ${user} ${password} Wait Unitl Command Success helm3.7 registry login ${ip} -u ${user} -p ${password} diff --git a/tests/resources/Nightly-Util.robot b/tests/resources/Nightly-Util.robot index fb5feaf58a68..56e16f606047 100644 --- a/tests/resources/Nightly-Util.robot +++ b/tests/resources/Nightly-Util.robot @@ -41,7 +41,6 @@ Nightly Test Setup In Photon Log To Console Start Containerd Daemon Locally ... Start Containerd Daemon Locally Log To Console wget mariadb ... - Run wget ${prometheus_chart_file_url} Prepare Helm Plugin Nightly Test Setup In Ubuntu @@ -88,7 +87,6 @@ Collect Logs SSHLibrary.Get File /var/log/harbor/postgresql.log SSHLibrary.Get File /var/log/harbor/notary-server.log SSHLibrary.Get File /var/log/harbor/notary-signer.log - SSHLibrary.Get File /var/log/harbor/chartmuseum.log SSHLibrary.Get File /var/log/harbor/registryctl.log Run rename 's/^/${ip}/' *.log Close All Connections diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot index bb733d8b0ff9..387cb2a200cd 100644 --- a/tests/resources/Util.robot +++ b/tests/resources/Util.robot @@ -40,8 +40,6 @@ Resource Harbor-Pages/Project-Artifact.robot Resource Harbor-Pages/Project-Artifact-Elements.robot Resource Harbor-Pages/Project-Config.robot Resource Harbor-Pages/Project-Config-Elements.robot -Resource Harbor-Pages/Project-Helmcharts.robot -Resource Harbor-Pages/Project-Helmcharts_Elements.robot Resource Harbor-Pages/Project-Copy.robot Resource Harbor-Pages/Project-Copy-Elements.robot Resource Harbor-Pages/Project-Tag-Retention.robot diff --git a/tests/robot-cases/Group0-BAT/API_DB.robot b/tests/robot-cases/Group0-BAT/API_DB.robot index 46145ca2fef2..da91d4ff64f4 100644 --- a/tests/robot-cases/Group0-BAT/API_DB.robot +++ b/tests/robot-cases/Group0-BAT/API_DB.robot @@ -48,10 +48,6 @@ Test Case - User View Logs [Tags] view_logs Harbor API Test ./tests/apitests/python/test_user_view_logs.py -Test Case - List Helm Charts - [Tags] list_helm_charts - Harbor API Test ./tests/apitests/python/test_list_helm_charts.py - Test Case - Assign Sys Admin [Tags] assign_adin Harbor API Test ./tests/apitests/python/test_assign_sys_admin.py @@ -140,10 +136,6 @@ Test Case - Push Artifact With ORAS CLI [Tags] oras Harbor API Test ./tests/apitests/python/test_push_files_by_oras.py -Test Case - Push Chart File To Chart Repository By Helm V2 With Robot Account - [Tags] helm2 - Harbor API Test ./tests/apitests/python/test_push_chart_by_helm2_helm3_with_robot_Account.py - Test Case - Replication From Dockerhub [Tags] replic_dockerhub Harbor API Test ./tests/apitests/python/test_replication_from_dockerhub.py diff --git a/tests/robot-cases/Group1-Nightly/Chartmuseum.robot b/tests/robot-cases/Group1-Nightly/Chartmuseum.robot index 8832b60ead80..7f5bd70cad8b 100644 --- a/tests/robot-cases/Group1-Nightly/Chartmuseum.robot +++ b/tests/robot-cases/Group1-Nightly/Chartmuseum.robot @@ -23,16 +23,6 @@ ${SSH_USER} root ${HARBOR_ADMIN} admin *** Test Cases *** -Test Case - List Helm Charts And Download And Delete Chart Files - Body Of List Helm Charts - -Test Case - Helm CLI Push - Init Chrome Driver - ${user}= Set Variable user004 - ${pwd}= Set Variable Test1@34 - Sign In Harbor ${HARBOR_URL} ${user} ${pwd} - Helm CLI Push Without Sign In Harbor ${user} ${pwd} - Test Case - Helm3 CLI Push Init Chrome Driver ${user}= Set Variable user004 diff --git a/tests/robot-cases/Group1-Nightly/Common.robot b/tests/robot-cases/Group1-Nightly/Common.robot index 17e6baabfe38..b0f49ac14188 100644 --- a/tests/robot-cases/Group1-Nightly/Common.robot +++ b/tests/robot-cases/Group1-Nightly/Common.robot @@ -712,29 +712,6 @@ Test Case - Push Docker Manifest Index and Display Go Into Index And Contain Artifacts index_tag${d} total_artifact_count=2 Close Browser -Test Case - Push Helm Chart and Display - Init Chrome Driver - ${d}= Get Current Date result_format=%m%s - ${chart_file}= Set Variable https://storage.googleapis.com/harbor-builds/helm-chart-test-files/harbor-0.2.0.tgz - ${archive}= Set Variable harbor/ - ${verion}= Set Variable 0.2.0 - ${repo_name}= Set Variable harbor_chart_test - - Sign In Harbor ${HARBOR_URL} user010 Test1@34 - Create An New Project And Go Into Project test${d} - - Retry Action Keyword Helm Chart Push ${ip} user010 Test1@34 ${chart_file} ${archive} test${d} ${repo_name} ${verion} - - Go Into Project test${d} - Wait Until Page Contains test${d}/${repo_name} - - Go Into Repo test${d}/${repo_name} - Wait Until Page Contains ${repo_name} - Go Into Project test${d} - Wait Until Page Contains test${d}/${repo_name} - Retry Double Keywords When Error Go Into Repo test${d}/${repo_name} Page Should Contain Element ${tag_table_column_vulnerabilities} - Close Browser - Test Case - Can Not Copy Image In ReadOnly Mode Init Chrome Driver ${random_num1}= Get Current Date result_format=%m%s diff --git a/tests/robot-cases/Group1-Nightly/Upgrade.robot b/tests/robot-cases/Group1-Nightly/Upgrade.robot index 2b0a1456d013..1cd7354a4dfc 100644 --- a/tests/robot-cases/Group1-Nightly/Upgrade.robot +++ b/tests/robot-cases/Group1-Nightly/Upgrade.robot @@ -30,9 +30,6 @@ Test Case - Scan A Tag In The Repo [Tags] trivy Body Of Scan A Tag In The Repo vmware/photon 1.0 -Test Case - List Helm Charts - Body Of List Helm Charts - Test Case - Admin Push Signed Image [tags] sign_image Body Of Push Signed Image