Skip to content

Commit

Permalink
feat(quay): add permissions support for quay plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
karthikjeeyar committed Jun 28, 2024
1 parent d2a76fd commit aa965b5
Show file tree
Hide file tree
Showing 15 changed files with 209 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugins/quay-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);
11 changes: 11 additions & 0 deletions plugins/quay-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Quay Common plugin

Welcome to the quay-common plugin!

This plugin contains common utilities for the quay plugin.

# Quay plugin for Backstage

The Quay plugin displays the information about your container images within the Quay registry in your Backstage application.

For more information about Quay plugin, see the [Quay plugin documentation](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/quay) on GitHub.
55 changes: 55 additions & 0 deletions plugins/quay-common/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "@janus-idp/backstage-plugin-quay-common",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "common-library",
"supported-versions": "1.26.5"
},
"author": "Red Hat",
"homepage": "https://red.ht/rhdh",
"bugs": "https://github.com/janus-idp/backstage-plugins/issues",
"maintainers": [
"janus-idp/rhtap"
],
"keywords": [
"support:production",
"lifecycle:active",
"backstage",
"plugin"
],
"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"
},
"repository": {
"type": "git",
"url": "https://github.com/janus-idp/backstage-plugins",
"directory": "plugins/quay-common"
},
"files": [
"dist"
],
"dependencies": {
"@backstage/plugin-permission-common": "^0.7.13"
},
"peerDependencies": {
"react": "16.13.1 || ^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@backstage/cli": "0.26.6"
}
}
7 changes: 7 additions & 0 deletions plugins/quay-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Common functionalities for the quay plugin.
*
* @packageDocumentation
*/

export * from './permissions';
13 changes: 13 additions & 0 deletions plugins/quay-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 quayViewPermission = createPermission({
name: 'quay.view.read',
attributes: {
action: 'read',
},
});

