diff --git a/app/scripts/modules/kubernetes/src/v2/manifest/manifest.service.ts b/app/scripts/modules/kubernetes/src/v2/manifest/manifest.service.ts index 42bbfcb463a..ee5644dfb54 100644 --- a/app/scripts/modules/kubernetes/src/v2/manifest/manifest.service.ts +++ b/app/scripts/modules/kubernetes/src/v2/manifest/manifest.service.ts @@ -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; @@ -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}`; + } } diff --git a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/DeployStatus.tsx b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/DeployStatus.tsx index 9d699d1ea7a..b892099528a 100644 --- a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/DeployStatus.tsx +++ b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/DeployStatus.tsx @@ -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/ @@ -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[]; @@ -67,11 +60,11 @@ export class DeployStatus extends React.Component !!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), @@ -103,18 +96,6 @@ export class DeployStatus extends React.Component 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) { @@ -160,7 +141,8 @@ export class DeployStatus extends React.Component
{manifests.map(manifest => { - const uid = manifest.manifest.metadata.uid || this.manifestIdentifier(manifest.manifest); + const uid = + manifest.manifest.metadata.uid || KubernetesManifestService.manifestIdentifier(manifest.manifest); return ; })}
diff --git a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/JobManifestPodLogs.tsx b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/JobManifestPodLogs.tsx index 7e459e94f72..aefccc10217 100644 --- a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/JobManifestPodLogs.tsx +++ b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/deployManifest/react/JobManifestPodLogs.tsx @@ -95,7 +95,7 @@ export class JobManifestPodLogs extends React.Component {this.props.linkName} - + Console Output: {this.podName()} diff --git a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/runJob/RunJobExecutionDetails.tsx b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/runJob/RunJobExecutionDetails.tsx index 7e5806860cf..d3b07a61b62 100644 --- a/app/scripts/modules/kubernetes/src/v2/pipelines/stages/runJob/RunJobExecutionDetails.tsx +++ b/app/scripts/modules/kubernetes/src/v2/pipelines/stages/runJob/RunJobExecutionDetails.tsx @@ -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 { + 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 (
@@ -55,14 +125,9 @@ export class RunJobExecutionDetails extends React.Component )}
- {stage.context.jobStatus && stage.context.jobStatus.logs && ( -
-
-
Logs
-
- Console Output (Raw) -
-
+ {manifest && event && ( +
+
)}