Skip to content

Commit

Permalink
feat(k8s): Enable new artifacts workflow in Patch Manifest (#7109)
Browse files Browse the repository at this point in the history
* feat(k8s): Enable new artifacts workflow in Patch Manifest

*  fix merge conflict
  • Loading branch information
Jon Schneider authored and maggieneterval committed Jun 26, 2019
1 parent ce194af commit aca6626
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 319 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { KUBERNETES_MANIFEST_DELETE_CTRL } from './manifest/delete/delete.contro
import { KUBERNETES_MANIFEST_SCALE_CTRL } from './manifest/scale/scale.controller';
import { KUBERNETES_V2_INSTANCE_DETAILS_CTRL } from './instance/details/details.controller';
import { KUBERNETES_DELETE_MANIFEST_STAGE } from './pipelines/stages/deleteManifest/deleteManifestStage';
import { KUBERNETES_PATCH_MANIFEST_STAGE } from './pipelines/stages/patchManifest/patchManifestStage';
import { KUBERNETES_SCALE_MANIFEST_STAGE } from './pipelines/stages/scaleManifest/scaleManifestStage';
import { KUBERNETES_UNDO_ROLLOUT_MANIFEST_STAGE } from './pipelines/stages/undoRolloutManifest/undoRolloutManifestStage';
import { KUBERNETES_FIND_ARTIFACTS_FROM_RESOURCE_STAGE } from './pipelines/stages/findArtifactsFromResource/findArtifactsFromResourceStage';
Expand Down Expand Up @@ -43,6 +42,7 @@ import { KUBERNETES_V2_RUN_JOB_STAGE } from 'kubernetes/v2/pipelines/stages/runJ

// React stages
import './pipelines/stages/deployManifest/deployManifestStage';
import './pipelines/stages/patchManifest/patchManifestStage';

import './pipelines/validation/manifestSelector.validator';

Expand Down Expand Up @@ -76,7 +76,6 @@ module(KUBERNETES_V2_MODULE, [
KUBERNETES_V2_SECURITY_GROUP_TRANSFORMER,
require('../securityGroup/reader').name,
KUBERNETES_DELETE_MANIFEST_STAGE,
KUBERNETES_PATCH_MANIFEST_STAGE,
KUBERNETES_SCALE_MANIFEST_STAGE,
KUBERNETES_UNDO_ROLLOUT_MANIFEST_STAGE,
KUBERNETES_FIND_ARTIFACTS_FROM_RESOURCE_STAGE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class DeployManifestStageForm extends React.Component<
ArtifactTypePatterns.KUBERNETES,
ArtifactTypePatterns.FRONT50_PIPELINE_TEMPLATE,
ArtifactTypePatterns.EMBEDDED_BASE64,
ArtifactTypePatterns.MAVEN_FILE,
];

public constructor(props: IDeployManifestStageConfigFormProps & IFormikStageConfigInjectedProps) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { CheckboxInput, StageConfigField } from '@spinnaker/core';
import Select, { Option } from 'react-select';

export interface IPatchManifestOptionsFormProps {
record: boolean;
onRecordChange: (record: boolean) => void;
strategy: string;
onStrategyChange: (strategy: string) => void;
}

export const PatchManifestOptionsForm: React.FunctionComponent<IPatchManifestOptionsFormProps> = (
props: IPatchManifestOptionsFormProps,
) => {
return (
<div className="form-horizontal">
<StageConfigField label="Record Patch Annotation" helpKey="kubernetes.manifest.patch.record">
<CheckboxInput
checked={props.record}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => props.onRecordChange(e.target.checked)}
/>
</StageConfigField>
<StageConfigField label="Merge Strategy" helpKey="kubernetes.manifest.patch.mergeStrategy" fieldColumns={3}>
<Select
clearable={false}
value={props.strategy}
options={['strategic', 'json', 'merge'].map(k => ({ value: k, label: k }))}
onChange={(e: Option<string>) => props.onStrategyChange(e.value)}
/>
</StageConfigField>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from 'react';
import { defaults } from 'lodash';
import { Observable, Subject } from 'rxjs';

import { FormikStageConfig, IStage, IStageConfigProps } from '@spinnaker/core';
import { KubernetesManifestCommandBuilder } from 'kubernetes/v2/manifest/manifestCommandBuilder.service';
import { PatchManifestStageForm } from './PatchManifestStageForm';

export class PatchManifestStageConfig extends React.Component<IStageConfigProps> {
private readonly stage: IStage;
private destroy$ = new Subject();

public constructor(props: IStageConfigProps) {
super(props);

// Intentionally initializing the stage config only once in the constructor
// The stage config is then completely owned within FormikStageConfig's Formik state
this.stage = props.stage;
}

public componentDidMount(): void {
Observable.fromPromise(
KubernetesManifestCommandBuilder.buildNewManifestCommand(
this.props.application,
this.stage.patchBody,
this.stage.moniker,
),
)
.takeUntil(this.destroy$)
.subscribe(builtCommand => {
if (this.stage.isNew) {
defaults(this.stage, {
account: builtCommand.command.account,
manifestArtifactAccount: 'embedded-artifact',
patchBody: builtCommand.command.manifest,
source: 'text',
options: {
record: true,
strategy: 'strategic',
},
location: '',
cloudProvider: 'kubernetes',
});
}
});
}

public render() {
return (
<FormikStageConfig
{...this.props}
stage={this.stage}
onChange={this.props.updateStage}
render={props => <PatchManifestStageForm {...props} updatePipeline={this.props.updatePipeline} />}
/>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import * as React from 'react';
import { Option } from 'react-select';
import { capitalize, get, isEmpty, map } from 'lodash';

import {
ArtifactTypePatterns,
IArtifact,
IExpectedArtifact,
IFormikStageConfigInjectedProps,
IPipeline,
RadioButtonInput,
StageArtifactSelectorDelegate,
StageConfigField,
yamlDocumentsToString,
YamlEditor,
} from '@spinnaker/core';

import { ManifestBindArtifactsSelectorDelegate } from 'kubernetes/v2/pipelines/stages/deployManifest/ManifestBindArtifactsSelectorDelegate';
import { IManifestBindArtifact } from 'kubernetes/v2/pipelines/stages/deployManifest/ManifestBindArtifactsSelector';
import { ManifestSelector } from 'kubernetes/v2/manifest/selector/ManifestSelector';
import { SelectorMode } from 'kubernetes/v2/manifest/selector/IManifestSelector';
import { PatchManifestOptionsForm } from './PatchManifestOptionsForm';

interface IPatchManifestStageConfigFormProps {
updatePipeline: (pipeline: IPipeline) => void;
}

interface IPatchManifestStageConfigFormState {
rawManifest: string;
}

export class PatchManifestStageForm extends React.Component<
IPatchManifestStageConfigFormProps & IFormikStageConfigInjectedProps,
IPatchManifestStageConfigFormState
> {
public readonly textSource = 'text';
public readonly artifactSource = 'artifact';
private readonly excludedManifestArtifactTypes = [
ArtifactTypePatterns.DOCKER_IMAGE,
ArtifactTypePatterns.KUBERNETES,
ArtifactTypePatterns.FRONT50_PIPELINE_TEMPLATE,
ArtifactTypePatterns.EMBEDDED_BASE64,
ArtifactTypePatterns.MAVEN_FILE,
];

public constructor(props: IPatchManifestStageConfigFormProps & IFormikStageConfigInjectedProps) {
super(props);
const patchBody: string = get(props.formik.values, 'patchBody');
const isTextManifest: boolean = get(props.formik.values, 'source') === this.textSource;
this.state = {
rawManifest: !isEmpty(patchBody) && isTextManifest ? yamlDocumentsToString([patchBody]) : '',
};
}

private onManifestArtifactSelected = (expectedArtifactId: string): void => {
this.props.formik.setFieldValue('manifestArtifactId', expectedArtifactId);
this.props.formik.setFieldValue('manifestArtifact', null);
};

private onManifestArtifactEdited = (artifact: IArtifact) => {
this.props.formik.setFieldValue('manifestArtifactId', null);
this.props.formik.setFieldValue('manifestArtifact', artifact);
};

private onManifestArtifactAccountSelected = (accountName: string): void => {
this.props.formik.setFieldValue('manifestArtifactAccount', accountName);
};

private getRequiredArtifacts = (): IManifestBindArtifact[] => {
const { requiredArtifactIds, requiredArtifacts } = this.props.formik.values;
return (requiredArtifactIds || [])
.map((id: string) => ({ expectedArtifactId: id }))
.concat(requiredArtifacts || []);
};

private onRequiredArtifactsChanged = (bindings: IManifestBindArtifact[]): void => {
this.props.formik.setFieldValue(
'requiredArtifactIds',
bindings.filter(b => b.expectedArtifactId).map(b => b.expectedArtifactId),
);
this.props.formik.setFieldValue('requiredArtifacts', bindings.filter(b => b.artifact));
};

private handleRawManifestChange = (rawManifest: string, manifests: any): void => {
this.setState({
rawManifest,
});
this.props.formik.setFieldValue('patchBody', manifests[0]);
};

private onManifestSelectorChange = (): void => {};

private getSourceOptions = (): Array<Option<string>> => {
return map([this.textSource, this.artifactSource], option => ({
label: capitalize(option),
value: option,
}));
};

public render() {
const stage = this.props.formik.values;
return (
<div className="container-fluid form-horizontal">
<h4>Resource to Patch</h4>
<ManifestSelector
application={this.props.application}
modes={[SelectorMode.Static, SelectorMode.Dynamic]}
onChange={this.onManifestSelectorChange}
selector={stage as any}
/>
<hr />
<h4>Patch Content</h4>
<StageConfigField label="Manifest Source" helpKey="kubernetes.manifest.source">
<RadioButtonInput
options={this.getSourceOptions()}
onChange={(e: any) => this.props.formik.setFieldValue('source', e.target.value)}
value={stage.source || 'text'}
inline={true}
/>
</StageConfigField>
{stage.source === this.textSource && (
<StageConfigField label="Manifest">
<YamlEditor onChange={this.handleRawManifestChange} value={this.state.rawManifest} />
</StageConfigField>
)}
{stage.source === this.artifactSource && (
<>
<StageArtifactSelectorDelegate
artifact={stage.manifestArtifact}
excludedArtifactTypePatterns={this.excludedManifestArtifactTypes}
expectedArtifactId={stage.manifestArtifactId}
helpKey="kubernetes.manifest.expectedArtifact"
label="Manifest Artifact"
onArtifactEdited={this.onManifestArtifactEdited}
onExpectedArtifactSelected={(artifact: IExpectedArtifact) => this.onManifestArtifactSelected(artifact.id)}
pipeline={this.props.pipeline}
selectedArtifactAccount={stage.manifestArtifactAccount}
selectedArtifactId={stage.manifestArtifactId}
setArtifactAccount={this.onManifestArtifactAccountSelected}
setArtifactId={this.onManifestArtifactSelected}
stage={stage}
updatePipeline={this.props.updatePipeline}
/>
</>
)}
<StageConfigField label="Required Artifacts to Bind" helpKey="kubernetes.manifest.requiredArtifactsToBind">
<ManifestBindArtifactsSelectorDelegate
bindings={this.getRequiredArtifacts()}
onChangeBindings={this.onRequiredArtifactsChanged}
pipeline={this.props.pipeline}
stage={stage}
/>
</StageConfigField>

<hr />
<h4>Patch Options</h4>
<PatchManifestOptionsForm
strategy={!!stage.options && stage.options.strategy}
onStrategyChange={(strategy: string) => this.props.formik.setFieldValue('options.strategy', strategy)}
record={!!stage.options && stage.options.record}
onRecordChange={(record: boolean) => this.props.formik.setFieldValue('options.record', record)}
/>
</div>
);
}
}
Loading

0 comments on commit aca6626

Please sign in to comment.