Skip to content

Commit

Permalink
feat(topology): add permissions to topology plugin (janus-idp#1665)
Browse files Browse the repository at this point in the history
* feat(topology): add permissions to topology plugin

* feat(topology): Update topology empty state

* feat(topology): small doc update
  • Loading branch information
PatAKnight committed May 21, 2024
1 parent 566dc8c commit 9d8f244
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 5 deletions.
7 changes: 7 additions & 0 deletions plugins/rbac-backend/docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,10 @@ When defining a permission for the RBAC Backend plugin to consume, follow these
| ---------------- | ------------- | ------ | ----------------------------------------------------------------- | ------------ |
| ocm.entity.read | | read | Allows the user to read from the ocm plugin | X |
| ocm.cluster.read | | read | Allows the user to read the cluster information in the ocm plugin | X |

## Topology

| Name | Resource Type | Policy | Description | Requirements |
| ------------------ | ------------- | ------ | ----------------------------------------------------------------------------------------------------------- | ------------------- |
| topology.view.read | | read | Allows the user to view the topology plugin | X |
| kubernetes.proxy | | | Allows the user to access the proxy endpoint (ability to read pod logs and events within Showcase and RHDH) | catalog.entity.read |
1 change: 1 addition & 0 deletions plugins/topology-common/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
5 changes: 5 additions & 0 deletions plugins/topology-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Topology plugin for Backstage

The Topology plugin enables you to visualize the workloads such as Deployment, Job, Daemonset, Statefulset, CronJob, and Pods powering any service on the Kubernetes cluster.

For more information about Topology plugin, see the [Topology plugin documentation](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/topology) on GitHub.
48 changes: 48 additions & 0 deletions plugins/topology-common/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@janus-idp/backstage-plugin-topology-common",
"description": "Common functionalities for the topology plugin",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "common-library"
},
"sideEffects": false,
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"postpack": "backstage-cli package postpack",
"prepack": "backstage-cli package prepack",
"test": "backstage-cli package test --passWithNoTests --coverage",
"tsc": "tsc"
},
"devDependencies": {
"@backstage/cli": "0.26.4"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/janus-idp/backstage-plugins",
"directory": "plugins/topology"
},
"keywords": [
"backstage",
"plugin"
],
"homepage": "https://janus-idp.io/",
"bugs": "https://github.com/janus-idp/backstage-plugins/issues",
"dependencies": {
"@backstage/plugin-permission-common": "^0.7.13"
}
}
7 changes: 7 additions & 0 deletions plugins/topology-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Common functionalities for the topology plugin.
*
* @packageDocumentation
*/

export * from './permissions';
13 changes: 13 additions & 0 deletions plugins/topology-common/src/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createPermission } from '@backstage/plugin-permission-common';

export const topologyViewPermission = createPermission({
name: 'topology.view.read',
attributes: {
action: 'read',
},
});

/**
* List of all permissions on permission polices.
*/
export const topologyPermissions = [topologyViewPermission];
9 changes: 9 additions & 0 deletions plugins/topology-common/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@backstage/cli/config/tsconfig.json",
"include": ["src"],
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "../../dist-types/plugins/topology-common",
"rootDir": "."
}
}
9 changes: 9 additions & 0 deletions plugins/topology-common/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": ["//"],
"pipeline": {
"tsc": {
"outputs": ["../../dist-types/plugins/topology-common/**"],
"dependsOn": ["^tsc"]
}
}
}
10 changes: 10 additions & 0 deletions plugins/topology/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,16 @@ Topology is a front-end plugin that enables you to view the workloads as nodes t

