Skip to content

Commit

Permalink
prevent race condition betweed detecting and listing Operator CRs
Browse files Browse the repository at this point in the history
  • Loading branch information
alecmerdler committed May 8, 2018
1 parent 3881bcb commit 58830f0
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ describe(ClusterServiceVersionResourcesPage.displayName, () => {
let wrapper: ShallowWrapper<ClusterServiceVersionResourcesPageProps>;

beforeEach(() => {
wrapper = shallow(<ClusterServiceVersionResourcesPage obj={testClusterServiceVersion} />);
wrapper = shallow(<ClusterServiceVersionResourcesPage.WrappedComponent obj={testClusterServiceVersion} />);
});

it('renders a `StatusBox` if given app has no owned or required custom resources', () => {
Expand All @@ -468,7 +468,6 @@ describe(ClusterServiceVersionResourcesPage.displayName, () => {
expect(listPage.props().resources).toEqual(owned.concat(required).map((crdDesc) => ({
kind: `${crdDesc.name.slice(crdDesc.name.indexOf('.') + 1)}:${crdDesc.version}:${crdDesc.kind}`,
namespaced: true,
optional: true,
prop: crdDesc.kind,
})));
expect(listPage.props().namespace).toEqual(testClusterServiceVersion.metadata.namespace);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
import * as React from 'react';
import { Link, match } from 'react-router-dom';
import * as _ from 'lodash-es';
import { connect } from 'react-redux';

import { ClusterServiceVersionResourceKind, ALMStatusDescriptors, ClusterServiceVersionKind, referenceForCRDDesc, ClusterServiceVersionPhase } from './index';
import { Resources } from './k8s-resource';
import { StatusDescriptor, PodStatusChart, ClusterServiceVersionResourceStatus } from './status-descriptors';
import { ClusterServiceVersionResourceSpec, SpecDescriptor } from './spec-descriptors';
import { List, MultiListPage, ListHeader, ColHead, DetailsPage, CompactExpandButtons } from '../factory';
import { ResourceLink, ResourceSummary, StatusBox, navFactory, Timestamp, LabelList, humanizeNumber, ResourceIcon, MsgBox, ResourceCog, Cog, Firehose } from '../utils';
import { connectToPlural } from '../../kinds';
import { connectToPlural, kindReducerName, inFlight } from '../../kinds';
import { kindForReference, K8sResourceKind, OwnerReference, K8sKind, referenceFor, GroupVersionKind, referenceForModel } from '../../module/k8s';
import { ClusterServiceVersionModel } from '../../models';
import { Gauge, Scalar, Line, Bar } from '../graphs';
Expand Down Expand Up @@ -82,43 +83,50 @@ export const ClusterServiceVersionPrometheusGraph: React.SFC<ClusterServiceVersi
}
};

export const ClusterServiceVersionResourcesPage: React.SFC<ClusterServiceVersionResourcesPageProps> = (props) => {
const {obj} = props;
const {owned = []} = obj.spec.customresourcedefinitions;
const firehoseResources = owned.map((desc) => ({kind: referenceForCRDDesc(desc), namespaced: true, optional: true, prop: desc.kind}));

const EmptyMsg = () => <MsgBox title="No Application Resources Defined" detail="This application was not properly installed or configured." />;
const createLink = (name: string) => `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${obj.metadata.name}/${referenceForCRDDesc(_.find(owned, {name}))}/new`;
const createProps = owned.length > 1
? {items: owned.reduce((acc, crd) => ({...acc, [crd.name]: crd.displayName}), {}), createLink}
: {to: owned.length === 1 ? createLink(owned[0].name) : null};

const owners = (ownerRefs: OwnerReference[], items: K8sResourceKind[]) => ownerRefs.filter(({uid}) => items.filter(({metadata}) => metadata.uid === uid).length > 0);
const flatten = (resources: {[kind: string]: {data: K8sResourceKind[]}}) => _.flatMap(resources, (resource) => _.map(resource.data, item => item))
.filter(({kind, metadata}, i, allResources) => owned.filter(item => item.kind === kind).length > 0 || owners(metadata.ownerReferences || [], allResources).length > 0);

const rowFilters = [{
type: 'clusterserviceversion-resource-kind',
selected: firehoseResources.map(({kind}) => kind),
reducer: ({kind}) => kind,
items: firehoseResources.map(({kind}) => ({id: kindForReference(kind), title: kindForReference(kind)})),
}];

return firehoseResources.length > 0
? <MultiListPage
{...props}
ListComponent={ClusterServiceVersionResourceList}
filterLabel="Resources by name"
resources={firehoseResources}
namespace={obj.metadata.namespace}
canCreate={owned.length > 0 && obj.status.phase === ClusterServiceVersionPhase.CSVPhaseSucceeded}
createProps={createProps}
createButtonText={owned.length > 1 ? 'Create New' : `Create ${owned[0].displayName}`}
flatten={flatten}
rowFilters={firehoseResources.length > 1 ? rowFilters : null}
/>
: <StatusBox loaded={true} EmptyMsg={EmptyMsg} />;
};
const inFlightStateToProps = ({[kindReducerName]: ns}) => ({inFlight: ns.get(inFlight)});

export const ClusterServiceVersionResourcesPage = connect(inFlightStateToProps, null)(
(props: ClusterServiceVersionResourcesPageProps) => {
const {obj} = props;
const {owned = []} = obj.spec.customresourcedefinitions;
const firehoseResources = owned.map((desc) => ({kind: referenceForCRDDesc(desc), namespaced: true, prop: desc.kind}));

const EmptyMsg = () => <MsgBox title="No Application Resources Defined" detail="This application was not properly installed or configured." />;
const createLink = (name: string) => `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${obj.metadata.name}/${referenceForCRDDesc(_.find(owned, {name}))}/new`;
const createProps = owned.length > 1
? {items: owned.reduce((acc, crd) => ({...acc, [crd.name]: crd.displayName}), {}), createLink}
: {to: owned.length === 1 ? createLink(owned[0].name) : null};

const owners = (ownerRefs: OwnerReference[], items: K8sResourceKind[]) => ownerRefs.filter(({uid}) => items.filter(({metadata}) => metadata.uid === uid).length > 0);
const flatten = (resources: {[kind: string]: {data: K8sResourceKind[]}}) => _.flatMap(resources, (resource) => _.map(resource.data, item => item))
.filter(({kind, metadata}, i, allResources) => owned.filter(item => item.kind === kind).length > 0 || owners(metadata.ownerReferences || [], allResources).length > 0);

const rowFilters = [{
type: 'clusterserviceversion-resource-kind',
selected: firehoseResources.map(({kind}) => kind),
reducer: ({kind}) => kind,
items: firehoseResources.map(({kind}) => ({id: kindForReference(kind), title: kindForReference(kind)})),
}];

if (props.inFlight) {
return null;
}

return firehoseResources.length > 0
? <MultiListPage
{...props}
ListComponent={ClusterServiceVersionResourceList}
filterLabel="Resources by name"
resources={firehoseResources}
namespace={obj.metadata.namespace}
canCreate={owned.length > 0 && obj.status.phase === ClusterServiceVersionPhase.CSVPhaseSucceeded}
createProps={createProps}
createButtonText={owned.length > 1 ? 'Create New' : `Create ${owned[0].displayName}`}
flatten={flatten}
rowFilters={firehoseResources.length > 1 ? rowFilters : null}
/>
: <StatusBox loaded={true} EmptyMsg={EmptyMsg} />;
});

export const ClusterServiceVersionResourceDetails = connectToPlural(
class ClusterServiceVersionResourceDetails extends React.Component<ClusterServiceVersionResourcesDetailsProps, ClusterServiceVersionResourcesDetailsState> {
Expand Down Expand Up @@ -258,6 +266,7 @@ export type ClusterServiceVersionResourceRowProps = {

export type ClusterServiceVersionResourcesPageProps = {
obj: ClusterServiceVersionKind;
inFlight?: boolean;
};

export type ClusterServiceVersionResourcesDetailsProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const ClusterServiceVersionHeader: React.SFC = () => <ListHeader>
export const ClusterServiceVersionRow = withFallback<ClusterServiceVersionRowProps>(({obj}) => {
const route = `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${obj.metadata.name}`;

const installStatus = obj.status.phase === ClusterServiceVersionPhase.CSVPhaseSucceeded
const installStatus = obj.status && obj.status.phase === ClusterServiceVersionPhase.CSVPhaseSucceeded
? <span>Enabled</span>
: <span className="co-error"><i className="fa fa-times-circle co-icon-space-r" /> {obj.status.reason}</span>;

Expand Down

0 comments on commit 58830f0

Please sign in to comment.