Skip to content

Commit

Permalink
refactor(runJob/kubernetes): use joblogviewer (#6917)
Browse files Browse the repository at this point in the history
instead of reading logs from execution context (which will be removed
soon), use the job log viewer modal to grab logs directly from the
infrastructure. large portions of code stolen directly from the
DeployStatus component.
  • Loading branch information
ethanfrogers authored May 2, 2019
1 parent 7312bb5 commit 98ca0ff
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 63 deletions.
21 changes: 21 additions & 0 deletions app/scripts/modules/kubernetes/src/v2/manifest/manifest.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ export interface IManifestContainer {
manifest: IManifest;
}

export interface IStageManifest {
kind: string;
apiVersion: string;
metadata: {
namespace: string;
name: string;
};
}

export interface IManifestParams {
account: string;
location: string;
Expand Down Expand Up @@ -32,4 +41,16 @@ export class KubernetesManifestService {
private static updateManifest(params: IManifestParams, fn: IManifestCallback) {
ManifestReader.getManifest(params.account, params.location, params.name).then(manifest => fn(manifest));
}

public static manifestIdentifier(manifest: IStageManifest) {
const kind = manifest.kind.toLowerCase();
// manifest.metadata.namespace doesn't exist if it's a namespace being deployed
const namespace = (manifest.metadata.namespace || '_').toLowerCase();
const name = manifest.metadata.name.toLowerCase();
const apiVersion = (manifest.apiVersion || '_').toLowerCase();
// assuming this identifier is opaque and not parsed anywhere. Including the
// apiVersion will prevent collisions with CRD kinds without having any visible
// effect elsewhere
return `${namespace} ${kind} ${apiVersion} ${name}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
StageFailureMessage,
IManifest,
} from '@spinnaker/core';
import { KubernetesManifestService } from 'kubernetes/v2/manifest/manifest.service';

import { KubernetesManifestService, IStageManifest } from 'kubernetes/v2/manifest/manifest.service';

import { ManifestStatus } from './ManifestStatus';

// from https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/
Expand Down Expand Up @@ -35,15 +37,6 @@ export interface IManifestSubscription {
manifest: IManifest;
}

interface IStageManifest {
kind: string;
apiVersion: string;
metadata: {
namespace: string;
name: string;
};
}

export interface IDeployStatusState {
subscriptions: IManifestSubscription[];
manifestIds: string[];
Expand All @@ -67,11 +60,11 @@ export class DeployStatus extends React.Component<IExecutionDetailsSectionProps,

public componentDidUpdate(_prevProps: IExecutionDetailsSectionProps, prevState: IDeployStatusState) {
const manifests: IStageManifest[] = get(this.props.stage, ['context', 'outputs.manifests'], []).filter(m => !!m);
const manifestIds = manifests.map(m => this.manifestIdentifier(m)).sort();
const manifestIds = manifests.map(m => KubernetesManifestService.manifestIdentifier(m)).sort();
if (prevState.manifestIds.join('') !== manifestIds.join('')) {
this.unsubscribeAll();
const subscriptions = manifests.map(manifest => {
const id = this.manifestIdentifier(manifest);
const id = KubernetesManifestService.manifestIdentifier(manifest);
return {
id,
unsubscribe: this.subscribeToManifestUpdates(id, manifest),
Expand Down Expand Up @@ -103,18 +96,6 @@ export class DeployStatus extends React.Component<IExecutionDetailsSectionProps,
this.state.subscriptions.forEach(({ unsubscribe }) => unsubscribe());
}

private manifestIdentifier(manifest: IStageManifest) {
const kind = manifest.kind.toLowerCase();
// manifest.metadata.namespace doesn't exist if it's a namespace being deployed
const namespace = (manifest.metadata.namespace || '_').toLowerCase();
const name = manifest.metadata.name.toLowerCase();
const apiVersion = (manifest.apiVersion || '_').toLowerCase();
// assuming this identifier is opaque and not parsed anywhere. Including the
// apiVersion will prevent collisions with CRD kinds without having any visible
// effect elsewhere
return `${namespace} ${kind} ${apiVersion} ${name}`;
}

private apiGroup(manifest: IStageManifest): string {
const parts = (manifest.apiVersion || '_').split('/');
if (parts.length < 2) {
Expand Down Expand Up @@ -160,7 +141,8 @@ export class DeployStatus extends React.Component<IExecutionDetailsSectionProps,
<div className="col-md-12">
<div className="well alert alert-info">
{manifests.map(manifest => {
const uid = manifest.manifest.metadata.uid || this.manifestIdentifier(manifest.manifest);
const uid =
manifest.manifest.metadata.uid || KubernetesManifestService.manifestIdentifier(manifest.manifest);
return <ManifestStatus key={uid} manifest={manifest} stage={stage} />;
})}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class JobManifestPodLogs extends React.Component<IJobManifestPodLogsProps
<a onClick={this.onClick} className="clickable">
{this.props.linkName}
</a>
<Modal show={showModal} onHide={this.close}>
<Modal show={showModal} onHide={this.close} dialogClassName="modal-lg modal-fullscreen">
<Modal.Header closeButton={true}>
<Modal.Title>Console Output: {this.podName()} </Modal.Title>
</Modal.Header>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,113 @@
import * as React from 'react';
import { get } from 'lodash';

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

import { JobManifestPodLogs } from '../deployManifest/react/JobManifestPodLogs';

import { KubernetesManifestService, IStageManifest } from 'kubernetes/v2/manifest/manifest.service';

import { IManifestSubscription } from '../deployManifest/react/DeployStatus';

interface IStageDeployedJobs {
[namespace: string]: string[];
}

interface IRunJobExecutionDetailsState {
subscription: IManifestSubscription;
manifestId: string;
}

export class RunJobExecutionDetails extends React.Component<
IExecutionDetailsSectionProps,
ExecutionDetailsSection,
AccountTag,
ReactModal,
ReactInjector,
LogsModal,
ILogsModalProps,
} from '@spinnaker/core';

export class RunJobExecutionDetails extends React.Component<IExecutionDetailsSectionProps> {
IRunJobExecutionDetailsState
> {
public static title = 'runJobConfig';
public state = {
subscription: { id: '', unsubscribe: () => {}, manifest: {} } as IManifestSubscription,
manifestId: '',
};

public componentDidMount() {
this.componentDidUpdate(this.props, this.state);
}

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(
LogsModal,
{
logs: get(fullStage, 'context.jobStatus.logs', 'No log output found.'),
} as ILogsModalProps,
modalProps,
);
public componentWillUnmount() {
this.unsubscribe();
}

public componentDidUpdate(_prevProps: IExecutionDetailsSectionProps, prevState: IRunJobExecutionDetailsState) {
const manifest: IStageManifest = get(this.props.stage, ['context', 'manifest']);
const manifestId = KubernetesManifestService.manifestIdentifier(manifest);
if (prevState.manifestId !== manifestId) {
this.unsubscribe();
const subscription = {
id: manifestId,
unsubscribe: this.subscribeToManifestUpdates(manifest),
manifest: this.stageManifestToIManifest(
manifest,
get(this.props.stage, ['context', 'deploy.jobs'], {}),
this.props.stage.context.account,
),
};
this.setState({
subscription,
manifestId,
});
}
}

private unsubscribe() {
this.state.subscription && this.state.subscription.unsubscribe && this.state.subscription.unsubscribe();
}

private subscribeToManifestUpdates(manifest: IStageManifest): () => void {
const params = {
account: this.props.stage.context.account,
name: this.extractDeployedJobName(manifest, get(this.props.stage, ['context', 'deploy.jobs'], {})),
location: manifest.metadata.namespace == null ? '_' : manifest.metadata.namespace,
};
return KubernetesManifestService.subscribe(this.props.application, params, (updated: IManifest) => {
const subscription = { ...this.state.subscription, manifest: updated };
this.setState({ subscription });
});
};
}

private extractDeployedJobName(manifest: IStageManifest, deployedJobs: IStageDeployedJobs): string {
const namespace = get(manifest, ['metadata', 'namespace'], '');
const jobNames = get(deployedJobs, namespace, []);
return jobNames.length > 0 ? jobNames[0] : '';
}

private stageManifestToIManifest(
manifest: IStageManifest,
deployedJobs: IStageDeployedJobs,
account: string,
): IManifest {
const namespace = get(manifest, ['metadata', 'namespace'], '');
const name = this.extractDeployedJobName(manifest, deployedJobs);

return {
name,
moniker: null,
account,
cloudProvider: 'kubernetes',
location: namespace,
manifest,
status: {},
artifacts: [],
events: [],
};
}

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

const manifest = get(this.state, ['subscription', 'manifest'], null);
let event: any = null;
if (manifest && manifest.events) {
event = manifest.events.find((e: any) => e.message.startsWith('Created pod'));
}
return (
<ExecutionDetailsSection name={name} current={current}>
<div className="row">
Expand All @@ -55,14 +125,9 @@ export class RunJobExecutionDetails extends React.Component<IExecutionDetailsSec
</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>
{manifest && event && (
<div className="col-md-9 well">
<JobManifestPodLogs manifest={manifest} manifestEvent={event} linkName="Console Output (Raw)" />
</div>
)}
</div>
Expand Down

0 comments on commit 98ca0ff

Please sign in to comment.