Skip to content

Commit

Permalink
Operator Hub is now namespaced, need to test end-to-end
Browse files Browse the repository at this point in the history
  • Loading branch information
alecmerdler committed Feb 6, 2019
1 parent 941b569 commit 11f60a0
Show file tree
Hide file tree
Showing 20 changed files with 418 additions and 132 deletions.
3 changes: 3 additions & 0 deletions frontend/__mocks__/k8sResourcesMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const testClusterServiceVersion: ClusterServiceVersionKind = {
'alm-owner-testapp': 'testapp.clusterserviceversions.operators.coreos.com.v1alpha1',
},
},
installModes: [],
install: {
strategy: 'Deployment',
spec: {
Expand Down Expand Up @@ -129,6 +130,7 @@ export const localClusterServiceVersion: ClusterServiceVersionKind = {
'alm-owner-local-testapp': 'local-testapp.clusterserviceversions.operators.coreos.com.v1alpha1',
},
},
installModes: [],
install: {
strategy: 'Deployment',
spec: {
Expand Down Expand Up @@ -268,6 +270,7 @@ export const testPackageManifest: PackageManifestKind = {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
},
}],
defaultChannel: 'alpha',
Expand Down
6 changes: 6 additions & 0 deletions frontend/__mocks__/operatorHubItemsMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const amqPackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
'alm-examples': '[{"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"Kafka","metadata":{"name":"my-cluster"},"spec":{"kafka":{"replicas":3,"listeners":{"plain":{},"tls":{}},"config":{"offsets.topic.replication.factor":3,"transaction.state.log.replication.factor":3,"transaction.state.log.min.isr":2},"storage":{"type":"ephemeral"}},"zookeeper":{"replicas":3,"storage":{"type":"ephemeral"}},"entityOperator":{"topicOperator":{},"userOperator":{}}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaConnect","metadata":{"name":"my-connect-cluster"},"spec":{"replicas":1,"bootstrapServers":"my-cluster-kafka-bootstrap:9093","tls":{"trustedCertificates":[{"secretName":"my-cluster-cluster-ca-cert","certificate":"ca.crt"}]}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaConnectS2I","metadata":{"name":"my-connect-cluster"},"spec":{"replicas":1,"bootstrapServers":"my-cluster-kafka-bootstrap:9093","tls":{"trustedCertificates":[{"secretName":"my-cluster-cluster-ca-cert","certificate":"ca.crt"}]}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaTopic","metadata":{"name":"my-topic","labels":{"strimzi.io/cluster":"my-cluster"}},"spec":{"partitions":10,"replicas":3,"config":{"retention.ms":604800000,"segment.bytes":1073741824}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaUser","metadata":{"name":"my-user","labels":{"strimzi.io/cluster":"my-cluster"}},"spec":{"authentication":{"type":"tls"},"authorization":{"type":"simple","acls":[{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Read","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Describe","host":"*"},{"resource":{"type":"group","name":"my-group","patternType":"literal"},"operation":"Read","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Write","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Create","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Describe","host":"*"}]}}}]',
description: '**Red Hat AMQ Streams** is a massively scalable, distributed, and high performance data streaming platform based on the Apache Kafka project. \nAMQ Streams provides an event streaming backbone that allows microservices and other application components to exchange data with extremely high throughput and low latency.\n\n**The core capabilities include**\n* A pub/sub messaging model, similar to a traditional enterprise messaging system, in which application components publish and consume events to/from an ordered stream\n* The long term, fault-tolerant storage of events\n* The ability for a consumer to replay streams of events\n* The ability to partition topics for horizontal scalability\n\n# Before you start\n\n1. Create AMQ Streams Cluster Roles\n```\n$ oc apply -f http://amq.io/amqstreams/rbac.yaml\n```\n2. Create following bindings\n```\n$ oc adm policy add-cluster-role-to-user strimzi-cluster-operator -z strimzi-cluster-operator --namespace <namespace>\n$ oc adm policy add-cluster-role-to-user strimzi-kafka-broker -z strimzi-cluster-operator --namespace <namespace>\n```',
Expand Down Expand Up @@ -89,6 +90,7 @@ const etcdPackageManifest = {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
annotations: {
'alm-examples': '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":["<etcd-cluster-endpoints>"],"storageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}}]',
'tectonic-visibility': 'ocs',
Expand Down Expand Up @@ -136,6 +138,7 @@ const federationv2PackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
description: 'Kubernetes Federation V2 namespace-scoped installation',
categories: '',
Expand Down Expand Up @@ -184,6 +187,7 @@ const prometheusPackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
'alm-examples': '[{"apiVersion":"monitoring.coreos.com/v1","kind":"Prometheus","metadata":{"name":"example","labels":{"prometheus":"k8s"}},"spec":{"replicas":2,"version":"v2.3.2","serviceAccountName":"prometheus-k8s","securityContext": {}, "serviceMonitorSelector":{"matchExpressions":[{"key":"k8s-app","operator":"Exists"}]},"ruleSelector":{"matchLabels":{"role":"prometheus-rulefiles","prometheus":"k8s"}},"alerting":{"alertmanagers":[{"namespace":"monitoring","name":"alertmanager-main","port":"web"}]}}},{"apiVersion":"monitoring.coreos.com/v1","kind":"ServiceMonitor","metadata":{"name":"example","labels":{"k8s-app":"prometheus"}},"spec":{"selector":{"matchLabels":{"k8s-app":"prometheus"}},"endpoints":[{"port":"web","interval":"30s"}]}},{"apiVersion":"monitoring.coreos.com/v1","kind":"Alertmanager","metadata":{"name":"alertmanager-main"},"spec":{"replicas":3, "securityContext": {}}}]',
description: 'The Prometheus Operator for Kubernetes provides easy monitoring definitions for Kubernetes services and deployment and management of Prometheus instances.',
Expand Down Expand Up @@ -230,6 +234,7 @@ const svcatPackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
description: 'Service Catalog lets you provision cloud services directly from the comfort of native Kubernetes tooling.',
categories: 'catalog',
Expand Down Expand Up @@ -276,6 +281,7 @@ const dummyPackageManifest = {
provider: {
name: 'Dummy',
},
installModes: [],
annotations: {
description: 'Dummy is not a real operator',
categories: 'dummy',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe(SubscriptionChannelModal.name, () => {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
},
}, {
name: 'nightly',
Expand All @@ -48,6 +49,7 @@ describe(SubscriptionChannelModal.name, () => {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
},
}];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable no-undef, no-unused-vars */

import * as React from 'react';
import * as _ from 'lodash-es';
import { shallow } from 'enzyme';

import { requireOperatorGroup, NoOperatorGroupMsg } from '../../../public/components/operator-lifecycle-manager/operator-group';
import { requireOperatorGroup, NoOperatorGroupMsg, supports, InstallModeSet, InstallModeType } from '../../../public/components/operator-lifecycle-manager/operator-group';
import { OperatorGroupKind } from '../../../public/components/operator-lifecycle-manager';
import { testOperatorGroup } from '../../../__mocks__/k8sResourcesMocks';

describe('requireOperatorGroup', () => {
Expand Down Expand Up @@ -33,3 +35,64 @@ describe('requireOperatorGroup', () => {
expect(wrapper.find(NoOperatorGroupMsg).exists()).toBe(false);
});
});

describe('supports', () => {
let set: InstallModeSet;
let ownNamespaceGroup: OperatorGroupKind;
let singleNamespaceGroup: OperatorGroupKind;
let multiNamespaceGroup: OperatorGroupKind;
let allNamespacesGroup: OperatorGroupKind;

beforeEach(() => {
ownNamespaceGroup = _.cloneDeep(testOperatorGroup);
ownNamespaceGroup.status = {namespaces: [ownNamespaceGroup.metadata.namespace], lastUpdated: null};
singleNamespaceGroup = _.cloneDeep(testOperatorGroup);
singleNamespaceGroup.status = {namespaces: ['test-ns'], lastUpdated: null};
multiNamespaceGroup = _.cloneDeep(testOperatorGroup);
multiNamespaceGroup.status = {namespaces: ['test-ns', 'default'], lastUpdated: null};
allNamespacesGroup = _.cloneDeep(testOperatorGroup);
allNamespacesGroup.status = {namespaces: [''], lastUpdated: null};
});

it('correctly returns for an Operator that can only run in its own namespace', () => {
set = [
{type: InstallModeType.InstallModeTypeOwnNamespace, supported: true},
{type: InstallModeType.InstallModeTypeSingleNamespace, supported: true},
{type: InstallModeType.InstallModeTypeMultiNamespace, supported: false},
{type: InstallModeType.InstallModeTypeAllNamespaces, supported: false},
];

expect(supports(set)(ownNamespaceGroup)).toBe(true);
expect(supports(set)(singleNamespaceGroup)).toBe(true);
expect(supports(set)(multiNamespaceGroup)).toBe(false);
expect(supports(set)(allNamespacesGroup)).toBe(false);
});

it('correctly returns for an Operator which can run in several namespaces', () => {
set = [
{type: InstallModeType.InstallModeTypeOwnNamespace, supported: true},
{type: InstallModeType.InstallModeTypeSingleNamespace, supported: true},
{type: InstallModeType.InstallModeTypeMultiNamespace, supported: true},
{type: InstallModeType.InstallModeTypeAllNamespaces, supported: false},
];

expect(supports(set)(ownNamespaceGroup)).toBe(true);
expect(supports(set)(singleNamespaceGroup)).toBe(true);
expect(supports(set)(multiNamespaceGroup)).toBe(true);
expect(supports(set)(allNamespacesGroup)).toBe(false);
});

it('correctly returns for an Operator which can only run in all namespaces', () => {
set = [
{type: InstallModeType.InstallModeTypeOwnNamespace, supported: true},
{type: InstallModeType.InstallModeTypeSingleNamespace, supported: false},
{type: InstallModeType.InstallModeTypeMultiNamespace, supported: false},
{type: InstallModeType.InstallModeTypeAllNamespaces, supported: true},
];

expect(supports(set)(ownNamespaceGroup)).toBe(false);
expect(supports(set)(singleNamespaceGroup)).toBe(false);
expect(supports(set)(multiNamespaceGroup)).toBe(false);
expect(supports(set)(allNamespacesGroup)).toBe(true);
});
});
5 changes: 4 additions & 1 deletion frontend/public/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,11 @@ class App extends React.PureComponent {
<LazyRoute path="/cluster-health" exact loader={() => import('./cluster-health' /* webpackChunkName: "cluster-health" */).then(m => m.ClusterHealth)} />
<LazyRoute path="/start-guide" exact loader={() => import('./start-guide' /* webpackChunkName: "start-guide" */).then(m => m.StartGuidePage)} />

<LazyRoute path="/operatorhub" exact loader={() => import('./operator-hub/operator-hub-page' /* webpackChunkName: "operator-hub" */).then(m => m.OperatorHubPage)} />
<LazyRoute path="/operatorhub/all-namespaces" exact loader={() => import('./operator-hub/operator-hub-page' /* webpackChunkName: "operator-hub" */).then(m => m.OperatorHubPage)} />
<LazyRoute path="/operatorhub/ns/:ns" exact loader={() => import('./operator-hub/operator-hub-page' /* webpackChunkName: "operator-hub" */).then(m => m.OperatorHubPage)} />
<Route path="/operatorhub" exact component={NamespaceRedirect} />
<LazyRoute path="/operatorhub/subscribe" exact loader={() => import('./operator-hub/operator-hub-subscribe' /* webpackChunkName: "operator-hub-subscribe" */).then(m => m.OperatorHubSubscribePage)} />

<LazyRoute path="/catalog/all-namespaces" exact loader={() => import('./catalog/catalog-page' /* webpackChunkName: "catalog" */).then(m => m.CatalogPage)} />
<LazyRoute path="/catalog/ns/:ns" exact loader={() => import('./catalog/catalog-page' /* webpackChunkName: "catalog" */).then(m => m.CatalogPage)} />
<Route path="/catalog" exact component={NamespaceRedirect} />
Expand Down
4 changes: 3 additions & 1 deletion frontend/public/components/nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {
MachineSetModel,
PackageManifestModel,
SubscriptionModel,
OperatorGroupModel,
} from '../models';
import { referenceForModel } from '../module/k8s';

import { history, stripBasePath } from './utils';

export const matchesPath = (resourcePath, prefix) => resourcePath === prefix || _.startsWith(resourcePath, `${prefix}/`);
Expand Down Expand Up @@ -277,6 +277,8 @@ const operatorManagementStartsWith = [
InstallPlanModel.path,
referenceForModel(CatalogSourceModel),
CatalogSourceModel.path,
referenceForModel(OperatorGroupModel),
OperatorGroupModel.path,
];
const provisionedServicesStartsWith = ['serviceinstances', 'servicebindings'];
const brokerManagementStartsWith = ['clusterservicebrokers', 'clusterserviceclasses'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MarkdownView } from '../operator-lifecycle-manager/clusterserviceversio
import { history, ExternalLink } from '../utils';
import { RH_OPERATOR_SUPPORT_POLICY_LINK } from '../../const';

export const OperatorHubItemDetails: React.SFC<OperatorHubItemDetailsProps> = ({item, closeOverlay}) => {
export const OperatorHubItemDetails: React.SFC<OperatorHubItemDetailsProps> = ({item, closeOverlay, namespace}) => {
if (!item) {
return null;
}
Expand Down Expand Up @@ -70,10 +70,9 @@ export const OperatorHubItemDetails: React.SFC<OperatorHubItemDetailsProps> = ({

const onActionClick = () => {
if (!installed) {
history.push(`/operatorhub/subscribe?pkg=${item.obj.metadata.name}&catalog=${catalogSource}&catalogNamespace=${catalogSourceNamespace}`);
return;
// TODO(alecmerdler): Add `targetNamespace` as query parameter
return history.push(`/operatorhub/subscribe?pkg=${item.obj.metadata.name}&catalog=${catalogSource}&catalogNamespace=${catalogSourceNamespace}&targetNamespace=${namespace}`);
}

// TODO: Allow for Manage button to navigate to the CSV details for the item
};

Expand Down Expand Up @@ -126,6 +125,7 @@ OperatorHubItemDetails.defaultProps = {
};

export type OperatorHubItemDetailsProps = {
namespace?: string;
item: any;
closeOverlay: () => void;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export const OperatorHubTileView = requireOperatorGroup(
const { uid, name, imgUrl, iconClass, provider, description, installed } = item;
const normalizedIconClass = iconClass && `icon ${normalizeIconClass(iconClass)}`;
const vendor = provider ? `provided by ${provider}` : null;

return (
<CatalogTile
id={uid}
Expand All @@ -345,7 +346,7 @@ export const OperatorHubTileView = requireOperatorGroup(
vendor={vendor}
description={description}
onClick={() => this.openOverlay(item)}
footer={installed ? <span><Icon type="pf" name="ok" /> Installed</span> : null}
footer={installed && !_.isEmpty(this.props.namespace) ? <span><Icon type="pf" name="ok" /> Installed</span> : null}
/>
);
}
Expand All @@ -367,7 +368,7 @@ export const OperatorHubTileView = requireOperatorGroup(
emptyStateInfo="No Operator Hub items are being shown due to the filters being applied."
/>
<Modal show={!!detailsItem} onHide={this.closeOverlay} bsSize={'lg'} className="co-catalog-page__overlay right-side-modal-pf">
{detailsItem && <OperatorHubItemDetails item={detailsItem} closeOverlay={this.closeOverlay} />}
{detailsItem && <OperatorHubItemDetails namespace={this.props.namespace} item={detailsItem} closeOverlay={this.closeOverlay} />}
</Modal>
</React.Fragment>;
}
Expand All @@ -380,6 +381,7 @@ OperatorHubTileView.propTypes = {
};

export type OperatorHubTileViewProps = {
namespace?: string;
items: any[];
catalogSourceConfig: K8sResourceKind;
};
Expand Down
Loading

0 comments on commit 11f60a0

Please sign in to comment.