Merge branch 'master' into scanner-im-v2

This commit is contained in:
Will Sun 2019-10-23 14:12:42 +08:00 committed by GitHub
commit 2bee9d7047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 352 additions and 149 deletions

View File

@ -11,17 +11,16 @@
|![notification](docs/img/bell-outline-badged.svg)Community Meeting| |![notification](docs/img/bell-outline-badged.svg)Community Meeting|
|------------------| |------------------|
|The Harbor Project holds bi-weekly community calls, to join them and watch previous meeting notes and recordings, please see [meeting schedule](https://github.com/goharbor/community/blob/master/MEETING_SCHEDULE.md).| |The Harbor Project holds bi-weekly community calls in two different timezones. To join the community calls or to watch previous meeting notes and recordings, please visit the [meeting schedule](https://github.com/goharbor/community/blob/master/MEETING_SCHEDULE.md).|
Welcome to join below Harbor community events and meet with project maintainers and users: We welcome you to join the below Harbor community events and meet with project maintainers and users:
**May 20-24, 2019**, [KubeCon EU, Barcelona](https://events.linuxfoundation.org/events/kubecon-cloudnativecon-europe-2019/): Harbor Community Reception, Intro and Deep-dive sessions. **November 18-21, 2019**, [KubeCon US, San Diego](https://events19.linuxfoundation.org/events/kubecon-cloudnativecon-north-america-2019): Harbor Lunch & Learn led by Joe Beda, Intro and Deep-dive sessions.
**June 24-26, 2019**, [KubeCon Shanghai](https://www.lfasiallc.com/events/kubecon-cloudnativecon-china-2019/): Harbor community meetup, Harbor session.
</br> </br> </br> </br>
**Note**: The `master` branch may be in an *unstable or even broken state* during development. **Note**: The `master` branch may be in an *unstable or even broken state* during development.
Please use [releases](https://github.com/vmware/harbor/releases) instead of the `master` branch in order to get stable binaries. Please use [releases](https://github.com/vmware/harbor/releases) instead of the `master` branch in order to get a stable set of binaries.
<img alt="Harbor" src="docs/img/harbor_logo.png"> <img alt="Harbor" src="docs/img/harbor_logo.png">

View File

@ -2,40 +2,11 @@
### About this document ### About this document
This document provides description of items that are gathered from the community and planned in Harbor's roadmap. This should serve as a reference point for Harbor users and contributors to understand where the project is heading, and help determine if a contribution could be conflicting with a longer term plan. This document provides a link to the [Harbor Project board](https://github.com/orgs/goharbor/projects/1) that serves as the up to date description of items that are in the Harbor release pipeline. The board has separate swim lanes for each release. Most items are gathered from the community or include a feedback loop with the community. This should serve as a reference point for Harbor users and contributors to understand where the project is heading, and help determine if a contribution could be conflicting with a longer term plan.
### How to help? ### How to help?
Discussion on the roadmap can take place in threads under [Issues](https://github.com/vmware/harbor/issues). Please open and comment on an issue if you want to provide suggestions and feedback to an item in the roadmap. Please review the roadmap to avoid potential duplicated effort. Discussion on the roadmap can take place in threads under [Issues](https://github.com/goharbor/harbor/issues) or in [community meetings](https://github.com/goharbor/community/blob/master/MEETING_SCHEDULE.md). Please open and comment on an issue if you want to provide suggestions and feedback to an item in the roadmap. Please review the roadmap to avoid potential duplicated effort.
### How to add an item to the roadmap? ### How to add an item to the roadmap?
Please open an issue to track any initiative on the roadmap of Harbor. We will work with and rely on our community to focus our efforts to improve Harbor. Please open an issue to track any initiative on the roadmap of Harbor (Usually driven by new feature requests). We will work with and rely on our community to focus our efforts to improve Harbor.
---
### 1. Notary
The notary feature allows publishers to sign their images offline and to push the signed content to a notary server. This ensures the authenticity of images.
### 2. Vulnerability Scanning
The capability to scan images for vulnerability.
### 3. Image replication enhancement
To provide more sophisticated rule for image replication.
- Image filtering by tags
- Replication can be scheduled at a certain time using a rule like: one time only, daily, weekly, etc.
- Image deletion can have the option not to be replicated to a remote instance.
- Global replication rule: Instead of setting the rule of individual project, system admin can set a global rule for all projects.
- Project admin can set replication policy of the project.
### 4. Authentication (OAuth2)
In addition to LDAP/AD and local users, OAuth 2.0 can be used to authenticate a user.
### 5. High Availability
Support multi-node deployment of Harbor for high availability, scalability and load-balancing purposes.
### 6. Statistics and description for repositories
User can add a description to a repository. The access count of a repo can be aggregated and displayed.
### 7. Migration tool to move from an existing registry to Harbor
A tool to migrate images from a vanilla registry server to Harbor, without the need to export/import a large amount of data.

View File

@ -12,7 +12,7 @@ This guide describes the steps to install and configure Harbor by using the onli
If you run a previous version of Harbor, you may need to update ```harbor.yml``` and migrate the data to fit the new database schema. For more details, please refer to **[Harbor Migration Guide](migration_guide.md)**. If you run a previous version of Harbor, you may need to update ```harbor.yml``` and migrate the data to fit the new database schema. For more details, please refer to **[Harbor Migration Guide](migration_guide.md)**.
In addition, the deployment instructions on Kubernetes has been created by the community. Refer to [Harbor on Kubernetes](kubernetes_deployment.md) for details. In addition, the deployment instructions on Kubernetes has been created by the community. Refer to [Harbor on Kubernetes using Helm](https://github.com/goharbor/harbor-helm) for details.
## Harbor Components ## Harbor Components

View File

@ -1089,6 +1089,8 @@ paths:
description: Forbidden. description: Forbidden.
'404': '404':
description: Repository not found. description: Repository not found.
'412':
description: Precondition Failed.
put: put:
summary: Update description of the repository. summary: Update description of the repository.
description: | description: |

View File

@ -73,6 +73,7 @@ type TagDetail struct {
Author string `json:"author"` Author string `json:"author"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Config *TagCfg `json:"config"` Config *TagCfg `json:"config"`
Immutable bool `json:"immutable"`
} }
// TagCfg ... // TagCfg ...

View File

@ -28,7 +28,7 @@ import (
"github.com/goharbor/harbor/src/jobservice/logger" "github.com/goharbor/harbor/src/jobservice/logger"
"github.com/goharbor/harbor/src/pkg/scan/api/scan" "github.com/goharbor/harbor/src/pkg/scan/api/scan"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
@ -45,6 +45,8 @@ import (
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
notifierEvt "github.com/goharbor/harbor/src/core/notifier/event" notifierEvt "github.com/goharbor/harbor/src/core/notifier/event"
coreutils "github.com/goharbor/harbor/src/core/utils" coreutils "github.com/goharbor/harbor/src/core/utils"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"github.com/goharbor/harbor/src/replication" "github.com/goharbor/harbor/src/replication"
"github.com/goharbor/harbor/src/replication/event" "github.com/goharbor/harbor/src/replication/event"
"github.com/goharbor/harbor/src/replication/model" "github.com/goharbor/harbor/src/replication/model"
@ -283,11 +285,6 @@ func (ra *RepositoryAPI) Delete() {
} }
for _, t := range tags { for _, t := range tags {
image := fmt.Sprintf("%s:%s", repoName, t)
if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
ra.SendInternalServerError(fmt.Errorf("failed to delete labels of image %s: %v", image, err))
return
}
if err = rc.DeleteTag(t); err != nil { if err = rc.DeleteTag(t); err != nil {
if regErr, ok := err.(*commonhttp.Error); ok { if regErr, ok := err.(*commonhttp.Error); ok {
if regErr.Code == http.StatusNotFound { if regErr.Code == http.StatusNotFound {
@ -298,6 +295,11 @@ func (ra *RepositoryAPI) Delete() {
return return
} }
log.Infof("delete tag: %s:%s", repoName, t) log.Infof("delete tag: %s:%s", repoName, t)
image := fmt.Sprintf("%s:%s", repoName, t)
if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
ra.SendInternalServerError(fmt.Errorf("failed to delete labels of image %s: %v", image, err))
return
}
go func(tag string) { go func(tag string) {
e := &event.Event{ e := &event.Event{
@ -711,6 +713,9 @@ func assembleTag(c chan *models.TagResp, client *registry.Repository, projectID
} }
} }
// get immutable status
item.Immutable = isImmutable(projectID, repository, tag)
c <- item c <- item
} }
@ -791,6 +796,21 @@ func populateAuthor(detail *models.TagDetail) {
} }
} }
// check whether the tag is immutable
func isImmutable(projectID int64, repo string, tag string) bool {
_, repoName := utils.ParseRepository(repo)
matched, err := rule.NewRuleMatcher(projectID).Match(art.Candidate{
Repository: repoName,
Tag: tag,
NamespaceID: projectID,
})
if err != nil {
log.Error(err)
return false
}
return matched
}
// GetManifests returns the manifest of a tag // GetManifests returns the manifest of a tag
func (ra *RepositoryAPI) GetManifests() { func (ra *RepositoryAPI) GetManifests() {
repoName := ra.GetString(":splat") repoName := ra.GetString(":splat")

View File

@ -35,4 +35,4 @@ var ChartMiddlewares = []string{CHART}
var Middlewares = []string{READONLY, URL, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA} var Middlewares = []string{READONLY, URL, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA}
// MiddlewaresLocal ... // MiddlewaresLocal ...
var MiddlewaresLocal = []string{SIZEQUOTA, COUNTQUOTA} var MiddlewaresLocal = []string{SIZEQUOTA, IMMUTABLE, COUNTQUOTA}

View File

@ -0,0 +1,54 @@
package immutable
import (
"fmt"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/interceptor/immutable"
"github.com/goharbor/harbor/src/core/middlewares/util"
"net/http"
)
var (
defaultBuilders = []interceptor.Builder{
&manifestDeletionBuilder{},
&manifestCreationBuilder{},
}
)
type manifestDeletionBuilder struct{}
func (*manifestDeletionBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
if match, _, _ := util.MatchDeleteManifest(req); !match {
return nil, nil
}
info, ok := util.ManifestInfoFromContext(req.Context())
if !ok {
var err error
info, err = util.ParseManifestInfoFromPath(req)
if err != nil {
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
}
}
return immutable.NewDeleteMFInteceptor(info), nil
}
type manifestCreationBuilder struct{}
func (*manifestCreationBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
if match, _, _ := util.MatchPushManifest(req); !match {
return nil, nil
}
info, ok := util.ManifestInfoFromContext(req.Context())
if !ok {
var err error
info, err = util.ParseManifestInfoFromReq(req)
if err != nil {
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
}
}
return immutable.NewPushMFInteceptor(info), nil
}

View File

@ -16,78 +16,74 @@ package immutable
import ( import (
"fmt" "fmt"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
common_util "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/util" "github.com/goharbor/harbor/src/core/middlewares/util"
"github.com/goharbor/harbor/src/pkg/art" middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"net/http" "net/http"
) )
type immutableHandler struct { type immutableHandler struct {
next http.Handler builders []interceptor.Builder
next http.Handler
} }
// New ... // New ...
func New(next http.Handler) http.Handler { func New(next http.Handler, builders ...interceptor.Builder) http.Handler {
if len(builders) == 0 {
builders = defaultBuilders
}
return &immutableHandler{ return &immutableHandler{
next: next, builders: builders,
next: next,
} }
} }
// ServeHTTP ... // ServeHTTP ...
func (rh immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (rh *immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if match, _, _ := util.MatchPushManifest(req); !match {
interceptor, err := rh.getInterceptor(req)
if err != nil {
log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
http.StatusInternalServerError)
return
}
if interceptor == nil {
rh.next.ServeHTTP(rw, req) rh.next.ServeHTTP(rw, req)
return return
} }
info, ok := util.ManifestInfoFromContext(req.Context())
if !ok { if err := interceptor.HandleRequest(req); err != nil {
var err error log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
info, err = util.ParseManifestInfoFromPath(req) if _, ok := err.(middlerware_err.ErrImmutable); ok {
if err != nil { http.Error(rw, util.MarshalError("DENIED",
log.Error(err) fmt.Sprintf("The tag is immutable, cannot be overwrite: %v", err)), http.StatusPreconditionFailed)
rh.next.ServeHTTP(rw, req)
return return
} }
http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
http.StatusInternalServerError)
return
}
rh.next.ServeHTTP(rw, req)
interceptor.HandleResponse(rw, req)
}
func (rh *immutableHandler) getInterceptor(req *http.Request) (interceptor.Interceptor, error) {
for _, builder := range rh.builders {
interceptor, err := builder.Build(req)
if err != nil {
return nil, err
}
if interceptor != nil {
return interceptor, nil
}
} }
_, repoName := common_util.ParseRepository(info.Repository) return nil, nil
matched, err := rule.NewRuleMatcher(info.ProjectID).Match(art.Candidate{
Repository: repoName,
Tag: info.Tag,
NamespaceID: info.ProjectID,
})
if err != nil {
log.Error(err)
rh.next.ServeHTTP(rw, req)
return
}
if !matched {
rh.next.ServeHTTP(rw, req)
return
}
artifactQuery := &models.ArtifactQuery{
PID: info.ProjectID,
Repo: info.Repository,
Tag: info.Tag,
}
afs, err := dao.ListArtifacts(artifactQuery)
if err != nil {
log.Error(err)
rh.next.ServeHTTP(rw, req)
return
}
if len(afs) == 0 {
rh.next.ServeHTTP(rw, req)
return
}
// rule matched and non-existent is a immutable tag
http.Error(rw, util.MarshalError("DENIED",
fmt.Sprintf("The tag:%s:%s is immutable, cannot be overwrite.", info.Repository, info.Tag)), http.StatusPreconditionFailed)
return
} }

View File

@ -0,0 +1,67 @@
package immutable
import (
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
common_util "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/util"
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"net/http"
)
// NewDeleteMFInteceptor ....
func NewDeleteMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
return &delmfInterceptor{
mf: mf,
}
}
type delmfInterceptor struct {
mf *util.ManifestInfo
}
// HandleRequest ...
func (dmf *delmfInterceptor) HandleRequest(req *http.Request) (err error) {
artifactQuery := &models.ArtifactQuery{
Digest: dmf.mf.Digest,
Repo: dmf.mf.Repository,
PID: dmf.mf.ProjectID,
}
var afs []*models.Artifact
afs, err = dao.ListArtifacts(artifactQuery)
if err != nil {
log.Error(err)
return
}
if len(afs) == 0 {
return
}
for _, af := range afs {
_, repoName := common_util.ParseRepository(dmf.mf.Repository)
var matched bool
matched, err = rule.NewRuleMatcher(dmf.mf.ProjectID).Match(art.Candidate{
Repository: repoName,
Tag: af.Tag,
NamespaceID: dmf.mf.ProjectID,
})
if err != nil {
log.Error(err)
return
}
if matched {
return middlerware_err.NewErrImmutable(repoName)
}
}
return
}
// HandleRequest ...
func (dmf *delmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
}

View File

@ -0,0 +1,65 @@
package immutable
import (
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
common_util "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/util"
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"net/http"
)
// NewPushMFInteceptor ....
func NewPushMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
return &pushmfInterceptor{
mf: mf,
}
}
type pushmfInterceptor struct {
mf *util.ManifestInfo
}
// HandleRequest ...
func (pmf *pushmfInterceptor) HandleRequest(req *http.Request) (err error) {
_, repoName := common_util.ParseRepository(pmf.mf.Repository)
var matched bool
matched, err = rule.NewRuleMatcher(pmf.mf.ProjectID).Match(art.Candidate{
Repository: repoName,
Tag: pmf.mf.Tag,
NamespaceID: pmf.mf.ProjectID,
})
if err != nil {
log.Error(err)
return
}
if !matched {
return
}
artifactQuery := &models.ArtifactQuery{
PID: pmf.mf.ProjectID,
Repo: pmf.mf.Repository,
Tag: pmf.mf.Tag,
}
var afs []*models.Artifact
afs, err = dao.ListArtifacts(artifactQuery)
if err != nil {
log.Error(err)
return
}
if len(afs) == 0 {
return
}
return middlerware_err.NewErrImmutable(repoName)
}
// HandleRequest ...
func (pmf *pushmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
}

View File

@ -0,0 +1,20 @@
package error
import (
"fmt"
)
// ErrImmutable ...
type ErrImmutable struct {
repo string
}
// Error ...
func (ei ErrImmutable) Error() string {
return fmt.Sprintf("Failed to process request, due to immutable. '%s'", ei.repo)
}
// NewErrImmutable ...
func NewErrImmutable(msg string) ErrImmutable {
return ErrImmutable{repo: msg}
}

View File

@ -253,7 +253,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
dest_registry: rule.dest_registry, dest_registry: rule.dest_registry,
trigger: rule.trigger, trigger: rule.trigger,
deletion: rule.deletion, deletion: rule.deletion,
enabled: true, enabled: rule.enabled,
override: rule.override override: rule.override
}); });
let filtersArray = this.getFilterArray(rule); let filtersArray = this.getFilterArray(rule);

View File

@ -24,7 +24,7 @@
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="row flex-items-xs-between jobsRow"> <div class="row flex-items-xs-between jobsRow">
<h5 class="flex-items-xs-bottom option-left-down">{{'REPLICATION.REPLICATION_EXECUTIONS' | translate}}</h5> <h5 class="flex-items-xs-bottom option-left-down">{{'REPLICATION.REPLICATION_EXECUTIONS' | translate}}</h5>
<div class="row flex-items-xs-between flex-items-xs-bottom"> <div class="row flex-items-xs-between flex-items-xs-bottom fiter-task">
<div class="execution-select"> <div class="execution-select">
<div class="select filter-tag" [hidden]="!isOpenFilterTag"> <div class="select filter-tag" [hidden]="!isOpenFilterTag">
<select (change)="doFilterJob($event)"> <select (change)="doFilterJob($event)">

View File

@ -49,7 +49,10 @@
.row-right { .row-right {
margin-left: 564px; margin-left: 564px;
} }
.fiter-task {
margin-left: .4rem;
margin-top: .05rem;
}
.replication-row { .replication-row {
position: relative; position: relative;
} }

View File

@ -168,6 +168,12 @@ describe('RepositoryComponent (inline template)', () => {
return of({}); return of({});
} }
}; };
const permissions = [
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE},
{resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL},
{resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE},
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE},
];
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
@ -224,16 +230,10 @@ describe('RepositoryComponent (inline template)', () => {
spyLabels = spyOn(labelService, 'getGLabels').and.returnValues(of(mockLabels).pipe(delay(0))); spyLabels = spyOn(labelService, 'getGLabels').and.returnValues(of(mockLabels).pipe(delay(0)));
spyLabels1 = spyOn(labelService, 'getPLabels').and.returnValues(of(mockLabels1).pipe(delay(0))); spyLabels1 = spyOn(labelService, 'getPLabels').and.returnValues(of(mockLabels1).pipe(delay(0)));
spyOn(userPermissionService, "getPermission") spyOn(userPermissionService, "hasProjectPermissions")
.withArgs(compRepo.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE ) .withArgs(compRepo.projectId, permissions )
.and.returnValue(of(mockHasAddLabelImagePermission)) .and.returnValue(of([mockHasAddLabelImagePermission, mockHasRetagImagePermission,
.withArgs(compRepo.projectId, USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.PULL ) mockHasDeleteImagePermission, mockHasScanImagePermission]));
.and.returnValue(of(mockHasRetagImagePermission))
.withArgs(compRepo.projectId, USERSTATICPERMISSION.REPOSITORY_TAG.KEY, USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE )
.and.returnValue(of(mockHasDeleteImagePermission))
.withArgs(compRepo.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY
, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE)
.and.returnValue(of(mockHasScanImagePermission));
fixture.detectChanges(); fixture.detectChanges();
}); });
let originalTimeout; let originalTimeout;

View File

@ -112,6 +112,15 @@ describe("TagComponent (inline template)", () => {
let mockHasRetagImagePermission: boolean = true; let mockHasRetagImagePermission: boolean = true;
let mockHasDeleteImagePermission: boolean = true; let mockHasDeleteImagePermission: boolean = true;
let mockHasScanImagePermission: boolean = true; let mockHasScanImagePermission: boolean = true;
const mockErrorHandler = {
error: () => {}
};
const permissions = [
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE},
{resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL},
{resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE},
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE},
];
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
@ -141,6 +150,7 @@ describe("TagComponent (inline template)", () => {
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }, { provide: ScanningResultService, useClass: ScanningResultDefaultService },
{ provide: LabelService, useClass: LabelDefaultService }, { provide: LabelService, useClass: LabelDefaultService },
{ provide: UserPermissionService, useClass: UserPermissionDefaultService }, { provide: UserPermissionService, useClass: UserPermissionDefaultService },
{ provide: mockErrorHandler, useValue: ErrorHandler },
{ provide: OperationService }, { provide: OperationService },
] ]
}).compileComponents(); }).compileComponents();
@ -169,15 +179,10 @@ describe("TagComponent (inline template)", () => {
let http: HttpClient; let http: HttpClient;
http = fixture.debugElement.injector.get(HttpClient); http = fixture.debugElement.injector.get(HttpClient);
spyScanner = spyOn(http, "get").and.returnValue(of(scannerMock)); spyScanner = spyOn(http, "get").and.returnValue(of(scannerMock));
spyOn(userPermissionService, "getPermission") spyOn(userPermissionService, "hasProjectPermissions")
.withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE ) .withArgs(comp.projectId, permissions )
.and.returnValue(of(mockHasAddLabelImagePermission)) .and.returnValue(of([mockHasAddLabelImagePermission, mockHasRetagImagePermission,
.withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.PULL ) mockHasDeleteImagePermission, mockHasScanImagePermission]));
.and.returnValue(of(mockHasRetagImagePermission))
.withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG.KEY, USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE )
.and.returnValue(of(mockHasDeleteImagePermission))
.withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE)
.and.returnValue(of(mockHasScanImagePermission));
labelService = fixture.debugElement.injector.get(LabelService); labelService = fixture.debugElement.injector.get(LabelService);

View File

@ -224,9 +224,6 @@ export class TagComponent implements OnInit, AfterViewInit {
} }
ngAfterViewInit() { ngAfterViewInit() {
if (!this.withAdmiral) {
this.getAllLabels();
}
} }
public get filterLabelPieceWidth() { public get filterLabelPieceWidth() {
@ -730,21 +727,24 @@ export class TagComponent implements OnInit, AfterViewInit {
return st !== VULNERABILITY_SCAN_STATUS.RUNNING; return st !== VULNERABILITY_SCAN_STATUS.RUNNING;
} }
getImagePermissionRule(projectId: number): void { getImagePermissionRule(projectId: number): void {
let hasAddLabelImagePermission = this.userPermissionService.getPermission(projectId, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, const permissions = [
USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE); {resource: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE},
let hasRetagImagePermission = this.userPermissionService.getPermission(projectId, {resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL},
USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.PULL); {resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE},
let hasDeleteImagePermission = this.userPermissionService.getPermission(projectId, {resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE},
USERSTATICPERMISSION.REPOSITORY_TAG.KEY, USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE); ];
let hasScanImagePermission = this.userPermissionService.getPermission(projectId, this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array<boolean>) => {
USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE); this.hasAddLabelImagePermission = results[0];
forkJoin(hasAddLabelImagePermission, hasRetagImagePermission, hasDeleteImagePermission, hasScanImagePermission) this.hasRetagImagePermission = results[1];
.subscribe(permissions => { this.hasDeleteImagePermission = results[2];
this.hasAddLabelImagePermission = permissions[0] as boolean; this.hasScanImagePermission = results[3];
this.hasRetagImagePermission = permissions[1] as boolean; // only has label permission
this.hasDeleteImagePermission = permissions[2] as boolean; if (this.hasAddLabelImagePermission) {
this.hasScanImagePermission = permissions[3] as boolean; if (!this.withAdmiral) {
}, error => this.errorHandler.error(error)); this.getAllLabels();
}
}
}, error => this.errorHandler.error(error));
} }
// Trigger scan // Trigger scan
scanNow(t: Tag[]): void { scanNow(t: Tag[]): void {

View File

@ -73,7 +73,7 @@ class TestProjects(unittest.TestCase):
#5. Get project quota #5. Get project quota
quota = self.system.get_project_quota("project", TestProjects.project_test_quota_id, **ADMIN_CLIENT) quota = self.system.get_project_quota("project", TestProjects.project_test_quota_id, **ADMIN_CLIENT)
self.assertEqual(quota[0].used["count"], 1) self.assertEqual(quota[0].used["count"], 1)
self.assertEqual(quota[0].used["storage"], 2791709) self.assertEqual(quota[0].used["storage"], 2789174)
#6. Delete repository(RA) by user(UA); #6. Delete repository(RA) by user(UA);
self.repo.delete_repoitory(TestProjects.repo_name, **ADMIN_CLIENT) self.repo.delete_repoitory(TestProjects.repo_name, **ADMIN_CLIENT)

View File

@ -54,6 +54,6 @@ Generate And Return Secret
Retry Element Click ${more_btn} Retry Element Click ${more_btn}
Retry Element Click ${generate_secret_btn} Retry Element Click ${generate_secret_btn}
Retry Double Keywords When Error Retry Element Click ${confirm_btn} Retry Wait Until Page Not Contains Element ${confirm_btn} Retry Double Keywords When Error Retry Element Click ${confirm_btn} Retry Wait Until Page Not Contains Element ${confirm_btn}
Retry Wait Until Page Contains generate CLI secret success Retry Wait Until Page Contains Cli secret setting is successful
${secret}= Get Secrete By API ${url} ${secret}= Get Secrete By API ${url}
[Return] ${secret} [Return] ${secret}