Skip to content

Commit

Permalink
feat(cf): add clone SG pipeline stage (spinnaker#6555)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy Louie authored and jkschneider committed Feb 15, 2019
1 parent c8a0312 commit 26471c4
Show file tree
Hide file tree
Showing 15 changed files with 515 additions and 34 deletions.
2 changes: 2 additions & 0 deletions app/scripts/modules/cloudfoundry/src/cf.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'cloudfoundry/pipeline/config/validation/instanceSize.validator';
import 'cloudfoundry/pipeline/config/validation/cfTargetImpedance.validator';
import 'cloudfoundry/pipeline/config/validation/validServiceParameterJson.validator';
import 'cloudfoundry/pipeline/config/validation/validateServiceRequiredField.validator';
import { CLOUD_FOUNDRY_CLONE_SERVER_GROUP_STAGE } from './pipeline/stages/cloneServerGroup/cloudfoundryCloneServerGroupStage.module';
import { CLOUD_FOUNDRY_DEPLOY_SERVICE_STAGE } from './pipeline/stages/deployService/cloudfoundryDeployServiceStage.module';
import { CLOUD_FOUNDRY_DESTROY_ASG_STAGE } from './pipeline/stages/destroyAsg/cloudfoundryDestroyAsgStage.module';
import { CLOUD_FOUNDRY_DESTROY_SERVICE_STAGE } from './pipeline/stages/destroyService/cloudfoundryDestroyServiceStage.module';
Expand All @@ -48,6 +49,7 @@ templates.keys().forEach(function(key) {

export const CLOUD_FOUNDRY_MODULE = 'spinnaker.cloudfoundry';
module(CLOUD_FOUNDRY_MODULE, [
CLOUD_FOUNDRY_CLONE_SERVER_GROUP_STAGE,
CLOUD_FOUNDRY_DESTROY_SERVICE_STAGE,
CLOUD_FOUNDRY_DEPLOY_SERVICE_STAGE,
CLOUD_FOUNDRY_DESTROY_ASG_STAGE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import * as React from 'react';

import { IPipeline, IStageConfigProps, StageConstants } from '@spinnaker/core';

import { CloudFoundryCreateServerGroupModal } from 'cloudfoundry/serverGroup/configure/wizard/CreateServerGroupModal';
import { CloudFoundryReactInjector } from 'cloudfoundry/reactShims';

export interface ICloudfoundryCloneServerGroupStageProps extends IStageConfigProps {
pipeline: IPipeline;
}

export interface ICloudfoundryCloneServerGroupStageConfigState {
buttonText: string;
}

export class CloudfoundryCloneServerGroupStageConfig extends React.Component<
ICloudfoundryCloneServerGroupStageProps,
ICloudfoundryCloneServerGroupStageConfigState
> {
constructor(props: ICloudfoundryCloneServerGroupStageProps) {
super(props);
props.stage.cloudProvider = 'cloudfoundry';
props.stage.application = props.application.name;
this.state = {
buttonText: props.stage.destination ? 'Edit clone configuration' : 'Add clone configuration',
};
}

private handleResult = (command: any) => {
this.props.stage.credentials = command.credentials;
this.props.stage.capacity = command.capacity;
this.props.stage.destination = command.destination;
this.props.stage.delayBeforeDisableSec = command.delayBeforeDisableSec;
this.props.stage.freeFormDetails = command.freeFormDetails;
this.props.stage.maxRemainingAsgs = command.maxRemainingAsgs;
this.props.stage.region = command.region;
this.props.stage.startApplication = command.startApplication;
this.props.stage.stack = command.stack;
this.props.stage.strategy = command.strategy;
this.props.stage.target = command.target;
this.props.stage.targetCluster = command.targetCluster;
this.props.stage.manifest = command.manifest;
this.setState({ buttonText: 'Edit clone configuration' });
this.props.stageFieldUpdated();
};

private addCluster = () => {
const { application, stage, pipeline } = this.props;
const title = 'Clone Cluster';
CloudFoundryReactInjector.cfServerGroupCommandBuilder
.buildCloneServerGroupCommandFromPipeline(stage, pipeline)
.then((command: any) => {
return CloudFoundryCreateServerGroupModal.show({
application,
command,
isSourceConstant: false,
title,
});
})
.then(this.handleResult)
.catch(() => {});
};

public render() {
const { stage } = this.props;
const { buttonText } = this.state;
const cloneTargets = StageConstants.TARGET_LIST;
return (
<div className="form-horizontal">
<div>
<h4>Clone Source</h4>
<table className="table table-condensed table-deployStage">
<thead>
<tr>
<th>Account</th>
<th>Region</th>
<th>Cluster</th>
<th>Target</th>
</tr>
</thead>
<tbody>
<tr>
<td>{stage.credentials}</td>
<td>{stage.region}</td>
<td>{stage.targetCluster}</td>
<td>{cloneTargets.filter(t => t.val === stage.target).map(t => t.label)}</td>
</tr>
</tbody>
</table>
</div>
<div>
<h4>Clone Destination</h4>
<table className="table table-condensed table-deployStage">
<thead>
<tr>
<th>Account</th>
<th>Region</th>
</tr>
</thead>
<tbody>
<tr>
<td>{stage.destination ? stage.destination.account : ''}</td>
<td>{stage.destination ? stage.destination.region : ''}</td>
</tr>
</tbody>
</table>
</div>
<button className="btn btn-block btn-sm add-new" onClick={() => this.addCluster()}>
<span className="glyphicon glyphicon-plus-sign" /> {buttonText}
</button>
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<cf-clone-server-group-stage
application="application"
pipeline="pipeline"
stage="stage"
stage-field-updated="stageFieldUpdated"
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { IController, IScope, module } from 'angular';
import { react2angular } from 'react2angular';
import { CloudfoundryCloneServerGroupStageConfig } from './CloudfoundryCloneServerGroupStageConfig';
import { IStage, Registry } from '@spinnaker/core';

class CloudfoundryCloneServerGroupStageCtrl implements IController {
constructor(public $scope: IScope) {
'ngInject';
}
}

export const CLOUD_FOUNDRY_CLONE_SERVER_GROUP_STAGE = 'spinnaker.cloudfoundry.pipeline.stage.cloneServerGroupStage';
module(CLOUD_FOUNDRY_CLONE_SERVER_GROUP_STAGE, [])
.config(function() {
Registry.pipeline.registerStage({
accountExtractor: (stage: IStage) => stage.context.credentials,
configAccountExtractor: (stage: IStage) => [stage.credentials],
provides: 'cloneServerGroup',
key: 'cloneServerGroup',
cloudProvider: 'cloudfoundry',
templateUrl: require('./cloudfoundryCloneServerGroupStage.html'),
controller: 'cfCloneServerGroupStageCtrl',
validators: [],
});
})
.component(
'cfCloneServerGroupStage',
react2angular(CloudfoundryCloneServerGroupStageConfig, ['application', 'pipeline', 'stage', 'stageFieldUpdated']),
)
.controller('cfCloneServerGroupStageCtrl', CloudfoundryCloneServerGroupStageCtrl);
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import * as React from 'react';

import { get } from 'lodash';

import {
Application,
AppListExtractor,
FormikFormField,
IAccount,
IServerGroup,
IServerGroupFilter,
ReactSelectInput,
} from '@spinnaker/core';
import { FormikProps } from 'formik';
import { ICloudFoundryCreateServerGroupCommand } from 'cloudfoundry/serverGroup';

export interface IFormikAccountRegionClusterSelectorProps {
accounts: IAccount[];
application: Application;
cloudProvider: string;
clusterField?: string;
componentName?: string;
formik: FormikProps<ICloudFoundryCreateServerGroupCommand>;
}

export interface IFormikAccountRegionClusterSelectorState {
availableRegions: string[];
cloudProvider: string;
clusterField: string;
clusters: string[];
componentName: string;
}

export class FormikAccountRegionClusterSelector extends React.Component<
IFormikAccountRegionClusterSelectorProps,
IFormikAccountRegionClusterSelectorState
> {
constructor(props: IFormikAccountRegionClusterSelectorProps) {
super(props);
const clusterField = props.clusterField || 'cluster';
this.state = {
availableRegions: [],
cloudProvider: props.cloudProvider,
clusterField: clusterField,
clusters: [],
componentName: props.componentName || '',
};
}

public componentDidMount(): void {
const { componentName, formik } = this.props;
const credentials = get(formik.values, componentName ? `${componentName}.credentials` : 'credentials', undefined);
const region = get(formik.values, componentName ? `${componentName}.region` : 'region', undefined);
this.setRegionList(credentials);
this.setClusterList(credentials, [region]);
}

private setRegionList = (credentials: string): void => {
const { application } = this.props;
const accountFilter: IServerGroupFilter = (serverGroup: IServerGroup) =>
serverGroup ? serverGroup.account === credentials : true;
application.ready().then(() => {
const availableRegions = AppListExtractor.getRegions([application], accountFilter);
availableRegions.sort();
this.setState({ availableRegions });
});
};

private setClusterList = (credentials: string, regions: string[]): void => {
const { application } = this.props;
application.ready().then(() => {
const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(credentials, regions);
const clusters = AppListExtractor.getClusters([application], clusterFilter);
this.setState({ clusters });
});
};

public accountChanged = (credentials: string): void => {
this.setRegionList(credentials);
this.setClusterList(credentials, []);
};

public regionChanged = (region: string): void => {
const { componentName, formik } = this.props;
const credentials = get(formik.values, componentName ? `${componentName}.credentials` : 'credentials', undefined);
this.setClusterList(credentials, [region]);
};

public render() {
const { accounts } = this.props;
const { availableRegions, clusters, clusterField, componentName } = this.state;
return (
<div className="col-md-9">
<div className="sp-margin-m-bottom">
<FormikFormField
name={componentName ? `${componentName}.credentials` : 'credentials'}
label="Account"
fastField={false}
input={props => (
<ReactSelectInput
inputClassName="cloudfoundry-react-select"
{...props}
stringOptions={accounts && accounts.map((acc: IAccount) => acc.name)}
clearable={false}
/>
)}
onChange={this.accountChanged}
required={true}
/>
</div>

<div className="sp-margin-m-bottom">
<FormikFormField
name={componentName ? `${componentName}.region` : 'region'}
label="Region"
fastField={false}
input={props => (
<ReactSelectInput
inputClassName="cloudfoundry-react-select"
{...props}
stringOptions={availableRegions}
clearable={false}
/>
)}
onChange={this.regionChanged}
required={true}
/>
</div>

<div className="sp-margin-m-bottom">
<FormikFormField
name={componentName ? `${componentName}.${clusterField}` : `${clusterField}`}
label="Cluster"
fastField={false}
input={props => (
<ReactSelectInput
inputClassName="cloudfoundry-react-select"
{...props}
stringOptions={clusters}
clearable={false}
/>
)}
required={true}
/>
</div>
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './AccountRegionClusterSelector';
export * from './FormikAccountRegionClusterSelector';
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './accountRegionClusterSelector/AccountRegionClusterSelector';
export * from './accountRegionClusterSelector/';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './serverGroupConfigurationModel.cf';
Loading

0 comments on commit 26471c4

Please sign in to comment.