Skip to content

Commit

Permalink
feat(kuberntes): v2 runJob (#6831)
Browse files Browse the repository at this point in the history
UI support for the V2 Run Job stage
  • Loading branch information
ethanfrogers authored Apr 10, 2019
1 parent 7338f7d commit ee89a64
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 0 deletions.
4 changes: 4 additions & 0 deletions app/scripts/modules/kubernetes/src/v2/kubernetes.v2.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { module } from 'angular';
import { CloudProviderRegistry, STAGE_ARTIFACT_SELECTOR_COMPONENT_REACT, YAML_EDITOR_COMPONENT } from '@spinnaker/core';

import '../logo/kubernetes.logo.less';

import { KUBERNETES_MANIFEST_BASIC_SETTINGS } from './manifest/wizard/basicSettings.component';
import { KUBERNETES_MANIFEST_DELETE_CTRL } from './manifest/delete/delete.controller';
import { KUBERNETES_MANIFEST_SCALE_CTRL } from './manifest/scale/scale.controller';
Expand Down Expand Up @@ -41,6 +42,8 @@ import { JSON_EDITOR_COMPONENT } from './manifest/editor/json/jsonEditor.compone
import { ManifestWizard } from 'kubernetes/v2/manifest/wizard/ManifestWizard';
import { KUBERNETES_ENABLE_MANIFEST_STAGE } from 'kubernetes/v2/pipelines/stages/traffic/enableManifest.stage';
import { KUBERNETES_DISABLE_MANIFEST_STAGE } from 'kubernetes/v2/pipelines/stages/traffic/disableManifest.stage';
import { KUBERNETES_V2_RUN_JOB_STAGE } from 'kubernetes/v2/pipelines/stages/runJob/runJobStage';

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

// load all templates into the $templateCache
Expand Down Expand Up @@ -93,6 +96,7 @@ module(KUBERNETES_V2_MODULE, [
KUBERNETES_ENABLE_MANIFEST_STAGE,
KUBERNETES_DISABLE_MANIFEST_STAGE,
STAGE_ARTIFACT_SELECTOR_COMPONENT_REACT,
KUBERNETES_V2_RUN_JOB_STAGE,
]).config(() => {
CloudProviderRegistry.registerProvider('kubernetes', {
name: 'Kubernetes',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as React from 'react';

import { IStageConfigProps, AccountService, YamlEditor, yamlDocumentsToString, IAccount } from '@spinnaker/core';

import { ManifestBasicSettings } from 'kubernetes/v2/manifest/wizard/BasicSettings';

export interface IKubernetesRunJobStageConfigState {
credentials: IAccount[];
rawManifest?: string;
}

export class KubernetesV2RunJobStageConfig extends React.Component<IStageConfigProps> {
public state: IKubernetesRunJobStageConfigState = {
credentials: [],
};

public accountChanged = (account: string) => {
this.props.updateStageField({
credentails: account,
account: account,
});
};

public handleRawManifestChange = (rawManifest: string, manifests: any) => {
if (manifests) {
this.props.updateStageField({ manifest: manifests[0] });
}
this.setState({ rawManifest });
};

public initRawManifest() {
const { stage } = this.props;
if (stage.manifest) {
this.setState({ rawManifest: yamlDocumentsToString([stage.manifest]) });
}
}

public componentDidMount() {
this.props.updateStageField({ cloudProvider: 'kubernetes' });
AccountService.getAllAccountDetailsForProvider('kubernetes', 'v2').then((accounts: any) => {
this.setState({ credentials: accounts });
});
this.initRawManifest();
}

public render() {
const { application, stage } = this.props;

return (
<div className="container-fluid form-horizontal">
<h4>Basic Settings</h4>
<ManifestBasicSettings
app={application}
selectedAccount={stage.account || ''}
accounts={this.state.credentials}
onAccountSelect={(selectedAccount: string) => this.accountChanged(selectedAccount)}
/>
<h4>Manifest Configuration</h4>
<YamlEditor value={this.state.rawManifest} onChange={this.handleRawManifestChange} />
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as React from 'react';
import { get } from 'lodash';

import { RunJobLogsModal, IRunJobLogsModalProps } from './RunJobLogsModal';

import {
IExecutionDetailsSectionProps,
ExecutionDetailsSection,
AccountTag,
ReactModal,
ReactInjector,
} from '@spinnaker/core';

export class RunJobExecutionDetails extends React.Component<IExecutionDetailsSectionProps> {
public static title = 'runJobConfig';

public showLogsModal = (_event: any): void => {
const { stage, execution } = this.props;
const { executionService } = ReactInjector;
executionService.getExecution(execution.id).then((fullExecution: any) => {
const fullStage = fullExecution.stages.find((s: any) => s.id === stage.id);
if (!fullStage) {
return;
}

const modalProps = { dialogClassName: 'modal-lg modal-fullscreen' };
ReactModal.show(
RunJobLogsModal,
{
logs: get(fullStage, 'context.jobStatus.logs', 'No log output found.'),
} as IRunJobLogsModalProps,
modalProps,
);
});
};

public render() {
const { stage, name, current } = this.props;
const { context } = stage;

return (
<ExecutionDetailsSection name={name} current={current}>
<div className="row">
<div className="col-md-9">
<dl className="dl-narrow dl-horizontal">
<dt>Account</dt>
<dd>
<AccountTag account={context.account} />
</dd>
</dl>
{stage.context.jobStatus && stage.context.jobStatus.location && (
<dl className="dl-narrow dl-horizontal">
<dt>Namespace</dt>
<dd>{stage.context.jobStatus.location}</dd>
</dl>
)}
</div>
{stage.context.jobStatus && stage.context.jobStatus.logs && (
<div className="col-md-9">
<dl className="dl-narrow dl-horizontal">
<dt>Logs</dt>
<dd>
<a onClick={this.showLogsModal}>Console Output (Raw)</a>
</dd>
</dl>
</div>
)}
</div>
</ExecutionDetailsSection>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';

import { IModalComponentProps } from '@spinnaker/core';

export interface IRunJobLogsModalProps extends IModalComponentProps {
logs: string;
}

export class RunJobLogsModal extends React.Component<IRunJobLogsModalProps> {
constructor(props: IRunJobLogsModalProps) {
super(props);
}

public render() {
const { dismissModal } = this.props;
return (
<div className="flex-fill">
<div className="modal-header">
<h3>Execution Logs</h3>
</div>
<div className="modal-body flex-fill">
<pre className="body-small flex-fill">{this.props.logs || ''}</pre>
</div>
<div className="modal-footer">
<button className="btn btn-default" onClick={dismissModal}>
Close
</button>
</div>
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { module } from 'angular';

import {
Registry,
ExecutionDetailsTasks,
IPipeline,
IStage,
IValidatorConfig,
IStageOrTriggerTypeConfig,
ICustomValidator,
} from '@spinnaker/core';

import { KubernetesV2RunJobStageConfig } from './KubernetesV2RunJobStageConfig';
import { RunJobExecutionDetails } from './RunJobExecutionDetails';

export const KUBERNETES_V2_RUN_JOB_STAGE = 'spinnaker.kubernetes.v2.pipeline.stage.runJob';

module(KUBERNETES_V2_RUN_JOB_STAGE, [])
.config(() => {
Registry.pipeline.registerStage({
label: 'Run Job (Manifest)',
description: 'Run a Kubernetes Job mainfest yaml/json file.',
key: 'runJobManifest',
alias: 'runJob',
addAliasToConfig: true,
cloudProvider: 'kubernetes',
component: KubernetesV2RunJobStageConfig,
executionDetailsSections: [ExecutionDetailsTasks, RunJobExecutionDetails],
defaultTimeoutMs: 30 * 60 * 1000,
validators: [
{
type: 'custom',
validate: (
_pipeline: IPipeline,
stage: IStage,
_validator: IValidatorConfig,
_config: IStageOrTriggerTypeConfig,
): string => {
if (!stage.manifest || !stage.manifest.kind) {
return '';
}
if (stage.manifest.kind !== 'Job') {
return 'Run Job (Manifest) only accepts manifest of kind Job.';
}
return '';
},
} as ICustomValidator,
],
});
})
.controller('KubernetesV2RunJobStageConfig', KubernetesV2RunJobStageConfig);

0 comments on commit ee89a64

Please sign in to comment.