diff --git a/src/ui_ng/lib/src/service/interface.ts b/src/ui_ng/lib/src/service/interface.ts index 176718961..d72edce88 100644 --- a/src/ui_ng/lib/src/service/interface.ts +++ b/src/ui_ng/lib/src/service/interface.ts @@ -11,6 +11,30 @@ export interface Base { update_time?: Date; } +/** + * Interface for tag history + * + * @export + * @interface TagCompatibility + */ +export interface TagCompatibility { + v1Compatibility: string; +} + +/** + * Interface for tag manifest + * + * @export + * @interface TagManifest + */ +export interface TagManifest { + schemaVersion: number; + name: string; + tag: string; + architecture: string; + history: TagCompatibility[]; +} + /** * Interface for Repository * @@ -35,7 +59,11 @@ export interface Repository extends Base { * @interface Tag * @extends {Base} */ -export interface Tag extends Base { } +export interface Tag extends Base { + tag: string; + manifest: TagManifest; + signed?: number; //May NOT exist +} /** * Interface for registry endpoints. @@ -69,14 +97,14 @@ export interface ReplicationJob { } * @interface AccessLog */ export interface AccessLog { - log_id: number, - project_id: number, - repo_name: string, - repo_tag: string, - operation: string, - op_time: string | Date, - user_id: number, - username: string, - keywords?: string, //NOT used now - guid?: string //NOT used now + log_id: number; + project_id: number; + repo_name: string; + repo_tag: string; + operation: string; + op_time: string | Date; + user_id: number; + username: string; + keywords?: string; //NOT used now + guid?: string; //NOT used now } \ No newline at end of file diff --git a/src/ui_ng/lib/src/service/tag.service.spec.ts b/src/ui_ng/lib/src/service/tag.service.spec.ts index aa0a15a1c..4ea229a98 100644 --- a/src/ui_ng/lib/src/service/tag.service.spec.ts +++ b/src/ui_ng/lib/src/service/tag.service.spec.ts @@ -1,15 +1,62 @@ -import { TestBed, inject } from '@angular/core/testing'; +import { TestBed, inject, async } from '@angular/core/testing'; import { TagService, TagDefaultService } from './tag.service'; +import { SharedModule } from '../shared/shared.module'; +import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; + +import { Tag, TagCompatibility, TagManifest } from './interface'; + +import { VerifiedSignature } from './tag.service'; +import { toPromise } from '../utils'; describe('TagService', () => { + let mockComp: TagCompatibility[] = [{ + v1Compatibility: '{"architecture":"amd64","author":"NGINX Docker Maintainers \\"docker-maint@nginx.com\\"","config":{"Hostname":"6b3797ab1e90","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"443/tcp":{},"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.5-1~jessie"],"Cmd":["nginx","-g","daemon off;"],"ArgsEscaped":true,"Image":"sha256:47a33f0928217b307cf9f20920a0c6445b34ae974a60c1b4fe73b809379ad928","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":[],"Labels":{}},"container":"f1883a3fb44b0756a2a3b1e990736a44b1387183125351370042ce7bd9ffc338","container_config":{"Hostname":"6b3797ab1e90","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"443/tcp":{},"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.5-1~jessie"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\\"nginx\\" \\"-g\\" \\"daemon off;\\"]"],"ArgsEscaped":true,"Image":"sha256:47a33f0928217b307cf9f20920a0c6445b34ae974a60c1b4fe73b809379ad928","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":[],"Labels":{}},"created":"2016-11-08T22:41:15.912313785Z","docker_version":"1.12.3","id":"db3700426e6d7c1402667f42917109b2467dd49daa85d38ac99854449edc20b3","os":"linux","parent":"f3ef5f96caf99a18c6821487102c136b00e0275b1da0c7558d7090351f9d447e","throwaway":true}' + }]; + let mockManifest: TagManifest = { + schemaVersion: 1, + name: 'library/nginx', + tag: '1.11.5', + architecture: 'amd64', + history: mockComp + }; + + let mockTags: Tag[] = [{ + tag: '1.11.5', + manifest: mockManifest + }]; + + let mockSignatures: VerifiedSignature[] = [{ + tag: '1.11.5', + hashes: { + sha256: 'fake' + } + }]; + + let mockSignatures2: VerifiedSignature[] = [{ + tag: '1.11.15', + hashes: { + sha256: 'fake2' + } + }]; + beforeEach(() => { + const mockConfig: IServiceConfig = { + repositoryBaseEndpoint: "/api/repositories/testing" + }; + TestBed.configureTestingModule({ + imports: [ + SharedModule + ], providers: [ TagDefaultService, { provide: TagService, useClass: TagDefaultService + }, { + provide: SERVICE_CONFIG, + useValue: mockConfig }] }); }); @@ -17,4 +64,51 @@ describe('TagService', () => { it('should be initialized', inject([TagDefaultService], (service: TagService) => { expect(service).toBeTruthy(); })); + + it('should get tags with signed status[1] if signatures exists', async(inject([TagDefaultService], (service: TagService) => { + expect(service).toBeTruthy(); + let spy1: jasmine.Spy = spyOn(service, '_getTags') + .and.returnValue(Promise.resolve(mockTags)); + let spy2: jasmine.Spy = spyOn(service, '_getSignatures') + .and.returnValue(Promise.resolve(mockSignatures)); + + toPromise(service.getTags('library/nginx')) + .then(tags => { + expect(tags).toBeTruthy(); + expect(tags.length).toBe(1); + expect(tags[0].signed).toBe(1); + }); + }))); + + it('should get tags with not-signed status[0] if signatures exists', async(inject([TagDefaultService], (service: TagService) => { + expect(service).toBeTruthy(); + let spy1: jasmine.Spy = spyOn(service, '_getTags') + .and.returnValue(Promise.resolve(mockTags)); + let spy2: jasmine.Spy = spyOn(service, '_getSignatures') + .and.returnValue(Promise.resolve(mockSignatures2)); + + toPromise(service.getTags('library/nginx')) + .then(tags => { + expect(tags).toBeTruthy(); + expect(tags.length).toBe(1); + expect(tags[0].signed).toBe(0); + }); + }))); + + it('should get tags with default signed status[-1] if signatures not exist', async(inject([TagDefaultService], (service: TagService) => { + expect(service).toBeTruthy(); + let spy1: jasmine.Spy = spyOn(service, '_getTags') + .and.returnValue(Promise.resolve(mockTags)); + let spy2: jasmine.Spy = spyOn(service, '_getSignatures') + .and.returnValue(Promise.reject("Error")); + + toPromise(service.getTags('library/nginx')) + .then(tags => { + expect(tags).toBeTruthy(); + expect(tags.length).toBe(1); + expect(tags[0].signed).toBe(-1); + }); + }))); + + }); diff --git a/src/ui_ng/lib/src/service/tag.service.ts b/src/ui_ng/lib/src/service/tag.service.ts index 3d66465c2..2af47837f 100644 --- a/src/ui_ng/lib/src/service/tag.service.ts +++ b/src/ui_ng/lib/src/service/tag.service.ts @@ -1,8 +1,25 @@ import { Observable } from 'rxjs/Observable'; import { RequestQueryParams } from './RequestQueryParams'; import { Tag } from './interface'; -import { Injectable } from "@angular/core"; +import { Injectable, Inject } from "@angular/core"; import 'rxjs/add/observable/of'; +import { Http } from '@angular/http'; +import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; +import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from '../utils'; + +/** + * For getting tag signatures. + * This is temporary, will be removed in future. + * + * @export + * @class VerifiedSignature + */ +export class VerifiedSignature { + tag: string; + hashes: { + sha256: string; + } +} /** * Define the service methods to handle the repository tag related things. @@ -19,11 +36,11 @@ export abstract class TagService { * @abstract * @param {string} repositoryName * @param {RequestQueryParams} [queryParams] - * @returns {(Observable | Tag[])} + * @returns {(Observable | Promise | Tag[])} * * @memberOf TagService */ - abstract getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable | Tag[]; + abstract getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable | Promise | Tag[]; /** * Delete the specified tag. @@ -35,7 +52,7 @@ export abstract class TagService { * * @memberOf TagService */ - abstract deleteTag(repositoryName: string, tag: string): Observable | any; + abstract deleteTag(repositoryName: string, tag: string): Observable | Promise | any; } /** @@ -47,11 +64,73 @@ export abstract class TagService { */ @Injectable() export class TagDefaultService extends TagService { - public getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable | Tag[] { - return Observable.of([]); + _baseUrl: string; + + constructor( + private http: Http, + @Inject(SERVICE_CONFIG) private config: IServiceConfig + ) { + super(); + this._baseUrl = this.config.repositoryBaseEndpoint ? this.config.repositoryBaseEndpoint : '/api/repositories'; } - public deleteTag(repositoryName: string, tag: string): Observable | any { - return Observable.of({}); + //Private methods + //These two methods are temporary, will be deleted in future after API refactored + _getTags(repositoryName: string, queryParams?: RequestQueryParams): Promise { + if (!queryParams) { + queryParams = new RequestQueryParams(); + } + + queryParams.set('detail', '1'); + let url: string = `${this._baseUrl}/${repositoryName}/tags`; + + return this.http.get(url, buildHttpRequestOptions(queryParams)).toPromise() + .then(response => response.json() as Tag[]) + .catch(error => Promise.reject(error)); + } + + _getSignatures(repositoryName: string): Promise { + let url: string = `${this._baseUrl}/${repositoryName}/signatures`; + return this.http.get(url, HTTP_JSON_OPTIONS).toPromise() + .then(response => response.json() as VerifiedSignature[]) + .catch(error => Promise.reject(error)) + } + + public getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable | Promise | Tag[] { + if (!repositoryName) { + return Promise.reject("Bad argument"); + } + + return this._getTags(repositoryName, queryParams) + .then(tags => { + return this._getSignatures(repositoryName) + .then(signatures => { + tags.forEach(tag => { + let foundOne: VerifiedSignature | undefined = signatures.find(signature => signature.tag === tag.tag); + if (foundOne) { + tag.signed = 1;//Signed + } else { + tag.signed = 0;//Not signed + } + }); + return tags; + }) + .catch(error => { + tags.forEach(tag => tag.signed = -1);//No signature info + return tags; + }) + }) + .catch(error => Promise.reject(error)) + } + + public deleteTag(repositoryName: string, tag: string): Observable | Promise | any { + if (!repositoryName || !tag) { + return Promise.reject("Bad argument"); + } + + let url: string = `${this._baseUrl}/${repositoryName}/tags/${tag}`; + return this.http.delete(url, HTTP_JSON_OPTIONS).toPromise() + .then(response => response) + .catch(error => Promise.reject(error)); } } \ No newline at end of file