mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-23 00:57:44 +01:00
Merge remote-tracking branch 'upstream/new-ui-with-sync-image' into author
This commit is contained in:
commit
c4734205e4
@ -93,8 +93,8 @@ create table access_log (
|
|||||||
log_id int NOT NULL AUTO_INCREMENT,
|
log_id int NOT NULL AUTO_INCREMENT,
|
||||||
user_id int NOT NULL,
|
user_id int NOT NULL,
|
||||||
project_id int NOT NULL,
|
project_id int NOT NULL,
|
||||||
repo_name varchar (40),
|
repo_name varchar (256),
|
||||||
repo_tag varchar (20),
|
repo_tag varchar (128),
|
||||||
GUID varchar(64),
|
GUID varchar(64),
|
||||||
operation varchar(20) NOT NULL,
|
operation varchar(20) NOT NULL,
|
||||||
op_time timestamp,
|
op_time timestamp,
|
||||||
@ -159,4 +159,4 @@ CREATE TABLE IF NOT EXISTS `alembic_version` (
|
|||||||
`version_num` varchar(32) NOT NULL
|
`version_num` varchar(32) NOT NULL
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
insert into alembic_version values ('0.1.1');
|
insert into alembic_version values ('0.2.0');
|
||||||
|
@ -38,6 +38,10 @@ self_registration = on
|
|||||||
#Number of job workers in job service, default is 3
|
#Number of job workers in job service, default is 3
|
||||||
max_job_workers = 3
|
max_job_workers = 3
|
||||||
|
|
||||||
|
#Toggle on and off to tell job service wheter or not verify the ssl cert
|
||||||
|
#when it tries to access a remote registry
|
||||||
|
verify_remote_cert = on
|
||||||
|
|
||||||
#Turn on or off the customize your certificate for registry's token.
|
#Turn on or off the customize your certificate for registry's token.
|
||||||
#If the value is on, the prepare script will generate new root cert and private key
|
#If the value is on, the prepare script will generate new root cert and private key
|
||||||
#for generating token to access the image in registry.
|
#for generating token to access the image in registry.
|
||||||
|
@ -47,6 +47,7 @@ crt_organizationalunit = rcp.get("configuration", "crt_organizationalunit")
|
|||||||
crt_commonname = rcp.get("configuration", "crt_commonname")
|
crt_commonname = rcp.get("configuration", "crt_commonname")
|
||||||
crt_email = rcp.get("configuration", "crt_email")
|
crt_email = rcp.get("configuration", "crt_email")
|
||||||
max_job_workers = rcp.get("configuration", "max_job_workers")
|
max_job_workers = rcp.get("configuration", "max_job_workers")
|
||||||
|
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
||||||
########
|
########
|
||||||
|
|
||||||
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16))
|
||||||
@ -122,7 +123,8 @@ render(os.path.join(templates_dir, "jobservice", "env"),
|
|||||||
db_password=db_password,
|
db_password=db_password,
|
||||||
ui_secret=ui_secret,
|
ui_secret=ui_secret,
|
||||||
max_job_workers=max_job_workers,
|
max_job_workers=max_job_workers,
|
||||||
ui_url=ui_url)
|
ui_url=ui_url,
|
||||||
|
verify_remote_cert=verify_remote_cert)
|
||||||
|
|
||||||
def validate_crt_subj(dirty_subj):
|
def validate_crt_subj(dirty_subj):
|
||||||
subj_list = [item for item in dirty_subj.strip().split("/") \
|
subj_list = [item for item in dirty_subj.strip().split("/") \
|
||||||
|
@ -3,7 +3,10 @@ MYSQL_PORT=3306
|
|||||||
MYSQL_USR=root
|
MYSQL_USR=root
|
||||||
MYSQL_PWD=$db_password
|
MYSQL_PWD=$db_password
|
||||||
UI_SECRET=$ui_secret
|
UI_SECRET=$ui_secret
|
||||||
HARBOR_URL=$ui_url
|
REGISTRY_URL=http://registry:5000
|
||||||
|
VERIFY_REMOTE_CERT=$verify_remote_cert
|
||||||
MAX_JOB_WORKERS=$max_job_workers
|
MAX_JOB_WORKERS=$max_job_workers
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
|
EXT_ENDPOINT=$ui_url
|
||||||
|
TOKEN_URL=http://ui
|
||||||
|
@ -3,10 +3,11 @@ MYSQL_PORT=3306
|
|||||||
MYSQL_USR=root
|
MYSQL_USR=root
|
||||||
MYSQL_PWD=$db_password
|
MYSQL_PWD=$db_password
|
||||||
REGISTRY_URL=http://registry:5000
|
REGISTRY_URL=http://registry:5000
|
||||||
|
UI_URL=http://ui
|
||||||
CONFIG_PATH=/etc/ui/app.conf
|
CONFIG_PATH=/etc/ui/app.conf
|
||||||
HARBOR_REG_URL=$hostname
|
HARBOR_REG_URL=$hostname
|
||||||
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
HARBOR_ADMIN_PASSWORD=$harbor_admin_password
|
||||||
HARBOR_URL=$hostname
|
HARBOR_URL=$ui_url
|
||||||
AUTH_MODE=$auth_mode
|
AUTH_MODE=$auth_mode
|
||||||
LDAP_URL=$ldap_url
|
LDAP_URL=$ldap_url
|
||||||
LDAP_BASE_DN=$ldap_basedn
|
LDAP_BASE_DN=$ldap_basedn
|
||||||
@ -14,3 +15,5 @@ UI_SECRET=$ui_secret
|
|||||||
SELF_REGISTRATION=$self_registration
|
SELF_REGISTRATION=$self_registration
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
|
EXT_ENDPOINT=$ui_url
|
||||||
|
TOKEN_URL=http://ui
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package api
|
package api
|
||||||
@ -165,7 +165,7 @@ func getRepoList(projectID int64) ([]string, error) {
|
|||||||
uiPwd = "Harbor12345"
|
uiPwd = "Harbor12345"
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
uiURL := config.LocalHarborURL()
|
uiURL := config.LocalUIURL()
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
req, err := http.NewRequest("GET", uiURL+"/api/repositories?project_id="+strconv.Itoa(int(projectID)), nil)
|
req, err := http.NewRequest("GET", uiURL+"/api/repositories?project_id="+strconv.Itoa(int(projectID)), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -159,7 +159,7 @@ func (p *ProjectAPI) List() {
|
|||||||
if len(isPublic) > 0 {
|
if len(isPublic) > 0 {
|
||||||
public, err = strconv.Atoi(isPublic)
|
public, err = strconv.Atoi(isPublic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error parsing public property: %d, error: %v", isPublic, err)
|
log.Errorf("Error parsing public property: %v, error: %v", isPublic, err)
|
||||||
p.CustomAbort(http.StatusBadRequest, "invalid project Id")
|
p.CustomAbort(http.StatusBadRequest, "invalid project Id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package api
|
package api
|
||||||
@ -121,6 +121,16 @@ func (pa *RepPolicyAPI) Post() {
|
|||||||
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
|
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err)
|
||||||
|
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(policies) > 0 {
|
||||||
|
pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target")
|
||||||
|
}
|
||||||
|
|
||||||
pid, err := dao.AddRepPolicy(*policy)
|
pid, err := dao.AddRepPolicy(*policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to add policy to DB, error: %v", err)
|
log.Errorf("Failed to add policy to DB, error: %v", err)
|
||||||
@ -159,6 +169,7 @@ func (pa *RepPolicyAPI) Put() {
|
|||||||
policy.ProjectID = originalPolicy.ProjectID
|
policy.ProjectID = originalPolicy.ProjectID
|
||||||
pa.Validate(policy)
|
pa.Validate(policy)
|
||||||
|
|
||||||
|
// check duplicate name
|
||||||
if policy.Name != originalPolicy.Name {
|
if policy.Name != originalPolicy.Name {
|
||||||
po, err := dao.GetRepPolicyByName(policy.Name)
|
po, err := dao.GetRepPolicyByName(policy.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,6 +183,12 @@ func (pa *RepPolicyAPI) Put() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if policy.TargetID != originalPolicy.TargetID {
|
if policy.TargetID != originalPolicy.TargetID {
|
||||||
|
//target of policy can not be modified when the policy is enabled
|
||||||
|
if originalPolicy.Enabled == 1 {
|
||||||
|
pa.CustomAbort(http.StatusBadRequest, "target of policy can not be modified when the policy is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the existance of target
|
||||||
target, err := dao.GetRepTarget(policy.TargetID)
|
target, err := dao.GetRepTarget(policy.TargetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get target %d: %v", policy.TargetID, err)
|
log.Errorf("failed to get target %d: %v", policy.TargetID, err)
|
||||||
@ -181,67 +198,95 @@ func (pa *RepPolicyAPI) Put() {
|
|||||||
if target == nil {
|
if target == nil {
|
||||||
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
|
pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check duplicate policy with the same project and target
|
||||||
|
policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err)
|
||||||
|
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(policies) > 0 {
|
||||||
|
pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
policy.ID = id
|
policy.ID = id
|
||||||
|
|
||||||
isTargetChanged := !(policy.TargetID == originalPolicy.TargetID)
|
/*
|
||||||
isEnablementChanged := !(policy.Enabled == policy.Enabled)
|
isTargetChanged := !(policy.TargetID == originalPolicy.TargetID)
|
||||||
|
isEnablementChanged := !(policy.Enabled == policy.Enabled)
|
||||||
|
|
||||||
var shouldStop, shouldTrigger bool
|
var shouldStop, shouldTrigger bool
|
||||||
|
|
||||||
// if target and enablement are not changed, do nothing
|
// if target and enablement are not changed, do nothing
|
||||||
if !isTargetChanged && !isEnablementChanged {
|
if !isTargetChanged && !isEnablementChanged {
|
||||||
shouldStop = false
|
|
||||||
shouldTrigger = false
|
|
||||||
} else if !isTargetChanged && isEnablementChanged {
|
|
||||||
// target is not changed, but enablement is changed
|
|
||||||
if policy.Enabled == 0 {
|
|
||||||
shouldStop = true
|
|
||||||
shouldTrigger = false
|
|
||||||
} else {
|
|
||||||
shouldStop = false
|
|
||||||
shouldTrigger = true
|
|
||||||
}
|
|
||||||
} else if isTargetChanged && !isEnablementChanged {
|
|
||||||
// target is changed, but enablement is not changed
|
|
||||||
if policy.Enabled == 0 {
|
|
||||||
// enablement is 0, do nothing
|
|
||||||
shouldStop = false
|
shouldStop = false
|
||||||
shouldTrigger = false
|
shouldTrigger = false
|
||||||
|
} else if !isTargetChanged && isEnablementChanged {
|
||||||
|
// target is not changed, but enablement is changed
|
||||||
|
if policy.Enabled == 0 {
|
||||||
|
shouldStop = true
|
||||||
|
shouldTrigger = false
|
||||||
|
} else {
|
||||||
|
shouldStop = false
|
||||||
|
shouldTrigger = true
|
||||||
|
}
|
||||||
|
} else if isTargetChanged && !isEnablementChanged {
|
||||||
|
// target is changed, but enablement is not changed
|
||||||
|
if policy.Enabled == 0 {
|
||||||
|
// enablement is 0, do nothing
|
||||||
|
shouldStop = false
|
||||||
|
shouldTrigger = false
|
||||||
|
} else {
|
||||||
|
// enablement is 1, so stop original target's jobs
|
||||||
|
// and trigger new target's jobs
|
||||||
|
shouldStop = true
|
||||||
|
shouldTrigger = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// enablement is 1, so stop original target's jobs
|
// both target and enablement are changed
|
||||||
// and trigger new target's jobs
|
|
||||||
shouldStop = true
|
|
||||||
shouldTrigger = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// both target and enablement are changed
|
|
||||||
|
|
||||||
// enablement: 1 -> 0
|
// enablement: 1 -> 0
|
||||||
if policy.Enabled == 0 {
|
if policy.Enabled == 0 {
|
||||||
shouldStop = true
|
shouldStop = true
|
||||||
shouldTrigger = false
|
shouldTrigger = false
|
||||||
} else {
|
} else {
|
||||||
shouldStop = false
|
shouldStop = false
|
||||||
shouldTrigger = true
|
shouldTrigger = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if shouldStop {
|
if shouldStop {
|
||||||
if err := postReplicationAction(id, "stop"); err != nil {
|
if err := postReplicationAction(id, "stop"); err != nil {
|
||||||
log.Errorf("failed to stop replication of %d: %v", id, err)
|
log.Errorf("failed to stop replication of %d: %v", id, err)
|
||||||
|
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
log.Infof("replication of %d has been stopped", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dao.UpdateRepPolicy(policy); err != nil {
|
||||||
|
log.Errorf("failed to update policy %d: %v", id, err)
|
||||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
log.Infof("replication of %d has been stopped", id)
|
|
||||||
}
|
if shouldTrigger {
|
||||||
|
go func() {
|
||||||
|
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
|
||||||
|
log.Errorf("failed to trigger replication of %d: %v", id, err)
|
||||||
|
} else {
|
||||||
|
log.Infof("replication of %d triggered", id)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if err = dao.UpdateRepPolicy(policy); err != nil {
|
if err = dao.UpdateRepPolicy(policy); err != nil {
|
||||||
log.Errorf("failed to update policy %d: %v", id, err)
|
log.Errorf("failed to update policy %d: %v", id, err)
|
||||||
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldTrigger {
|
if policy.Enabled != originalPolicy.Enabled && policy.Enabled == 1 {
|
||||||
go func() {
|
go func() {
|
||||||
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
|
if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
|
||||||
log.Errorf("failed to trigger replication of %d: %v", id, err)
|
log.Errorf("failed to trigger replication of %d: %v", id, err)
|
||||||
|
@ -255,11 +255,13 @@ func (ra *RepositoryAPI) GetManifests() {
|
|||||||
|
|
||||||
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
|
||||||
endpoint := os.Getenv("REGISTRY_URL")
|
endpoint := os.Getenv("REGISTRY_URL")
|
||||||
|
// TODO read variable from config file
|
||||||
|
insecure := true
|
||||||
|
|
||||||
username, password, ok := ra.Ctx.Request.BasicAuth()
|
username, password, ok := ra.Ctx.Request.BasicAuth()
|
||||||
if ok {
|
if ok {
|
||||||
credential := auth.NewBasicAuthCredential(username, password)
|
return newRepositoryClient(endpoint, insecure, username, password,
|
||||||
return registry.NewRepositoryWithCredential(repoName, endpoint, credential)
|
repoName, "repository", repoName, "pull", "push", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
username, err = ra.getUsername()
|
username, err = ra.getUsername()
|
||||||
@ -267,7 +269,8 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return registry.NewRepositoryWithUsername(repoName, endpoint, username)
|
return cache.NewRepositoryClient(endpoint, insecure, username, repoName,
|
||||||
|
"repository", repoName, "pull", "push", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *RepositoryAPI) getUsername() (string, error) {
|
func (ra *RepositoryAPI) getUsername() (string, error) {
|
||||||
@ -327,3 +330,21 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
|||||||
ra.Data["json"] = repos
|
ra.Data["json"] = repos
|
||||||
ra.ServeJSON()
|
ra.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRepositoryClient(endpoint string, insecure bool, username, password, repository, scopeType, scopeName string,
|
||||||
|
scopeActions ...string) (*registry.Repository, error) {
|
||||||
|
|
||||||
|
credential := auth.NewBasicAuthCredential(username, password)
|
||||||
|
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
"github.com/vmware/harbor/utils"
|
"github.com/vmware/harbor/utils"
|
||||||
"github.com/vmware/harbor/utils/log"
|
"github.com/vmware/harbor/utils/log"
|
||||||
registry_util "github.com/vmware/harbor/utils/registry"
|
"github.com/vmware/harbor/utils/registry"
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
)
|
)
|
||||||
@ -92,8 +92,10 @@ func (t *TargetAPI) Ping() {
|
|||||||
password = t.GetString("password")
|
password = t.GetString("password")
|
||||||
}
|
}
|
||||||
|
|
||||||
credential := auth.NewBasicAuthCredential(username, password)
|
// TODO read variable from config file
|
||||||
registry, err := registry_util.NewRegistryWithCredential(endpoint, credential)
|
insecure := true
|
||||||
|
registry, err := newRegistryClient(endpoint, insecure, username, password,
|
||||||
|
"", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// timeout, dns resolve error, connection refused, etc.
|
// timeout, dns resolve error, connection refused, etc.
|
||||||
if urlErr, ok := err.(*url.Error); ok {
|
if urlErr, ok := err.(*url.Error); ok {
|
||||||
@ -190,6 +192,16 @@ func (t *TargetAPI) Post() {
|
|||||||
t.CustomAbort(http.StatusConflict, "name is already used")
|
t.CustomAbort(http.StatusConflict, "name is already used")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ta, err = dao.GetRepTargetByConnInfo(target.URL, target.Username)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get target [ %s %s ]: %v", target.URL, target.Username, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ta != nil {
|
||||||
|
t.CustomAbort(http.StatusConflict, "the connection information[ endpoint, username ] is conflict with other target")
|
||||||
|
}
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
target.Password = utils.ReversibleEncrypt(target.Password)
|
target.Password = utils.ReversibleEncrypt(target.Password)
|
||||||
}
|
}
|
||||||
@ -217,6 +229,24 @@ func (t *TargetAPI) Put() {
|
|||||||
t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
policies, err := dao.GetRepPolicyByTarget(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get policies according target %d: %v", id, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEnabledPolicy := false
|
||||||
|
for _, policy := range policies {
|
||||||
|
if policy.Enabled == 1 {
|
||||||
|
hasEnabledPolicy = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasEnabledPolicy {
|
||||||
|
t.CustomAbort(http.StatusBadRequest, "the target is associated with policy which is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
target := &models.RepTarget{}
|
target := &models.RepTarget{}
|
||||||
t.DecodeJSONReqAndValidate(target)
|
t.DecodeJSONReqAndValidate(target)
|
||||||
|
|
||||||
@ -232,6 +262,18 @@ func (t *TargetAPI) Put() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if target.URL != originalTarget.URL || target.Username != originalTarget.Username {
|
||||||
|
ta, err := dao.GetRepTargetByConnInfo(target.URL, target.Username)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get target [ %s %s ]: %v", target.URL, target.Username, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ta != nil {
|
||||||
|
t.CustomAbort(http.StatusConflict, "the connection information[ endpoint, username ] is conflict with other target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
target.ID = id
|
target.ID = id
|
||||||
|
|
||||||
if len(target.Password) != 0 {
|
if len(target.Password) != 0 {
|
||||||
@ -273,3 +315,44 @@ func (t *TargetAPI) Delete() {
|
|||||||
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string,
|
||||||
|
scopeActions ...string) (*registry.Registry, error) {
|
||||||
|
credential := auth.NewBasicAuthCredential(username, password)
|
||||||
|
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPolicies ...
|
||||||
|
func (t *TargetAPI) ListPolicies() {
|
||||||
|
id := t.GetIDFromURL()
|
||||||
|
|
||||||
|
target, err := dao.GetRepTarget(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get target %d: %v", id, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
if target == nil {
|
||||||
|
t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
policies, err := dao.GetRepPolicyByTarget(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get policies according target %d: %v", id, err)
|
||||||
|
t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Data["json"] = policies
|
||||||
|
t.ServeJSON()
|
||||||
|
}
|
||||||
|
32
controllers/addnew.go
Normal file
32
controllers/addnew.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/dao"
|
||||||
|
"github.com/vmware/harbor/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddNewController handles requests to /add_new
|
||||||
|
type AddNewController struct {
|
||||||
|
BaseController
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get renders the add new page
|
||||||
|
func (anc *AddNewController) Get() {
|
||||||
|
sessionUserID := anc.GetSession("userId")
|
||||||
|
anc.Data["AddNew"] = false
|
||||||
|
if sessionUserID != nil {
|
||||||
|
isAdmin, err := dao.IsAdminRole(sessionUserID.(int))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error occurred in IsAdminRole: %v", err)
|
||||||
|
anc.CustomAbort(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
if isAdmin && anc.AuthMode == "db_auth" {
|
||||||
|
anc.Data["AddNew"] = true
|
||||||
|
anc.Forward("Add User", "sign-up.htm")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anc.CustomAbort(http.StatusUnauthorized, "Status Unauthorized.")
|
||||||
|
}
|
@ -18,6 +18,8 @@ func (omc *OptionalMenuController) Get() {
|
|||||||
sessionUserID := omc.GetSession("userId")
|
sessionUserID := omc.GetSession("userId")
|
||||||
|
|
||||||
var hasLoggedIn bool
|
var hasLoggedIn bool
|
||||||
|
var allowAddNew bool
|
||||||
|
|
||||||
if sessionUserID != nil {
|
if sessionUserID != nil {
|
||||||
hasLoggedIn = true
|
hasLoggedIn = true
|
||||||
userID := sessionUserID.(int)
|
userID := sessionUserID.(int)
|
||||||
@ -31,7 +33,18 @@ func (omc *OptionalMenuController) Get() {
|
|||||||
omc.CustomAbort(http.StatusUnauthorized, "")
|
omc.CustomAbort(http.StatusUnauthorized, "")
|
||||||
}
|
}
|
||||||
omc.Data["Username"] = u.Username
|
omc.Data["Username"] = u.Username
|
||||||
|
|
||||||
|
isAdmin, err := dao.IsAdminRole(sessionUserID.(int))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error occurred in IsAdminRole: %v", err)
|
||||||
|
omc.CustomAbort(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAdmin && omc.AuthMode == "db_auth" {
|
||||||
|
allowAddNew = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
omc.Data["AddNew"] = allowAddNew
|
||||||
omc.Data["HasLoggedIn"] = hasLoggedIn
|
omc.Data["HasLoggedIn"] = hasLoggedIn
|
||||||
omc.TplName = "optional-menu.htm"
|
omc.TplName = "optional-menu.htm"
|
||||||
omc.Render()
|
omc.Render()
|
||||||
|
@ -32,6 +32,7 @@ func (sic *SignInController) Get() {
|
|||||||
}
|
}
|
||||||
username = u.Username
|
username = u.Username
|
||||||
}
|
}
|
||||||
|
sic.Data["AuthMode"] = sic.AuthMode
|
||||||
sic.Data["Username"] = username
|
sic.Data["Username"] = username
|
||||||
sic.Data["HasLoggedIn"] = hasLoggedIn
|
sic.Data["HasLoggedIn"] = hasLoggedIn
|
||||||
sic.TplName = "sign-in.htm"
|
sic.TplName = "sign-in.htm"
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// SignUpController handles requests to /sign_up
|
// SignUpController handles requests to /sign_up
|
||||||
type SignUpController struct {
|
type SignUpController struct {
|
||||||
BaseController
|
BaseController
|
||||||
@ -7,5 +11,9 @@ type SignUpController struct {
|
|||||||
|
|
||||||
// Get renders sign up page
|
// Get renders sign up page
|
||||||
func (suc *SignUpController) Get() {
|
func (suc *SignUpController) Get() {
|
||||||
|
if suc.AuthMode != "db_auth" {
|
||||||
|
suc.CustomAbort(http.StatusUnauthorized, "Status unauthorized.")
|
||||||
|
}
|
||||||
|
suc.Data["AddNew"] = false
|
||||||
suc.Forward("Sign Up", "sign-up.htm")
|
suc.Forward("Sign Up", "sign-up.htm")
|
||||||
}
|
}
|
||||||
|
@ -926,6 +926,21 @@ func TestGetRepPolicyByTarget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetRepPolicyByProjectAndTarget(t *testing.T) {
|
||||||
|
policies, err := GetRepPolicyByProjectAndTarget(1, targetID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get policy according project %d and target %d: %v", 1, targetID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(policies) == 0 {
|
||||||
|
t.Fatal("unexpected length of policies 0, expected is >0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if policies[0].ID != policyID {
|
||||||
|
t.Fatalf("unexpected policy: %d, expected: %d", policies[0].ID, policyID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetRepPolicyByName(t *testing.T) {
|
func TestGetRepPolicyByName(t *testing.T) {
|
||||||
policy, err := GetRepPolicy(policyID)
|
policy, err := GetRepPolicy(policyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -52,6 +52,20 @@ func GetRepTargetByName(name string) (*models.RepTarget, error) {
|
|||||||
return &t, err
|
return &t, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRepTargetByConnInfo ...
|
||||||
|
func GetRepTargetByConnInfo(endpoint, username string) (*models.RepTarget, error) {
|
||||||
|
o := GetOrmer()
|
||||||
|
t := models.RepTarget{
|
||||||
|
URL: endpoint,
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
err := o.Read(&t, "URL", "Username")
|
||||||
|
if err == orm.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &t, err
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRepTarget ...
|
// DeleteRepTarget ...
|
||||||
func DeleteRepTarget(id int64) error {
|
func DeleteRepTarget(id int64) error {
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
@ -206,6 +220,20 @@ func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) {
|
|||||||
return policies, nil
|
return policies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRepPolicyByProjectAndTarget ...
|
||||||
|
func GetRepPolicyByProjectAndTarget(projectID, targetID int64) ([]*models.RepPolicy, error) {
|
||||||
|
o := GetOrmer()
|
||||||
|
sql := `select * from replication_policy where project_id = ? and target_id = ?`
|
||||||
|
|
||||||
|
var policies []*models.RepPolicy
|
||||||
|
|
||||||
|
if _, err := o.Raw(sql, projectID, targetID).QueryRows(&policies); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return policies, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateRepPolicy ...
|
// UpdateRepPolicy ...
|
||||||
func UpdateRepPolicy(policy *models.RepPolicy) error {
|
func UpdateRepPolicy(policy *models.RepPolicy) error {
|
||||||
o := GetOrmer()
|
o := GetOrmer()
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package config
|
package config
|
||||||
@ -26,9 +26,11 @@ import (
|
|||||||
const defaultMaxWorkers int = 10
|
const defaultMaxWorkers int = 10
|
||||||
|
|
||||||
var maxJobWorkers int
|
var maxJobWorkers int
|
||||||
var localURL string
|
var localUIURL string
|
||||||
|
var localRegURL string
|
||||||
var logDir string
|
var logDir string
|
||||||
var uiSecret string
|
var uiSecret string
|
||||||
|
var verifyRemoteCert string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
maxWorkersEnv := os.Getenv("MAX_JOB_WORKERS")
|
maxWorkersEnv := os.Getenv("MAX_JOB_WORKERS")
|
||||||
@ -39,9 +41,14 @@ func init() {
|
|||||||
maxJobWorkers = defaultMaxWorkers
|
maxJobWorkers = defaultMaxWorkers
|
||||||
}
|
}
|
||||||
|
|
||||||
localURL = os.Getenv("HARBOR_URL")
|
localRegURL = os.Getenv("REGISTRY_URL")
|
||||||
if len(localURL) == 0 {
|
if len(localRegURL) == 0 {
|
||||||
localURL = "http://registry:5000/"
|
localRegURL = "http://registry:5000"
|
||||||
|
}
|
||||||
|
|
||||||
|
localUIURL = os.Getenv("UI_URL")
|
||||||
|
if len(localUIURL) == 0 {
|
||||||
|
localUIURL = "http://ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
logDir = os.Getenv("LOG_DIR")
|
logDir = os.Getenv("LOG_DIR")
|
||||||
@ -67,8 +74,15 @@ func init() {
|
|||||||
panic("UI Secret is not set")
|
panic("UI Secret is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifyRemoteCert = os.Getenv("VERIFY_REMOTE_CERT")
|
||||||
|
if len(verifyRemoteCert) == 0 {
|
||||||
|
verifyRemoteCert = "on"
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
|
||||||
log.Debugf("config: localHarborURL: %s", localURL)
|
log.Debugf("config: localUIURL: %s", localUIURL)
|
||||||
|
log.Debugf("config: localRegURL: %s", localRegURL)
|
||||||
|
log.Debugf("config: verifyRemoteCert: %s", verifyRemoteCert)
|
||||||
log.Debugf("config: logDir: %s", logDir)
|
log.Debugf("config: logDir: %s", logDir)
|
||||||
log.Debugf("config: uiSecret: ******")
|
log.Debugf("config: uiSecret: ******")
|
||||||
}
|
}
|
||||||
@ -78,9 +92,14 @@ func MaxJobWorkers() int {
|
|||||||
return maxJobWorkers
|
return maxJobWorkers
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalHarborURL returns the local registry url, job service will use this URL to pull manifest and repository.
|
// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process
|
||||||
func LocalHarborURL() string {
|
func LocalUIURL() string {
|
||||||
return localURL
|
return localUIURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalRegURL returns the local registry url, job service will use this URL to pull image from the registry
|
||||||
|
func LocalRegURL() string {
|
||||||
|
return localRegURL
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogDir returns the absolute path to which the log file will be written
|
// LogDir returns the absolute path to which the log file will be written
|
||||||
@ -92,3 +111,8 @@ func LogDir() string {
|
|||||||
func UISecret() string {
|
func UISecret() string {
|
||||||
return uiSecret
|
return uiSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry
|
||||||
|
func VerifyRemoteCert() bool {
|
||||||
|
return verifyRemoteCert != "off"
|
||||||
|
}
|
||||||
|
@ -16,13 +16,10 @@
|
|||||||
package replication
|
package replication
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
"github.com/vmware/harbor/utils/log"
|
"github.com/vmware/harbor/utils/log"
|
||||||
|
"github.com/vmware/harbor/utils/registry"
|
||||||
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -39,22 +36,35 @@ type Deleter struct {
|
|||||||
dstUsr string // username ...
|
dstUsr string // username ...
|
||||||
dstPwd string // username ...
|
dstPwd string // username ...
|
||||||
|
|
||||||
|
insecure bool
|
||||||
|
|
||||||
|
dstClient *registry.Repository
|
||||||
|
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeleter returns a Deleter
|
// NewDeleter returns a Deleter
|
||||||
func NewDeleter(repository string, tags []string, dstURL, dstUsr, dstPwd string, logger *log.Logger) *Deleter {
|
func NewDeleter(repository string, tags []string, dstURL, dstUsr, dstPwd string, insecure bool, logger *log.Logger) (*Deleter, error) {
|
||||||
|
dstCred := auth.NewBasicAuthCredential(dstUsr, dstPwd)
|
||||||
|
dstClient, err := newRepositoryClient(dstURL, insecure, dstCred,
|
||||||
|
repository, "repository", repository, "pull", "push", "*")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
deleter := &Deleter{
|
deleter := &Deleter{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
dstURL: dstURL,
|
dstURL: dstURL,
|
||||||
dstUsr: dstUsr,
|
dstUsr: dstUsr,
|
||||||
dstPwd: dstPwd,
|
dstPwd: dstPwd,
|
||||||
|
insecure: insecure,
|
||||||
|
dstClient: dstClient,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
deleter.logger.Infof("initialization completed: repository: %s, tags: %v, destination URL: %s, destination user: %s",
|
deleter.logger.Infof("initialization completed: repository: %s, tags: %v, destination URL: %s, destination user: %s",
|
||||||
deleter.repository, deleter.tags, deleter.dstURL, deleter.dstUsr)
|
deleter.repository, deleter.tags, deleter.dstURL, deleter.dstUsr)
|
||||||
return deleter
|
return deleter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit ...
|
// Exit ...
|
||||||
@ -64,25 +74,22 @@ func (d *Deleter) Exit() error {
|
|||||||
|
|
||||||
// Enter deletes repository or tags
|
// Enter deletes repository or tags
|
||||||
func (d *Deleter) Enter() (string, error) {
|
func (d *Deleter) Enter() (string, error) {
|
||||||
url := strings.TrimRight(d.dstURL, "/") + "/api/repositories/"
|
|
||||||
|
|
||||||
// delete repository
|
|
||||||
if len(d.tags) == 0 {
|
if len(d.tags) == 0 {
|
||||||
u := url + "?repo_name=" + d.repository
|
tags, err := d.dstClient.ListTag()
|
||||||
if err := del(u, d.dstUsr, d.dstPwd); err != nil {
|
if err != nil {
|
||||||
d.logger.Errorf("an error occurred while deleting repository %s on %s with user %s: %v", d.repository, d.dstURL, d.dstUsr, err)
|
d.logger.Errorf("an error occurred while listing tags of repository %s on %s with user %s: %v", d.repository, d.dstURL, d.dstUsr, err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.logger.Infof("repository %s on %s has been deleted", d.repository, d.dstURL)
|
d.tags = append(d.tags, tags...)
|
||||||
|
|
||||||
return models.JobFinished, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// delele tags
|
d.logger.Infof("tags %v will be deleted", d.tags)
|
||||||
|
|
||||||
for _, tag := range d.tags {
|
for _, tag := range d.tags {
|
||||||
u := url + "?repo_name=" + d.repository + "&tag=" + tag
|
|
||||||
if err := del(u, d.dstUsr, d.dstPwd); err != nil {
|
if err := d.dstClient.DeleteTag(tag); err != nil {
|
||||||
d.logger.Errorf("an error occurred while deleting repository %s:%s on %s with user %s: %v", d.repository, tag, d.dstURL, d.dstUsr, err)
|
d.logger.Errorf("an error occurred while deleting repository %s:%s on %s with user %s: %v", d.repository, tag, d.dstURL, d.dstUsr, err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -92,28 +99,3 @@ func (d *Deleter) Enter() (string, error) {
|
|||||||
|
|
||||||
return models.JobFinished, nil
|
return models.JobFinished, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func del(url, username, password string) error {
|
|
||||||
req, err := http.NewRequest("DELETE", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.SetBasicAuth(username, password)
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%d %s", resp.StatusCode, string(b))
|
|
||||||
}
|
|
||||||
|
@ -61,6 +61,8 @@ type BaseHandler struct {
|
|||||||
dstUsr string // username ...
|
dstUsr string // username ...
|
||||||
dstPwd string // password ...
|
dstPwd string // password ...
|
||||||
|
|
||||||
|
insecure bool // whether skip secure check when using https
|
||||||
|
|
||||||
srcClient *registry.Repository
|
srcClient *registry.Repository
|
||||||
dstClient *registry.Repository
|
dstClient *registry.Repository
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ type BaseHandler struct {
|
|||||||
// InitBaseHandler initializes a BaseHandler: creating clients for source and destination registry,
|
// InitBaseHandler initializes a BaseHandler: creating clients for source and destination registry,
|
||||||
// listing tags of the repository if parameter tags is nil.
|
// listing tags of the repository if parameter tags is nil.
|
||||||
func InitBaseHandler(repository, srcURL, srcSecret,
|
func InitBaseHandler(repository, srcURL, srcSecret,
|
||||||
dstURL, dstUsr, dstPwd string, tags []string, logger *log.Logger) (*BaseHandler, error) {
|
dstURL, dstUsr, dstPwd string, insecure bool, tags []string, logger *log.Logger) (*BaseHandler, error) {
|
||||||
|
|
||||||
logger.Infof("initializing: repository: %s, tags: %v, source URL: %s, destination URL: %s, destination user: %s",
|
logger.Infof("initializing: repository: %s, tags: %v, source URL: %s, destination URL: %s, destination user: %s",
|
||||||
repository, tags, srcURL, dstURL, dstUsr)
|
repository, tags, srcURL, dstURL, dstUsr)
|
||||||
@ -96,14 +98,16 @@ func InitBaseHandler(repository, srcURL, srcSecret,
|
|||||||
c := &http.Cookie{Name: models.UISecretCookie, Value: srcSecret}
|
c := &http.Cookie{Name: models.UISecretCookie, Value: srcSecret}
|
||||||
srcCred := auth.NewCookieCredential(c)
|
srcCred := auth.NewCookieCredential(c)
|
||||||
// srcCred := auth.NewBasicAuthCredential("admin", "Harbor12345")
|
// srcCred := auth.NewBasicAuthCredential("admin", "Harbor12345")
|
||||||
srcClient, err := registry.NewRepositoryWithCredential(base.repository, base.srcURL, srcCred)
|
srcClient, err := newRepositoryClient(base.srcURL, base.insecure, srcCred,
|
||||||
|
base.repository, "repository", base.repository, "pull", "push", "*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
base.srcClient = srcClient
|
base.srcClient = srcClient
|
||||||
|
|
||||||
dstCred := auth.NewBasicAuthCredential(base.dstUsr, base.dstPwd)
|
dstCred := auth.NewBasicAuthCredential(base.dstUsr, base.dstPwd)
|
||||||
dstClient, err := registry.NewRepositoryWithCredential(base.repository, base.dstURL, dstCred)
|
dstClient, err := newRepositoryClient(base.dstURL, base.insecure, dstCred,
|
||||||
|
base.repository, "repository", base.repository, "pull", "push", "*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -416,3 +420,34 @@ func (m *ManifestPusher) Enter() (string, error) {
|
|||||||
|
|
||||||
return StatePullManifest, nil
|
return StatePullManifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
|
||||||
|
scopeActions ...string) (*registry.Repository, error) {
|
||||||
|
|
||||||
|
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uam := &userAgentModifier{
|
||||||
|
userAgent: "harbor-registry-client",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store, uam)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type userAgentModifier struct {
|
||||||
|
userAgent string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify adds user-agent header to the request
|
||||||
|
func (u *userAgentModifier) Modify(req *http.Request) error {
|
||||||
|
req.Header.Set(http.CanonicalHeaderKey("User-Agent"), u.userAgent)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package job
|
package job
|
||||||
@ -38,6 +38,7 @@ type RepJobParm struct {
|
|||||||
Tags []string
|
Tags []string
|
||||||
Enabled int
|
Enabled int
|
||||||
Operation string
|
Operation string
|
||||||
|
Insecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SM is the state machine to handle job, it handles one job at a time.
|
// SM is the state machine to handle job, it handles one job at a time.
|
||||||
@ -205,11 +206,12 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
|
return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
|
||||||
}
|
}
|
||||||
sm.Parms = &RepJobParm{
|
sm.Parms = &RepJobParm{
|
||||||
LocalRegURL: config.LocalHarborURL(),
|
LocalRegURL: config.LocalRegURL(),
|
||||||
Repository: job.Repository,
|
Repository: job.Repository,
|
||||||
Tags: job.TagList,
|
Tags: job.TagList,
|
||||||
Enabled: policy.Enabled,
|
Enabled: policy.Enabled,
|
||||||
Operation: job.Operation,
|
Operation: job.Operation,
|
||||||
|
Insecure: !config.VerifyRemoteCert(),
|
||||||
}
|
}
|
||||||
if policy.Enabled == 0 {
|
if policy.Enabled == 0 {
|
||||||
//worker will cancel this job
|
//worker will cancel this job
|
||||||
@ -260,7 +262,7 @@ func (sm *SM) Reset(jid int64) error {
|
|||||||
func addImgTransferTransition(sm *SM) error {
|
func addImgTransferTransition(sm *SM) error {
|
||||||
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
|
||||||
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
|
||||||
sm.Parms.Tags, sm.Logger)
|
sm.Parms.Insecure, sm.Parms.Tags, sm.Logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -274,8 +276,11 @@ func addImgTransferTransition(sm *SM) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addImgDeleteTransition(sm *SM) error {
|
func addImgDeleteTransition(sm *SM) error {
|
||||||
deleter := replication.NewDeleter(sm.Parms.Repository, sm.Parms.Tags, sm.Parms.TargetURL,
|
deleter, err := replication.NewDeleter(sm.Parms.Repository, sm.Parms.Tags, sm.Parms.TargetURL,
|
||||||
sm.Parms.TargetUsername, sm.Parms.TargetPassword, sm.Logger)
|
sm.Parms.TargetUsername, sm.Parms.TargetPassword, sm.Parms.Insecure, sm.Logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
|
sm.AddTransition(models.JobRunning, replication.StateDelete, deleter)
|
||||||
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{DummyHandler{JobID: sm.JobID}, models.JobFinished})
|
||||||
|
@ -17,3 +17,11 @@ Changelog for harbor database schema
|
|||||||
- delete data `AMDRWS` from table `role`
|
- delete data `AMDRWS` from table `role`
|
||||||
- delete data `A` from table `access`
|
- delete data `A` from table `access`
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
- create table `replication_policy`
|
||||||
|
- create table `replication_target`
|
||||||
|
- create table `replication_job`
|
||||||
|
- add column `repo_tag` to table `access_log`
|
||||||
|
- alter column `repo_name` on table `access_log`
|
||||||
|
- alter column `email` on table `user`
|
||||||
|
@ -85,3 +85,42 @@ class Project(Base):
|
|||||||
deleted = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
deleted = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
||||||
public = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
public = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
||||||
owner = relationship(u'User')
|
owner = relationship(u'User')
|
||||||
|
|
||||||
|
class ReplicationPolicy(Base):
|
||||||
|
__tablename__ = "replication_policy"
|
||||||
|
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
name = sa.Column(sa.String(256))
|
||||||
|
project_id = sa.Column(sa.Integer, nullable=False)
|
||||||
|
target_id = sa.Column(sa.Integer, nullable=False)
|
||||||
|
enabled = sa.Column(mysql.TINYINT(1), nullable=False, server_default=sa.text("'1'"))
|
||||||
|
description = sa.Column(sa.Text)
|
||||||
|
cron_str = sa.Column(sa.String(256))
|
||||||
|
start_time = sa.Column(mysql.TIMESTAMP)
|
||||||
|
creation_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP"))
|
||||||
|
update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
|
||||||
|
|
||||||
|
class ReplicationTarget(Base):
|
||||||
|
__tablename__ = "replication_target"
|
||||||
|
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
name = sa.Column(sa.String(64))
|
||||||
|
url = sa.Column(sa.String(64))
|
||||||
|
username = sa.Column(sa.String(40))
|
||||||
|
password = sa.Column(sa.String(40))
|
||||||
|
target_type = sa.Column(mysql.TINYINT(1), nullable=False, server_default=sa.text("'0'"))
|
||||||
|
creation_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP"))
|
||||||
|
update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
|
||||||
|
|
||||||
|
class ReplicationJob(Base):
|
||||||
|
__tablename__ = "replication_job"
|
||||||
|
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
status = sa.Column(sa.String(64), nullable=False)
|
||||||
|
policy_id = sa.Column(sa.Integer, nullable=False)
|
||||||
|
repository = sa.Column(sa.String(256), nullable=False)
|
||||||
|
operation = sa.Column(sa.String(64), nullable=False)
|
||||||
|
tags = sa.Column(sa.String(16384))
|
||||||
|
creation_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP"))
|
||||||
|
update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
|
||||||
|
|
||||||
|
52
migration/migration_harbor/versions/0_2_0.py
Normal file
52
migration/migration_harbor/versions/0_2_0.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (c) 2008-2016 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.
|
||||||
|
|
||||||
|
"""0.1.1 to 0.2.0
|
||||||
|
|
||||||
|
Revision ID: 0.1.1
|
||||||
|
Revises:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0.2.0'
|
||||||
|
down_revision = '0.1.1'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from db_meta import *
|
||||||
|
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
"""
|
||||||
|
update schema&data
|
||||||
|
"""
|
||||||
|
bind = op.get_bind()
|
||||||
|
#alter column user.email, alter column access_log.repo_name, and add column access_log.repo_tag
|
||||||
|
op.alter_column('user', 'email', type_=sa.String(128), existing_type=sa.String(30))
|
||||||
|
op.alter_column('access_log', 'repo_name', type_=sa.String(256), existing_type=sa.String(40))
|
||||||
|
op.add_column('access_log', sa.Column('repo_tag', sa.String(128)))
|
||||||
|
|
||||||
|
#create tables: replication_policy, replication_target, replication_job
|
||||||
|
ReplicationPolicy.__table__.create(bind)
|
||||||
|
ReplicationTarget.__table__.create(bind)
|
||||||
|
ReplicationJob.__table__.create(bind)
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
"""
|
||||||
|
Downgrade has been disabled.
|
||||||
|
"""
|
||||||
|
pass
|
71
service/cache/cache.go
vendored
71
service/cache/cache.go
vendored
@ -21,17 +21,16 @@ import (
|
|||||||
|
|
||||||
"github.com/vmware/harbor/utils/log"
|
"github.com/vmware/harbor/utils/log"
|
||||||
"github.com/vmware/harbor/utils/registry"
|
"github.com/vmware/harbor/utils/registry"
|
||||||
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Cache is the global cache in system.
|
// Cache is the global cache in system.
|
||||||
Cache cache.Cache
|
Cache cache.Cache
|
||||||
endpoint string
|
endpoint string
|
||||||
username string
|
username string
|
||||||
registryClient *registry.Registry
|
|
||||||
repositoryClients map[string]*registry.Repository
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const catalogKey string = "catalog"
|
const catalogKey string = "catalog"
|
||||||
@ -45,23 +44,18 @@ func init() {
|
|||||||
|
|
||||||
endpoint = os.Getenv("REGISTRY_URL")
|
endpoint = os.Getenv("REGISTRY_URL")
|
||||||
username = "admin"
|
username = "admin"
|
||||||
repositoryClients = make(map[string]*registry.Repository, 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshCatalogCache calls registry's API to get repository list and write it to cache.
|
// RefreshCatalogCache calls registry's API to get repository list and write it to cache.
|
||||||
func RefreshCatalogCache() error {
|
func RefreshCatalogCache() error {
|
||||||
log.Debug("refreshing catalog cache...")
|
log.Debug("refreshing catalog cache...")
|
||||||
|
|
||||||
if registryClient == nil {
|
registryClient, err := NewRegistryClient(endpoint, true, username,
|
||||||
var err error
|
"registry", "catalog", "*")
|
||||||
registryClient, err = registry.NewRegistryWithUsername(endpoint, username)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Errorf("error occurred while initializing registry client used by cache: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
rs, err := registryClient.Catalog()
|
rs, err := registryClient.Catalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -70,15 +64,13 @@ func RefreshCatalogCache() error {
|
|||||||
repos := []string{}
|
repos := []string{}
|
||||||
|
|
||||||
for _, repo := range rs {
|
for _, repo := range rs {
|
||||||
rc, ok := repositoryClients[repo]
|
rc, err := NewRepositoryClient(endpoint, true, username,
|
||||||
if !ok {
|
repo, "repository", repo, "pull", "push", "*")
|
||||||
rc, err = registry.NewRepositoryWithUsername(repo, endpoint, username)
|
if err != nil {
|
||||||
if err != nil {
|
log.Errorf("error occurred while initializing repository client used by cache: %s %v", repo, err)
|
||||||
log.Errorf("error occurred while initializing repository client used by cache: %s %v", repo, err)
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
repositoryClients[repo] = rc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := rc.ListTag()
|
tags, err := rc.ListTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error occurred while list tag for %s: %v", repo, err)
|
log.Errorf("error occurred while list tag for %s: %v", repo, err)
|
||||||
@ -112,3 +104,38 @@ func GetRepoFromCache() ([]string, error) {
|
|||||||
}
|
}
|
||||||
return result.([]string), nil
|
return result.([]string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRegistryClient ...
|
||||||
|
func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string,
|
||||||
|
scopeActions ...string) (*registry.Registry, error) {
|
||||||
|
authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRepositoryClient ...
|
||||||
|
func NewRepositoryClient(endpoint string, insecure bool, username, repository, scopeType, scopeName string,
|
||||||
|
scopeActions ...string) (*registry.Repository, error) {
|
||||||
|
|
||||||
|
authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
|
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/vmware/harbor/models"
|
"github.com/vmware/harbor/models"
|
||||||
"github.com/vmware/harbor/service/cache"
|
"github.com/vmware/harbor/service/cache"
|
||||||
"github.com/vmware/harbor/utils/log"
|
"github.com/vmware/harbor/utils/log"
|
||||||
"github.com/vmware/harbor/utils/registry"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
)
|
)
|
||||||
@ -57,7 +56,7 @@ func (n *NotificationHandler) Post() {
|
|||||||
matched = false
|
matched = false
|
||||||
}
|
}
|
||||||
if matched && (strings.HasPrefix(e.Request.UserAgent, "docker") ||
|
if matched && (strings.HasPrefix(e.Request.UserAgent, "docker") ||
|
||||||
strings.ToLower(strings.TrimSpace(e.Request.UserAgent)) == strings.ToLower(registry.UserAgent)) {
|
strings.ToLower(strings.TrimSpace(e.Request.UserAgent)) == "harbor-registry-client") {
|
||||||
username = e.Actor.Name
|
username = e.Actor.Name
|
||||||
action = e.Action
|
action = e.Action
|
||||||
repo = e.Target.Repository
|
repo = e.Target.Repository
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
vm.currentLanguage = I18nService().getCurrentLanguage();
|
vm.currentLanguage = I18nService().getCurrentLanguage();
|
||||||
vm.languageName = I18nService().getLanguageName(vm.currentLanguage);
|
vm.languageName = I18nService().getLanguageName(vm.currentLanguage);
|
||||||
|
|
||||||
|
I18nService().setCurrentLanguage(vm.currentLanguage);
|
||||||
|
|
||||||
console.log('current language:' + vm.languageName);
|
console.log('current language:' + vm.languageName);
|
||||||
|
|
||||||
vm.supportLanguages = I18nService().getSupportLanguages();
|
vm.supportLanguages = I18nService().getSupportLanguages();
|
||||||
|
@ -44,13 +44,13 @@
|
|||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="destinationName" class="col-md-3 control-label">// 'name' | tr //:</label>
|
<label for="destinationName" class="col-md-3 control-label">// 'name' | tr //:</label>
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<select class="form-control form-control-custom" id="destinationName" ng-model="replication.destination.selection" ng-options="d as d.name for d in vm.destinations track by d.id" ng-click="vm.selectDestination(replication.destination.selection)"></select>
|
<select class="form-control form-control-custom" id="destinationName" ng-model="replication.destination.selection" ng-options="d as d.name for d in vm.destinations track by d.id" ng-click="vm.selectDestination(replication.destination.selection)" ng-disabled="!vm.targetEditable"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="endpoint" class="col-md-3 control-label">// 'endpoint' | tr //:</label>
|
<label for="endpoint" class="col-md-3 control-label">// 'endpoint' | tr //:</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="text" class="form-control form-control-custom" id="endpoint" ng-model="replication.destination.endpoint" name="uEndpoint" ng-value="vm.endpoint" required>
|
<input type="text" class="form-control form-control-custom" id="endpoint" ng-model="replication.destination.endpoint" name="uEndpoint" ng-value="vm.endpoint" required ng-disabled="!vm.targetEditable">
|
||||||
<div ng-messages="form.$submitted && form.uEndpoint.$error">
|
<div ng-messages="form.$submitted && form.uEndpoint.$error">
|
||||||
<span ng-message="required">// 'endpoint_is_required' | tr //</span>
|
<span ng-message="required">// 'endpoint_is_required' | tr //</span>
|
||||||
</div>
|
</div>
|
||||||
@ -59,7 +59,7 @@
|
|||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="username" class="col-md-3 control-label">// 'username' | tr //:</label>
|
<label for="username" class="col-md-3 control-label">// 'username' | tr //:</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="text" class="form-control" id="username" ng-model="replication.destination.username" name="uUsername" ng-value="vm.username" required>
|
<input type="text" class="form-control" id="username" ng-model="replication.destination.username" name="uUsername" ng-value="vm.username" required ng-disabled="!vm.targetEditable">
|
||||||
<div ng-messages="form.$submitted && form.uUsername.$error">
|
<div ng-messages="form.$submitted && form.uUsername.$error">
|
||||||
<span ng-message="required">// 'username_is_required' | tr //</span>
|
<span ng-message="required">// 'username_is_required' | tr //</span>
|
||||||
</div>
|
</div>
|
||||||
@ -68,7 +68,7 @@
|
|||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="password" class="col-md-3 control-label">// 'password' | tr //:</label>
|
<label for="password" class="col-md-3 control-label">// 'password' | tr //:</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="password" class="form-control" id="password" ng-model="replication.destination.password" name="uPassword" ng-value="vm.password" required>
|
<input type="password" class="form-control" id="password" ng-model="replication.destination.password" name="uPassword" ng-value="vm.password" required ng-disabled="!vm.targetEditable">
|
||||||
<div ng-messages="form.$submitted && form.uPassword.$error">
|
<div ng-messages="form.$submitted && form.uPassword.$error">
|
||||||
<span ng-message="required">// 'password_is_required' | tr //</span>
|
<span ng-message="required">// 'password_is_required' | tr //</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
.module('harbor.replication')
|
.module('harbor.replication')
|
||||||
.directive('createPolicy', createPolicy);
|
.directive('createPolicy', createPolicy);
|
||||||
|
|
||||||
CreatePolicyController.$inject = ['$scope', 'ListReplicationPolicyService', 'ListDestinationService', 'UpdateDestinationService', 'PingDestinationService', 'CreateReplicationPolicyService', 'UpdateReplicationPolicyService', '$location', 'getParameterByName'];
|
CreatePolicyController.$inject = ['$scope', 'ListReplicationPolicyService', 'ListDestinationService', 'UpdateDestinationService', 'PingDestinationService', 'CreateReplicationPolicyService', 'UpdateReplicationPolicyService', 'ListDestinationPolicyService','$location', 'getParameterByName', '$filter', 'trFilter'];
|
||||||
|
|
||||||
function CreatePolicyController($scope, ListReplicationPolicyService, ListDestinationService, UpdateDestinationService, PingDestinationService, CreateReplicationPolicyService, UpdateReplicationPolicyService, $location, getParameterByName) {
|
function CreatePolicyController($scope, ListReplicationPolicyService, ListDestinationService, UpdateDestinationService, PingDestinationService, CreateReplicationPolicyService, UpdateReplicationPolicyService, ListDestinationPolicyService, $location, getParameterByName, $filter, trFilter) {
|
||||||
var vm = this;
|
var vm = this;
|
||||||
|
|
||||||
//Since can not set value for textarea by using vm
|
//Since can not set value for textarea by using vm
|
||||||
@ -34,6 +34,8 @@
|
|||||||
vm.update = update;
|
vm.update = update;
|
||||||
vm.pingDestination = pingDestination;
|
vm.pingDestination = pingDestination;
|
||||||
|
|
||||||
|
vm.targetEditable = true;
|
||||||
|
|
||||||
$scope.$watch('vm.destinations', function(current) {
|
$scope.$watch('vm.destinations', function(current) {
|
||||||
if(current) {
|
if(current) {
|
||||||
vm1.selection = current[0];
|
vm1.selection = current[0];
|
||||||
@ -43,23 +45,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.$watch('vm.action+","+vm.policyId', function(current) {
|
|
||||||
if(current) {
|
|
||||||
console.log('Current action for replication policy:' + current);
|
|
||||||
var parts = current.split(',');
|
|
||||||
vm.action = parts[0];
|
|
||||||
vm.policyId = Number(parts[1]);
|
|
||||||
switch(parts[0]) {
|
|
||||||
case 'ADD_NEW':
|
|
||||||
vm.addNew();
|
|
||||||
break;
|
|
||||||
case 'EDIT':
|
|
||||||
vm.edit(vm.policyId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function selectDestination(item) {
|
function selectDestination(item) {
|
||||||
vm1.selection = item;
|
vm1.selection = item;
|
||||||
vm1.endpoint = item.endpoint;
|
vm1.endpoint = item.endpoint;
|
||||||
@ -74,6 +59,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addNew() {
|
function addNew() {
|
||||||
|
vm.targetEditable = true;
|
||||||
|
$filter('tr')('add_new_policy', []);
|
||||||
vm0.name = '';
|
vm0.name = '';
|
||||||
vm0.description = '';
|
vm0.description = '';
|
||||||
vm0.enabled = true;
|
vm0.enabled = true;
|
||||||
@ -81,6 +68,9 @@
|
|||||||
|
|
||||||
function edit(policyId) {
|
function edit(policyId) {
|
||||||
console.log('Edit policy ID:' + policyId);
|
console.log('Edit policy ID:' + policyId);
|
||||||
|
vm.policyId = policyId;
|
||||||
|
vm.targetEditable = true;
|
||||||
|
$filter('tr')('edit_policy', []);
|
||||||
ListReplicationPolicyService(policyId)
|
ListReplicationPolicyService(policyId)
|
||||||
.success(listReplicationPolicySuccess)
|
.success(listReplicationPolicySuccess)
|
||||||
.error(listReplicationPolicyFailed);
|
.error(listReplicationPolicyFailed);
|
||||||
@ -129,12 +119,37 @@
|
|||||||
function listDestinationFailed(data, status) {
|
function listDestinationFailed(data, status) {
|
||||||
console.log('Failed list destination:' + data);
|
console.log('Failed list destination:' + data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listDestinationPolicySuccess(data, status) {
|
||||||
|
vm.targetEditable = true;
|
||||||
|
for(var i in data) {
|
||||||
|
if(data[i].enabled === 1) {
|
||||||
|
vm.targetEditable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('current target editable:' + vm.targetEditable + ', policy ID:' + vm.policyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function listDestinationPolicyFailed(data, status) {
|
||||||
|
console.log('Failed list destination policy:' + data);
|
||||||
|
}
|
||||||
|
|
||||||
function listReplicationPolicySuccess(data, status) {
|
function listReplicationPolicySuccess(data, status) {
|
||||||
|
console.log(data);
|
||||||
var replicationPolicy = data;
|
var replicationPolicy = data;
|
||||||
vm0.name = replicationPolicy.name;
|
vm0.name = replicationPolicy.name;
|
||||||
vm0.description = replicationPolicy.description;
|
vm0.description = replicationPolicy.description;
|
||||||
vm0.enabled = replicationPolicy.enabled == 1;
|
vm0.enabled = replicationPolicy.enabled == 1;
|
||||||
vm.targetId = replicationPolicy.target_id;
|
vm.targetId = replicationPolicy.target_id;
|
||||||
|
|
||||||
|
if(vm0.enabled) {
|
||||||
|
vm.targetEditable = false;
|
||||||
|
}else{
|
||||||
|
ListDestinationPolicyService(vm.targetId)
|
||||||
|
.success(listDestinationPolicySuccess)
|
||||||
|
.error(listDestinationPolicyFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function listReplicationPolicyFailed(data, status) {
|
function listReplicationPolicyFailed(data, status) {
|
||||||
console.log('Failed list replication policy:' + data);
|
console.log('Failed list replication policy:' + data);
|
||||||
@ -145,7 +160,7 @@
|
|||||||
}
|
}
|
||||||
function createReplicationPolicyFailed(data, status) {
|
function createReplicationPolicyFailed(data, status) {
|
||||||
if(status === 409) {
|
if(status === 409) {
|
||||||
alert('Policy name already exists.');
|
alert($filter('tr')('policy_already_exists', []));
|
||||||
}
|
}
|
||||||
console.log('Failed create replication policy.');
|
console.log('Failed create replication policy.');
|
||||||
}
|
}
|
||||||
@ -163,10 +178,10 @@
|
|||||||
console.log('Failed update destination.');
|
console.log('Failed update destination.');
|
||||||
}
|
}
|
||||||
function pingDestinationSuccess(data, status) {
|
function pingDestinationSuccess(data, status) {
|
||||||
alert('Successful ping target.');
|
alert($filter('tr')('successful_ping_target', []));
|
||||||
}
|
}
|
||||||
function pingDestinationFailed(data, status) {
|
function pingDestinationFailed(data, status) {
|
||||||
alert('Failed ping target:' + data);
|
alert($filter('tr')('failed_ping_target', []) + ':' + data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,9 +205,20 @@
|
|||||||
function link(scope, element, attr, ctrl) {
|
function link(scope, element, attr, ctrl) {
|
||||||
|
|
||||||
element.find('#createPolicyModal').on('show.bs.modal', function() {
|
element.find('#createPolicyModal').on('show.bs.modal', function() {
|
||||||
ctrl.prepareDestination();
|
|
||||||
scope.form.$setPristine();
|
scope.form.$setPristine();
|
||||||
scope.form.$setUntouched();
|
scope.form.$setUntouched();
|
||||||
|
|
||||||
|
ctrl.prepareDestination();
|
||||||
|
switch(ctrl.action) {
|
||||||
|
case 'ADD_NEW':
|
||||||
|
ctrl.addNew();
|
||||||
|
break;
|
||||||
|
case 'EDIT':
|
||||||
|
ctrl.edit(ctrl.policyId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scope.$apply();
|
||||||
});
|
});
|
||||||
|
|
||||||
ctrl.save = save;
|
ctrl.save = save;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="name" class="col-md-3 control-label">// 'name' | tr //:</label>
|
<label for="name" class="col-md-3 control-label">// 'name' | tr //:</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="text" class="form-control form-control-custom" id="name" ng-model="destination.name" name="uName" required>
|
<input type="text" class="form-control form-control-custom" id="name" ng-model="destination.name" name="uName" required ng-disabled="!vm.editable">
|
||||||
<div ng-messages="form.$submitted && form.uName.$error">
|
<div ng-messages="form.$submitted && form.uName.$error">
|
||||||
<span ng-message="required">// 'name_is_required' | tr //</span>
|
<span ng-message="required">// 'name_is_required' | tr //</span>
|
||||||
</div>
|
</div>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="description" class="col-md-3 control-label">// 'endpoint' | tr //:</label>
|
<label for="description" class="col-md-3 control-label">// 'endpoint' | tr //:</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="text" class="form-control form-control-custom" id="endpoint" ng-model="destination.endpoint" name="uEndpoint" required >
|
<input type="text" class="form-control form-control-custom" id="endpoint" ng-model="destination.endpoint" name="uEndpoint" required ng-disabled="!vm.editable">
|
||||||
<div ng-messages="form.$submitted && form.uEndpoint.$error">
|
<div ng-messages="form.$submitted && form.uEndpoint.$error">
|
||||||
<span ng-message="required">// 'endpoint_is_required' | tr //</span>
|
<span ng-message="required">// 'endpoint_is_required' | tr //</span>
|
||||||
</div>
|
</div>
|
||||||
@ -29,7 +29,7 @@
|
|||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="username" class="col-md-3 control-label">// 'username' | tr //:</label>
|
<label for="username" class="col-md-3 control-label">// 'username' | tr //:</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="text" class="form-control" id="username" ng-model="destination.username" name="uUsername" required>
|
<input type="text" class="form-control" id="username" ng-model="destination.username" name="uUsername" required ng-disabled="!vm.editable">
|
||||||
<div ng-messages="form.$submitted && form.uUsername.$error">
|
<div ng-messages="form.$submitted && form.uUsername.$error">
|
||||||
<span ng-message="required">// 'username_is_required' | tr //</span>
|
<span ng-message="required">// 'username_is_required' | tr //</span>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<div class="form-group col-md-12 form-group-custom">
|
<div class="form-group col-md-12 form-group-custom">
|
||||||
<label for="password" class="col-md-3 control-label">// 'password' | tr //:</label>
|
<label for="password" class="col-md-3 control-label">// 'password' | tr //:</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="password" class="form-control" id="password" ng-model="destination.password" name="uPassword" required>
|
<input type="password" class="form-control" id="password" ng-model="destination.password" name="uPassword" required ng-disabled="!vm.editable">
|
||||||
<div ng-messages="form.$submitted && form.uPassword.$error">
|
<div ng-messages="form.$submitted && form.uPassword.$error">
|
||||||
<span ng-message="required">// 'password_is_required' | tr //</span>
|
<span ng-message="required">// 'password_is_required' | tr //</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
.module('harbor.system.management')
|
.module('harbor.system.management')
|
||||||
.directive('createDestination', createDestination);
|
.directive('createDestination', createDestination);
|
||||||
|
|
||||||
CreateDestinationController.$inject = ['$scope', 'ListDestinationService', 'CreateDestinationService', 'UpdateDestinationService', 'PingDestinationService'];
|
CreateDestinationController.$inject = ['$scope', 'ListDestinationService', 'CreateDestinationService', 'UpdateDestinationService', 'PingDestinationService', 'ListDestinationPolicyService', '$filter', 'trFilter'];
|
||||||
|
|
||||||
function CreateDestinationController($scope, ListDestinationService, CreateDestinationService, UpdateDestinationService, PingDestinationService) {
|
function CreateDestinationController($scope, ListDestinationService, CreateDestinationService, UpdateDestinationService, PingDestinationService, ListDestinationPolicyService, $filter, trFilter) {
|
||||||
var vm = this;
|
var vm = this;
|
||||||
|
|
||||||
$scope.destination = {};
|
$scope.destination = {};
|
||||||
@ -20,25 +20,11 @@
|
|||||||
vm.update = update;
|
vm.update = update;
|
||||||
vm.pingDestination = pingDestination;
|
vm.pingDestination = pingDestination;
|
||||||
|
|
||||||
$scope.$watch('vm.action+","+vm.targetId', function(current) {
|
vm.editable = true;
|
||||||
if(current) {
|
|
||||||
var parts = current.split(',');
|
|
||||||
vm.action = parts[0];
|
|
||||||
vm.targetId = parts[1];
|
|
||||||
switch(vm.action) {
|
|
||||||
case 'ADD_NEW':
|
|
||||||
vm.modalTitle = 'Create destination';
|
|
||||||
vm.addNew();
|
|
||||||
break;
|
|
||||||
case 'EDIT':
|
|
||||||
vm.modalTitle = 'Edit destination';
|
|
||||||
vm.edit(vm.targetId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function addNew() {
|
function addNew() {
|
||||||
|
vm.editable = true;
|
||||||
|
vm.modalTitle = $filter('tr')('add_new_destination', []);
|
||||||
vm0.name = '';
|
vm0.name = '';
|
||||||
vm0.endpoint = '';
|
vm0.endpoint = '';
|
||||||
vm0.username = '';
|
vm0.username = '';
|
||||||
@ -46,7 +32,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function edit(targetId) {
|
function edit(targetId) {
|
||||||
getDestination(targetId);
|
vm.editable = true;
|
||||||
|
vm.modalTitle = $filter('tr')('edit_destination', []);
|
||||||
|
ListDestinationService(targetId)
|
||||||
|
.success(getDestinationSuccess)
|
||||||
|
.error(getDestinationFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(destination) {
|
function create(destination) {
|
||||||
@ -63,7 +53,7 @@
|
|||||||
|
|
||||||
function createDestinationFailed(data, status) {
|
function createDestinationFailed(data, status) {
|
||||||
if(status === 409) {
|
if(status === 409) {
|
||||||
alert('Destination already exists.');
|
alert($filter('tr')('destination_already_exists', []));
|
||||||
}
|
}
|
||||||
console.log('Failed create destination:' + data);
|
console.log('Failed create destination:' + data);
|
||||||
}
|
}
|
||||||
@ -83,11 +73,6 @@
|
|||||||
console.log('Failed update destination.');
|
console.log('Failed update destination.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDestination(targetId) {
|
|
||||||
ListDestinationService(targetId)
|
|
||||||
.success(getDestinationSuccess)
|
|
||||||
.error(getDestinationFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDestinationSuccess(data, status) {
|
function getDestinationSuccess(data, status) {
|
||||||
var destination = data;
|
var destination = data;
|
||||||
@ -95,12 +80,29 @@
|
|||||||
vm0.endpoint = destination.endpoint;
|
vm0.endpoint = destination.endpoint;
|
||||||
vm0.username = destination.username;
|
vm0.username = destination.username;
|
||||||
vm0.password = destination.password;
|
vm0.password = destination.password;
|
||||||
|
|
||||||
|
ListDestinationPolicyService(destination.id)
|
||||||
|
.success(listDestinationPolicySuccess)
|
||||||
|
.error(listDestinationPolicyFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDestinationFailed(data, status) {
|
function getDestinationFailed(data, status) {
|
||||||
console.log('Failed get destination.');
|
console.log('Failed get destination.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listDestinationPolicySuccess(data, status) {
|
||||||
|
for(var i in data) {
|
||||||
|
if(data[i].enabled === 1) {
|
||||||
|
vm.editable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listDestinationPolicyFailed(data, status) {
|
||||||
|
console.log('Failed list destination policy:' + data);
|
||||||
|
}
|
||||||
|
|
||||||
function pingDestination() {
|
function pingDestination() {
|
||||||
var target = {
|
var target = {
|
||||||
'name': vm0.name,
|
'name': vm0.name,
|
||||||
@ -113,10 +115,10 @@
|
|||||||
.error(pingDestinationFailed);
|
.error(pingDestinationFailed);
|
||||||
}
|
}
|
||||||
function pingDestinationSuccess(data, status) {
|
function pingDestinationSuccess(data, status) {
|
||||||
alert('Successful ping target.');
|
alert($filter('tr')('successful_ping_target', []));
|
||||||
}
|
}
|
||||||
function pingDestinationFailed(data, status) {
|
function pingDestinationFailed(data, status) {
|
||||||
alert('Failed ping target:' + data);
|
alert($filter('tr')('failed_ping_target', []) + ':' + data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +141,19 @@
|
|||||||
function link(scope, element, attrs, ctrl) {
|
function link(scope, element, attrs, ctrl) {
|
||||||
|
|
||||||
element.find('#createDestinationModal').on('show.bs.modal', function() {
|
element.find('#createDestinationModal').on('show.bs.modal', function() {
|
||||||
|
|
||||||
scope.form.$setPristine();
|
scope.form.$setPristine();
|
||||||
scope.form.$setUntouched();
|
scope.form.$setUntouched();
|
||||||
|
|
||||||
|
switch(ctrl.action) {
|
||||||
|
case 'ADD_NEW':
|
||||||
|
ctrl.addNew();
|
||||||
|
break;
|
||||||
|
case 'EDIT':
|
||||||
|
ctrl.edit(ctrl.targetId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scope.$apply();
|
||||||
});
|
});
|
||||||
|
|
||||||
ctrl.save = save;
|
ctrl.save = save;
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
.module('harbor.system.management')
|
.module('harbor.system.management')
|
||||||
.directive('destination', destination);
|
.directive('destination', destination);
|
||||||
|
|
||||||
DestinationController.$inject = ['$scope', 'ListDestinationService', 'DeleteDestinationService'];
|
DestinationController.$inject = ['$scope', 'ListDestinationService', 'DeleteDestinationService', '$filter', 'trFilter'];
|
||||||
|
|
||||||
function DestinationController($scope, ListDestinationService, DeleteDestinationService) {
|
function DestinationController($scope, ListDestinationService, DeleteDestinationService, $filter, trFilter) {
|
||||||
var vm = this;
|
var vm = this;
|
||||||
|
|
||||||
vm.retrieve = retrieve;
|
vm.retrieve = retrieve;
|
||||||
@ -66,6 +66,7 @@
|
|||||||
|
|
||||||
function deleteDestinationFailed(data, status) {
|
function deleteDestinationFailed(data, status) {
|
||||||
console.log('Failed delete destination.');
|
console.log('Failed delete destination.');
|
||||||
|
alert($filter('tr')('failed_delete_destination', []) + ':' + data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,10 @@
|
|||||||
'ngCookies',
|
'ngCookies',
|
||||||
'harbor.session',
|
'harbor.session',
|
||||||
'harbor.layout.header',
|
'harbor.layout.header',
|
||||||
|
'harbor.layout.footer',
|
||||||
'harbor.layout.navigation',
|
'harbor.layout.navigation',
|
||||||
'harbor.layout.sign.up',
|
'harbor.layout.sign.up',
|
||||||
|
'harbor.layout.add.new',
|
||||||
'harbor.layout.account.setting',
|
'harbor.layout.account.setting',
|
||||||
'harbor.layout.forgot.password',
|
'harbor.layout.forgot.password',
|
||||||
'harbor.layout.reset.password',
|
'harbor.layout.reset.password',
|
||||||
|
15
static/resources/js/layout/add-new/add-new.controller.js
Normal file
15
static/resources/js/layout/add-new/add-new.controller.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('harbor.layout.add.new')
|
||||||
|
.controller('AddNewController', AddNewController);
|
||||||
|
|
||||||
|
AddNewController.$inject = [];
|
||||||
|
|
||||||
|
function AddNewController() {
|
||||||
|
var vm = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
8
static/resources/js/layout/add-new/add-new.module.js
Normal file
8
static/resources/js/layout/add-new/add-new.module.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('harbor.layout.add.new', []);
|
||||||
|
|
||||||
|
})();
|
13
static/resources/js/layout/footer/footer.controller.js
Normal file
13
static/resources/js/layout/footer/footer.controller.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('harbor.layout.footer')
|
||||||
|
.controller('FooterController', FooterController);
|
||||||
|
|
||||||
|
function FooterController() {
|
||||||
|
var vm = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
8
static/resources/js/layout/footer/footer.module.js
Normal file
8
static/resources/js/layout/footer/footer.module.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('harbor.layout.footer', []);
|
||||||
|
|
||||||
|
})();
|
@ -39,7 +39,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function confirm() {
|
function confirm() {
|
||||||
$window.location.href = '/';
|
if(location.pathname === '/add_new') {
|
||||||
|
$window.location.href = '/dashboard';
|
||||||
|
}else{
|
||||||
|
$window.location.href = '/';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('harbor.services.destination')
|
||||||
|
.factory('ListDestinationPolicyService', ListDestinationPolicyService);
|
||||||
|
|
||||||
|
ListDestinationPolicyService.$inject = ['$http'];
|
||||||
|
|
||||||
|
function ListDestinationPolicyService($http) {
|
||||||
|
return listDestinationPolicy;
|
||||||
|
function listDestinationPolicy(targetId) {
|
||||||
|
return $http
|
||||||
|
.get('/api/targets/' + targetId + '/policies/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
@ -160,6 +160,7 @@ var locale_messages = {
|
|||||||
'endpoint': 'Endpoint',
|
'endpoint': 'Endpoint',
|
||||||
'test_connection': 'Test connection',
|
'test_connection': 'Test connection',
|
||||||
'add_new_destination': 'New Destination',
|
'add_new_destination': 'New Destination',
|
||||||
|
'edit_destination': 'Edit Destination',
|
||||||
'successful_changed_password': 'Password has been changed successfully.',
|
'successful_changed_password': 'Password has been changed successfully.',
|
||||||
'change_profile': 'Change Profile',
|
'change_profile': 'Change Profile',
|
||||||
'successful_changed_profile': 'User profile has been changed successfully.',
|
'successful_changed_profile': 'User profile has been changed successfully.',
|
||||||
@ -172,5 +173,15 @@ var locale_messages = {
|
|||||||
'send': 'Send',
|
'send': 'Send',
|
||||||
'successful_signed_up': 'Signed up successfully.',
|
'successful_signed_up': 'Signed up successfully.',
|
||||||
'add_new_policy': 'Add New Policy',
|
'add_new_policy': 'Add New Policy',
|
||||||
'edit_policy': 'Edit Policy'
|
'edit_policy': 'Edit Policy',
|
||||||
|
'add_new_title': 'Add User',
|
||||||
|
'add_new': 'Add',
|
||||||
|
'successful_added': 'Added new user successfully.',
|
||||||
|
'copyright': 'Copyright',
|
||||||
|
'all_rights_reserved': 'All Rights Reserved.',
|
||||||
|
'successful_ping_target': 'Pinged target successfully.',
|
||||||
|
'failed_ping_target': 'Pinged target failed:',
|
||||||
|
'policy_already_exists': 'Policy alreay exists.',
|
||||||
|
'destination_already_exists': 'Destination already exists.',
|
||||||
|
'failed_delete_destination': 'Delete destination failed:'
|
||||||
};
|
};
|
@ -159,10 +159,11 @@ var locale_messages = {
|
|||||||
'endpoint_is_required': '终端URL为必填项。',
|
'endpoint_is_required': '终端URL为必填项。',
|
||||||
'test_connection': '测试连接',
|
'test_connection': '测试连接',
|
||||||
'add_new_destination': '新建目标',
|
'add_new_destination': '新建目标',
|
||||||
|
'edit_destination': '编辑目标',
|
||||||
'successful_changed_password': '修改密码操作成功。',
|
'successful_changed_password': '修改密码操作成功。',
|
||||||
'change_profile': '修改个人信息',
|
'change_profile': '修改个人信息',
|
||||||
'successful_changed_profile': '修改个人信息操作成功。',
|
'successful_changed_profile': '修改个人信息操作成功。',
|
||||||
'form_is_invalid': '表单内容无效',
|
'form_is_invalid': '表单内容无sign_up效',
|
||||||
'form_is_invalid_message': '表单内容无效,请填写必填字段。',
|
'form_is_invalid_message': '表单内容无效,请填写必填字段。',
|
||||||
'administrator': '管理员',
|
'administrator': '管理员',
|
||||||
'popular_repositories': '热门镜像仓库',
|
'popular_repositories': '热门镜像仓库',
|
||||||
@ -171,5 +172,15 @@ var locale_messages = {
|
|||||||
'send': '发送',
|
'send': '发送',
|
||||||
'successful_signed_up': '注册成功。',
|
'successful_signed_up': '注册成功。',
|
||||||
'add_new_policy': '新增策略',
|
'add_new_policy': '新增策略',
|
||||||
'edit_policy': '修改策略'
|
'edit_policy': '修改策略',
|
||||||
|
'add_new_title': '新增用户',
|
||||||
|
'add_new': '新增',
|
||||||
|
'successful_added': '新增用户成功。',
|
||||||
|
'copyright': '版权所有',
|
||||||
|
'all_rights_reserved': '保留所有权利。',
|
||||||
|
'successful_ping_target': 'Ping 目标成功。',
|
||||||
|
'failed_ping_target': 'Ping 目标失败:',
|
||||||
|
'policy_already_exists': '策略已存在。',
|
||||||
|
'destination_already_exists': '目标已存在。',
|
||||||
|
'failed_delete_destination': '删除目标失败:'
|
||||||
};
|
};
|
@ -9,9 +9,11 @@
|
|||||||
I18nService.$inject = ['$cookies', '$window'];
|
I18nService.$inject = ['$cookies', '$window'];
|
||||||
|
|
||||||
function I18nService($cookies, $window) {
|
function I18nService($cookies, $window) {
|
||||||
|
|
||||||
var cookieOptions = {'path': '/'};
|
var cookieOptions = {'path': '/'};
|
||||||
|
|
||||||
var messages = $.extend(true, {}, eval('locale_messages'));
|
var messages = $.extend(true, {}, eval('locale_messages'));
|
||||||
var defaultLanguage = navigator.language || 'en-US';
|
var defaultLanguage = 'en-US';
|
||||||
var supportLanguages = {
|
var supportLanguages = {
|
||||||
'en-US': 'English',
|
'en-US': 'English',
|
||||||
'zh-CN': '中文'
|
'zh-CN': '中文'
|
||||||
@ -25,6 +27,7 @@
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return tr;
|
return tr;
|
||||||
function tr() {
|
function tr() {
|
||||||
|
|
||||||
@ -45,6 +48,7 @@
|
|||||||
if(!angular.isDefined(language) || !isSupportLanguage(language)) {
|
if(!angular.isDefined(language) || !isSupportLanguage(language)) {
|
||||||
language = defaultLanguage;
|
language = defaultLanguage;
|
||||||
}
|
}
|
||||||
|
$cookies.put('language', language, cookieOptions);
|
||||||
return supportLanguages[language];
|
return supportLanguages[language];
|
||||||
},
|
},
|
||||||
'getSupportLanguages': function() {
|
'getSupportLanguages': function() {
|
||||||
|
@ -35,6 +35,7 @@ func initRouters() {
|
|||||||
beego.Router("/project", &controllers.ProjectController{})
|
beego.Router("/project", &controllers.ProjectController{})
|
||||||
beego.Router("/repository", &controllers.RepositoryController{})
|
beego.Router("/repository", &controllers.RepositoryController{})
|
||||||
beego.Router("/sign_up", &controllers.SignUpController{})
|
beego.Router("/sign_up", &controllers.SignUpController{})
|
||||||
|
beego.Router("/add_new", &controllers.AddNewController{})
|
||||||
beego.Router("/account_setting", &controllers.AccountSettingController{})
|
beego.Router("/account_setting", &controllers.AccountSettingController{})
|
||||||
beego.Router("/admin_option", &controllers.AdminOptionController{})
|
beego.Router("/admin_option", &controllers.AdminOptionController{})
|
||||||
beego.Router("/forgot_password", &controllers.ForgotPasswordController{})
|
beego.Router("/forgot_password", &controllers.ForgotPasswordController{})
|
||||||
@ -76,6 +77,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/targets/", &api.TargetAPI{}, "get:List")
|
beego.Router("/api/targets/", &api.TargetAPI{}, "get:List")
|
||||||
beego.Router("/api/targets/", &api.TargetAPI{}, "post:Post")
|
beego.Router("/api/targets/", &api.TargetAPI{}, "post:Post")
|
||||||
beego.Router("/api/targets/:id([0-9]+)", &api.TargetAPI{})
|
beego.Router("/api/targets/:id([0-9]+)", &api.TargetAPI{})
|
||||||
|
beego.Router("/api/targets/:id([0-9]+)/policies/", &api.TargetAPI{}, "get:ListPolicies")
|
||||||
beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
|
beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
|
||||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||||
|
@ -16,40 +16,63 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
au "github.com/docker/distribution/registry/client/auth"
|
au "github.com/docker/distribution/registry/client/auth"
|
||||||
|
"github.com/vmware/harbor/utils/registry/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler authorizes requests according to the schema
|
// Authorizer authorizes requests according to the schema
|
||||||
type Handler interface {
|
type Authorizer interface {
|
||||||
// Scheme : basic, bearer
|
// Scheme : basic, bearer
|
||||||
Scheme() string
|
Scheme() string
|
||||||
//AuthorizeRequest adds basic auth or token auth to the header of request
|
//Authorize adds basic auth or token auth to the header of request
|
||||||
AuthorizeRequest(req *http.Request, params map[string]string) error
|
Authorize(req *http.Request, params map[string]string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestAuthorizer holds a handler list, which will authorize request.
|
// AuthorizerStore holds a authorizer list, which will authorize request.
|
||||||
// Implements interface RequestModifier
|
// And it implements interface Modifier
|
||||||
type RequestAuthorizer struct {
|
type AuthorizerStore struct {
|
||||||
handlers []Handler
|
authorizers []Authorizer
|
||||||
challenges []au.Challenge
|
challenges []au.Challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequestAuthorizer ...
|
// NewAuthorizerStore ...
|
||||||
func NewRequestAuthorizer(handlers []Handler, challenges []au.Challenge) *RequestAuthorizer {
|
func NewAuthorizerStore(endpoint string, insecure bool, authorizers ...Authorizer) (*AuthorizerStore, error) {
|
||||||
return &RequestAuthorizer{
|
endpoint = utils.FormatEndpoint(endpoint)
|
||||||
handlers: handlers,
|
|
||||||
challenges: challenges,
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: insecure,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get(buildPingURL(endpoint))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
challenges := ParseChallengeFromResponse(resp)
|
||||||
|
return &AuthorizerStore{
|
||||||
|
authorizers: authorizers,
|
||||||
|
challenges: challenges,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModifyRequest adds authorization to the request
|
func buildPingURL(endpoint string) string {
|
||||||
func (r *RequestAuthorizer) ModifyRequest(req *http.Request) error {
|
return fmt.Sprintf("%s/v2/", endpoint)
|
||||||
for _, challenge := range r.challenges {
|
}
|
||||||
for _, handler := range r.handlers {
|
|
||||||
if handler.Scheme() == challenge.Scheme {
|
// Modify adds authorization to the request
|
||||||
if err := handler.AuthorizeRequest(req, challenge.Parameters); err != nil {
|
func (a *AuthorizerStore) Modify(req *http.Request) error {
|
||||||
|
for _, challenge := range a.challenges {
|
||||||
|
for _, authorizer := range a.authorizers {
|
||||||
|
if authorizer.Scheme() == challenge.Scheme {
|
||||||
|
if err := authorizer.Authorize(req, challenge.Parameters); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
au "github.com/docker/distribution/registry/client/auth"
|
au "github.com/docker/distribution/registry/client/auth"
|
||||||
"github.com/vmware/harbor/utils/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseChallengeFromResponse ...
|
// ParseChallengeFromResponse ...
|
||||||
func ParseChallengeFromResponse(resp *http.Response) []au.Challenge {
|
func ParseChallengeFromResponse(resp *http.Response) []au.Challenge {
|
||||||
challenges := au.ResponseChallenges(resp)
|
challenges := au.ResponseChallenges(resp)
|
||||||
|
|
||||||
log.Debugf("challenges: %v", challenges)
|
|
||||||
|
|
||||||
return challenges
|
return challenges
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,13 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -42,8 +44,8 @@ func (s *scope) string() string {
|
|||||||
|
|
||||||
type tokenGenerator func(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error)
|
type tokenGenerator func(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error)
|
||||||
|
|
||||||
// Implements interface Handler
|
// Implements interface Authorizer
|
||||||
type tokenHandler struct {
|
type tokenAuthorizer struct {
|
||||||
scope *scope
|
scope *scope
|
||||||
tg tokenGenerator
|
tg tokenGenerator
|
||||||
cache string // cached token
|
cache string // cached token
|
||||||
@ -53,12 +55,12 @@ type tokenHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scheme returns the scheme that the handler can handle
|
// Scheme returns the scheme that the handler can handle
|
||||||
func (t *tokenHandler) Scheme() string {
|
func (t *tokenAuthorizer) Scheme() string {
|
||||||
return "bearer"
|
return "bearer"
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizeRequest will add authorization header which contains a token before the request is sent
|
// AuthorizeRequest will add authorization header which contains a token before the request is sent
|
||||||
func (t *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
|
func (t *tokenAuthorizer) Authorize(req *http.Request, params map[string]string) error {
|
||||||
var scopes []*scope
|
var scopes []*scope
|
||||||
var token string
|
var token string
|
||||||
|
|
||||||
@ -100,26 +102,23 @@ func (t *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]str
|
|||||||
|
|
||||||
if !hasFrom {
|
if !hasFrom {
|
||||||
t.updateCachedToken(to, expiresIn, issuedAt)
|
t.updateCachedToken(to, expiresIn, issuedAt)
|
||||||
log.Debug("add token to cache")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token = cachedToken
|
token = cachedToken
|
||||||
log.Debug("get token from cache")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
|
req.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("Bearer %s", token))
|
||||||
log.Debugf("add token to request: %s %s", req.Method, req.URL.String())
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenHandler) getCachedToken() (string, int, *time.Time) {
|
func (t *tokenAuthorizer) getCachedToken() (string, int, *time.Time) {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
return t.cache, t.expiresIn, t.issuedAt
|
return t.cache, t.expiresIn, t.issuedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenHandler) updateCachedToken(token string, expiresIn int, issuedAt *time.Time) {
|
func (t *tokenAuthorizer) updateCachedToken(token string, expiresIn int, issuedAt *time.Time) {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
t.cache = token
|
t.cache = token
|
||||||
@ -127,38 +126,45 @@ func (t *tokenHandler) updateCachedToken(token string, expiresIn int, issuedAt *
|
|||||||
t.issuedAt = issuedAt
|
t.issuedAt = issuedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements interface Handler
|
// Implements interface Authorizer
|
||||||
type standardTokenHandler struct {
|
type standardTokenAuthorizer struct {
|
||||||
tokenHandler
|
tokenAuthorizer
|
||||||
client *http.Client
|
client *http.Client
|
||||||
credential Credential
|
credential Credential
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStandardTokenHandler returns a standard token handler. The handler will request a token
|
// NewStandardTokenAuthorizer returns a standard token authorizer. The authorizer will request a token
|
||||||
// from token server and add it to the origin request
|
// from token server and add it to the origin request
|
||||||
// TODO deal with https
|
func NewStandardTokenAuthorizer(credential Credential, insecure bool, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
func NewStandardTokenHandler(credential Credential, scopeType, scopeName string, scopeActions ...string) Handler {
|
t := &http.Transport{
|
||||||
handler := &standardTokenHandler{
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: insecure,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizer := &standardTokenAuthorizer{
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Transport: http.DefaultTransport,
|
Transport: t,
|
||||||
},
|
},
|
||||||
credential: credential,
|
credential: credential,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(scopeType) != 0 || len(scopeName) != 0 {
|
if len(scopeType) != 0 || len(scopeName) != 0 {
|
||||||
handler.scope = &scope{
|
authorizer.scope = &scope{
|
||||||
Type: scopeType,
|
Type: scopeType,
|
||||||
Name: scopeName,
|
Name: scopeName,
|
||||||
Actions: scopeActions,
|
Actions: scopeActions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.tg = handler.generateToken
|
authorizer.tg = authorizer.generateToken
|
||||||
|
|
||||||
return handler
|
return authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *standardTokenHandler) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
func (s *standardTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||||
|
realm = tokenURL(realm)
|
||||||
|
|
||||||
u, err := url.Parse(realm)
|
u, err := url.Parse(realm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -217,37 +223,50 @@ func (s *standardTokenHandler) generateToken(realm, service string, scopes []str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("get token from token server")
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when the registry client is used inside Harbor, the token request
|
||||||
|
// can be posted to token service directly rather than going through nginx.
|
||||||
|
// this solution can resolve two problems:
|
||||||
|
// 1. performance issue
|
||||||
|
// 2. the realm field returned by registry is an IP which can not reachable
|
||||||
|
// inside Harbor
|
||||||
|
func tokenURL(realm string) string {
|
||||||
|
extEndpoint := os.Getenv("EXT_ENDPOINT")
|
||||||
|
tokenURL := os.Getenv("TOKEN_URL")
|
||||||
|
if len(extEndpoint) != 0 && len(tokenURL) != 0 &&
|
||||||
|
strings.Contains(realm, extEndpoint) {
|
||||||
|
realm = strings.TrimRight(tokenURL, "/") + "/service/token"
|
||||||
|
}
|
||||||
|
return realm
|
||||||
|
}
|
||||||
|
|
||||||
// Implements interface Handler
|
// Implements interface Handler
|
||||||
type usernameTokenHandler struct {
|
type usernameTokenAuthorizer struct {
|
||||||
tokenHandler
|
tokenAuthorizer
|
||||||
username string
|
username string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUsernameTokenHandler returns a handler which will generate a token according to
|
// NewUsernameTokenAuthorizer returns a authorizer which will generate a token according to
|
||||||
// the user's privileges
|
// the user's privileges
|
||||||
func NewUsernameTokenHandler(username string, scopeType, scopeName string, scopeActions ...string) Handler {
|
func NewUsernameTokenAuthorizer(username string, scopeType, scopeName string, scopeActions ...string) Authorizer {
|
||||||
handler := &usernameTokenHandler{
|
authorizer := &usernameTokenAuthorizer{
|
||||||
username: username,
|
username: username,
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.scope = &scope{
|
authorizer.scope = &scope{
|
||||||
Type: scopeType,
|
Type: scopeType,
|
||||||
Name: scopeName,
|
Name: scopeName,
|
||||||
Actions: scopeActions,
|
Actions: scopeActions,
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.tg = handler.generateToken
|
authorizer.tg = authorizer.generateToken
|
||||||
|
|
||||||
return handler
|
return authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *usernameTokenHandler) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
func (u *usernameTokenAuthorizer) generateToken(realm, service string, scopes []string) (token string, expiresIn int, issuedAt *time.Time, err error) {
|
||||||
token, expiresIn, issuedAt, err = token_util.GenTokenForUI(u.username, service, scopes)
|
token, expiresIn, issuedAt, err = token_util.GenTokenForUI(u.username, service, scopes)
|
||||||
log.Debug("get token by calling GenTokenForUI directly")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
25
utils/registry/modifier.go
Normal file
25
utils/registry/modifier.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 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 registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Modifier modifies request
|
||||||
|
type Modifier interface {
|
||||||
|
Modify(*http.Request) error
|
||||||
|
}
|
@ -16,21 +16,15 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/log"
|
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
)
|
"github.com/vmware/harbor/utils/registry/utils"
|
||||||
|
|
||||||
const (
|
|
||||||
// UserAgent is used to decorate the request so it can be identified by webhook.
|
|
||||||
UserAgent string = "registry-client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Registry holds information of a registry entity
|
// Registry holds information of a registry entity
|
||||||
@ -41,9 +35,7 @@ type Registry struct {
|
|||||||
|
|
||||||
// NewRegistry returns an instance of registry
|
// NewRegistry returns an instance of registry
|
||||||
func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
|
func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -53,64 +45,30 @@ func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
|
|||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("initialized a registry client: %s", endpoint)
|
|
||||||
|
|
||||||
return registry, nil
|
return registry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRegistryWithUsername returns a Registry instance which will authorize the request
|
// NewRegistryWithModifiers returns an instance of Registry according to the modifiers
|
||||||
// according to the privileges of user
|
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
|
||||||
func NewRegistryWithUsername(endpoint, username string) (*Registry, error) {
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := newClient(endpoint, username, nil, "registry", "catalog", "*")
|
t := &http.Transport{
|
||||||
if err != nil {
|
TLSClientConfig: &tls.Config{
|
||||||
return nil, err
|
InsecureSkipVerify: insecure,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
registry := &Registry{
|
transport := NewTransport(t, modifiers...)
|
||||||
|
|
||||||
|
return &Registry{
|
||||||
Endpoint: u,
|
Endpoint: u,
|
||||||
client: client,
|
client: &http.Client{
|
||||||
}
|
Transport: transport,
|
||||||
|
},
|
||||||
log.Debugf("initialized a registry client with username: %s %s", endpoint, username)
|
}, nil
|
||||||
|
|
||||||
return registry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRegistryWithCredential returns a Registry instance which associate to a crendential.
|
|
||||||
// And Credential is essentially a decorator for client to docorate the request before sending it to the registry.
|
|
||||||
func NewRegistryWithCredential(endpoint string, credential auth.Credential) (*Registry, error) {
|
|
||||||
endpoint = strings.TrimSpace(endpoint)
|
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
if !strings.HasPrefix(endpoint, "http://") &&
|
|
||||||
!strings.HasPrefix(endpoint, "https://") {
|
|
||||||
endpoint = "http://" + endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := newClient(endpoint, "", credential, "", "", "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
registry := &Registry{
|
|
||||||
Endpoint: u,
|
|
||||||
client: client,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("initialized a registry client with credential: %s", endpoint)
|
|
||||||
|
|
||||||
return registry, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catalog ...
|
// Catalog ...
|
||||||
@ -163,16 +121,6 @@ func (r *Registry) Ping() error {
|
|||||||
|
|
||||||
resp, err := r.client.Do(req)
|
resp, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if urlErr, ok := err.(*url.Error); ok {
|
|
||||||
// if regErr, ok := urlErr.Err.(*registry_error.Error); ok {
|
|
||||||
// return ®istry_error.Error{
|
|
||||||
// StatusCode: regErr.StatusCode,
|
|
||||||
// Detail: regErr.Detail,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return urlErr.Err
|
|
||||||
// }
|
|
||||||
|
|
||||||
return parseError(err)
|
return parseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,32 +144,3 @@ func (r *Registry) Ping() error {
|
|||||||
func buildCatalogURL(endpoint string) string {
|
func buildCatalogURL(endpoint string) string {
|
||||||
return fmt.Sprintf("%s/v2/_catalog", endpoint)
|
return fmt.Sprintf("%s/v2/_catalog", endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(endpoint, username string, credential auth.Credential,
|
|
||||||
scopeType, scopeName string, scopeActions ...string) (*http.Client, error) {
|
|
||||||
|
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
resp, err := http.Get(buildPingURL(endpoint))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var handlers []auth.Handler
|
|
||||||
var handler auth.Handler
|
|
||||||
if credential != nil {
|
|
||||||
handler = auth.NewStandardTokenHandler(credential, scopeType, scopeName, scopeActions...)
|
|
||||||
} else {
|
|
||||||
handler = auth.NewUsernameTokenHandler(username, scopeType, scopeName, scopeActions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
handlers = append(handlers, handler)
|
|
||||||
|
|
||||||
challenges := auth.ParseChallengeFromResponse(resp)
|
|
||||||
authorizer := auth.NewRequestAuthorizer(handlers, challenges)
|
|
||||||
headerModifier := NewHeaderModifier(map[string]string{http.CanonicalHeaderKey("User-Agent"): UserAgent})
|
|
||||||
|
|
||||||
transport := NewTransport(http.DefaultTransport, []RequestModifier{authorizer, headerModifier})
|
|
||||||
return &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
@ -17,6 +17,7 @@ package registry
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -28,9 +29,9 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/vmware/harbor/utils/log"
|
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
|
||||||
registry_error "github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
|
"github.com/vmware/harbor/utils/registry/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repository holds information of a repository entity
|
// Repository holds information of a repository entity
|
||||||
@ -40,14 +41,11 @@ type Repository struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add agent to header of request, notifications need it
|
|
||||||
|
|
||||||
// NewRepository returns an instance of Repository
|
// NewRepository returns an instance of Repository
|
||||||
func NewRepository(name, endpoint string, client *http.Client) (*Repository, error) {
|
func NewRepository(name, endpoint string, client *http.Client) (*Repository, error) {
|
||||||
name = strings.TrimSpace(name)
|
name = strings.TrimSpace(name)
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -61,55 +59,30 @@ func NewRepository(name, endpoint string, client *http.Client) (*Repository, err
|
|||||||
return repository, nil
|
return repository, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepositoryWithCredential returns a Repository instance which will authorize the request
|
// NewRepositoryWithModifiers returns an instance of Repository according to the modifiers
|
||||||
// according to the credenttial
|
func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers ...Modifier) (*Repository, error) {
|
||||||
func NewRepositoryWithCredential(name, endpoint string, credential auth.Credential) (*Repository, error) {
|
|
||||||
name = strings.TrimSpace(name)
|
name = strings.TrimSpace(name)
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
u, err := utils.ParseEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := newClient(endpoint, "", credential, "repository", name, "pull", "push")
|
t := &http.Transport{
|
||||||
if err != nil {
|
TLSClientConfig: &tls.Config{
|
||||||
return nil, err
|
InsecureSkipVerify: insecure,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
repository := &Repository{
|
transport := NewTransport(t, modifiers...)
|
||||||
|
|
||||||
|
return &Repository{
|
||||||
Name: name,
|
Name: name,
|
||||||
Endpoint: u,
|
Endpoint: u,
|
||||||
client: client,
|
client: &http.Client{
|
||||||
}
|
Transport: transport,
|
||||||
|
},
|
||||||
log.Debugf("initialized a repository client with credential: %s %s", endpoint, name)
|
}, nil
|
||||||
|
|
||||||
return repository, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRepositoryWithUsername returns a Repository instance which will authorize the request
|
|
||||||
// according to the privileges of user
|
|
||||||
func NewRepositoryWithUsername(name, endpoint, username string) (*Repository, error) {
|
|
||||||
name = strings.TrimSpace(name)
|
|
||||||
endpoint = strings.TrimRight(endpoint, "/")
|
|
||||||
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := newClient(endpoint, username, nil, "repository", name, "pull", "push")
|
|
||||||
|
|
||||||
repository := &Repository{
|
|
||||||
Name: name,
|
|
||||||
Endpoint: u,
|
|
||||||
client: client,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("initialized a repository client with username: %s %s %s", endpoint, name, username)
|
|
||||||
|
|
||||||
return repository, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseError(err error) error {
|
func parseError(err error) error {
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vmware/harbor/utils/registry/auth"
|
"github.com/vmware/harbor/utils/registry/auth"
|
||||||
"github.com/vmware/harbor/utils/registry/error"
|
registry_error "github.com/vmware/harbor/utils/registry/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -139,7 +139,8 @@ func serveToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListTag(t *testing.T) {
|
func TestListTag(t *testing.T) {
|
||||||
client, err := NewRepositoryWithCredential(repo, registryServer.URL, credential)
|
client, err := newRepositoryClient(registryServer.URL, true, credential,
|
||||||
|
repo, "repository", repo, "pull", "push", "*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -158,13 +159,14 @@ func TestListTag(t *testing.T) {
|
|||||||
|
|
||||||
func TestListTagWithInvalidCredential(t *testing.T) {
|
func TestListTagWithInvalidCredential(t *testing.T) {
|
||||||
credential := auth.NewBasicAuthCredential(username, "wrong_password")
|
credential := auth.NewBasicAuthCredential(username, "wrong_password")
|
||||||
client, err := NewRepositoryWithCredential(repo, registryServer.URL, credential)
|
client, err := newRepositoryClient(registryServer.URL, true, credential,
|
||||||
|
repo, "repository", repo, "pull", "push", "*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = client.ListTag(); err != nil {
|
if _, err = client.ListTag(); err != nil {
|
||||||
e, ok := err.(*error.Error)
|
e, ok := err.(*registry_error.Error)
|
||||||
if ok && e.StatusCode == http.StatusUnauthorized {
|
if ok && e.StatusCode == http.StatusUnauthorized {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -173,3 +175,20 @@ func TestListTagWithInvalidCredential(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
|
||||||
|
scopeActions ...string) (*Repository, error) {
|
||||||
|
|
||||||
|
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
|
||||||
|
|
||||||
|
store, err := auth.NewAuthorizerStore(endpoint, true, authorizer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewRepositoryWithModifiers(repository, endpoint, insecure, store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
@ -21,39 +21,14 @@ import (
|
|||||||
"github.com/vmware/harbor/utils/log"
|
"github.com/vmware/harbor/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestModifier modifies request
|
|
||||||
type RequestModifier interface {
|
|
||||||
ModifyRequest(*http.Request) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderModifier adds headers to request
|
|
||||||
type HeaderModifier struct {
|
|
||||||
headers map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHeaderModifier ...
|
|
||||||
func NewHeaderModifier(headers map[string]string) *HeaderModifier {
|
|
||||||
return &HeaderModifier{
|
|
||||||
headers: headers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyRequest adds headers to the request
|
|
||||||
func (h *HeaderModifier) ModifyRequest(req *http.Request) error {
|
|
||||||
for key, value := range h.headers {
|
|
||||||
req.Header.Add(key, value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transport holds information about base transport and modifiers
|
// Transport holds information about base transport and modifiers
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
transport http.RoundTripper
|
transport http.RoundTripper
|
||||||
modifiers []RequestModifier
|
modifiers []Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTransport ...
|
// NewTransport ...
|
||||||
func NewTransport(transport http.RoundTripper, modifiers []RequestModifier) *Transport {
|
func NewTransport(transport http.RoundTripper, modifiers ...Modifier) *Transport {
|
||||||
return &Transport{
|
return &Transport{
|
||||||
transport: transport,
|
transport: transport,
|
||||||
modifiers: modifiers,
|
modifiers: modifiers,
|
||||||
@ -63,7 +38,7 @@ func NewTransport(transport http.RoundTripper, modifiers []RequestModifier) *Tra
|
|||||||
// RoundTrip ...
|
// RoundTrip ...
|
||||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
for _, modifier := range t.modifiers {
|
for _, modifier := range t.modifiers {
|
||||||
if err := modifier.ModifyRequest(req); err != nil {
|
if err := modifier.Modify(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
utils/registry/utils/utils.go
Normal file
44
utils/registry/utils/utils.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2016 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatEndpoint formats endpoint
|
||||||
|
func FormatEndpoint(endpoint string) string {
|
||||||
|
endpoint = strings.TrimSpace(endpoint)
|
||||||
|
endpoint = strings.TrimRight(endpoint, "/")
|
||||||
|
if !strings.HasPrefix(endpoint, "http://") &&
|
||||||
|
!strings.HasPrefix(endpoint, "https://") {
|
||||||
|
endpoint = "http://" + endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEndpoint parses endpoint to a URL
|
||||||
|
func ParseEndpoint(endpoint string) (*url.URL, error) {
|
||||||
|
endpoint = FormatEndpoint(endpoint)
|
||||||
|
|
||||||
|
u, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
<h1 class="col-md-12 col-md-offset-2 main-title title-color">// 'forgot_password' | tr //</h1>
|
<h1 class="col-md-12 col-md-offset-2 main-title title-color">// 'forgot_password' | tr //</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 col-md-offset-2 main-content">
|
<div class="col-md-12 col-md-offset-2 main-content">
|
||||||
<form name="form" class="form-horizontal css-form">
|
<form name="form" class="form-horizontal css-form" novalidate>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email" class="col-sm-3 control-label">// 'email' | tr //:</label>
|
<label for="email" class="col-sm-3 control-label">// 'email' | tr //:</label>
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-7">
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
<span class="glyphicon glyphicon-user"></span> {{ .Username }}
|
<span class="glyphicon glyphicon-user"></span> {{ .Username }}
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
|
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
|
||||||
|
{{ if eq .AddNew true }}
|
||||||
|
<li><a href="/add_new"><span class="glyphicon glyphicon-plus"></span> // 'add_new_title' | tr //</a></li>
|
||||||
|
{{ end }}
|
||||||
<li><a href="/account_setting"><span class="glyphicon glyphicon-pencil"></span> // 'account_setting' | tr //</a></li>
|
<li><a href="/account_setting"><span class="glyphicon glyphicon-pencil"></span> // 'account_setting' | tr //</a></li>
|
||||||
<li class="dropdown-submenu">
|
<li class="dropdown-submenu">
|
||||||
<a tabindex="-1" href="#"><span class="glyphicon glyphicon-globe"></span> //vm.languageName//</a>
|
<a tabindex="-1" href="#"><span class="glyphicon glyphicon-globe"></span> //vm.languageName//</a>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div class="footer-absolute footer">
|
<div class="footer-absolute footer" ng-controller="FooterController as vm">
|
||||||
<p>Copyright © 2015-2016 VMware, Inc. All Rights Reserved.</p>
|
<p>// 'copyright' | tr // © 2015-2016 VMware, Inc. // 'all_rights_reserved' | tr //</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +48,9 @@
|
|||||||
<script src="/static/resources/js/layout/header/header.module.js"></script>
|
<script src="/static/resources/js/layout/header/header.module.js"></script>
|
||||||
<script src="/static/resources/js/layout/header/header.controller.js"></script>
|
<script src="/static/resources/js/layout/header/header.controller.js"></script>
|
||||||
|
|
||||||
|
<script src="/static/resources/js/layout/footer/footer.module.js"></script>
|
||||||
|
<script src="/static/resources/js/layout/footer/footer.controller.js"></script>
|
||||||
|
|
||||||
<script src="/static/resources/js/layout/navigation/navigation.module.js"></script>
|
<script src="/static/resources/js/layout/navigation/navigation.module.js"></script>
|
||||||
<script src="/static/resources/js/layout/navigation/navigation-header.directive.js"></script>
|
<script src="/static/resources/js/layout/navigation/navigation-header.directive.js"></script>
|
||||||
<script src="/static/resources/js/layout/navigation/navigation-details.directive.js"></script>
|
<script src="/static/resources/js/layout/navigation/navigation-details.directive.js"></script>
|
||||||
@ -56,6 +59,9 @@
|
|||||||
<script src="/static/resources/js/layout/sign-up/sign-up.module.js"></script>
|
<script src="/static/resources/js/layout/sign-up/sign-up.module.js"></script>
|
||||||
<script src="/static/resources/js/layout/sign-up/sign-up.controller.js"></script>
|
<script src="/static/resources/js/layout/sign-up/sign-up.controller.js"></script>
|
||||||
|
|
||||||
|
<script src="/static/resources/js/layout/add-new/add-new.module.js"></script>
|
||||||
|
<script src="/static/resources/js/layout/add-new/add-new.controller.js"></script>
|
||||||
|
|
||||||
<script src="/static/resources/js/layout/account-setting/account-setting.module.js"></script>
|
<script src="/static/resources/js/layout/account-setting/account-setting.module.js"></script>
|
||||||
<script src="/static/resources/js/layout/account-setting/account-setting.controller.js"></script>
|
<script src="/static/resources/js/layout/account-setting/account-setting.controller.js"></script>
|
||||||
|
|
||||||
@ -151,6 +157,7 @@
|
|||||||
<script src="/static/resources/js/services/destination/services.ping-destination.js"></script>
|
<script src="/static/resources/js/services/destination/services.ping-destination.js"></script>
|
||||||
<script src="/static/resources/js/services/destination/services.update-destination.js"></script>
|
<script src="/static/resources/js/services/destination/services.update-destination.js"></script>
|
||||||
<script src="/static/resources/js/services/destination/services.delete-destination.js"></script>
|
<script src="/static/resources/js/services/destination/services.delete-destination.js"></script>
|
||||||
|
<script src="/static/resources/js/services/destination/services.list-destination-policy.js"></script>
|
||||||
|
|
||||||
<script src="/static/resources/js/session/session.module.js"></script>
|
<script src="/static/resources/js/session/session.module.js"></script>
|
||||||
<script src="/static/resources/js/session/session.current-user.js"></script>
|
<script src="/static/resources/js/session/session.current-user.js"></script>
|
||||||
|
@ -32,10 +32,13 @@
|
|||||||
<div class="col-sm-offset-1 col-sm-10">
|
<div class="col-sm-offset-1 col-sm-10">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<button type="submit" class="btn btn-default" ng-click="vm.doSignIn(user)">// 'sign_in' | tr //</button>
|
<button type="submit" class="btn btn-default" ng-click="vm.doSignIn(user)">// 'sign_in' | tr //</button>
|
||||||
|
{{ if eq .AuthMode "db_auth" }}
|
||||||
<button type="button" class="btn btn-success" ng-click="vm.doSignUp()">// 'sign_up' | tr //</button>
|
<button type="button" class="btn btn-success" ng-click="vm.doSignUp()">// 'sign_up' | tr //</button>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if eq .AuthMode "db_auth" }}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-offset-1 col-sm-10">
|
<div class="col-sm-offset-1 col-sm-10">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@ -43,5 +46,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
</form>
|
</form>
|
||||||
{{ end }}
|
{{ end }}
|
@ -2,8 +2,18 @@
|
|||||||
<div class="container container-custom">
|
<div class="container container-custom">
|
||||||
<div class="row extend-height">
|
<div class="row extend-height">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
{{ if eq .AddNew true }}
|
||||||
|
<modal-dialog modal-title="// 'add_new_title' | tr //" modal-message="// 'successful_added' | tr //" confirm-only="true" action="vm.confirm()"></modal-dialog>
|
||||||
|
{{ else }}
|
||||||
<modal-dialog modal-title="// 'sign_up' | tr //" modal-message="// 'successful_signed_up' | tr //" confirm-only="true" action="vm.confirm()"></modal-dialog>
|
<modal-dialog modal-title="// 'sign_up' | tr //" modal-message="// 'successful_signed_up' | tr //" confirm-only="true" action="vm.confirm()"></modal-dialog>
|
||||||
<h1 class="col-md-12 col-md-offset-2 main-title title-color">// 'sign_up' | tr //</h1>
|
{{ end }}
|
||||||
|
<h1 class="col-md-12 col-md-offset-2 main-title title-color">
|
||||||
|
{{ if eq .AddNew true }}
|
||||||
|
// 'add_new_title' | tr //
|
||||||
|
{{ else }}
|
||||||
|
// 'sign_up' | tr //
|
||||||
|
{{ end }}
|
||||||
|
</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 col-md-offset-2 main-content">
|
<div class="col-md-12 col-md-offset-2 main-content">
|
||||||
<form name="form" class="form-horizontal css-form" ng-submit="form.$valid">
|
<form name="form" class="form-horizontal css-form" ng-submit="form.$valid">
|
||||||
@ -90,7 +100,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-md-offset-8 col-md-10">
|
<div class="col-md-offset-8 col-md-10">
|
||||||
<input type="submit" class="btn btn-success" ng-disabled="form.$invalid" ng-click="vm.signUp(user)" value="Sign Up">
|
{{ if eq .AddNew true }}
|
||||||
|
<input type="submit" class="btn btn-success" ng-disabled="form.$invalid" ng-click="vm.signUp(user)" value="// 'add_new' | tr //">
|
||||||
|
{{ else }}
|
||||||
|
<input type="submit" class="btn btn-success" ng-disabled="form.$invalid" ng-click="vm.signUp(user)" value="// 'sign_up' | tr //">
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
Loading…
Reference in New Issue
Block a user