- Your Backstage application is installed and running.
- You have installed the Topology plugin. For the installation process, see [Installation](#installation).
- If RBAC permission framework is enabled, ensure to add the following permission policies in an external permission policies configuration file named `rbac-policy.csv` to allow the rbac admins or your desired user(s)/group(s) to access the topology plugin:

```csv rbac-policy.csv
g, user:default/<YOUR_USERNAME>, role:default/topology-viewer
p, role:default/topology-viewer, topology.view.read, read, allow
p, role:default/topology-viewer, kubernetes.proxy, use, allow
p, role:default/topology-viewer, catalog-entity, read, allow
```

`p, role:default/topology-viewer, topology.view.read, read, allow` grants the user the ability to see the Topology panel. `p, role:default/topology-viewer, kubernetes.proxy, use, allow` grants the user the ability to view the pod logs. `p, role:default/topology-viewer, catalog-entity, read, allow` grants the user the ability to see the catalog item.

#### Procedure

Expand Down
3 changes: 3 additions & 0 deletions plugins/topology/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@
"@backstage/catalog-model": "^1.4.5",
"@backstage/core-components": "^0.14.6",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/plugin-catalog-common": "^1.0.23",
"@backstage/plugin-catalog-react": "^1.11.3",
"@backstage/plugin-kubernetes": "^0.11.9",
"@backstage/plugin-kubernetes-common": "^0.7.5",
"@backstage/plugin-permission-react": "^0.4.22",
"@backstage/theme": "^0.5.3",
"@janus-idp/shared-react": "2.6.2",
"@janus-idp/backstage-plugin-topology-common": "0.1.0",
"@kubernetes/client-node": "^0.20.0",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.11.3",
Expand Down
17 changes: 14 additions & 3 deletions plugins/topology/src/components/Topology/TopologyEmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ import React from 'react';

import {
EmptyState,
EmptyStateBody,
EmptyStateHeader,
EmptyStateIcon,
EmptyStateVariant,
} from '@patternfly/react-core';
import { TopologyIcon } from '@patternfly/react-icons';

export const TopologyEmptyState = () => {
type TopologyEmptyStateProps = {
title?: string;
description?: string;
};

export const TopologyEmptyState = ({
title,
description,
}: TopologyEmptyStateProps) => {
return (
<EmptyState
variant={EmptyStateVariant.full}
isFullHeight
className="pf-topology-visualization-surface"
>
<EmptyStateHeader
titleText="No resources found"
titleText={title || 'No resources found'}
icon={<EmptyStateIcon icon={TopologyIcon} />}
headingLevel="h3"
/>
>
<EmptyStateBody>{description}</EmptyStateBody>
</EmptyStateHeader>
</EmptyState>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';

import { usePermission } from '@backstage/plugin-permission-react';

import { render } from '@testing-library/react';

import { K8sResourcesContext } from '../../hooks/K8sResourcesContext';
Expand All @@ -12,6 +14,14 @@ jest.mock('../../hooks/useWorkloadWatcher', () => ({

const mockUseWorkloadsWatcher = useWorkloadsWatcher as jest.Mock;

jest.mock('@backstage/plugin-permission-react', () => ({
usePermission: jest.fn(),
}));

const mockUsePermission = usePermission as jest.MockedFunction<
typeof usePermission
>;

jest.mock('@patternfly/react-topology', () => ({
useVisualizationController: () => ({
getGraph: () => ({
Expand All @@ -35,6 +45,10 @@ jest.mock('@patternfly/react-topology', () => ({
}));

describe('TopologyViewWorkloadComponent', () => {
beforeEach(() => {
mockUsePermission.mockReturnValue({ loading: false, allowed: true });
});

it('should show loading state when loading is true', () => {
mockUseWorkloadsWatcher.mockReturnValue({
loaded: false,
Expand Down Expand Up @@ -71,4 +85,28 @@ describe('TopologyViewWorkloadComponent', () => {
);
expect(getByText(/topologyview/i)).not.toBeNull();
});

it('should not render TopologyView when data is available, loading is false, but user is not allowed', () => {
mockUsePermission.mockReturnValue({ loading: false, allowed: false });

mockUseWorkloadsWatcher.mockReturnValue({
loaded: true,
dataModel: { nodes: [{}] },
});
const { getByText } = render(
<K8sResourcesContext.Provider
value={{ clusters: ['ocp'], setSelectedCluster: () => {} }}
>
<TopologyViewWorkloadComponent />
</K8sResourcesContext.Provider>,
);
expect(getByText(/topologyview/i)).not.toBeNull();

const { getByRole } = render(<TopologyViewWorkloadComponent />);
expect(
getByRole('heading', {
name: /no resources found/i,
}),
).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import TopologyToolbar from './TopologyToolbar';

import './TopologyToolbar.css';

import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
import { usePermission } from '@backstage/plugin-permission-react';

import { topologyViewPermission } from '@janus-idp/backstage-plugin-topology-common';

type TopologyViewWorkloadComponentProps = {
useToolbar?: boolean;
};
Expand All @@ -49,6 +54,15 @@ const TopologyViewWorkloadComponent = ({
removeSelectedIdParam,
] = useSideBar();

const topologyViewPermissionResult = usePermission({
permission: topologyViewPermission,
});

const catalogEntityPermissionResult = usePermission({
permission: catalogEntityReadPermission,
resourceRef: catalogEntityReadPermission.resourceType,
});

const allErrors: ClusterErrors = [
...(responseError ? [{ message: responseError }] : []),
...(selectedClusterErrors ?? []),
Expand Down Expand Up @@ -126,8 +140,15 @@ const TopologyViewWorkloadComponent = ({
sideBarOpen={sideBarOpen}
minSideBarSize="400px"
>
{loaded && dataModel?.nodes?.length === 0 ? (
<TopologyEmptyState />
{isDataModelEmpty ||
!(
topologyViewPermissionResult.allowed &&
catalogEntityPermissionResult.allowed
) ? (
<TopologyEmptyState
title="Permission required"
description="To view Topology, contact your administrator to give you the topology.view.read and catalog.entity.read permissions"
/>
) : (
<VisualizationSurface state={{ selectedIds: [selectedId] }} />
)}
Expand Down
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3226,6 +3226,16 @@
ajv "^8.10.0"
lodash "^4.17.21"

"@backstage/catalog-model@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@backstage/catalog-model/-/catalog-model-1.5.0.tgz#7f5c4a80a3341555db5209fbc6fc2d25f6500707"
integrity sha512-CfLO5/DMGahneuLU4KTQEs1tgNhBciUtyGUDZB4Ii9i1Uha1poWcqp4HKg61lj1hmXNDUHmlbFqY9W7kmzRC0A==
dependencies:
"@backstage/errors" "^1.2.4"
"@backstage/types" "^1.1.1"
ajv "^8.10.0"
lodash "^4.17.21"

"@backstage/cli-common@^0.1.13":
version "0.1.13"
resolved "https://registry.yarnpkg.com/@backstage/cli-common/-/cli-common-0.1.13.tgz#cbeda6a359ca4437fc782f0ac51bb957e8d49e73"
Expand Down Expand Up @@ -4077,6 +4087,15 @@
"@backstage/plugin-permission-common" "^0.7.13"
"@backstage/plugin-search-common" "^1.2.11"

"@backstage/plugin-catalog-common@^1.0.23":
version "1.0.23"
resolved "https://registry.yarnpkg.com/@backstage/plugin-catalog-common/-/plugin-catalog-common-1.0.23.tgz#2ba1fe13450f6283e049acc83aa4fcebda6153e8"
integrity sha512-u04VUq/2wNjF9ikpGxdt1kXSQf5VlPDWTwzYyJYKD80qGa6l/klUXJ3IBs8P4XyQObkPNyS/Tho/H8XDFNeqEw==
dependencies:
"@backstage/catalog-model" "^1.5.0"
"@backstage/plugin-permission-common" "^0.7.13"
"@backstage/plugin-search-common" "^1.2.11"

"@backstage/plugin-catalog-graph@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@backstage/plugin-catalog-graph/-/plugin-catalog-graph-0.4.4.tgz#ff26dcec8ae437d07bbdc98093aa52503fe2a1b1"
Expand Down

0 comments on commit 9d8f244

Please sign in to comment.