mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-21 17:55:30 +01:00
Add the registry controller httpserver, it's responsible for controlling (#5265)
docker regsitry. This version has the API to call regsitry GC with jobservice secret. Seprates it into a standalone container as do not want to invoke two processes in one container. It needs to mount the registry storage into this container in order to do GC, and needs to copy the registry binary into it.
This commit is contained in:
parent
8a92019e8e
commit
d5b85a6748
10
Makefile
10
Makefile
@ -136,10 +136,12 @@ GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
||||
GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver
|
||||
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
||||
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
||||
GOBUILDPATH_REGISTRYCTL=$(GOBUILDPATH)/src/registryctl
|
||||
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
||||
GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver
|
||||
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
||||
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
||||
GOBUILDMAKEPATH_REGISTRYCTL=$(GOBUILDMAKEPATH)/dev/registryctl
|
||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||
|
||||
# binary
|
||||
@ -149,6 +151,8 @@ UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||
UIBINARYNAME=harbor_ui
|
||||
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
||||
JOBSERVICEBINARYNAME=harbor_jobservice
|
||||
REGISTRYCTLBINARYPATH=$(MAKEDEVPATH)/registryctl
|
||||
REGISTRYCTLBINARYNAME=harbor_registryctl
|
||||
|
||||
# configfile
|
||||
CONFIGPATH=$(MAKEPATH)
|
||||
@ -179,6 +183,7 @@ DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||
DOCKERIMAGENAME_DB=vmware/harbor-db
|
||||
DOCKERIMAGENAME_CLARITY=vmware/harbor-clarity-ui-builder
|
||||
DOCKERIMAGENAME_REGCTL=vmware/harbor-registryctl
|
||||
|
||||
# docker-compose files
|
||||
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
|
||||
@ -209,6 +214,7 @@ DOCKERSAVE_PARA=$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_REGCTL):$(VERSIONTAG) \
|
||||
vmware/redis-photon:$(REDISVERSION) \
|
||||
vmware/nginx-photon:$(NGINXVERSION) vmware/registry-photon:$(REGISTRYVERSION)-$(VERSIONTAG) \
|
||||
vmware/photon:$(PHOTONVERSION)
|
||||
@ -273,6 +279,10 @@ compile_golangimage: compile_clarity
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
@echo "compiling binary for harbor regsitry controller (golang image)..."
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_REGISTRYCTL) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -o $(GOBUILDMAKEPATH_REGISTRYCTL)/$(REGISTRYCTLBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
compile:check_environment compile_golangimage
|
||||
|
||||
prepare:
|
||||
|
@ -61,4 +61,5 @@ REGISTRY_STORAGE_PROVIDER_NAME=$storage_provider_name
|
||||
READ_ONLY=false
|
||||
SKIP_RELOAD_ENV_PATTERN=$skip_reload_env_pattern
|
||||
RELOAD_KEY=$reload_key
|
||||
LDAP_GROUP_ADMIN_DN=$ldap_group_admin_dn
|
||||
LDAP_GROUP_ADMIN_DN=$ldap_group_admin_dn
|
||||
REGISTRY_CONTROLLER_URL=$registry_controller_url
|
8
make/common/templates/registryctl/config.yml
Normal file
8
make/common/templates/registryctl/config.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
protocol: "http"
|
||||
port: 8080
|
||||
log_level: "INFO"
|
||||
|
||||
#https_config:
|
||||
# cert: "server.crt"
|
||||
# key: "server.key"
|
3
make/common/templates/registryctl/env
Normal file
3
make/common/templates/registryctl/env
Normal file
@ -0,0 +1,3 @@
|
||||
UI_SECRET=$ui_secret
|
||||
JOBSERVICE_SECRET=$jobservice_secret
|
||||
|
12
make/dev/registryctl/Dockerfile
Normal file
12
make/dev/registryctl/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM golang:1.9.2
|
||||
|
||||
MAINTAINER wangyan@vmware.com
|
||||
|
||||
COPY . /go/src/github.com/vmware/harbor
|
||||
|
||||
WORKDIR /go/src/github.com/vmware/harbor/src/registryctl
|
||||
|
||||
RUN go build -a -o /go/bin/harbor_registryctl \
|
||||
&& chmod u+x /go/bin/harbor_registryctl
|
||||
WORKDIR /go/bin/
|
||||
ENTRYPOINT ["/go/bin/harbor_registryctl"]
|
@ -22,8 +22,6 @@ services:
|
||||
- harbor
|
||||
environment:
|
||||
- GODEBUG=netdns=cgo
|
||||
command:
|
||||
["serve", "/etc/registry/config.yml"]
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
@ -31,6 +29,27 @@ services:
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "registry"
|
||||
registryctl:
|
||||
image: vmware/harbor-registryctl:__version__
|
||||
container_name: registryctl
|
||||
env_file:
|
||||
- ./common/config/registryctl/env
|
||||
restart: always
|
||||
volumes:
|
||||
- /data/registry:/storage:z
|
||||
- ./common/config/registry/:/etc/registry/:z
|
||||
- ./common/config/registryctl/config.yml:/etc/registryctl/config.yml:z
|
||||
networks:
|
||||
- harbor
|
||||
environment:
|
||||
- GODEBUG=netdns=cgo
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "registryctl"
|
||||
postgresql:
|
||||
image: vmware/harbor-db:__version__
|
||||
container_name: harbor-db
|
||||
|
@ -71,6 +71,10 @@ DOCKERFILEPATH_REG=$(DOCKERFILEPATH)/registry
|
||||
DOCKERFILENAME_REG=Dockerfile
|
||||
DOCKERIMAGENAME_REG=vmware/registry-photon
|
||||
|
||||
DOCKERFILEPATH_REGISTRYCTL=$(DOCKERFILEPATH)/registryctl
|
||||
DOCKERFILENAME_REGISTRYCTL=Dockerfile
|
||||
DOCKERIMAGENAME_REGISTRYCTL=vmware/harbor-registryctl
|
||||
|
||||
DOCKERFILEPATH_NOTARY=$(DOCKERFILEPATH)/notary
|
||||
DOCKERFILENAME_NOTARYSIGNER=signer.Dockerfile
|
||||
DOCKERIMAGENAME_NOTARYSIGNER=vmware/notary-signer-photon
|
||||
@ -156,6 +160,11 @@ _build_registry:
|
||||
fi
|
||||
@echo "building registry container for photon..."
|
||||
@cd $(DOCKERFILEPATH_REG) && chmod 655 $(DOCKERFILEPATH_REG)/binary/registry && $(DOCKERBUILD) -f $(DOCKERFILEPATH_REG)/$(DOCKERFILENAME_REG) -t $(DOCKERIMAGENAME_REG):$(REGISTRYVERSION)-$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
_build_registryctl:
|
||||
@echo "building registry controller for photon..."
|
||||
$(DOCKERBUILD) -f $(DOCKERFILEPATH_REGISTRYCTL)/$(DOCKERFILENAME_REGISTRYCTL) -t $(DOCKERIMAGENAME_REGISTRYCTL):$(VERSIONTAG) .
|
||||
@rm -rf $(DOCKERFILEPATH_REG)/binary
|
||||
@echo "Done."
|
||||
|
||||
@ -173,7 +182,7 @@ define _get_binary
|
||||
$(WGET) --timeout 30 --no-check-certificate $1 -O $2
|
||||
endef
|
||||
|
||||
build: _build_db _build_adminiserver _build_ui _build_jobservice _build_log _build_nginx _build_registry _build_notary _build_clair _build_redis _build_migrator
|
||||
build: _build_db _build_adminiserver _build_ui _build_jobservice _build_log _build_nginx _build_registry _build_registryctl _build_notary _build_clair _build_redis _build_migrator
|
||||
|
||||
cleanimage:
|
||||
@echo "cleaning image for photon..."
|
||||
|
@ -22,4 +22,4 @@ HEALTHCHECK CMD curl 127.0.0.1:5000/
|
||||
VOLUME ["/var/lib/registry"]
|
||||
EXPOSE 5000
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["/etc/registry/config.yml"]
|
||||
CMD ["/etc/registry/config.yml"]
|
25
make/photon/registryctl/Dockerfile
Normal file
25
make/photon/registryctl/Dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
FROM vmware/photon:1.0
|
||||
|
||||
MAINTAINER wangyan@vmware.com
|
||||
|
||||
RUN tdnf distro-sync -y || echo \
|
||||
&& tdnf erase vim -y \
|
||||
&& tdnf install sudo -y >> /dev/null\
|
||||
&& tdnf clean all \
|
||||
&& groupadd -r -g 10000 harbor && useradd --no-log-init -r -g 10000 -u 10000 harbor \
|
||||
&& mkdir -p /etc/registry
|
||||
|
||||
COPY ./make/photon/registry/binary/registry /usr/bin
|
||||
COPY ./make/photon/registryctl/start.sh /harbor/
|
||||
COPY ./make/dev/registryctl/harbor_registryctl /harbor/
|
||||
|
||||
RUN chmod u+x /harbor/harbor_registryctl \
|
||||
&& chmod u+x /usr/bin/registry \
|
||||
&& chmod u+x /harbor/start.sh
|
||||
|
||||
HEALTHCHECK CMD curl --fail -s http://127.0.0.1:8080/api/health || exit 1
|
||||
|
||||
VOLUME ["/var/lib/registry"]
|
||||
WORKDIR /harbor/
|
||||
|
||||
ENTRYPOINT ["/harbor/start.sh"]
|
20
make/photon/registryctl/start.sh
Normal file
20
make/photon/registryctl/start.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# The directory /var/lib/registry is within the container, and used to store image in CI testing.
|
||||
# So for now we need to chown to it to avoid failure in CI.
|
||||
if [ -d /var/lib/registry ]; then
|
||||
chown 10000:10000 -R /var/lib/registry
|
||||
fi
|
||||
|
||||
if [ -d /storage ]; then
|
||||
if ! stat -c '%u:%g' /storage | grep -q '10000:10000' ; then
|
||||
# 10000 is the id of harbor user/group.
|
||||
# Usually NFS Server does not allow changing owner of the export directory,
|
||||
# so need to skip this step and requires NFS Server admin to set its owner to 10000.
|
||||
chown 10000:10000 -R /storage
|
||||
fi
|
||||
fi
|
||||
|
||||
sudo -E -u \#10000 "/harbor/harbor_registryctl" "-c" "/etc/registryctl/config.yml"
|
15
make/prepare
15
make/prepare
@ -284,7 +284,7 @@ storage_provider_config = rcp.get("configuration", "registry_storage_provider_co
|
||||
# yaml requires 1 or more spaces between the key and value
|
||||
storage_provider_config = storage_provider_config.replace(":", ": ", 1)
|
||||
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||
jobservice_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||
jobservice_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||
|
||||
adminserver_config_dir = os.path.join(config_dir,"adminserver")
|
||||
if not os.path.exists(adminserver_config_dir):
|
||||
@ -295,6 +295,7 @@ ui_certificates_dir = prep_conf_dir(ui_config_dir,"certificates")
|
||||
db_config_dir = prep_conf_dir(config_dir, "db")
|
||||
job_config_dir = prep_conf_dir(config_dir, "jobservice")
|
||||
registry_config_dir = prep_conf_dir(config_dir, "registry")
|
||||
registryctl_config_dir = prep_conf_dir(config_dir, "registryctl")
|
||||
nginx_config_dir = prep_conf_dir (config_dir, "nginx")
|
||||
nginx_conf_d = prep_conf_dir(nginx_config_dir, "conf.d")
|
||||
log_config_dir = prep_conf_dir (config_dir, "log")
|
||||
@ -305,6 +306,8 @@ ui_conf = os.path.join(config_dir, "ui", "app.conf")
|
||||
ui_cert_dir = os.path.join(config_dir, "ui", "certificates")
|
||||
jobservice_conf = os.path.join(config_dir, "jobservice", "config.yml")
|
||||
registry_conf = os.path.join(config_dir, "registry", "config.yml")
|
||||
registryctl_conf_env = os.path.join(config_dir, "registryctl", "env")
|
||||
registryctl_conf_yml = os.path.join(config_dir, "registryctl", "config.yml")
|
||||
db_conf_env = os.path.join(config_dir, "db", "env")
|
||||
job_conf_env = os.path.join(config_dir, "jobservice", "env")
|
||||
nginx_conf = os.path.join(config_dir, "nginx", "nginx.conf")
|
||||
@ -312,6 +315,7 @@ cert_dir = os.path.join(config_dir, "nginx", "cert")
|
||||
log_rotate_config = os.path.join(config_dir, "log", "logrotate.conf")
|
||||
adminserver_url = "http://adminserver:8080"
|
||||
registry_url = "http://registry:5000"
|
||||
registry_controller_url = "http://registryctl:8080"
|
||||
ui_url = "http://ui:8080"
|
||||
token_service_url = "http://ui:8080/service/token"
|
||||
|
||||
@ -404,7 +408,8 @@ render(os.path.join(templates_dir, "adminserver", "env"),
|
||||
clair_url=clair_url,
|
||||
notary_url=notary_url,
|
||||
reload_key=reload_key,
|
||||
skip_reload_env_pattern=skip_reload_env_pattern
|
||||
skip_reload_env_pattern=skip_reload_env_pattern,
|
||||
registry_controller_url = registry_controller_url
|
||||
)
|
||||
|
||||
render(os.path.join(templates_dir, "ui", "env"),
|
||||
@ -461,7 +466,13 @@ render(os.path.join(templates_dir, "log", "logrotate.conf"),
|
||||
log_rotate_count=log_rotate_count,
|
||||
log_rotate_size=log_rotate_size)
|
||||
|
||||
render(os.path.join(templates_dir, "registryctl", "env"),
|
||||
registryctl_conf_env,
|
||||
jobservice_secret=jobservice_secret,
|
||||
ui_secret=ui_secret)
|
||||
|
||||
shutil.copyfile(os.path.join(templates_dir, "ui", "app.conf"), ui_conf)
|
||||
shutil.copyfile(os.path.join(templates_dir, "registryctl", "config.yml"), registryctl_conf_yml)
|
||||
print("Generated configuration file: %s" % ui_conf)
|
||||
|
||||
if auth_mode == "uaa_auth":
|
||||
|
@ -38,75 +38,76 @@ const (
|
||||
ResourceTypeRepository = "r"
|
||||
ResourceTypeImage = "i"
|
||||
|
||||
ExtEndpoint = "ext_endpoint"
|
||||
AUTHMode = "auth_mode"
|
||||
DatabaseType = "database_type"
|
||||
PostGreSQLHOST = "postgresql_host"
|
||||
PostGreSQLPort = "postgresql_port"
|
||||
PostGreSQLUsername = "postgresql_username"
|
||||
PostGreSQLPassword = "postgresql_password"
|
||||
PostGreSQLDatabase = "postgresql_database"
|
||||
PostGreSQLSSLMode = "postgresql_sslmode"
|
||||
SelfRegistration = "self_registration"
|
||||
UIURL = "ui_url"
|
||||
JobServiceURL = "jobservice_url"
|
||||
LDAPURL = "ldap_url"
|
||||
LDAPSearchDN = "ldap_search_dn"
|
||||
LDAPSearchPwd = "ldap_search_password"
|
||||
LDAPBaseDN = "ldap_base_dn"
|
||||
LDAPUID = "ldap_uid"
|
||||
LDAPFilter = "ldap_filter"
|
||||
LDAPScope = "ldap_scope"
|
||||
LDAPTimeout = "ldap_timeout"
|
||||
LDAPVerifyCert = "ldap_verify_cert"
|
||||
LDAPGroupBaseDN = "ldap_group_base_dn"
|
||||
LDAPGroupSearchFilter = "ldap_group_search_filter"
|
||||
LDAPGroupAttributeName = "ldap_group_attribute_name"
|
||||
LDAPGroupSearchScope = "ldap_group_search_scope"
|
||||
TokenServiceURL = "token_service_url"
|
||||
RegistryURL = "registry_url"
|
||||
EmailHost = "email_host"
|
||||
EmailPort = "email_port"
|
||||
EmailUsername = "email_username"
|
||||
EmailPassword = "email_password"
|
||||
EmailFrom = "email_from"
|
||||
EmailSSL = "email_ssl"
|
||||
EmailIdentity = "email_identity"
|
||||
EmailInsecure = "email_insecure"
|
||||
ProjectCreationRestriction = "project_creation_restriction"
|
||||
MaxJobWorkers = "max_job_workers"
|
||||
TokenExpiration = "token_expiration"
|
||||
CfgExpiration = "cfg_expiration"
|
||||
JobLogDir = "job_log_dir"
|
||||
AdminInitialPassword = "admin_initial_password"
|
||||
AdmiralEndpoint = "admiral_url"
|
||||
WithNotary = "with_notary"
|
||||
WithClair = "with_clair"
|
||||
ScanAllPolicy = "scan_all_policy"
|
||||
ClairDBPassword = "clair_db_password"
|
||||
ClairDBHost = "clair_db_host"
|
||||
ClairDBPort = "clair_db_port"
|
||||
ClairDB = "clair_db"
|
||||
ClairDBUsername = "clair_db_username"
|
||||
UAAEndpoint = "uaa_endpoint"
|
||||
UAAClientID = "uaa_client_id"
|
||||
UAAClientSecret = "uaa_client_secret"
|
||||
UAAVerifyCert = "uaa_verify_cert"
|
||||
DefaultClairEndpoint = "http://clair:6060"
|
||||
CfgDriverDB = "db"
|
||||
CfgDriverJSON = "json"
|
||||
NewHarborAdminName = "admin@harbor.local"
|
||||
RegistryStorageProviderName = "registry_storage_provider_name"
|
||||
UserMember = "u"
|
||||
GroupMember = "g"
|
||||
ReadOnly = "read_only"
|
||||
ClairURL = "clair_url"
|
||||
NotaryURL = "notary_url"
|
||||
DefaultAdminserverEndpoint = "http://adminserver:8080"
|
||||
DefaultJobserviceEndpoint = "http://jobservice:8080"
|
||||
DefaultUIEndpoint = "http://ui:8080"
|
||||
DefaultNotaryEndpoint = "http://notary-server:4443"
|
||||
LdapGroupType = 1
|
||||
ReloadKey = "reload_key"
|
||||
LdapGroupAdminDn = "ldap_group_admin_dn"
|
||||
ExtEndpoint = "ext_endpoint"
|
||||
AUTHMode = "auth_mode"
|
||||
DatabaseType = "database_type"
|
||||
PostGreSQLHOST = "postgresql_host"
|
||||
PostGreSQLPort = "postgresql_port"
|
||||
PostGreSQLUsername = "postgresql_username"
|
||||
PostGreSQLPassword = "postgresql_password"
|
||||
PostGreSQLDatabase = "postgresql_database"
|
||||
PostGreSQLSSLMode = "postgresql_sslmode"
|
||||
SelfRegistration = "self_registration"
|
||||
UIURL = "ui_url"
|
||||
JobServiceURL = "jobservice_url"
|
||||
LDAPURL = "ldap_url"
|
||||
LDAPSearchDN = "ldap_search_dn"
|
||||
LDAPSearchPwd = "ldap_search_password"
|
||||
LDAPBaseDN = "ldap_base_dn"
|
||||
LDAPUID = "ldap_uid"
|
||||
LDAPFilter = "ldap_filter"
|
||||
LDAPScope = "ldap_scope"
|
||||
LDAPTimeout = "ldap_timeout"
|
||||
LDAPVerifyCert = "ldap_verify_cert"
|
||||
LDAPGroupBaseDN = "ldap_group_base_dn"
|
||||
LDAPGroupSearchFilter = "ldap_group_search_filter"
|
||||
LDAPGroupAttributeName = "ldap_group_attribute_name"
|
||||
LDAPGroupSearchScope = "ldap_group_search_scope"
|
||||
TokenServiceURL = "token_service_url"
|
||||
RegistryURL = "registry_url"
|
||||
EmailHost = "email_host"
|
||||
EmailPort = "email_port"
|
||||
EmailUsername = "email_username"
|
||||
EmailPassword = "email_password"
|
||||
EmailFrom = "email_from"
|
||||
EmailSSL = "email_ssl"
|
||||
EmailIdentity = "email_identity"
|
||||
EmailInsecure = "email_insecure"
|
||||
ProjectCreationRestriction = "project_creation_restriction"
|
||||
MaxJobWorkers = "max_job_workers"
|
||||
TokenExpiration = "token_expiration"
|
||||
CfgExpiration = "cfg_expiration"
|
||||
JobLogDir = "job_log_dir"
|
||||
AdminInitialPassword = "admin_initial_password"
|
||||
AdmiralEndpoint = "admiral_url"
|
||||
WithNotary = "with_notary"
|
||||
WithClair = "with_clair"
|
||||
ScanAllPolicy = "scan_all_policy"
|
||||
ClairDBPassword = "clair_db_password"
|
||||
ClairDBHost = "clair_db_host"
|
||||
ClairDBPort = "clair_db_port"
|
||||
ClairDB = "clair_db"
|
||||
ClairDBUsername = "clair_db_username"
|
||||
UAAEndpoint = "uaa_endpoint"
|
||||
UAAClientID = "uaa_client_id"
|
||||
UAAClientSecret = "uaa_client_secret"
|
||||
UAAVerifyCert = "uaa_verify_cert"
|
||||
DefaultClairEndpoint = "http://clair:6060"
|
||||
CfgDriverDB = "db"
|
||||
CfgDriverJSON = "json"
|
||||
NewHarborAdminName = "admin@harbor.local"
|
||||
RegistryStorageProviderName = "registry_storage_provider_name"
|
||||
UserMember = "u"
|
||||
GroupMember = "g"
|
||||
ReadOnly = "read_only"
|
||||
ClairURL = "clair_url"
|
||||
NotaryURL = "notary_url"
|
||||
DefaultAdminserverEndpoint = "http://adminserver:8080"
|
||||
DefaultJobserviceEndpoint = "http://jobservice:8080"
|
||||
DefaultUIEndpoint = "http://ui:8080"
|
||||
DefaultNotaryEndpoint = "http://notary-server:4443"
|
||||
LdapGroupType = 1
|
||||
ReloadKey = "reload_key"
|
||||
LdapGroupAdminDn = "ldap_group_admin_dn"
|
||||
DefaultRegistryControllerEndpoint = "http://registryctl:8080"
|
||||
)
|
||||
|
46
src/common/registryctl/client.go
Normal file
46
src/common/registryctl/client.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 registryctl
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/registryctl/client"
|
||||
)
|
||||
|
||||
var (
|
||||
// RegistryCtlClient is a client for registry controller
|
||||
RegistryCtlClient client.Client
|
||||
)
|
||||
|
||||
// Init ...
|
||||
func Init() {
|
||||
initRegistryCtlClient()
|
||||
}
|
||||
|
||||
func initRegistryCtlClient() {
|
||||
registryCtlURL := os.Getenv("REGISTRY_CONTROLLER_URL")
|
||||
if len(registryCtlURL) == 0 {
|
||||
registryCtlURL = common.DefaultRegistryControllerEndpoint
|
||||
}
|
||||
|
||||
log.Infof("initializing client for reigstry %s ...", registryCtlURL)
|
||||
cfg := &client.Config{
|
||||
Secret: os.Getenv("JOBSERVICE_SECRET"),
|
||||
}
|
||||
RegistryCtlClient = client.NewClient(registryCtlURL, cfg)
|
||||
}
|
62
src/common/utils/test/registryctl.go
Normal file
62
src/common/utils/test/registryctl.go
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GCResult ...
|
||||
type GCResult struct {
|
||||
Status bool `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
StartTime time.Time `json:"starttime"`
|
||||
EndTime time.Time `json:"endtime"`
|
||||
}
|
||||
|
||||
// NewRegistryCtl returns a mock registry server
|
||||
func NewRegistryCtl(config map[string]interface{}) (*httptest.Server, error) {
|
||||
m := []*RequestHandlerMapping{}
|
||||
|
||||
gcr := GCResult{true, "hello-world", time.Now(), time.Now()}
|
||||
b, err := json.Marshal(gcr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: b,
|
||||
}
|
||||
|
||||
m = append(m, &RequestHandlerMapping{
|
||||
Method: "GET",
|
||||
Pattern: "/api/health",
|
||||
Handler: Handler(&Response{
|
||||
StatusCode: http.StatusOK,
|
||||
}),
|
||||
})
|
||||
|
||||
m = append(m, &RequestHandlerMapping{
|
||||
Method: "POST",
|
||||
Pattern: "/api/registry/gc",
|
||||
Handler: Handler(resp),
|
||||
})
|
||||
|
||||
return NewServer(m...), nil
|
||||
}
|
44
src/registryctl/api/base.go
Normal file
44
src/registryctl/api/base.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func handleInternalServerError(w http.ResponseWriter) {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError),
|
||||
http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func handleUnauthorized(w http.ResponseWriter) {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized),
|
||||
http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
// response status code will be written automatically if there is an error
|
||||
func writeJSON(w http.ResponseWriter, v interface{}) error {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
handleInternalServerError(w)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
31
src/registryctl/api/base_test.go
Normal file
31
src/registryctl/api/base_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleInternalServerError(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
handleInternalServerError(w)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
}
|
29
src/registryctl/api/health.go
Normal file
29
src/registryctl/api/health.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// Health ...
|
||||
func Health(w http.ResponseWriter, r *http.Request) {
|
||||
if err := writeJSON(w, "healthy"); err != nil {
|
||||
log.Errorf("Failed to write response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
33
src/registryctl/api/health_test.go
Normal file
33
src/registryctl/api/health_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "", nil)
|
||||
Health(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
result, _ := ioutil.ReadAll(w.Body)
|
||||
assert.Equal(t, "\"healthy\"", string(result))
|
||||
}
|
58
src/registryctl/api/registry.go
Normal file
58
src/registryctl/api/registry.go
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"os/exec"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
const (
|
||||
regConf = "/etc/registry/config.yml"
|
||||
)
|
||||
|
||||
// GCResult ...
|
||||
type GCResult struct {
|
||||
Status bool `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
StartTime time.Time `json:"starttime"`
|
||||
EndTime time.Time `json:"endtime"`
|
||||
}
|
||||
|
||||
// StartGC ...
|
||||
func StartGC(w http.ResponseWriter, r *http.Request) {
|
||||
cmd := exec.Command("/bin/bash", "-c", "registry garbage-collect "+regConf)
|
||||
var outBuf, errBuf bytes.Buffer
|
||||
cmd.Stdout = &outBuf
|
||||
cmd.Stderr = &errBuf
|
||||
|
||||
start := time.Now()
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Errorf("Fail to execute GC: %v, command err: %s", err, errBuf.String())
|
||||
handleInternalServerError(w)
|
||||
return
|
||||
}
|
||||
|
||||
gcr := GCResult{true, outBuf.String(), start, time.Now()}
|
||||
if err := writeJSON(w, gcr); err != nil {
|
||||
log.Errorf("failed to write response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
32
src/registryctl/auth/auth.go
Normal file
32
src/registryctl/auth/auth.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidCredential is returned when the auth token does not authenticate correctly.
|
||||
ErrInvalidCredential = errors.New("invalid authorization credential")
|
||||
)
|
||||
|
||||
// AuthenticationHandler is an interface for authorizing a request
|
||||
type AuthenticationHandler interface {
|
||||
|
||||
// AuthorizeRequest ...
|
||||
AuthorizeRequest(req *http.Request) error
|
||||
}
|
63
src/registryctl/auth/secret.go
Normal file
63
src/registryctl/auth/secret.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/common/secret"
|
||||
)
|
||||
|
||||
//HarborSecret is the prefix of the value of Authorization header.
|
||||
const HarborSecret = secret.HeaderPrefix
|
||||
|
||||
var (
|
||||
// ErrNoSecret ...
|
||||
ErrNoSecret = errors.New("no secret auth credentials")
|
||||
)
|
||||
|
||||
type secretHandler struct {
|
||||
secrets map[string]string
|
||||
}
|
||||
|
||||
// NewSecretHandler creaters a new authentiation handler which adds
|
||||
// basic authentication credentials to a request.
|
||||
func NewSecretHandler(secrets map[string]string) AuthenticationHandler {
|
||||
return &secretHandler{
|
||||
secrets: secrets,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *secretHandler) AuthorizeRequest(req *http.Request) error {
|
||||
if len(s.secrets) == 0 || req == nil {
|
||||
return ErrNoSecret
|
||||
}
|
||||
|
||||
auth := req.Header.Get("Authorization")
|
||||
if !strings.HasPrefix(auth, HarborSecret) {
|
||||
return ErrInvalidCredential
|
||||
}
|
||||
secInReq := strings.TrimPrefix(auth, HarborSecret)
|
||||
|
||||
for _, v := range s.secrets {
|
||||
if secInReq == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrInvalidCredential
|
||||
}
|
51
src/registryctl/auth/secret_test.go
Normal file
51
src/registryctl/auth/secret_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
commonsecret "github.com/vmware/harbor/src/common/secret"
|
||||
)
|
||||
|
||||
func TestAuthorizeRequestInvalid(t *testing.T) {
|
||||
secret := "correct"
|
||||
req, err := http.NewRequest("", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
_ = commonsecret.AddToRequest(req, secret)
|
||||
|
||||
authenticator := NewSecretHandler(map[string]string{"secret1": "incorrect"})
|
||||
err = authenticator.AuthorizeRequest(req)
|
||||
assert.Equal(t, err, ErrInvalidCredential)
|
||||
|
||||
}
|
||||
|
||||
func TestAuthorizeRequestValid(t *testing.T) {
|
||||
secret := "correct"
|
||||
req, err := http.NewRequest("", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", err)
|
||||
}
|
||||
_ = commonsecret.AddToRequest(req, secret)
|
||||
|
||||
authenticator := NewSecretHandler(map[string]string{"secret1": "correct"})
|
||||
err = authenticator.AuthorizeRequest(req)
|
||||
assert.Nil(t, err)
|
||||
|
||||
}
|
102
src/registryctl/client/client.go
Normal file
102
src/registryctl/client/client.go
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
common_http "github.com/vmware/harbor/src/common/http"
|
||||
"github.com/vmware/harbor/src/common/http/modifier/auth"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/registryctl/api"
|
||||
)
|
||||
|
||||
// Client defines methods that an Regsitry client should implement
|
||||
type Client interface {
|
||||
// Health tests the connection with registry server
|
||||
Health() error
|
||||
// StartGC enable the gc of registry server
|
||||
StartGC() (*api.GCResult, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
baseURL string
|
||||
client *common_http.Client
|
||||
}
|
||||
|
||||
// Config contains configurations needed for client
|
||||
type Config struct {
|
||||
Secret string
|
||||
}
|
||||
|
||||
// NewClient return an instance of Registry client
|
||||
func NewClient(baseURL string, cfg *Config) Client {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
if !strings.Contains(baseURL, "://") {
|
||||
baseURL = "http://" + baseURL
|
||||
}
|
||||
client := &client{
|
||||
baseURL: baseURL,
|
||||
}
|
||||
if cfg != nil {
|
||||
authorizer := auth.NewSecretAuthorizer(cfg.Secret)
|
||||
client.client = common_http.NewClient(nil, authorizer)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// Health ...
|
||||
func (c *client) Health() error {
|
||||
addr := strings.Split(c.baseURL, "://")[1]
|
||||
if !strings.Contains(addr, ":") {
|
||||
addr = addr + ":80"
|
||||
}
|
||||
return utils.TestTCPConn(addr, 60, 2)
|
||||
}
|
||||
|
||||
// StartGC ...
|
||||
func (c *client) StartGC() (*api.GCResult, error) {
|
||||
url := c.baseURL + "/api/registry/gc"
|
||||
gcr := &api.GCResult{}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Errorf("Failed to start gc: %d", resp.StatusCode)
|
||||
return nil, fmt.Errorf("Failed to start GC: %d", resp.StatusCode)
|
||||
}
|
||||
if err := json.Unmarshal(data, gcr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcr, nil
|
||||
}
|
51
src/registryctl/client/client_test.go
Normal file
51
src/registryctl/client/client_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common/utils/test"
|
||||
)
|
||||
|
||||
var c Client
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
server, err := test.NewRegistryCtl(nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create regsitry: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c = NewClient(server.URL, &Config{})
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TesHealth(t *testing.T) {
|
||||
err := c.Health()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TesStartGC(t *testing.T) {
|
||||
gcr, err := c.StartGC()
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, gcr.Msg, "hello-world")
|
||||
assert.Equal(t, gcr.Status, true)
|
||||
}
|
103
src/registryctl/config/config.go
Normal file
103
src/registryctl/config/config.go
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
//DefaultConfig ...
|
||||
var DefaultConfig = &Configuration{}
|
||||
|
||||
//Configuration loads the configuration of registry controller.
|
||||
type Configuration struct {
|
||||
Protocol string `yaml:"protocol"`
|
||||
Port string `yaml:"port"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
HTTPSConfig struct {
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
} `yaml:"https_config,omitempty"`
|
||||
}
|
||||
|
||||
//Load the configuration options from the specified yaml file.
|
||||
func (c *Configuration) Load(yamlFilePath string, detectEnv bool) error {
|
||||
if len(yamlFilePath) != 0 {
|
||||
//Try to load from file first
|
||||
data, err := ioutil.ReadFile(yamlFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = yaml.Unmarshal(data, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if detectEnv {
|
||||
c.loadEnvs()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//GetLogLevel returns the log level
|
||||
func GetLogLevel() string {
|
||||
return DefaultConfig.LogLevel
|
||||
}
|
||||
|
||||
//GetJobAuthSecret get the auth secret from the env
|
||||
func GetJobAuthSecret() string {
|
||||
return os.Getenv("JOBSERVICE_SECRET")
|
||||
}
|
||||
|
||||
//GetUIAuthSecret get the auth secret of UI side
|
||||
func GetUIAuthSecret() string {
|
||||
return os.Getenv("UI_SECRET")
|
||||
}
|
||||
|
||||
//loadEnvs Load env variables
|
||||
func (c *Configuration) loadEnvs() {
|
||||
prot := os.Getenv("REGISTRYCTL_PROTOCOL")
|
||||
if len(prot) != 0 {
|
||||
c.Protocol = prot
|
||||
}
|
||||
|
||||
p := os.Getenv("PORT")
|
||||
if len(p) != 0 {
|
||||
c.Port = p
|
||||
}
|
||||
|
||||
//Only when protocol is https
|
||||
if c.Protocol == "HTTPS" {
|
||||
cert := os.Getenv("REGISTRYCTL_HTTPS_CERT")
|
||||
if len(cert) != 0 {
|
||||
c.HTTPSConfig.Cert = cert
|
||||
}
|
||||
|
||||
certKey := os.Getenv("REGISTRYCTL_HTTPS_KEY")
|
||||
if len(certKey) != 0 {
|
||||
c.HTTPSConfig.Key = certKey
|
||||
}
|
||||
}
|
||||
|
||||
loggerLevel := os.Getenv("LOG_LEVEL")
|
||||
if len(loggerLevel) != 0 {
|
||||
c.LogLevel = loggerLevel
|
||||
}
|
||||
|
||||
}
|
66
src/registryctl/config/config_test.go
Normal file
66
src/registryctl/config/config_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConfigDoesNotExists(t *testing.T) {
|
||||
cfg := &Configuration{}
|
||||
err := cfg.Load("./config.not-existing.yaml", false)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestConfigLoadingWithEnv(t *testing.T) {
|
||||
os.Setenv("REGISTRYCTL_PROTOCOL", "https")
|
||||
os.Setenv("PORT", "1000")
|
||||
os.Setenv("LOG_LEVEL", "DEBUG")
|
||||
|
||||
cfg := &Configuration{}
|
||||
err := cfg.Load("../config_test.yml", true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "https", cfg.Protocol)
|
||||
assert.Equal(t, "1000", cfg.Port)
|
||||
assert.Equal(t, "DEBUG", cfg.LogLevel)
|
||||
}
|
||||
|
||||
func TestConfigLoadingWithYml(t *testing.T) {
|
||||
cfg := &Configuration{}
|
||||
err := cfg.Load("../config_test.yml", false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "http", cfg.Protocol)
|
||||
assert.Equal(t, "1234", cfg.Port)
|
||||
assert.Equal(t, "ERROR", cfg.LogLevel)
|
||||
}
|
||||
|
||||
func TestGetLogLevel(t *testing.T) {
|
||||
err := DefaultConfig.Load("../config_test.yml", false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "ERROR", GetLogLevel())
|
||||
}
|
||||
|
||||
func TestGetJobAuthSecret(t *testing.T) {
|
||||
os.Setenv("JOBSERVICE_SECRET", "test_job_secret")
|
||||
assert.Equal(t, "test_job_secret", GetJobAuthSecret())
|
||||
}
|
||||
|
||||
func TestGetUIAuthSecret(t *testing.T) {
|
||||
os.Setenv("UI_SECRET", "test_ui_secret")
|
||||
assert.Equal(t, "test_ui_secret", GetUIAuthSecret())
|
||||
}
|
8
src/registryctl/config_test.yml
Normal file
8
src/registryctl/config_test.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
protocol: "http"
|
||||
port: 1234
|
||||
log_level: "ERROR"
|
||||
|
||||
https_config:
|
||||
cert: "server.crt"
|
||||
key: "server.key"
|
82
src/registryctl/handlers/handler.go
Normal file
82
src/registryctl/handlers/handler.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
gorilla_handlers "github.com/gorilla/handlers"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/registryctl/auth"
|
||||
)
|
||||
|
||||
// NewHandlerChain returns a gorilla router which is wrapped by authenticate handler
|
||||
// and logging handler
|
||||
func NewHandlerChain() http.Handler {
|
||||
h := newRouter()
|
||||
secrets := map[string]string{
|
||||
"jobSecret": os.Getenv("JOBSERVICE_SECRET"),
|
||||
}
|
||||
insecureAPIs := map[string]bool{
|
||||
"/api/health": true,
|
||||
}
|
||||
h = newAuthHandler(auth.NewSecretHandler(secrets), h, insecureAPIs)
|
||||
h = gorilla_handlers.LoggingHandler(os.Stdout, h)
|
||||
return h
|
||||
}
|
||||
|
||||
type authHandler struct {
|
||||
authenticator auth.AuthenticationHandler
|
||||
handler http.Handler
|
||||
insecureAPIs map[string]bool
|
||||
}
|
||||
|
||||
func newAuthHandler(authenticator auth.AuthenticationHandler, handler http.Handler, insecureAPIs map[string]bool) http.Handler {
|
||||
return &authHandler{
|
||||
authenticator: authenticator,
|
||||
handler: handler,
|
||||
insecureAPIs: insecureAPIs,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if a.authenticator == nil {
|
||||
log.Errorf("No authenticator found in regsitry controller.")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if a.insecureAPIs != nil && a.insecureAPIs[r.URL.Path] {
|
||||
if a.handler != nil {
|
||||
a.handler.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err := a.authenticator.AuthorizeRequest(r)
|
||||
if err != nil {
|
||||
log.Errorf("failed to authenticate request: %v", err)
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized),
|
||||
http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if a.handler != nil {
|
||||
a.handler.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
}
|
69
src/registryctl/handlers/handler_test.go
Normal file
69
src/registryctl/handlers/handler_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/registryctl/auth"
|
||||
)
|
||||
|
||||
type fakeAuthenticator struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeAuthenticator) AuthorizeRequest(req *http.Request) error {
|
||||
return f.err
|
||||
}
|
||||
|
||||
type fakeHandler struct {
|
||||
responseCode int
|
||||
}
|
||||
|
||||
func (f *fakeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(f.responseCode)
|
||||
}
|
||||
|
||||
func TestNewAuthHandler(t *testing.T) {
|
||||
cases := []struct {
|
||||
authenticator auth.AuthenticationHandler
|
||||
handler http.Handler
|
||||
insecureAPIs map[string]bool
|
||||
responseCode int
|
||||
requestURL string
|
||||
}{
|
||||
{nil, nil, nil, http.StatusInternalServerError, "http://localhost/good"},
|
||||
{&fakeAuthenticator{err: nil}, nil, nil, http.StatusOK, "http://localhost/hello"},
|
||||
{&fakeAuthenticator{err: errors.New("error")}, nil, nil, http.StatusUnauthorized, "http://localhost/hello"},
|
||||
{&fakeAuthenticator{err: nil}, &fakeHandler{http.StatusNotFound}, nil, http.StatusNotFound, "http://localhost/notexsit"}, {&fakeAuthenticator{err: nil}, &fakeHandler{http.StatusOK}, map[string]bool{"/api/insecure": true}, http.StatusOK, "http://localhost/api/insecure"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
handler := newAuthHandler(c.authenticator, c.handler, c.insecureAPIs)
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", c.requestURL, nil)
|
||||
handler.ServeHTTP(w, r)
|
||||
assert.Equal(t, c.responseCode, w.Code, "unexpected response code")
|
||||
}
|
||||
handler := NewHandlerChain()
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "http://localhost/api/health", nil)
|
||||
handler.ServeHTTP(w, r)
|
||||
|
||||
}
|
29
src/registryctl/handlers/router.go
Normal file
29
src/registryctl/handlers/router.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/vmware/harbor/src/registryctl/api"
|
||||
)
|
||||
|
||||
func newRouter() http.Handler {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/api/registry/gc", api.StartGC).Methods("POST")
|
||||
r.HandleFunc("/api/health", api.Health).Methods("GET")
|
||||
return r
|
||||
}
|
91
src/registryctl/main.go
Normal file
91
src/registryctl/main.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/registryctl/config"
|
||||
"github.com/vmware/harbor/src/registryctl/handlers"
|
||||
)
|
||||
|
||||
// RegistryCtl for registry controller
|
||||
type RegistryCtl struct {
|
||||
ServerConf config.Configuration
|
||||
Handler http.Handler
|
||||
}
|
||||
|
||||
// Start the registry controller
|
||||
func (s *RegistryCtl) Start() {
|
||||
regCtl := &http.Server{
|
||||
Addr: ":" + s.ServerConf.Port,
|
||||
Handler: s.Handler,
|
||||
}
|
||||
|
||||
if s.ServerConf.Protocol == "HTTPS" {
|
||||
tlsCfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
}
|
||||
|
||||
regCtl.TLSConfig = tlsCfg
|
||||
regCtl.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0)
|
||||
}
|
||||
|
||||
var err error
|
||||
if s.ServerConf.Protocol == "HTTPS" {
|
||||
err = regCtl.ListenAndServeTLS(s.ServerConf.HTTPSConfig.Cert, s.ServerConf.HTTPSConfig.Key)
|
||||
} else {
|
||||
err = regCtl.ListenAndServe()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
configPath := flag.String("c", "", "Specify the yaml config file path")
|
||||
flag.Parse()
|
||||
|
||||
if configPath == nil || len(*configPath) == 0 {
|
||||
flag.Usage()
|
||||
log.Fatal("Config file should be specified")
|
||||
}
|
||||
|
||||
if err := config.DefaultConfig.Load(*configPath, true); err != nil {
|
||||
log.Fatalf("Failed to load configurations with error: %s\n", err)
|
||||
}
|
||||
|
||||
regCtl := &RegistryCtl{
|
||||
ServerConf: *config.DefaultConfig,
|
||||
Handler: handlers.NewHandlerChain(),
|
||||
}
|
||||
|
||||
regCtl.Start()
|
||||
}
|
Loading…
Reference in New Issue
Block a user