Skip to content

Commit

Permalink
feat(travis): Add explicit Travis integration
Browse files Browse the repository at this point in the history
This will add a new Travis trigger and a new Travis stage.
Corresponds with spinnaker/echo#141 and
spinnaker/orca#1213.

BREAKING CHANGE: Existing pipelines will continue to work, but `travis`
will have to be added to `triggerTypes` in `settings.js` before you can
set up new travis triggers/stages or edit any existing travis triggers/stages.
  • Loading branch information
jervi committed Mar 28, 2017
1 parent 77e40e6 commit f74119d
Show file tree
Hide file tree
Showing 33 changed files with 1,339 additions and 56 deletions.
Expand Up @@ -4,7 +4,7 @@ import {intersection} from 'lodash';
import {Application} from 'core/application/application.model';
import {AccountService, ACCOUNT_SERVICE} from 'core/account/account.service';
import {IAppengineAccount, IAppengineGitTrigger, IAppengineJenkinsTrigger, GitCredentialType, IAppengineServerGroup} from 'appengine/domain/index';
import {IStage, IPipeline, IGitTrigger, IJenkinsTrigger} from 'core/domain/index';
import {IStage, IPipeline, IGitTrigger, IBuildTrigger} from 'core/domain/index';
import {AppengineDeployDescription} from '../transformer';
import {AppengineProviderSettings} from '../../appengine.settings';

