Allow Spinnaker to associate your workload with one or more Services and manage traffic based on your selected rollout strategy options. Valid for ReplicaSets only.
`,
+ 'kubernetes.manifest.rolloutStrategy': `
+
The rollout strategy tells Spinnaker what to do with the previous version(s) of the ReplicaSet in the cluster.
+ `,
'kubernetes.manifest.expectedArtifact':
'The artifact that is to be applied to the Kubernetes account for this stage. The artifact should represent a valid Kubernetes manifest.',
'kubernetes.manifest.requiredArtifactsToBind':
diff --git a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx
index ad128b5d83b..ed8c86139fe 100644
--- a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx
+++ b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx
@@ -29,10 +29,10 @@ describe('', () => {
expect(wrapper.find(StageConfigField).length).toEqual(1);
expect(wrapper.find('input[type="checkbox"]').length).toEqual(1);
});
- it('renders config fields for `namespace`, `services`, and `enableTraffic` when config is enabled', () => {
+ it('renders config fields for `namespace`, `services`, `enableTraffic`, and `strategy` when config is enabled', () => {
props.config.enabled = true;
wrapper = shallow();
- expect(wrapper.find(StageConfigField).length).toEqual(4);
+ expect(wrapper.find(StageConfigField).length).toEqual(5);
});
});
@@ -47,5 +47,15 @@ describe('', () => {
enabled: true,
});
});
+ it('disables the traffic checkbox when a non-None rollout strategy is selected', () => {
+ props.config.options.strategy = 'redblack';
+ wrapper = shallow();
+ expect(
+ wrapper
+ .find('input[type="checkbox"]')
+ .at(1)
+ .props().disabled,
+ ).toEqual(true);
+ });
});
});
diff --git a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx
index 96bf03503fc..2b1728739da 100644
--- a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx
+++ b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx
@@ -1,12 +1,14 @@
import { module } from 'angular';
+import * as DOMPurify from 'dompurify';
import * as React from 'react';
import { react2angular } from 'react2angular';
import { cloneDeep, find, get, map, set, split } from 'lodash';
import Select, { Option } from 'react-select';
-import { IAccountDetails, StageConfigField } from '@spinnaker/core';
+import { IAccountDetails, IDeploymentStrategy, StageConfigField } from '@spinnaker/core';
import { ManifestKindSearchService } from 'kubernetes/v2/manifest/ManifestKindSearch';
+import { rolloutStrategies } from 'kubernetes/v2/rolloutStrategy';
export interface ITrafficManagementConfig {
enabled: boolean;
@@ -73,6 +75,19 @@ export class ManifestDeploymentOptions extends React.Component<
return map(namespaces, n => ({ label: n, value: n }));
};
+ private strategyOptionRenderer = (option: IDeploymentStrategy) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+ };
+
public componentDidMount() {
this.fetchServices();
}
@@ -86,6 +101,10 @@ export class ManifestDeploymentOptions extends React.Component<
this.onConfigChange('options.services', null);
this.fetchServices();
}
+
+ if (!this.props.config.options.enableTraffic && !!this.props.config.options.strategy) {
+ this.onConfigChange('options.enableTraffic', true);
+ }
}
public render() {
@@ -130,6 +149,7 @@ export class ManifestDeploymentOptions extends React.Component<
+
+
>
)}
>
diff --git a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/deployManifest.validator.ts b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/deployManifest.validator.ts
new file mode 100644
index 00000000000..d8b58032d90
--- /dev/null
+++ b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/deployManifest.validator.ts
@@ -0,0 +1,18 @@
+import { isEmpty } from 'lodash';
+
+import { IPipeline, IStage, IValidatorConfig, ICustomValidator } from '@spinnaker/core';
+
+export const deployManifestValidators = (): IValidatorConfig[] => {
+ return [
+ {
+ type: 'custom',
+ validate: (_pipeline: IPipeline, stage: IStage) => {
+ const { enabled = false, options = {} } = stage.trafficManagement;
+ if (enabled && isEmpty(options.services)) {
+ return `Select at least one Service to enable Spinnaker-managed rollout strategy options.`;
+ }
+ return null;
+ },
+ } as ICustomValidator,
+ ];
+};
diff --git a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/deployManifestStage.ts b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/deployManifestStage.ts
index cad1fac9738..8b7dca1640d 100644
--- a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/deployManifestStage.ts
+++ b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/deployManifestStage.ts
@@ -15,6 +15,7 @@ import { KubernetesV2DeployManifestConfigCtrl } from './deployManifestConfig.con
import { MANIFEST_BIND_ARTIFACTS_SELECTOR_REACT } from './ManifestBindArtifactsSelector';
import { MANIFEST_DEPLOYMENT_OPTIONS } from './ManifestDeploymentOptions';
import { DeployStatus } from './react/DeployStatus';
+import { deployManifestValidators } from './deployManifest.validator';
export const KUBERNETES_DEPLOY_MANIFEST_STAGE = 'spinnaker.kubernetes.v2.pipeline.stage.deployManifestStage';
@@ -37,7 +38,7 @@ module(KUBERNETES_DEPLOY_MANIFEST_STAGE, [
executionDetailsSections: [DeployStatus, ExecutionDetailsTasks, ExecutionArtifactTab],
producesArtifacts: true,
defaultTimeoutMs: 30 * 60 * 1000, // 30 minutes
- validators: [],
+ validators: deployManifestValidators(),
accountExtractor: (stage: IStage): string => (stage.account ? stage.account : ''),
configAccountExtractor: (stage: any): string[] => (stage.account ? [stage.account] : []),
artifactExtractor: ExpectedArtifactService.accumulateArtifacts(['manifestArtifactId', 'requiredArtifactIds']),
diff --git a/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/highlander.strategy.ts b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/highlander.strategy.ts
new file mode 100644
index 00000000000..8003e4d2730
--- /dev/null
+++ b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/highlander.strategy.ts
@@ -0,0 +1,7 @@
+import { IDeploymentStrategy } from 'core/deploymentStrategy';
+
+export const strategyHighlander: IDeploymentStrategy = {
+ label: 'Highlander',
+ description: 'Destroys all previous ReplicaSets in the cluster as soon as the new ReplicaSet is ready',
+ key: 'highlander',
+};
diff --git a/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/index.ts b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/index.ts
new file mode 100644
index 00000000000..c0413d0c1c8
--- /dev/null
+++ b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/index.ts
@@ -0,0 +1,5 @@
+import { strategyHighlander } from 'kubernetes/v2/rolloutStrategy/highlander.strategy';
+import { strategyNone } from 'kubernetes/v2/rolloutStrategy/none.strategy';
+import { strategyRedBlack } from 'kubernetes/v2/rolloutStrategy/redblack.strategy';
+
+export const rolloutStrategies = [strategyNone, strategyRedBlack, strategyHighlander];
diff --git a/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/none.strategy.ts b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/none.strategy.ts
new file mode 100644
index 00000000000..2659d122986
--- /dev/null
+++ b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/none.strategy.ts
@@ -0,0 +1,8 @@
+import { IDeploymentStrategy } from 'core/deploymentStrategy';
+
+export const strategyNone: IDeploymentStrategy = {
+ label: 'None',
+ description: 'Creates the new ReplicaSet with no impact on existing ReplicaSets in the cluster',
+ key: null,
+ providerRestricted: false,
+};
diff --git a/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/redblack.strategy.ts b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/redblack.strategy.ts
new file mode 100644
index 00000000000..fe6ca229c21
--- /dev/null
+++ b/app/scripts/modules/kubernetes/src/v2/rolloutStrategy/redblack.strategy.ts
@@ -0,0 +1,7 @@
+import { IDeploymentStrategy } from 'core/deploymentStrategy';
+
+export const strategyRedBlack: IDeploymentStrategy = {
+ label: 'Red/Black',
+ description: 'Disables all previous ReplicaSets in the cluster as soon as the new ReplicaSet is ready',
+ key: 'redblack',
+};