Skip to content

Commit

Permalink
#70 implementation for planNamespaceDeployment step
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Kogan committed Feb 6, 2019
1 parent 69b82f7 commit bba8009
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/jobs/steps/PlanNamespaceDeployment.ts
Original file line number Diff line number Diff line change
@@ -0,0 1,63 @@
import { call } from '../../dispatcher'
import { IO } from '../../IO'

import { ServerGroup } from '../../cluster/model/ServerGroup'
import { Deployment } from '../../cluster/model/Deployment'
import * as clusterModule from '../../cluster/ClusterModule'

import { Filter, matchesDeployment, matchesServerGroup } from '../model/Filter'
import { JobPlan } from '../model/JobPlan'
import { DeploymentPlan } from '../model/DeploymentPlan'

export class PlanNamespaceDeployment {
public constructor(
readonly name: string,
readonly sourceCluster: string,
readonly sourceNamespace: string,
readonly targetNamespace: string,
readonly filter?: Partial<Filter>,
readonly io: IO = new IO()
) {}

public async plan(targetGroup: ServerGroup, name: string, targetDeployments: Deployment[]): Promise<JobPlan> {
if (!matchesServerGroup({ namespaces: [this.targetNamespace] }, targetGroup)) {
return { name, deployments: [] }
}

const sourceDeployments = await call(clusterModule.deployments)({
cluster: this.sourceCluster,
namespace: this.sourceNamespace,
})

const deployments: DeploymentPlan[] = []
for (const targetDeployment of targetDeployments) {
if (!matchesDeployment(this.filter, targetDeployment)) {
continue
}
this.io.out(`planning namespace deployment ${targetDeployment.name} in ${this.name}`)
const sourceDeployment = sourceDeployments.find(d => d.name === targetDeployment.name)
if (sourceDeployment === undefined) {
this.io.error(
`${targetDeployment.name} in {cluster: ${targetGroup.cluster}, namespace: ${targetGroup.namespace}} `
`has no appropriate deployment in {cluster: ${this.sourceCluster}, namespace: ${this.sourceNamespace}}`
)
continue
}
if (sourceDeployment.image === undefined) {
this.io.error(
`${targetDeployment.name} in {cluster: ${this.sourceCluster}, namespace: ${this.sourceNamespace}} ` `has no image`
)
continue
}
if (targetDeployment.image === undefined || targetDeployment.image.url !== sourceDeployment.image.url) {
deployments.push({
group: targetGroup,
deployment: targetDeployment,
image: sourceDeployment.image,
})
}
}

return { name, deployments }
}
}
164 changes: 164 additions & 0 deletions test/jobs/steps/plan-namespace-deployment-step.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 1,164 @@
import { Request } from '../../../src/dispatcher/model/Request'

import { Deployment } from '../../../src/cluster/model/Deployment'
import { ServerGroup } from '../../../src/cluster/model/ServerGroup'

import { PlanNamespaceDeployment } from '../../../src/jobs/steps/PlanNamespaceDeployment'

let mockDeployments: { [key: string]: Deployment[] } = {}

jest.mock('../../../src/dispatcher/index', () => ({
get: jest.fn(),
call: (request: Request<any, any>) => {
switch (request.module) {
case 'cluster': {
switch (request.procedure) {
case 'deployments': {
return (input: { cluster: string }) => Promise.resolve(mockDeployments[input.cluster])
}
}
break
}
}
return undefined
},
add: () => {},
path: () => '',
}))

