-
Notifications
You must be signed in to change notification settings - Fork 0
Event bus #31
Changes from all commits
a8d3206
d3144a1
11b967d
55752a6
865ece4
588ff8a
b51bafc
37735df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,13 @@ import * as Boom from 'boom'; | |
|
||
import { inject, injectable } from 'inversify'; | ||
|
||
import * as rx from '@reactivex/rxjs'; | ||
|
||
import { EventBus, injectSymbol as eventBusInjectSymbol } from '../event-bus'; | ||
import { GitlabClient } from '../shared/gitlab-client'; | ||
import { Deployment } from '../shared/gitlab.d.ts'; | ||
import * as logger from '../shared/logger'; | ||
import * as logger from '../shared/logger'; | ||
import { DEPLOYMENT_EVENT_TYPE, Deployment, DeploymentEvent, DeploymentStatus, | ||
MinardDeployment, createDeploymentEvent } from './types'; | ||
|
||
import * as fs from 'fs'; | ||
import * as os from 'os'; | ||
|
@@ -17,28 +21,6 @@ const deepcopy = require('deepcopy'); | |
|
||
export const deploymentFolderInjectSymbol = Symbol('deployment-folder'); | ||
|
||
export interface DeploymentKey { | ||
projectId: number; | ||
deploymentId: number; | ||
} | ||
|
||
export interface MinardDeploymentPlain { | ||
ref: string; | ||
status: string; | ||
url?: string; | ||
screenshot?: string; | ||
finished_at: string; | ||
} | ||
|
||
interface CommitRef { | ||
id: string; | ||
} | ||
|
||
export interface MinardDeployment extends MinardDeploymentPlain { | ||
id: number; | ||
commitRef: CommitRef; | ||
} | ||
|
||
export function isRawDeploymentHostname(hostname: string) { | ||
return getDeploymentKey(hostname) !== null; | ||
} | ||
|
@@ -63,15 +45,52 @@ export default class DeploymentModule { | |
private readonly deploymentFolder: string; | ||
private readonly logger: logger.Logger; | ||
|
||
private eventBus: EventBus; | ||
private buildToProject = new Map<number, number>(); | ||
private events: rx.Observable<DeploymentEvent>; | ||
|
||
public constructor( | ||
@inject(GitlabClient.injectSymbol) gitlab: GitlabClient, | ||
@inject(deploymentFolderInjectSymbol) deploymentFolder: string, | ||
@inject(eventBusInjectSymbol) eventBus: EventBus, | ||
@inject(logger.loggerInjectSymbol) logger: logger.Logger) { | ||
this.gitlab = gitlab; | ||
this.deploymentFolder = deploymentFolder; | ||
this.logger = logger; | ||
this.eventBus = eventBus; | ||
|
||
this.events = eventBus | ||
.filterEvents<DeploymentEvent>(DEPLOYMENT_EVENT_TYPE) | ||
.map(e => e.payload); | ||
|
||
this.subscribeToEvents(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the DeploymentModule is subscribing to events like this, should we make it a singleton in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, there absolutely should only exist a single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's already bound to a constant value There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. EventBus is, but DeploymentModule is not. Should DeploymentModule not be also a singleton? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, misunderstood the question.
No, not really. It's the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, looks like I'm missing something. What is the logic of the "at-most-once delivery semantics"? This (of course) outputs the event twice:
With two DeploymentModules there are two similar subscriptions, and both will be getting the event. So what am I missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant that when we have a real EventBus, it will make sure that each event is delivered just once. Yes, with current implementation it broadcasts every event to every subscriber and you are right that it will cause problems if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A REALLY good catch actually. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Being a singleton is thus a requirement for every class that uses the |
||
|
||
} | ||
|
||
private subscribeToEvents() { | ||
this.events.subscribe(e => this.setDeploymentState(e.id, e.status, e.projectId)); | ||
// On successfully completed deployments, download, extract and post an 'extracted' event | ||
this.completedDeployments() | ||
.filter(event => event.status === 'success') | ||
.flatMap(event => this.downloadAndExtractDeployment(event.projectId, event.id).then(_ => event)) | ||
.subscribe(event => | ||
this.eventBus.post(createDeploymentEvent(Object.assign({}, event, {status: 'extracted'})))); | ||
} | ||
|
||
private completedDeployments() { | ||
const events = this.events; | ||
// The initial events for a new deployment have status 'running' and always include the projectId | ||
const started = events.filter(e => e.status === 'running' && e.projectId !== undefined); | ||
// We use a flatMap to return a single event *with* the projectId, when the deployment has finished | ||
return started | ||
.flatMap(initial => events.filter(later => later.id === initial.id && this.isFinished(later.status)) | ||
.map(later => ({id: later.id, status: later.status, projectId: initial.projectId as number})) | ||
); | ||
|
||
} | ||
|
||
private isFinished(status: DeploymentStatus) { | ||
return status === 'success' || status === 'failed' || status === 'canceled'; | ||
} | ||
|
||
private async getDeployments(url: string): Promise<MinardDeployment[]> { | ||
|
@@ -154,21 +173,15 @@ export default class DeploymentModule { | |
} | ||
} | ||
|
||
public setDeploymentState(buildId: number, state: string, projectId?: number) { | ||
public setDeploymentState(deploymentId: number, state: string, projectId?: number) { | ||
if (projectId) { | ||
this.buildToProject.set(buildId, projectId); | ||
this.buildToProject.set(deploymentId, projectId); | ||
} | ||
const _projectId = this.buildToProject.get(buildId); | ||
const _projectId = this.buildToProject.get(deploymentId); | ||
if (!_projectId) { | ||
throw new Error(`Couldn't find projectId for build ${buildId}`); | ||
} | ||
console.log(`Build ${_projectId}/${buildId}: ${state}`); | ||
if (state === 'success') { | ||
this.downloadAndExtractDeployment(_projectId, buildId) | ||
.then(path => { | ||
console.log(`Extracted the artifacts to path ${path}`); | ||
}); | ||
throw new Error(`Couldn't find projectId for build ${deploymentId}`); | ||
} | ||
// console.log(`Build ${_projectId}/${deploymentId}: ${state}`); | ||
} | ||
|
||
/* | ||
|
@@ -182,7 +195,7 @@ export default class DeploymentModule { | |
const tempDir = path.join(os.tmpdir(), 'minard'); | ||
mkpath.sync(tempDir); | ||
let readableStream = (<any> response).body; | ||
const tempFileName = path.join(tempDir, `minard-${projectId}-${deploymentId}.zip`); | ||
const tempFileName = path.join(tempDir, `minard-${projectId}-${deploymentId}.zip`); | ||
const writeStream = fs.createWriteStream(tempFileName); | ||
|
||
await new Promise<void>((resolve, reject) => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would not
expect(ret).to.exist
be cleaner?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but this way TypeScript knows to narrow down
DeploymentKey | null
without explicit casting. The error message could be a bit more descriptive, though :D .