mirror of https://github.com/goharbor/harbor.git
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
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
protocol: "http"
|
||||
port: 8080
|
||||
log_level: "INFO"
|
||||
|
||||
#https_config:
|
||||
# cert: "server.crt"
|
||||
# key: "server.key"
|
|
@ -0,0 +1,3 @@
|
|||
UI_SECRET=$ui_secret
|
||||
JOBSERVICE_SECRET=$jobservice_secret
|
||||
|
|
@ -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"]
|
|
@ -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"]
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
protocol: "http"
|
||||
port: 1234
|
||||
log_level: "ERROR"
|
||||
|
||||
https_config:
|
||||
cert: "server.crt"
|
||||
key: "server.key"
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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