describe(PlanNamespaceDeployment.name, () => {
const targetGroup: ServerGroup = { cluster: 'targetCluster', namespace: 'targetNamespace' }
const step = new PlanNamespaceDeployment('stepName', 'sourceCluster', 'sourceNamespace', 'targetNamespace', {
deployments: ['deployment1'],
})
const targetDeployments: Deployment[] = [{ name: 'deployment1' }]

it('plans deployments', async () => {
// given
mockDeployments = { sourceCluster: [{ name: 'deployment1', image: { name: 'image1', url: 'url1' } }] }

// when
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments)

// then
expect(plan.deployments).toEqual([
{
deployment: { name: 'deployment1' },
group: { cluster: 'targetCluster', namespace: 'targetNamespace' },
image: { name: 'image1', url: 'url1' },
},
])
})

it('does not plan if target namespace does not match', async () => {
// when
const plan = await step.plan(
{
cluster: 'targetCluster', // Cluster is always set to the target cluster from the pipeline, so it can't be different.
namespace: 'otherNamespace',
},
'pipeline1',
targetDeployments
)

// then
expect(plan.deployments).toEqual([])
})

it('ignores deployments that do not match', async () => {
// given
mockDeployments = { sourceCluster: [{ name: 'deployment2', image: { name: 'image1', url: 'url1' } }] }

// when
const plan = await step.plan(targetGroup, 'pipeline1', [
{
name: 'deployment2',
image: { name: 'image1', url: 'url2' },
},
])

// then
expect(plan.deployments).toEqual([])
})

it('ignores deployments that cannot be found in source', async () => {
// given
step.io.error = jest.fn()
mockDeployments = { sourceCluster: [{ name: 'otherDeployment', image: { name: 'image1', url: 'url1' } }] }

// when
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments)

// then
expect(plan.deployments).toEqual([])
expect(step.io.error).toHaveBeenCalledWith(
'deployment1 in {cluster: targetCluster, namespace: targetNamespace} has no appropriate deployment '
'in {cluster: sourceCluster, namespace: sourceNamespace}'
)
})

it('ignores deployments that have no image in source', async () => {
// given
step.io.error = jest.fn()
mockDeployments = { sourceCluster: [{ name: 'deployment1' }] }

// when
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments)

// then
expect(plan.deployments).toEqual([])
expect(step.io.error).toHaveBeenCalledWith('deployment1 in {cluster: sourceCluster, namespace: sourceNamespace} has no image')
})

it('ignores deployments that have same image as source', async () => {
// given
const deployments = [{ name: 'deployment1', image: { name: 'image1', url: 'url1' } }]
mockDeployments = { sourceCluster: deployments }

// when
const plan = await step.plan(targetGroup, 'pipeline1', deployments)

// then
expect(plan.deployments).toEqual([])
})

it('filters deployments correctly', async () => {
// given
mockDeployments = {
sourceCluster: [
{ name: 'deployment1', image: { name: 'image1', url: 'url1' } },
{ name: 'deployment3' },
{ name: 'deployment4', image: { name: 'image4', url: 'url4' } },
{ name: 'deployment5', image: { name: 'image5', url: 'url5' } },
],
}
const step = new PlanNamespaceDeployment('stepName', 'sourceCluster', 'sourceNamespace', 'targetNamespace', {
deployments: ['deployment2', 'deployment3', 'deployment4', 'deployment5'],
})
step.io.error = jest.fn()
const targetDeployments: Deployment[] = [
{ name: 'deployment1' },
{ name: 'deployment2' },
{ name: 'deployment3' },
{ name: 'deployment5' },
]

// when
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments)

// then
expect(plan.deployments).toEqual([
{
deployment: { name: 'deployment5' },
group: { cluster: 'targetCluster', namespace: 'targetNamespace' },
image: { name: 'image5', url: 'url5' },
},
])
expect(step.io.error).toHaveBeenCalledTimes(2)
expect(step.io.error).toHaveBeenCalledWith('deployment3 in {cluster: sourceCluster, namespace: sourceNamespace} has no image')
expect(step.io.error).toHaveBeenCalledWith(
'deployment2 in {cluster: targetCluster, namespace: targetNamespace} has no appropriate deployment '
'in {cluster: sourceCluster, namespace: sourceNamespace}'
)
})
})

0 comments on commit bba8009

Please sign in to comment.