Skip to content

Commit

Permalink
feat(argocd): add permission support for argocd
Browse files Browse the repository at this point in the history
  • Loading branch information
karthikjeeyar committed Jun 28, 2024
1 parent 9bb8c47 commit 0265215
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 3 deletions.
1 change: 1 addition & 0 deletions plugins/argocd-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/argocd-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# argocd-common

Welcome to the argocd-common plugin!

This plugin contains common utilities for the argocd plugin.

# Argocd plugin for Backstage

The Argocd plugin displays the information about your argocd applications in your Backstage application.

For more information about Argocd plugin, see the [Argocd plugin documentation](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/argocd) on GitHub.
49 changes: 49 additions & 0 deletions plugins/argocd-common/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@janus-idp/backstage-plugin-argocd-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"
},
"sideEffects": false,
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"postpack": "backstage-cli packag e postpack",
"prepack": "backstage-cli package prepack",
"test": "backstage-cli package test --passWithNoTests --coverage",
"tsc": "tsc"
},
"dependencies": {
"@backstage/plugin-permission-common": "^0.7.13"
},
"devDependencies": {
"@backstage/cli": "0.26.6"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/janus-idp/backstage-plugins",
"directory": "plugins/argocd-common"
},
"keywords": [
"support:production",
"lifecycle:active",
"backstage",
"plugin"
],
"homepage": "https://red.ht/rhdh",
"bugs": "https://github.com/janus-idp/backstage-plugins/issues"
}
7 changes: 7 additions & 0 deletions plugins/argocd-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Common functionalities for the argocd plugin.
*
* @packageDocumentation
*/

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

/**
* List of all permissions on permission polices.
*/
export const argocdPermissions = [argocdViewPermission];
9 changes: 9 additions & 0 deletions plugins/argocd-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/argocd-common",
"rootDir": "."
}
}
9 changes: 9 additions & 0 deletions plugins/argocd-common/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": ["//"],
"pipeline": {
"tsc": {
"outputs": ["../../dist-types/plugins/argocd-common/**"],
"dependsOn": ["^tsc"]
}
}
}
6 changes: 4 additions & 2 deletions plugins/argocd/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { ConfigReader } from '@backstage/config';
import { configApiRef } from '@backstage/core-plugin-api';
import { createDevApp } from '@backstage/dev-utils';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { TestApiProvider } from '@backstage/test-utils';
import { permissionApiRef } from '@backstage/plugin-permission-react';
import { MockPermissionApi, TestApiProvider } from '@backstage/test-utils';

import { Box } from '@material-ui/core';
import { createDevAppThemes } from '@redhat-developer/red-hat-developer-hub-theme';
Expand Down Expand Up @@ -47,7 +48,7 @@ const mockEntity: Entity = {
owner: 'user:guest',
},
};

const mockPermissionApi = new MockPermissionApi();
export class MockArgoCDApiClient implements ArgoCDApi {
async listApps(_options: listAppsOptions): Promise<any> {
return { items: [mockApplication, preProdApplication, prodApplication] };
Expand All @@ -72,6 +73,7 @@ createDevApp()
apis={[
[configApiRef, new ConfigReader(mockArgocdConfig)],
[argoCDApiRef, new MockArgoCDApiClient()],
[permissionApiRef, mockPermissionApi],
]}
>
<EntityProvider entity={mockEntity}>
Expand Down
2 changes: 2 additions & 0 deletions plugins/argocd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/plugin-catalog-react": "^1.12.0",
"@backstage/theme": "^0.5.5",
"@janus-idp/backstage-plugin-argocd-common": "0.1.0",
"@backstage/plugin-permission-react": "^0.4.22",
"@kubernetes/client-node": "^0.20.0",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
Expand Down
14 changes: 14 additions & 0 deletions plugins/argocd/src/components/Common/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 argocd plugin, contact your administrator to give you the
argocd.view.read permission.
</Alert>
);
};
export default PermissionAlert;
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import { createStyles, makeStyles, Theme, Typography } from '@material-ui/core';
import { argoCDApiRef } from '../../api';
import { useApplications } from '../../hooks/useApplications';
import { useArgocdConfig } from '../../hooks/useArgocdConfig';
import { useArgocdViewPermission } from '../../hooks/useArgocdViewPermission';
import { Application, Revision } from '../../types';
import {
getAppSelector,
getInstanceName,
getUniqueRevisions,
} from '../../utils/utils';
import PermissionAlert from '../Common/PermissionAlert';
import DeploymentLifecycleCard from './DeploymentLifecycleCard';
import DeploymentLifecycleDrawer from './DeploymentLifecycleDrawer';

Expand Down Expand Up @@ -50,6 +52,8 @@ const DeploymentLifecycle = () => {
appSelector: encodeURIComponent(getAppSelector(entity)),
});

const hasArgocdViewAccess = useArgocdViewPermission();