/**
* List of all permissions on permission polices.
*/
export const quayPermissions = [quayViewPermission];
9 changes: 9 additions & 0 deletions plugins/quay-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", "dev"],
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "../../dist-types/plugins/quay-common",
"rootDir": "."
}
}
9 changes: 9 additions & 0 deletions plugins/quay-common/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": ["//"],
"pipeline": {
"tsc": {
"outputs": ["../../dist-types/plugins/quay-common/**"],
"dependsOn": ["^tsc"]
}
}
}
3 changes: 3 additions & 0 deletions plugins/quay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
},
"dependencies": {
"@backstage/catalog-model": "^1.5.0",
"@backstage/plugin-catalog-common": "^1.0.23",
"@backstage/plugin-permission-react": "^0.4.22",
"@janus-idp/backstage-plugin-quay-common": "0.1.0",
"@backstage/core-components": "^0.14.7",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/plugin-catalog-react": "^1.12.0",
Expand Down
14 changes: 14 additions & 0 deletions plugins/quay/src/components/PermissionAlert/PermissionAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import { Alert, AlertTitle } from '@material-ui/lab';

const PermissionAlert = () => {
return (
<Alert severity="warning" data-testid="no-permission-alert">
<AlertTitle>Permission required</AlertTitle>
To view quay image registry, contact your administrator to give you the
quay.view.read and catalog.entity.read permissions.
</Alert>
);
};
export default PermissionAlert;
24 changes: 24 additions & 0 deletions plugins/quay/src/components/QuayRepository/QuayRepository.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { BrowserRouter } from 'react-router-dom';

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

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

import { useTags } from '../../hooks';
Expand All @@ -26,11 +28,33 @@ jest.mock('../../hooks/', () => ({
useTags: jest.fn().mockReturnValue({}),
}));

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

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

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

afterAll(() => {
jest.resetAllMocks();
});

it('should render permission alert when user does not have view permission', () => {
mockUsePermission.mockReturnValue({ loading: false, allowed: false });
const { queryByTestId } = render(<QuayRepository />);

expect(queryByTestId('no-permission-alert')).toBeInTheDocument();

expect(queryByTestId('quay-repo-progress')).toBeNull();
expect(queryByTestId('quay-repo-table')).toBeNull();
});

it('should show loading if loading is true', () => {
(useTags as jest.Mock).mockReturnValue({ loading: true, data: [] });
const { getByTestId } = render(<QuayRepository />);
Expand Down
8 changes: 8 additions & 0 deletions plugins/quay/src/components/QuayRepository/QuayRepository.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Link, Progress, Table } from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';

import { useRepository, useTags } from '../../hooks';
import { useQuayViewPermission } from '../../hooks/useQuayViewPermission';
import PermissionAlert from '../PermissionAlert/PermissionAlert';
import { columns, useStyles } from './tableHeading';

type QuayRepositoryProps = Record<never, any>;
Expand All @@ -14,6 +16,8 @@ export function QuayRepository(_props: QuayRepositoryProps) {
const configApi = useApi(configApiRef);
const quayUiUrl = configApi.getOptionalString('quay.uiUrl');

const hasViewPermission = useQuayViewPermission();

const title = quayUiUrl ? (
<>
{`Quay repository: `}
Expand All @@ -26,6 +30,10 @@ export function QuayRepository(_props: QuayRepositoryProps) {
);
const { loading, data } = useTags(organization, repository);

if (!hasViewPermission) {
return <PermissionAlert />;
}

if (loading) {
return (
<div data-testid="quay-repo-progress">
Expand Down
22 changes: 22 additions & 0 deletions plugins/quay/src/components/QuayTagPage/QuayTagPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom';

import { errorApiRef } from '@backstage/core-plugin-api';
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
import { usePermission } from '@backstage/plugin-permission-react';
import { MockErrorApi, TestApiProvider } from '@backstage/test-utils';
import { MockTranslationApi } from '@backstage/test-utils/alpha';

Expand Down Expand Up @@ -37,11 +38,32 @@ jest.mock('../QuayTagDetails', () => ({
QuayTagDetails: () => <div>QuayTagDetails</div>,
}));

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

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

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

afterAll(() => {
jest.resetAllMocks();
});

it('should render permission alert when user does not have view permission', () => {
mockUsePermission.mockReturnValue({ loading: false, allowed: false });
const { queryByText, queryByTestId } = render(<QuayTagPage />);

expect(queryByTestId('no-permission-alert')).toBeInTheDocument();
expect(queryByTestId('quay-tag-page-progress')).toBeNull();
expect(queryByText(/QuayTagDetails/i)).not.toBeInTheDocument();
});

it('should throw error if digest is not defined', () => {
expect(() => render(<QuayTagPage />)).toThrow('digest is not defined');
});
Expand Down
8 changes: 8 additions & 0 deletions plugins/quay/src/components/QuayTagPage/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@ import { ErrorPanel, Progress } from '@backstage/core-components';
import { useRouteRef } from '@backstage/core-plugin-api';

import { useRepository, useTagDetails } from '../../hooks';
import { useQuayViewPermission } from '../../hooks/useQuayViewPermission';
import { rootRouteRef } from '../../routes';
import PermissionAlert from '../PermissionAlert/PermissionAlert';
import { QuayTagDetails } from '../QuayTagDetails';

export const QuayTagPage = () => {
const rootLink = useRouteRef(rootRouteRef);
const { repository, organization } = useRepository();
const { digest } = useParams();
const hasViewPermission = useQuayViewPermission();
if (!digest) {
throw new Error('digest is not defined');
}
const { loading, value } = useTagDetails(organization, repository, digest);

if (!hasViewPermission) {
return <PermissionAlert />;
}

if (loading) {
return (
<div data-testid="quay-tag-page-progress">
Expand Down
19 changes: 19 additions & 0 deletions plugins/quay/src/hooks/useQuayViewPermission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
import { usePermission } from '@backstage/plugin-permission-react';

import { quayViewPermission } from '@janus-idp/backstage-plugin-quay-common';

export const useQuayViewPermission = () => {
const quayViewPermissionResult = usePermission({
permission: quayViewPermission,
});

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

return (
quayViewPermissionResult.allowed && catalogEntityPermissionResult.allowed
);
};
6 changes: 6 additions & 0 deletions plugins/rbac-backend/docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,9 @@ Resource type permissions on the other hand are basic named permissions with a r
| ------------------ | ------------- | ------ | ----------------------------------------------------------------------------------------------------------- | ------------------- |
| 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 |

## Quay

| Name | Resource Type | Policy | Description | Requirements |
| -------------- | ------------- | ------ | --------------------------------------- | ------------------- |
| quay.view.read | | read | Allows the user to view the quay plugin | catalog.entity.read |

0 comments on commit aa965b5

Please sign in to comment.