From 386e353c59bf5e076e5cd5c4fa931ec094d4bc01 Mon Sep 17 00:00:00 2001 From: Samuel Padgett Date: Tue, 26 Mar 2019 15:12:52 -0400 Subject: [PATCH 001/452] Auth test updates * `login` and `challenge` are no longer in the API * Prefix test env vars with `BRIDGE` * Update README for recent changes --- README.md | 6 +++--- .../integration-tests/data/htpasswd-idp.yaml | 2 -- .../integration-tests/tests/login.scenario.ts | 18 +++++++++--------- test-prow-e2e.sh | 2 +- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c565b123deb..0c31ba20c7f 100644 --- a/README.md +++ b/README.md @@ -214,9 +214,9 @@ CI runs the [test-prow-e2e.sh](test-prow-e2e.sh) script, which uses the `e2e` su You can simulate an e2e run against an existing 4.0 cluster with the following commands (replace `/path/to/install-dir` with your OpenShift 4.0 install directory): ``` -$ export BRIDGE_AUTH_USERNAME=kubeadmin -$ export BRIDGE_BASE_ADDRESS="https://$(oc get route console -n openshift-console -o jsonpath='{.spec.host}')" -$ export BRIDGE_AUTH_PASSWORD=$(cat "/path/to/install-dir/auth/kubeadmin-password") +$ oc apply -f ./frontend/integration-tests/data/htpasswd-idp.yaml +$ export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')" +$ export BRIDGE_KUBEADMIN_PASSWORD=$(cat "/path/to/install-dir/auth/kubeadmin-password") $ ./test-gui.sh e2e ``` diff --git a/frontend/integration-tests/data/htpasswd-idp.yaml b/frontend/integration-tests/data/htpasswd-idp.yaml index e139bed1584..fc99d745102 100644 --- a/frontend/integration-tests/data/htpasswd-idp.yaml +++ b/frontend/integration-tests/data/htpasswd-idp.yaml @@ -17,8 +17,6 @@ metadata: spec: identityProviders: - name: test - challenge: true - login: true mappingMethod: claim type: HTPasswd htpasswd: diff --git a/frontend/integration-tests/tests/login.scenario.ts b/frontend/integration-tests/tests/login.scenario.ts index 7c485c2d674..9ae87f8965c 100644 --- a/frontend/integration-tests/tests/login.scenario.ts +++ b/frontend/integration-tests/tests/login.scenario.ts @@ -9,10 +9,10 @@ const JASMINE_EXTENDED_TIMEOUT_INTERVAL = 1000 * 60 * 3; const KUBEADMIN_IDP = 'kube:admin'; const KUBEADMIN_USERNAME = 'kubeadmin'; const { - HTPASSWD_IDP = 'test', - HTPASSWD_USERNAME = 'test', - HTPASSWD_PASSWORD = 'test', - KUBEADMIN_PASSWORD, + BRIDGE_HTPASSWD_IDP = 'test', + BRIDGE_HTPASSWD_USERNAME = 'test', + BRIDGE_HTPASSWD_PASSWORD = 'test', + BRIDGE_KUBEADMIN_PASSWORD, } = process.env; describe('Auth test', () => { @@ -21,7 +21,7 @@ describe('Auth test', () => { await browser.sleep(3000); // Wait long enough for the login redirect to complete }); - if (KUBEADMIN_PASSWORD) { + if (BRIDGE_KUBEADMIN_PASSWORD) { describe('Login test', async() => { beforeAll(() => { // Extend the default jasmine timeout interval just in case it takes a while for the htpasswd idp to be ready @@ -34,9 +34,9 @@ describe('Auth test', () => { }); it('logs in via htpasswd identity provider', async() => { - await loginView.login(HTPASSWD_IDP, HTPASSWD_USERNAME, HTPASSWD_PASSWORD); + await loginView.login(BRIDGE_HTPASSWD_IDP, BRIDGE_HTPASSWD_USERNAME, BRIDGE_HTPASSWD_PASSWORD); expect(browser.getCurrentUrl()).toContain(appHost); - expect(loginView.userDropdown.getText()).toContain(HTPASSWD_USERNAME); + expect(loginView.userDropdown.getText()).toContain(BRIDGE_HTPASSWD_USERNAME); }); it('logs out htpasswd user', async() => { @@ -46,7 +46,7 @@ describe('Auth test', () => { }); it('logs in as kubeadmin user', async() => { - await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, KUBEADMIN_PASSWORD); + await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); expect(browser.getCurrentUrl()).toContain(appHost); expect(loginView.userDropdown.getText()).toContain('kube:admin'); await browser.wait(until.presenceOf($('.co-global-notification'))); @@ -59,7 +59,7 @@ describe('Auth test', () => { expect($('.login-pf').isPresent()).toBeTruthy(); // Log back in so that remaining tests can be run - await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, KUBEADMIN_PASSWORD); + await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); expect(loginView.userDropdown.getText()).toContain('kube:admin'); }); }); diff --git a/test-prow-e2e.sh b/test-prow-e2e.sh index a00a6d8bbda..154c1cf88ec 100755 --- a/test-prow-e2e.sh +++ b/test-prow-e2e.sh @@ -17,7 +17,7 @@ trap copyArtifacts EXIT # don't log kubeadmin-password set +x -export KUBEADMIN_PASSWORD="$(cat "${INSTALLER_DIR}/auth/kubeadmin-password")" +export BRIDGE_KUBEADMIN_PASSWORD="$(cat "${INSTALLER_DIR}/auth/kubeadmin-password")" set -x export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')" From b057484cb17d60aa301d48099051eabaa00f9d24 Mon Sep 17 00:00:00 2001 From: Jon Jackson Date: Mon, 25 Mar 2019 12:53:27 -0400 Subject: [PATCH 002/452] Move overview group and filter state to redux --- .../public/components/overview/constants.ts | 14 + frontend/public/components/overview/index.tsx | 301 +++++++++--------- frontend/public/ui/ui-actions.js | 9 + frontend/public/ui/ui-reducers.js | 16 + 4 files changed, 187 insertions(+), 153 deletions(-) create mode 100644 frontend/public/components/overview/constants.ts diff --git a/frontend/public/components/overview/constants.ts b/frontend/public/components/overview/constants.ts new file mode 100644 index 00000000000..e6fa1c48560 --- /dev/null +++ b/frontend/public/components/overview/constants.ts @@ -0,0 +1,14 @@ +/* eslint-disable no-undef */ + +// Keys for special 'group by' options +// Should not be valid label keys to avoid conflicts. https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set +export enum OverviewSpecialGroup { + GROUP_BY_APPLICATION = '#GROUP_BY_APPLICATION#', + GROUP_BY_RESOURCE = '#GROUP_BY_RESOURCE#', +} + +// View options for overview page +export enum OverviewViewOption { + RESOURCES = 'resources', + DASHBOARD = 'dashboard', +} diff --git a/frontend/public/components/overview/index.tsx b/frontend/public/components/overview/index.tsx index facac15cfd6..f1314ab3465 100644 --- a/frontend/public/components/overview/index.tsx +++ b/frontend/public/components/overview/index.tsx @@ -1,5 +1,4 @@ /* eslint-disable no-unused-vars, no-undef */ - import * as _ from 'lodash-es'; import * as classnames from 'classnames'; import * as fuzzy from 'fuzzysearch'; @@ -14,6 +13,7 @@ import { coFetchJSON } from '../../co-fetch'; import { getBuildNumber } from '../../module/k8s/builds'; import { prometheusTenancyBasePath } from '../graphs'; import { TextFilter } from '../factory'; +import { PodStatus } from '../pod'; import { UIActions, formatNamespacedRouteForResource } from '../../ui/ui-actions'; import { apiVersionForModel, @@ -48,31 +48,40 @@ import { import { overviewMenuActions, OverviewNamespaceDashboard } from './namespace-overview'; import { ProjectOverview } from './project-overview'; import { ResourceOverviewPage } from './resource-overview-page'; -import { PodStatus } from '../pod'; +import { OverviewViewOption, OverviewSpecialGroup } from './constants'; -enum View { - Resources = 'resources', - Dashboard = 'dashboard', -} -// The following values should not be valid label keys to avoid conflicts. -// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set -const GROUP_BY_APPLICATION = '#GROUP_BY_APPLICATION#'; -const GROUP_BY_RESOURCE = '#GROUP_BY_RESOURCE#'; -const EMPTY_GROUP_NAME = 'other resources'; +// List of container status waiting reason values that we should call out as errors in project status rows. +const CONTAINER_WAITING_STATE_ERROR_REASONS = ['CrashLoopBackOff', 'ErrImagePull', 'ImagePullBackOff']; -const DEPLOYMENT_REVISION_ANNOTATION = 'deployment.kubernetes.io/revision'; +// Annotation key for deployment config latest version const DEPLOYMENT_CONFIG_LATEST_VERSION_ANNOTATION = 'openshift.io/deployment-config.latest-version'; + +// Annotation key for deployment phase const DEPLOYMENT_PHASE_ANNOTATION = 'openshift.io/deployment.phase'; -const TRIGGERS_ANNOTATION = 'image.openshift.io/triggers'; + +// Annotaton key for deployment revision +const DEPLOYMENT_REVISION_ANNOTATION = 'deployment.kubernetes.io/revision'; + +// Display name for default overview group. +// Should not be a valid label key to avoid conflicts. https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-setexport +const DEFAULT_GROUP_NAME = 'other resources'; + +// Interval at which metrics are retrieved and updated const METRICS_POLL_INTERVAL = 30 * 1000; +// Namespace prefixes that are reserved and should not have calls to action on empty state +const RESERVED_NS_PREFIXES = ['openshift-', 'kube-', 'kubernetes-']; + +// Annotation key for image triggers +const TRIGGERS_ANNOTATION = 'image.openshift.io/triggers'; + const asOverviewGroups = (keyedItems: { [name: string]: OverviewItem[] }): OverviewGroup[] => { const compareGroups = (a: OverviewGroup, b: OverviewGroup) => { - if (a.name === EMPTY_GROUP_NAME) { + if (a.name === DEFAULT_GROUP_NAME) { return 1; } - if (b.name === EMPTY_GROUP_NAME) { + if (b.name === DEFAULT_GROUP_NAME) { return -1; } return a.name.localeCompare(b.name); @@ -88,7 +97,7 @@ const asOverviewGroups = (keyedItems: { [name: string]: OverviewItem[] }): Overv const getApplication = (item: OverviewItem): string => { const labels = _.get(item, 'obj.metadata.labels') || {}; - return labels['app.kubernetes.io/part-of'] || labels['app.kubernetes.io/name'] || labels.app || EMPTY_GROUP_NAME; + return labels['app.kubernetes.io/part-of'] || labels['app.kubernetes.io/name'] || labels.app || DEFAULT_GROUP_NAME; }; const groupByApplication = (items: OverviewItem[]): OverviewGroup[] => { @@ -102,24 +111,21 @@ const groupByResource = (items: OverviewItem[]): OverviewGroup[] => { }; const groupByLabel = (items: OverviewItem[], label: string): OverviewGroup[] => { - const byLabel = _.groupBy(items, (item): string => _.get(item, ['obj', 'metadata', 'labels', label]) || EMPTY_GROUP_NAME); + const byLabel = _.groupBy(items, (item): string => _.get(item, ['obj', 'metadata', 'labels', label]) || DEFAULT_GROUP_NAME); return asOverviewGroups(byLabel); }; const groupItems = (items: OverviewItem[], selectedGroup: string): OverviewGroup[] => { switch (selectedGroup) { - case GROUP_BY_APPLICATION: + case OverviewSpecialGroup.GROUP_BY_APPLICATION: return groupByApplication(items); - case GROUP_BY_RESOURCE: + case OverviewSpecialGroup.GROUP_BY_RESOURCE: return groupByResource(items); default: return groupByLabel(items, selectedGroup); } }; -// List of container status waiting reason values that we should call out as errors in overview rows. -const CONTAINER_WAITING_STATE_ERROR_REASONS = ['CrashLoopBackOff', 'ErrImagePull', 'ImagePullBackOff']; - const getAnnotation = (obj: K8sResourceKind, annotation: string): string => { return _.get(obj, ['metadata', 'annotations', annotation]); }; @@ -293,8 +299,7 @@ const sortBuilds = (builds: K8sResourceKind[]): K8sResourceKind[] => { return builds.sort(byBuildNumber); }; -const reservedNSPrefixes = ['openshift-', 'kube-', 'kubernetes-']; -const isReservedNamespace = (ns: string) => ns === 'default' || ns === 'openshift' || reservedNSPrefixes.some(prefix => _.startsWith(ns, prefix)); +const isReservedNamespace = (ns: string) => ns === 'default' || ns === 'openshift' || RESERVED_NS_PREFIXES.some(prefix => _.startsWith(ns, prefix)); const OverviewItemReadiness: React.SFC = ({desired = 0, ready = 0, resource}) => { const href = `${resourceObjPath(resource, resource.kind)}/pods`; @@ -337,106 +342,114 @@ const OverviewEmptyState = connect(overviewEmptyStateToProps)(({activeNamespace, }); const headingStateToProps = ({UI}): OverviewHeadingPropsFromState => { - const selectedView = UI.getIn(['overview', 'selectedView']); - return { selectedView }; + const {selectedView, selectedGroup, groupOptions, filterValue} = UI.get('overview').toJS(); + return {groupOptions, selectedGroup, selectedView, filterValue}; }; const headingDispatchToProps = (dispatch): OverviewHeadingPropsFromDispatch => ({ - selectView: (view: View) => dispatch(UIActions.selectOverviewView(view)), + selectView: (view: OverviewViewOption) => dispatch(UIActions.selectOverviewView(view)), + selectGroup: (group: string) => dispatch(UIActions.updateOverviewSelectedGroup(group)), + changeFilter: (value: string) => dispatch(UIActions.updateOverviewFilterValue(value)), }); -const OverviewHeading_: React.SFC = ({disabled, firstLabel = '', groupOptions, handleFilterChange = _.noop, handleGroupChange = _.noop, selectedGroup = '', selectView, selectedView, title, project}) => ( -
- { - title && -

-
{title}
-

- } - {!_.isEmpty(project) &&
-
-