const [open, setOpen] = React.useState(false);
const [activeItem, setActiveItem] = React.useState<string>();
const [, setRevisions] = React.useState<{
Expand Down Expand Up @@ -87,6 +91,10 @@ const DeploymentLifecycle = () => {

const activeApp = apps.find(a => a.metadata.name === activeItem);

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

if (error) {
return <ResponseErrorPanel data-testid="error-panel" error={error} />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { PropsWithChildren } from 'react';

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

import { createTheme, ThemeProvider } from '@material-ui/core';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
Expand All @@ -12,6 +13,13 @@ import DeploymentLifecycle from '../DeploymentLifecycle';
jest.mock('../../../hooks/useArgocdConfig', () => ({
useArgocdConfig: jest.fn(),
}));
jest.mock('@backstage/plugin-permission-react', () => ({
usePermission: jest.fn(),
}));

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

jest.mock('@backstage/core-plugin-api', () => ({
...jest.requireActual('@backstage/core-plugin-api'),
Expand All @@ -33,6 +41,8 @@ describe('DeploymentLifecycle', () => {
beforeEach(() => {
jest.clearAllMocks();

mockUsePermission.mockReturnValue({ loading: false, allowed: true });

(useArgocdConfig as any).mockReturnValue({
baseUrl: 'https://baseurl.com',
instances: [{ name: 'main', url: 'https://main-instance-url.com' }],
Expand All @@ -55,6 +65,12 @@ describe('DeploymentLifecycle', () => {
jest.spyOn(console, 'warn').mockImplementation(() => {});
});

it('should render Permission alert if the user does not have view permission', () => {
mockUsePermission.mockReturnValue({ loading: false, allowed: false });
const { getByTestId } = render(<DeploymentLifecycle />);
expect(getByTestId('no-permission-alert')).toBeInTheDocument();
});

test('should render the loader component', async () => {
render(<DeploymentLifecycle />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import moment from 'moment';

import { useApplications } from '../../hooks/useApplications';
import { useArgocdConfig } from '../../hooks/useArgocdConfig';
import { useArgocdViewPermission } from '../../hooks/useArgocdViewPermission';
import { Application, HealthStatus, SyncStatuses } from '../../types';
import {
getAppSelector,
Expand All @@ -18,6 +19,7 @@ import {
} from '../../utils/utils';
import AppSyncStatus from '../AppStatus/AppSyncStatus';
import { AppHealthIcon } from '../AppStatus/StatusIcons';
import PermissionAlert from '../Common/PermissionAlert';

const DeploymentSummary = () => {
const { entity } = useEntity();
Expand All @@ -32,6 +34,8 @@ const DeploymentSummary = () => {
projectName: getProjectName(entity),
});

const hasArgocdViewAccess = useArgocdViewPermission();

const supportsMultipleArgoInstances = !!instances.length;
const getBaseUrl = (row: any): string | undefined => {
if (supportsMultipleArgoInstances && !baseUrl) {
Expand Down Expand Up @@ -162,7 +166,7 @@ const DeploymentSummary = () => {
},
];

return !error ? (
return !error && hasArgocdViewAccess ? (
<Table
title="Deployment summary"
options={{
Expand Down
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 {
fireEvent,
render,
Expand All @@ -22,6 +24,14 @@ jest.mock('../../../hooks/useApplications', () => ({
useApplications: jest.fn(),
}));

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

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

jest.mock('@backstage/plugin-catalog-react', () => ({
useEntity: () => mockEntity,
}));
Expand All @@ -34,6 +44,8 @@ describe('DeploymentSummary', () => {
error: undefined,
});

mockUsePermission.mockReturnValue({ loading: false, allowed: true });

(useArgocdConfig as any).mockReturnValue({
baseUrl: '',
instances: [{ name: 'main', url: 'https://main-instance-url.com' }],
Expand Down Expand Up @@ -76,6 +88,15 @@ describe('DeploymentSummary', () => {
expect(screen.queryByText('No records to display')).toBeInTheDocument();
});
});
test('should not render deployment summary table when the user does not have view permission', async () => {
mockUsePermission.mockReturnValue({ loading: false, allowed: false });

render(<DeploymentSummary />);

await waitFor(() => {
expect(screen.queryByText('Deployment summary')).not.toBeInTheDocument();
});
});

test('should not render deployment summary table incase of error', async () => {
(useApplications as any).mockReturnValue({
Expand Down
11 changes: 11 additions & 0 deletions plugins/argocd/src/hooks/useArgocdViewPermission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { usePermission } from '@backstage/plugin-permission-react';

import { argocdViewPermission } from '@janus-idp/backstage-plugin-argocd-common';

export const useArgocdViewPermission = () => {
const argocdViewPermissionResult = usePermission({
permission: argocdViewPermission,
});

return argocdViewPermissionResult.allowed;
};
5 changes: 5 additions & 0 deletions plugins/argocd/src/plugin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { Route, Routes } from 'react-router-dom';

import { configApiRef } from '@backstage/core-plugin-api';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { permissionApiRef } from '@backstage/plugin-permission-react';
import {
MockConfigApi,
MockPermissionApi,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
Expand Down Expand Up @@ -33,6 +35,8 @@ describe('argocd', () => {
},
};

const mockPermissionApi = new MockPermissionApi();

const mockConfiguration = new MockConfigApi({
backend: {
baseUrl: 'http://localhost:7007',
Expand All @@ -50,6 +54,7 @@ describe('argocd', () => {
apis={[
[argoCDApiRef, mockedApi],
[configApiRef, mockConfiguration],
[permissionApiRef, mockPermissionApi],
]}
>
<EntityProvider entity={mockEntity}>{children}</EntityProvider>
Expand Down
8 changes: 8 additions & 0 deletions plugins/rbac-backend/docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,11 @@ 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 |

## Argocd

| Name | Resource Type | Policy | Description | Requirements |

| -------------- | ------------- | ------ | --------------------------------------- | ------------------- |

| argocd.view.read | | read | Allows the user to view the argocd plugin | catalog.entity.read |

0 comments on commit 0265215

Please sign in to comment.