Expand Down Expand Up @@ -45,12 +45,12 @@ export class AppengineServerGroupCommandBuilder {

private static getTriggerOptions(pipeline: IPipeline): Array<IAppengineGitTrigger | IAppengineJenkinsTrigger> {
return (pipeline.triggers || [])
.filter(trigger => trigger.type === 'git' || trigger.type === 'jenkins')
.map((trigger: IGitTrigger | IJenkinsTrigger) => {
.filter(trigger => trigger.type === 'git' || trigger.type === 'jenkins' || trigger.type === 'travis')
.map((trigger: IGitTrigger | IBuildTrigger) => {
if (trigger.type === 'git') {
return {source: trigger.source, project: trigger.project, slug: trigger.slug, branch: trigger.branch, type: 'git'};
} else {
return {master: trigger.master, job: trigger.job, type: 'jenkins'};
return {master: trigger.master, job: trigger.job, type: trigger.type};
}
});
}
Expand Down
Expand Up @@ -7,6 +7,7 @@ import {APPLICATION_READ_SERVICE, ApplicationReader} from 'core/application/serv
import {INFRASTRUCTURE_CACHE_SERVICE, InfrastructureCacheService} from 'core/cache/infrastructureCaches.service';
import {ICache} from 'core/cache/deckCache.service';
import {SECURITY_GROUP_READER, SecurityGroupReader} from 'core/securityGroup/securityGroupReader.service';
import {IGOR_SERVICE, IgorService} from 'core/ci/igor.service';

interface IKeys {
[key: string]: string[];
Expand All @@ -29,7 +30,7 @@ describe('Service: cacheInitializer', function () {
let accountService: AccountService;
let securityGroupReader: SecurityGroupReader;
let applicationReader: ApplicationReader;
let igorService: any;
let igorService: IgorService;

beforeEach(
mock.module(
Expand All @@ -38,7 +39,7 @@ describe('Service: cacheInitializer', function () {
ACCOUNT_SERVICE,
SECURITY_GROUP_READER,
APPLICATION_READ_SERVICE,
require('core/ci/jenkins/igor.service')
IGOR_SERVICE,
));
beforeEach(
mock.inject(function (_$q_: ng.IQService,
Expand All @@ -48,7 +49,7 @@ describe('Service: cacheInitializer', function () {
_accountService_: AccountService,
_securityGroupReader_: SecurityGroupReader,
_applicationReader_: ApplicationReader,
_igorService_: any) {
_igorService_: IgorService) {
$q = _$q_;
$root = _$rootScope_;
cacheInitializer = _cacheInitializer_;
Expand Down
8 changes: 4 additions & 4 deletions app/scripts/modules/core/cache/cacheInitializer.service.ts
Expand Up @@ -2,14 +2,14 @@ import * as moment from 'moment';
import {cloneDeep, uniq} from 'lodash';
import {module, noop} from 'angular';

import {
APPLICATION_READ_SERVICE, ApplicationReader} from 'core/application/service/application.read.service';
import {APPLICATION_READ_SERVICE, ApplicationReader} from 'core/application/service/application.read.service';
import {ACCOUNT_SERVICE, AccountService} from 'core/account/account.service';
import {CLOUD_PROVIDER_REGISTRY, CloudProviderRegistry} from 'core/cloudProvider/cloudProvider.registry';
import {INFRASTRUCTURE_CACHE_CONFIG, IInfrastructureCacheConfig} from './infrastructureCacheConfig';
import {INFRASTRUCTURE_CACHE_SERVICE, InfrastructureCacheService} from './infrastructureCaches.service';
import {ICacheConfig} from './deckCache.service';
import {SECURITY_GROUP_READER, SecurityGroupReader} from 'core/securityGroup/securityGroupReader.service';
import {IGOR_SERVICE, IgorService} from 'core/ci/igor.service';

interface IInitializers {
[key: string]: any[];
Expand Down Expand Up @@ -91,7 +91,7 @@ export class CacheInitializerService {
private accountService: AccountService,
private securityGroupReader: SecurityGroupReader,
private cloudProviderRegistry: CloudProviderRegistry,
private igorService: any,
private igorService: IgorService,
private serviceDelegate: any) {}

public initialize(): ng.IPromise<any[]> {
Expand Down Expand Up @@ -127,7 +127,7 @@ module(CACHE_INITIALIZER_SERVICE, [
ACCOUNT_SERVICE,
SECURITY_GROUP_READER,
APPLICATION_READ_SERVICE,
require('../ci/jenkins/igor.service.js'),
IGOR_SERVICE,
INFRASTRUCTURE_CACHE_SERVICE,
CLOUD_PROVIDER_REGISTRY,
])
Expand Down
47 changes: 47 additions & 0 deletions app/scripts/modules/core/ci/igor.service.ts
@@ -0,0 +1,47 @@
import {IPromise, module, IQService} from 'angular';

import {API_SERVICE, Api} from 'core/api/api.service';
import {IBuild} from 'core/domain/IBuild';
import {IJobConfig} from 'core/domain/IJobConfig';

export enum BuildServiceType {
Jenkins, Travis
}

export class IgorService {
static get $inject() { return ['API', '$q']; }

constructor(private API: Api, private $q: IQService) {}

public listMasters(type: BuildServiceType = null): IPromise<string[]> {
const allMasters: IPromise<string[]> = this.API.one('v2').one('builds').get();
if (!allMasters) {
return this.$q.reject('An error occurred when retrieving build masters');
}
switch (type) {
case BuildServiceType.Jenkins:
return allMasters.then(masters => masters.filter(master => !(/^travis-/.test(master))));
case BuildServiceType.Travis:
return allMasters.then(masters => masters.filter(master => /^travis-/.test(master)));
default:
return allMasters;
}
}

public listJobsForMaster(master: string): IPromise<string[]> {
return this.API.one('v2').one('builds').one(master).one('jobs').get();
}

public listBuildsForJob(master: string, job: string): IPromise<IBuild[]> {
return this.API.one('v2').one('builds').one(master).one('builds').one(job).get();
}

public getJobConfig(master: string, job: string): IPromise<IJobConfig> {
return this.API.one('v2').one('builds').one(master).one('jobs').one(job).get();
}
}

export const IGOR_SERVICE = 'spinnaker.core.ci.jenkins.igor.service';
module(IGOR_SERVICE, [
API_SERVICE,
]).factory('igorService', (API: Api, $q: IQService) => new IgorService(API, $q));
35 changes: 0 additions & 35 deletions app/scripts/modules/core/ci/jenkins/igor.service.js

This file was deleted.

2 changes: 2 additions & 0 deletions app/scripts/modules/core/core.module.js
Expand Up @@ -12,6 +12,7 @@ import {APPLICATIONS_STATE_PROVIDER} from './application/applications.state.prov
import {INFRASTRUCTURE_STATES} from './search/infrastructure/infrastructure.states';
import {VERSION_CHECK_SERVICE} from './config/versionCheck.service';
import {CORE_WIDGETS_MODULE} from './widgets';
import {TRAVIS_STAGE_MODULE} from './pipeline/config/stages/travis/travisStage.module';
import {SETTINGS} from 'core/config/settings';

require('../../../fonts/spinnaker/icons.css');
Expand Down Expand Up @@ -108,6 +109,7 @@ module.exports = angular
require('./pipeline/config/stages/findAmi/findAmiStage.module.js'),
require('./pipeline/config/stages/findImageFromTags/findImageFromTagsStage.module.js'),
require('./pipeline/config/stages/jenkins/jenkinsStage.module.js'),
TRAVIS_STAGE_MODULE,
require('./pipeline/config/stages/manualJudgment/manualJudgmentStage.module.js'),
require('./pipeline/config/stages/tagImage/tagImageStage.module.js'),
require('./pipeline/config/stages/pipeline/pipelineStage.module.js'),
Expand Down
16 changes: 16 additions & 0 deletions app/scripts/modules/core/domain/IBuild.ts
@@ -0,0 +1,16 @@
export interface IArtifact {
displayPath: string;
fileName: string;
relativePath: string;
}

export interface IBuild {
building: boolean;
duration: number;
name: string;
number: number;
result: string;
timestamp: Date;
url: string;
artifacts: IArtifact[];
}
15 changes: 15 additions & 0 deletions app/scripts/modules/core/domain/IJobConfig.ts
@@ -0,0 +1,15 @@
export interface ParameterDefinitionList {
defaultValue: string;
description?: string;
name: string;
}

export interface IJobConfig {
buildable: boolean;
concurrentBuild: boolean;
description: string;
displayName: string;
name: string;
parameterDefinitionList: ParameterDefinitionList[];
url: string;
}
4 changes: 2 additions & 2 deletions app/scripts/modules/core/domain/ITrigger.ts
Expand Up @@ -12,10 +12,10 @@ export interface IGitTrigger extends ITrigger {
type: 'git';
}

export interface IJenkinsTrigger extends ITrigger {
export interface IBuildTrigger extends ITrigger {
job: string;
master: string;
type: 'jenkins';
type: 'jenkins' | 'travis';
}

export interface IPipelineTrigger extends ITrigger {
Expand Down
8 changes: 8 additions & 0 deletions app/scripts/modules/core/help/helpContents.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -1,10 +1,11 @@
'use strict';

import {IGOR_SERVICE, BuildServiceType} from 'core/ci/igor.service';
let angular = require('angular');


module.exports = angular.module('spinnaker.core.pipeline.stage.jenkinsStage', [
require('core/ci/jenkins/igor.service.js'),
IGOR_SERVICE,
require('../../pipelineConfigProvider.js'),
])
.config(function(pipelineConfigProvider) {
Expand Down Expand Up @@ -53,7 +54,7 @@ module.exports = angular.module('spinnaker.core.pipeline.stage.jenkinsStage', [
this.waitForCompletionChanged = () => stage.waitForCompletion = $scope.viewState.waitForCompletion;

function initializeMasters() {
igorService.listMasters().then(function (masters) {
igorService.listMasters(BuildServiceType.Jenkins).then(function (masters) {
$scope.masters = masters;
$scope.viewState.mastersLoaded = true;
$scope.viewState.mastersRefreshing = false;
Expand Down
@@ -1,12 +1,13 @@
'use strict';

let angular = require('angular');
import {IGOR_SERVICE} from 'core/ci/igor.service';

module.exports = angular.module('spinnaker.core.pipeline.stage.jenkins', [
require('./jenkinsStage.js'),
require('../stage.module.js'),
require('../core/stage.core.module.js'),
require('core/utils/timeFormatters.js'),
require('core/ci/jenkins/igor.service.js'),
IGOR_SERVICE,
require('./jenkinsExecutionDetails.controller.js'),
]);
@@ -0,0 +1,79 @@
import {IScope, IControllerService, IRootScopeService, mock} from 'angular';

import {TRAVIS_EXECUTION_DETAILS_CONTROLLER, TravisExecutionDetailsCtrl} from './travisExecutionDetails.controller';

describe('Travis Execution Details Controller:', () => {
let $scope: IScope,
$ctrl: IControllerService;

beforeEach(mock.module(TRAVIS_EXECUTION_DETAILS_CONTROLLER));

beforeEach(mock.inject(($controller: IControllerService, $rootScope: IRootScopeService) => {
$ctrl = $controller;
$scope = $rootScope.$new();
}));

const initializeController = ((stage: any): TravisExecutionDetailsCtrl => {
$scope.stage = stage;
return $ctrl(TravisExecutionDetailsCtrl, {
$scope,
executionDetailsSectionService: { synchronizeSection: ({}, fn: () => any) => fn(), },
});
});

describe('getting failure message', () => {

it('should count number of failing tests', () => {
const stage = {
context: {
buildInfo: {
testResults: [
{ failCount: 0 },
{ failCount: 3 },
{ failCount: 2 }
]
}
}
};

const controller = initializeController(stage);

expect(controller.failureMessage).toBe('5 tests failed.');
});

it ('should fall back to "build failed" message when no failed tests found, but result is "FAILURE"', () => {
let stage = {
context: {
buildInfo: {
result: 'FAILURE',
testResults: [] as any
}
}
};

let controller = initializeController(stage);

expect(controller.failureMessage).toBe('Build failed.');

stage = {
context: {
buildInfo: {
result: 'FAILURE',
testResults: [ { failCount: 0 }]
}
}
};

controller = initializeController(stage);

expect(controller.failureMessage).toBe('Build failed.');
});

it ('should set failureMessage to undefined when not failing', function () {
const controller = initializeController({});
expect(controller.failureMessage).toBeUndefined();
});

});

});

0 comments on commit f74119d

Please sign in to comment.