From c267aaa47408acb1dcfbc1240a8a7592e5089bf0 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 7 Feb 2020 09:11:54 +0800 Subject: [PATCH 1/4] Implement repository deletion API Implement repository deletion API based on the new design Signed-off-by: Wenkai Yin --- api/v2.0/swagger.yaml | 22 ++++++++++++ src/api/artifact/controller.go | 1 + src/api/repository/controller.go | 27 ++++++++++++++ src/api/repository/controller_test.go | 15 ++++++++ src/server/v2.0/handler/handler.go | 3 +- src/server/v2.0/handler/repository.go | 45 ++++++++++++++++++++++++ src/testing/api/repository/controller.go | 6 ++++ 7 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/server/v2.0/handler/repository.go diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index fd1cc80842d..52fdfeee55d 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -229,6 +229,28 @@ paths: $ref: '#/responses/404' '500': $ref: '#/responses/500' + /projects/{project_name}/repositories/{repository_name}: + delete: + summary: Delete repository + description: Delete the repository specified by name + tags: + - repository + operationId: deleteRepository + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/projectName' + - $ref: '#/parameters/repositoryName' + responses: + '200': + $ref: '#/responses/200' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '500': + $ref: '#/responses/500' parameters: requestId: name: X-Request-Id diff --git a/src/api/artifact/controller.go b/src/api/artifact/controller.go index 3866a069d3c..7c4ad8e794d 100644 --- a/src/api/artifact/controller.go +++ b/src/api/artifact/controller.go @@ -295,6 +295,7 @@ func (c *controller) Delete(ctx context.Context, id int64) error { } func (c *controller) CreateTag(ctx context.Context, tag *Tag) (int64, error) { + // TODO fire event return c.tagMgr.Create(ctx, &(tag.Tag)) } func (c *controller) ListTags(ctx context.Context, query *q.Query, option *TagOption) (int64, []*Tag, error) { diff --git a/src/api/repository/controller.go b/src/api/repository/controller.go index abb41600286..2eb159d399a 100644 --- a/src/api/repository/controller.go +++ b/src/api/repository/controller.go @@ -16,6 +16,7 @@ package repository import ( "context" + "github.com/goharbor/harbor/src/api/artifact" "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/utils" ierror "github.com/goharbor/harbor/src/internal/error" @@ -41,6 +42,8 @@ type Controller interface { Get(ctx context.Context, id int64) (repository *models.RepoRecord, err error) // GetByName gets the repository specified by name GetByName(ctx context.Context, name string) (repository *models.RepoRecord, err error) + // Delete the repository specified by ID + Delete(ctx context.Context, id int64) (err error) } // NewController creates an instance of the default repository controller @@ -48,12 +51,14 @@ func NewController() Controller { return &controller{ proMgr: project.Mgr, repoMgr: repository.Mgr, + artCtl: artifact.Ctl, } } type controller struct { proMgr project.Manager repoMgr repository.Manager + artCtl artifact.Controller } func (c *controller) Ensure(ctx context.Context, name string) (bool, int64, error) { @@ -108,3 +113,25 @@ func (c *controller) Get(ctx context.Context, id int64) (*models.RepoRecord, err func (c *controller) GetByName(ctx context.Context, name string) (*models.RepoRecord, error) { return c.repoMgr.GetByName(ctx, name) } + +func (c *controller) Delete(ctx context.Context, id int64) error { + // TODO auth + // TODO how to make sure the logic included by middlewares(immutable, readonly, quota, etc) + // TODO is covered when deleting the artifacts of the repository + _, artifacts, err := c.artCtl.List(ctx, &q.Query{ + Keywords: map[string]interface{}{ + "RepositoryID": id, + }, + }, nil) + if err != nil { + return err + } + for _, artifact := range artifacts { + if err = c.artCtl.Delete(ctx, artifact.ID); err != nil { + return err + } + } + return c.repoMgr.Delete(ctx, id) + + // TODO fire event +} diff --git a/src/api/repository/controller_test.go b/src/api/repository/controller_test.go index 8e7fe8f1f9d..fadef2d9c17 100644 --- a/src/api/repository/controller_test.go +++ b/src/api/repository/controller_test.go @@ -15,7 +15,9 @@ package repository import ( + "github.com/goharbor/harbor/src/api/artifact" "github.com/goharbor/harbor/src/common/models" + artifacttesting "github.com/goharbor/harbor/src/testing/api/artifact" "github.com/goharbor/harbor/src/testing/pkg/project" "github.com/goharbor/harbor/src/testing/pkg/repository" "github.com/stretchr/testify/suite" @@ -27,14 +29,17 @@ type controllerTestSuite struct { ctl *controller proMgr *project.FakeManager repoMgr *repository.FakeManager + artCtl *artifacttesting.FakeController } func (c *controllerTestSuite) SetupTest() { c.proMgr = &project.FakeManager{} c.repoMgr = &repository.FakeManager{} + c.artCtl = &artifacttesting.FakeController{} c.ctl = &controller{ proMgr: c.proMgr, repoMgr: c.repoMgr, + artCtl: c.artCtl, } } @@ -104,6 +109,16 @@ func (c *controllerTestSuite) TestGetByName() { c.Equal(int64(1), repository.RepositoryID) } +func (c *controllerTestSuite) TestDelete() { + art := &artifact.Artifact{} + art.ID = 1 + c.artCtl.On("List").Return(1, []*artifact.Artifact{art}, nil) + c.artCtl.On("Delete").Return(nil) + c.repoMgr.On("Delete").Return(nil) + err := c.ctl.Delete(nil, 1) + c.Require().Nil(err) +} + func TestControllerTestSuite(t *testing.T) { suite.Run(t, &controllerTestSuite{}) } diff --git a/src/server/v2.0/handler/handler.go b/src/server/v2.0/handler/handler.go index d6f354eee5f..157f3b04376 100644 --- a/src/server/v2.0/handler/handler.go +++ b/src/server/v2.0/handler/handler.go @@ -30,7 +30,8 @@ import ( // New returns http handler for API V2.0 func New() http.Handler { h, api, err := restapi.HandlerAPI(restapi.Config{ - ArtifactAPI: newArtifactAPI(), + ArtifactAPI: newArtifactAPI(), + RepositoryAPI: newRepositoryAPI(), }) if err != nil { log.Fatal(err) diff --git a/src/server/v2.0/handler/repository.go b/src/server/v2.0/handler/repository.go new file mode 100644 index 00000000000..78229ebbeac --- /dev/null +++ b/src/server/v2.0/handler/repository.go @@ -0,0 +1,45 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +import ( + "context" + "fmt" + "github.com/go-openapi/runtime/middleware" + "github.com/goharbor/harbor/src/api/repository" + operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/repository" +) + +func newRepositoryAPI() *repositoryAPI { + return &repositoryAPI{ + repoCtl: repository.Ctl, + } +} + +type repositoryAPI struct { + BaseAPI + repoCtl repository.Controller +} + +func (r *repositoryAPI) DeleteRepository(ctx context.Context, params operation.DeleteRepositoryParams) middleware.Responder { + repository, err := r.repoCtl.GetByName(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName)) + if err != nil { + return r.SendError(ctx, err) + } + if err := r.repoCtl.Delete(ctx, repository.RepositoryID); err != nil { + return r.SendError(ctx, err) + } + return operation.NewDeleteRepositoryOK() +} diff --git a/src/testing/api/repository/controller.go b/src/testing/api/repository/controller.go index 3ab1ecc2084..0a95233f4c2 100644 --- a/src/testing/api/repository/controller.go +++ b/src/testing/api/repository/controller.go @@ -62,3 +62,9 @@ func (f *FakeController) GetByName(ctx context.Context, name string) (*models.Re } return repository, args.Error(1) } + +// Delete ... +func (f *FakeController) Delete(ctx context.Context, id int64) error { + args := f.Called() + return args.Error(0) +} From 0fbbd674c2c74c9cbced1f80d9e3af009d34de54 Mon Sep 17 00:00:00 2001 From: wang yan Date: Thu, 6 Feb 2020 13:17:13 +0800 Subject: [PATCH 2/4] Use controller rather than manager in the API handler and middleware Signed-off-by: wang yan --- src/server/middleware/immutable/deletemf.go | 61 +++-------------- src/server/middleware/immutable/pushmf.go | 72 ++++----------------- src/server/middleware/util.go | 48 ++------------ 3 files changed, 26 insertions(+), 155 deletions(-) diff --git a/src/server/middleware/immutable/deletemf.go b/src/server/middleware/immutable/deletemf.go index 32d2c9e6fe0..dc908d0fdb3 100644 --- a/src/server/middleware/immutable/deletemf.go +++ b/src/server/middleware/immutable/deletemf.go @@ -3,14 +3,9 @@ package immutable import ( "errors" "fmt" + "github.com/goharbor/harbor/src/api/artifact" common_util "github.com/goharbor/harbor/src/common/utils" internal_errors "github.com/goharbor/harbor/src/internal/error" - "github.com/goharbor/harbor/src/pkg/art" - "github.com/goharbor/harbor/src/pkg/artifact" - "github.com/goharbor/harbor/src/pkg/immutabletag/match/rule" - "github.com/goharbor/harbor/src/pkg/q" - "github.com/goharbor/harbor/src/pkg/repository" - "github.com/goharbor/harbor/src/pkg/tag" "github.com/goharbor/harbor/src/server/middleware" "net/http" ) @@ -43,56 +38,20 @@ func handleDelete(req *http.Request) error { return errors.New("cannot get the manifest information from request context") } - _, repoName := common_util.ParseRepository(mf.Repository) - total, repos, err := repository.Mgr.List(req.Context(), &q.Query{ - Keywords: map[string]interface{}{ - "Name": mf.Repository, - }, - }) - if err != nil { - return err - } - if total == 0 { - return nil - } - - total, afs, err := artifact.Mgr.List(req.Context(), &q.Query{ - Keywords: map[string]interface{}{ - "ProjectID": mf.ProjectID, - "RepositoryID": repos[0].RepositoryID, - "Digest": mf.Digest, - }, - }) - if err != nil { - return err - } - if total == 0 { - return nil - } - - total, tags, err := tag.Mgr.List(req.Context(), &q.Query{ - Keywords: map[string]interface{}{ - "ArtifactID": afs[0].ID, - }, + af, err := artifact.Ctl.GetByReference(req.Context(), mf.Repository, mf.Digest, &artifact.Option{ + WithTag: true, + TagOption: &artifact.TagOption{WithImmutableStatus: true}, }) if err != nil { + if internal_errors.IsErr(err, internal_errors.NotFoundCode) { + return nil + } return err } - if total == 0 { - return nil - } - for _, tag := range tags { - var matched bool - matched, err = rule.NewRuleMatcher().Match(mf.ProjectID, art.Candidate{ - Repository: repoName, - Tag: tag.Name, - NamespaceID: mf.ProjectID, - }) - if err != nil { - return err - } - if matched { + _, repoName := common_util.ParseRepository(mf.Repository) + for _, tag := range af.Tags { + if tag.Immutable { return NewErrImmutable(repoName, tag.Name) } } diff --git a/src/server/middleware/immutable/pushmf.go b/src/server/middleware/immutable/pushmf.go index 3e3f57ba5be..6964fa4aeb7 100644 --- a/src/server/middleware/immutable/pushmf.go +++ b/src/server/middleware/immutable/pushmf.go @@ -3,14 +3,9 @@ package immutable import ( "errors" "fmt" + "github.com/goharbor/harbor/src/api/artifact" common_util "github.com/goharbor/harbor/src/common/utils" internal_errors "github.com/goharbor/harbor/src/internal/error" - "github.com/goharbor/harbor/src/pkg/art" - "github.com/goharbor/harbor/src/pkg/artifact" - "github.com/goharbor/harbor/src/pkg/immutabletag/match/rule" - "github.com/goharbor/harbor/src/pkg/q" - "github.com/goharbor/harbor/src/pkg/repository" - "github.com/goharbor/harbor/src/pkg/tag" "github.com/goharbor/harbor/src/server/middleware" "net/http" ) @@ -45,65 +40,22 @@ func handlePush(req *http.Request) error { return errors.New("cannot get the manifest information from request context") } - _, repoName := common_util.ParseRepository(mf.Repository) - var matched bool - matched, err := rule.NewRuleMatcher().Match(mf.ProjectID, art.Candidate{ - Repository: repoName, - Tag: mf.Tag, - NamespaceID: mf.ProjectID, - }) - if err != nil { - return err - } - if !matched { - return nil - } - - // match repository ... - total, repos, err := repository.Mgr.List(req.Context(), &q.Query{ - Keywords: map[string]interface{}{ - "Name": mf.Repository, - }, - }) - if err != nil { - return err - } - if total == 0 { - return nil - } - - // match artifacts ... - total, afs, err := artifact.Mgr.List(req.Context(), &q.Query{ - Keywords: map[string]interface{}{ - "ProjectID": mf.ProjectID, - "RepositoryID": repos[0].RepositoryID, - }, + af, err := artifact.Ctl.GetByReference(req.Context(), mf.Repository, mf.Tag, &artifact.Option{ + WithTag: true, + TagOption: &artifact.TagOption{WithImmutableStatus: true}, }) if err != nil { + if internal_errors.IsErr(err, internal_errors.NotFoundCode) { + return nil + } return err } - if total == 0 { - return nil - } - // match tags ... - for _, af := range afs { - total, tags, err := tag.Mgr.List(req.Context(), &q.Query{ - Keywords: map[string]interface{}{ - "ArtifactID": af.ID, - }, - }) - if err != nil { - return err - } - if total == 0 { - continue - } - for _, tag := range tags { - // push a existing immutable tag, reject the request - if tag.Name == mf.Tag { - return NewErrImmutable(repoName, mf.Tag) - } + _, repoName := common_util.ParseRepository(mf.Repository) + for _, tag := range af.Tags { + // push a existing immutable tag, reject th e request + if tag.Name == mf.Tag && tag.Immutable { + return NewErrImmutable(repoName, mf.Tag) } } diff --git a/src/server/middleware/util.go b/src/server/middleware/util.go index 76e8d9a3a2a..30930b87634 100644 --- a/src/server/middleware/util.go +++ b/src/server/middleware/util.go @@ -4,16 +4,13 @@ import ( "context" "fmt" "github.com/docker/distribution/reference" + "github.com/goharbor/harbor/src/api/artifact" "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/promgr" - "github.com/goharbor/harbor/src/pkg/artifact" - "github.com/goharbor/harbor/src/pkg/q" - "github.com/goharbor/harbor/src/pkg/repository" "github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/scan/whitelist" - "github.com/goharbor/harbor/src/pkg/tag" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "net/http" @@ -70,50 +67,13 @@ type ManifestInfo struct { // ManifestExists ... func (info *ManifestInfo) ManifestExists(ctx context.Context) (bool, error) { info.manifestExistOnce.Do(func() { - - // ToDo: use the artifact controller method - total, repos, err := repository.Mgr.List(ctx, &q.Query{ - Keywords: map[string]interface{}{ - "Name": info.Repository, - }, - }) - if err != nil { - info.manifestExistErr = err - return - } - if total == 0 { - return - } - - total, tags, err := tag.Mgr.List(ctx, &q.Query{ - Keywords: map[string]interface{}{ - "Name": info.Tag, - "RepositoryID": repos[0].RepositoryID, - }, - }) - if err != nil { - info.manifestExistErr = err - return - } - if total == 0 { - return - } - - total, afs, err := artifact.Mgr.List(ctx, &q.Query{ - Keywords: map[string]interface{}{ - "ID": tags[0].ArtifactID, - }, - }) + af, err := artifact.Ctl.GetByReference(ctx, info.Repository, info.Tag, nil) if err != nil { info.manifestExistErr = err return } - if total == 0 { - return - } - - info.Digest = afs[0].Digest - info.manifestExist = total > 0 + info.manifestExist = true + info.Digest = af.Digest }) return info.manifestExist, info.manifestExistErr From 348748a4bec5c3cde1e4bcf77e0c2df741038555 Mon Sep 17 00:00:00 2001 From: AllForNothing Date: Fri, 7 Feb 2020 16:55:30 +0800 Subject: [PATCH 3/4] Clear build errors Signed-off-by: AllForNothing --- .../dependencies.component.spec.ts | 2 +- .../summary/summary.component.spec.ts | 64 ----- .../values/values.component.spec.ts | 2 +- .../artifact-summary.component.spec.ts | 251 ------------------ 4 files changed, 2 insertions(+), 317 deletions(-) diff --git a/src/portal/src/lib/components/artifact/artifact-additions/dependencies/dependencies.component.spec.ts b/src/portal/src/lib/components/artifact/artifact-additions/dependencies/dependencies.component.spec.ts index 2f277da5e78..e458c6b9654 100644 --- a/src/portal/src/lib/components/artifact/artifact-additions/dependencies/dependencies.component.spec.ts +++ b/src/portal/src/lib/components/artifact/artifact-additions/dependencies/dependencies.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { DependenciesComponent } from './chart-detail-dependency.component'; +import { DependenciesComponent } from "./dependencies.component"; describe('ChartDetailDependencyComponent', () => { let component: DependenciesComponent; diff --git a/src/portal/src/lib/components/artifact/artifact-additions/summary/summary.component.spec.ts b/src/portal/src/lib/components/artifact/artifact-additions/summary/summary.component.spec.ts index 1ca942ebdba..8b137891791 100644 --- a/src/portal/src/lib/components/artifact/artifact-additions/summary/summary.component.spec.ts +++ b/src/portal/src/lib/components/artifact/artifact-additions/summary/summary.component.spec.ts @@ -1,65 +1 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { SummaryComponent } from './chart-detail-summary.component'; -import { ClarityModule } from '@clr/angular'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { MarkedOptions, MarkdownModule, MarkdownService } from 'ngx-markdown'; -import { HelmChartService } from '../../helm-chart.service'; -import { DefaultErrorHandler, ErrorHandler } from "../../../../../lib/utils/error-handler"; -describe('ChartDetailSummaryComponent', () => { - let component: SummaryComponent; - let fixture: ComponentFixture; - const mockHelmChartService = { - downloadChart: function () { - } - }; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - ClarityModule, - FormsModule, - MarkdownModule, - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ], - declarations: [SummaryComponent], - providers: [ - TranslateService, - MarkdownService, - { provide: MarkedOptions, useValue: {} }, - { provide: ErrorHandler, useValue: DefaultErrorHandler }, - { provide: HelmChartService, useValue: mockHelmChartService }, - ] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SummaryComponent); - // markdownService = TestBed.get(MarkdownService); - component = fixture.componentInstance; - component.summary = { - name: "string", - home: "string", - sources: [], - version: "string", - description: "string", - keywords: [], - maintainers: [], - engine: "string", - icon: "string", - appVersion: "string", - urls: [], - created: new Date().toDateString(), - digest: "string", - }; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/portal/src/lib/components/artifact/artifact-additions/values/values.component.spec.ts b/src/portal/src/lib/components/artifact/artifact-additions/values/values.component.spec.ts index 55df8fe1c53..e7a4511795f 100644 --- a/src/portal/src/lib/components/artifact/artifact-additions/values/values.component.spec.ts +++ b/src/portal/src/lib/components/artifact/artifact-additions/values/values.component.spec.ts @@ -1,11 +1,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { ValuesComponent } from './chart-detail-value.component'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ClarityModule } from '@clr/angular'; import { FormsModule } from '@angular/forms'; import { MarkdownModule, MarkdownService, MarkedOptions } from 'ngx-markdown'; import { BrowserModule } from '@angular/platform-browser'; +import { ValuesComponent } from "./values.component"; describe('ChartDetailValueComponent', () => { let component: ValuesComponent; diff --git a/src/portal/src/lib/components/artifact/artifact-summary.component.spec.ts b/src/portal/src/lib/components/artifact/artifact-summary.component.spec.ts index 8ea30555beb..e69de29bb2d 100644 --- a/src/portal/src/lib/components/artifact/artifact-summary.component.spec.ts +++ b/src/portal/src/lib/components/artifact/artifact-summary.component.spec.ts @@ -1,251 +0,0 @@ -import { ComponentFixture, TestBed, async } from "@angular/core/testing"; - -import { SharedModule } from "../../utils/shared/shared.module"; -import { ResultGridComponent } from "../vulnerability-scanning/result-grid.component"; -import { ArtifactSummaryComponent } from "./artifact-summary.component"; -import { TagHistoryComponent } from "./tag-history.component"; - -import { ErrorHandler } from "../../utils/error-handler/error-handler"; -import { - Tag, - Manifest, - VulnerabilitySummary, - VulnerabilityItem, - VulnerabilitySeverity -} from "../../services/interface"; -import { SERVICE_CONFIG, IServiceConfig } from "../../entities/service.config"; -import { - TagService, - TagDefaultService, - ScanningResultService, - ScanningResultDefaultService -} from "../../services"; -import { VULNERABILITY_SCAN_STATUS, VULNERABILITY_SEVERITY } from "../../utils/utils"; -import { LabelPieceComponent } from "../label-piece/label-piece.component"; -import { ChannelService } from "../../services/channel.service"; -import { of } from "rxjs"; -import { - JobLogService, - JobLogDefaultService -} from "../../services/job-log.service"; -import { UserPermissionService, UserPermissionDefaultService } from "../../services/permission.service"; -import { USERSTATICPERMISSION } from "../../services/permission-static"; -import { FilterComponent } from "../filter/filter.component"; -import { HarborLibraryModule } from "../../harbor-library.module"; -import { Artifact, Reference } from "./artifact"; - -describe("ArtifactSummaryComponent (inline template)", () => { - let comp: ArtifactSummaryComponent; - let fixture: ComponentFixture; - let tagService: TagService; - let userPermissionService: UserPermissionService; - let scanningService: ScanningResultService; - let spy: jasmine.Spy; - let vulSpy: jasmine.Spy; - let manifestSpy: jasmine.Spy; - let mockVulnerability: VulnerabilitySummary = { - scan_status: VULNERABILITY_SCAN_STATUS.SUCCESS, - severity: "High", - end_time: new Date(), - summary: { - total: 124, - fixable: 50, - summary: { - "High": 5, - "Low": 5 - } - } - }; - let mockTag: Artifact = - { - "id": 1, - type: 'image', - repository: "goharbor/harbor-portal", - tags: [{ - id: '1', - name: 'tag1', - artifact_id: 1, - upload_time: '2020-01-06T09:40:08.036866579Z', - }, - { - id: '2', - name: 'tag2', - artifact_id: 2, - pull_time: '2020-01-06T09:40:08.036866579Z', - push_time: '2020-01-06T09:40:08.036866579Z', - },], - references: [new Reference(1), new Reference(2)], - media_type: 'string', - "digest": "sha256:4875cda368906fd670c9629b5e416ab3d6c0292015f3c3f12ef37dc9a32fc8d4", - "size": 20372934, - "scan_overview": { - "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0": { - "report_id": "5e64bc05-3102-11ea-93ae-0242ac140004", - "scan_status": "Error", - "severity": "", - "duration": 118, - "summary": null, - "start_time": "2020-01-07T04:01:23.157711Z", - "end_time": "2020-01-07T04:03:21.662766Z" - } - }, - "labels": [ - { - "id": 3, - "name": "aaa", - "description": "", - "color": "#0095D3", - "scope": "g", - "project_id": 0, - "creation_time": "2020-01-13T05:44:00.580198Z", - "update_time": "2020-01-13T05:44:00.580198Z", - "deleted": false - }, - { - "id": 6, - "name": "dbc", - "description": "", - "color": "", - "scope": "g", - "project_id": 0, - "creation_time": "2020-01-13T08:27:19.279123Z", - "update_time": "2020-01-13T08:27:19.279123Z", - "deleted": false - } - ], - "push_time": "2020-01-07T03:33:41.162319Z", - "pull_time": "0001-01-01T00:00:00Z", - hasReferenceArtifactList: [], - noReferenceArtifactList: [] - - }; - - let config: IServiceConfig = { - repositoryBaseEndpoint: "/api/repositories/testing" - }; - let mockHasVulnerabilitiesListPermission: boolean = false; - let mockHasBuildHistoryPermission: boolean = true; - let mockManifest: Manifest = { - manifset: {}, - // tslint:disable-next-line:max-line-length - config: `{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:fbef17698ac8605733924d5662f0cbfc0b27a51e83ab7d7a4b8d8a9a9fe0d1c2","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"30e1a2427aa2325727a092488d304505780501585a6ccf5a6a53c4d83a826101","container_config":{"Hostname":"30e1a2427aa2","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\\"/bin/sh\\"]"],"ArgsEscaped":true,"Image":"sha256:fbef17698ac8605733924d5662f0cbfc0b27a51e83ab7d7a4b8d8a9a9fe0d1c2","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2018-01-09T21:10:58.579708634Z","docker_version":"17.06.2-ce","history":[{"created":"2018-01-09T21:10:58.365737589Z","created_by":"/bin/sh -c #(nop) ADD file:093f0723fa46f6cdbd6f7bd146448bb70ecce54254c35701feeceb956414622f in / "},{"created":"2018-01-09T21:10:58.579708634Z","created_by":"/bin/sh -c #(nop) CMD [\\"/bin/sh\\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:cd7100a72410606589a54b932cabd804a17f9ae5b42a1882bd56d263e02b6215"]}}` - }; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - SharedModule, - HarborLibraryModule - ], - providers: [ - ErrorHandler, - ChannelService, - JobLogDefaultService, - { provide: JobLogService, useClass: JobLogDefaultService }, - { provide: SERVICE_CONFIG, useValue: config }, - { provide: TagService, useClass: TagDefaultService }, - { provide: UserPermissionService, useClass: UserPermissionDefaultService }, - { - provide: ScanningResultService, - useClass: ScanningResultDefaultService - } - ] - }); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ArtifactSummaryComponent); - comp = fixture.componentInstance; - - comp.artifactDigest = 'sha256:23456'; - comp.repositoryName = "mock_repo"; - comp.projectId = 1; - - - tagService = fixture.debugElement.injector.get(TagService); - spy = spyOn(tagService, "getTag").and.returnValues( - of(mockTag) - ); - - let mockData: VulnerabilityItem[] = []; - for (let i = 0; i < 30; i++) { - let res: VulnerabilityItem = { - id: "CVE-2016-" + (8859 + i), - severity: - i % 2 === 0 - ? VULNERABILITY_SEVERITY.HIGH - : VULNERABILITY_SEVERITY.MEDIUM, - package: "package_" + i, - links: ["https://security-tracker.debian.org/tracker/CVE-2016-4484"], - layer: "layer_" + i, - version: "4." + i + ".0", - fix_version: "4." + i + ".11", - description: "Mock data" - }; - mockData.push(res); - } - scanningService = fixture.debugElement.injector.get(ScanningResultService); - vulSpy = spyOn( - scanningService, - "getVulnerabilityScanningResults" - ).and.returnValue(of(mockData)); - manifestSpy = spyOn(tagService, "getManifest").and.returnValues( - of(mockManifest) - ); - userPermissionService = fixture.debugElement.injector.get(UserPermissionService); - - spyOn(userPermissionService, "getPermission") - .withArgs(comp.projectId, - USERSTATICPERMISSION.REPOSITORY_TAG_VULNERABILITY.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_VULNERABILITY.VALUE.LIST ) - .and.returnValue(of(mockHasVulnerabilitiesListPermission)) - .withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_MANIFEST.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_MANIFEST.VALUE.READ ) - .and.returnValue(of(mockHasBuildHistoryPermission)); - fixture.detectChanges(); - }); - - it("should load data", async(() => { - expect(spy.calls.any).toBeTruthy(); - })); - - it("should load history data", async(() => { - expect(manifestSpy.calls.any).toBeTruthy(); - })); - - it("should rightly display tag name and version", async(() => { - fixture.detectChanges(); - - fixture.whenStable().then(() => { - fixture.detectChanges(); - - let el: HTMLElement = fixture.nativeElement.querySelector(".custom-h2"); - expect(el).toBeTruthy(); - expect(el.textContent.trim()).toEqual("mock_repo:nginx"); - }); - })); - - it("should display tag details", async(() => { - fixture.detectChanges(); - - fixture.whenStable().then(() => { - fixture.detectChanges(); - - let el: HTMLElement = fixture.nativeElement.querySelector( - ".image-detail-label .image-details" - ); - expect(el).toBeTruthy(); - expect(el.textContent).toEqual("steven"); - }); - })); - - it("should render history info", async(() => { - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - - let els: HTMLElement[] = fixture.nativeElement.querySelectorAll( - ".history-item" - ); - expect(els).toBeTruthy(); - expect(els.length).toBe(2); - }); - })); -}); From e00a4bce0de6435e4eea45f737f368491c6cdbf7 Mon Sep 17 00:00:00 2001 From: AllForNothing Date: Mon, 10 Feb 2020 11:30:41 +0800 Subject: [PATCH 4/4] use new repo deletion API Signed-off-by: AllForNothing --- .../repository-gridview/repository-gridview.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts index 83642abfcf7..36561bfdbb7 100644 --- a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts +++ b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts @@ -22,6 +22,7 @@ import { UserPermissionService } from "../../services/permission.service"; import { of } from "rxjs"; import { HarborLibraryModule } from "../../harbor-library.module"; import { delay } from 'rxjs/operators'; +import { RepositoryService as NewRepositoryService } from "../../../../ng-swagger-gen/services/repository.service"; describe('RepositoryComponentGridview (inline template)', () => { let compRepo: RepositoryGridviewComponent; @@ -117,6 +118,7 @@ describe('RepositoryComponentGridview (inline template)', () => { { provide: ErrorHandler, useValue: fakedErrorHandler }, { provide: SERVICE_CONFIG, useValue: config }, { provide: RepositoryService, useValue: fakedRepositoryService }, + { provide: NewRepositoryService, useValue: fakedRepositoryService }, { provide: TagService, useClass: TagDefaultService }, { provide: ProjectService, useClass: ProjectDefaultService }, { provide: RetagService, useClass: RetagDefaultService },