Merge pull request #2269 from steven-zou/master

Implement tag service interface
This commit is contained in:
Steven Zou 2017-05-10 14:37:22 +08:00 committed by GitHub
commit b354653735
3 changed files with 221 additions and 20 deletions

View File

@ -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
}

View File

@ -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<Tag[]>(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<Tag[]>(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<Tag[]>(service.getTags('library/nginx'))
.then(tags => {
expect(tags).toBeTruthy();
expect(tags.length).toBe(1);
expect(tags[0].signed).toBe(-1);
});
})));
});

View File

@ -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[]> | Tag[])}
* @returns {(Observable<Tag[]> | Promise<Tag[]> | Tag[])}
*
* @memberOf TagService
*/
abstract getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable<Tag[]> | Tag[];
abstract getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable<Tag[]> | Promise<Tag[]> | Tag[];
/**
* Delete the specified tag.
@ -35,7 +52,7 @@ export abstract class TagService {
*
* @memberOf TagService
*/
abstract deleteTag(repositoryName: string, tag: string): Observable<any> | any;
abstract deleteTag(repositoryName: string, tag: string): Observable<any> | Promise<Tag> | any;
}
/**
@ -47,11 +64,73 @@ export abstract class TagService {
*/
@Injectable()
export class TagDefaultService extends TagService {
public getTags(repositoryName: string, queryParams?: RequestQueryParams): Observable<Tag[]> | 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> | 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<Tag[]> {
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<VerifiedSignature[]> {
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<Tag[]> | Promise<Tag[]> | 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<any> | Promise<Tag> | 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));
}
}