Skip to content

Commit b096ca6

Browse files
authored
Merge pull request #165 from mgalesloot/feature/least-privilege
flux: support users with only namespace permissions
2 parents 631acd8 + 82d12ca commit b096ca6

15 files changed

+698
-887
lines changed

flux/src/checkflux/index.tsx

+37-45
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,44 @@
1-
import { K8s } from '@kinvolk/headlamp-plugin/lib';
1+
import { SectionBox} from '@kinvolk/headlamp-plugin/lib/components/common';
22
import { Box, Link } from '@mui/material';
3-
import { useTheme } from '@mui/material';
43

5-
export default function Flux404() {
6-
const theme = useTheme();
4+
export default function Flux404(props: Readonly<{ message: string; docs?: string }>) {
5+
const { message, docs } = props;
76

8-
// return a box with a message that flux is not installed and a link to the flux installation guide
9-
return (
10-
<Box
11-
// center this box and also wrap it in a white background with some box shadow
12-
sx={{
13-
padding: '1rem',
14-
alignItems: 'center',
15-
margin: '2rem auto',
16-
maxWidth: '600px',
17-
}}
18-
>
19-
<h1>Flux is not installed</h1>
20-
<p>
21-
Follow the{' '}
22-
<Link target="_blank" href="https://fluxcd.io/docs/installation/">
23-
installation guide
24-
</Link>{' '}
25-
to install flux on your cluster
26-
</p>
27-
</Box>
28-
);
29-
}
30-
31-
export function useFluxInstallCheck() {
32-
const [deployments] = K8s.ResourceClasses.Deployment.useList({
33-
labelSelector: 'app.kubernetes.io/part-of=flux,app.kubernetes.io/component=source-controller',
34-
});
35-
36-
return deployments;
7+
// return a box with a message that flux is not installed and a link to the flux installation guide
8+
return (
9+
<Box
10+
// center this box and also wrap it in a white background with some box shadow
11+
sx={{
12+
padding: '1rem',
13+
alignItems: 'center',
14+
margin: '2rem auto',
15+
maxWidth: '600px',
16+
}}
17+
>
18+
<h1>{message}</h1>
19+
<p>
20+
Follow the{' '}
21+
<Link target="_blank" href={docs ?? 'https://fluxcd.io/docs/installation/'}>
22+
installation guide
23+
</Link>{' '}
24+
to install flux on your cluster
25+
</p>
26+
</Box>
27+
);
3728
}
3829

39-
export function useFluxControllerAvailableCheck(props: { name: string }) {
40-
const { name } = props;
41-
const [deployments] = K8s.ResourceClasses.Deployment.useList({
42-
labelSelector: 'app.kubernetes.io/part-of=flux',
43-
});
44-
45-
if (deployments === null) {
46-
return null;
47-
}
48-
49-
return deployments?.find(
50-
deployment => deployment.jsonData.metadata?.labels['app.kubernetes.io/component'] === name
30+
export function NotSupported(props: { typeName: string }) {
31+
const { typeName } = props;
32+
return (
33+
<SectionBox title={typeName}>
34+
<p>Flux installation has no support for {typeName}.</p>
35+
<p>
36+
Follow the{' '}
37+
<Link target="_blank" href="https://fluxcd.io/docs/installation/">
38+
installation guide
39+
</Link>{' '}
40+
to support {typeName} on your cluster
41+
</p>
42+
</SectionBox>
5143
);
5244
}

flux/src/helm-releases/HelmReleaseList.tsx

+24-64
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,44 @@
1-
import { K8s } from '@kinvolk/headlamp-plugin/lib';
2-
import {
3-
Loader,
4-
SectionBox,
5-
SectionFilterHeader,
6-
} from '@kinvolk/headlamp-plugin/lib/components/common';
1+
import { SectionBox, SectionFilterHeader } from '@kinvolk/headlamp-plugin/lib/components/common';
2+
import { makeCustomResourceClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/crd';
73
import { useFilterFunc } from '@kinvolk/headlamp-plugin/lib/Utils';
8-
import { Link as MuiLink } from '@mui/material';
9-
import React from 'react';
10-
import { useTheme } from '@mui/material/styles';
11-
import { useFluxControllerAvailableCheck, useFluxInstallCheck } from '../checkflux';
4+
import { NotSupported } from '../checkflux';
125
import Table from '../common/Table';
13-
import Flux404 from '../checkflux';
6+
import React from 'react';
147

158
export function HelmReleases() {
16-
const isHelmReleasesControllerAvailable = useFluxControllerAvailableCheck({
17-
name: 'helm-controller',
18-
});
9+
return <HelmReleasesList />;
10+
}
1911

20-
if (isHelmReleasesControllerAvailable === null) {
21-
return <Loader />;
22-
}
12+
export function helmReleaseClass() {
13+
const helmreleaseGroup = 'helm.toolkit.fluxcd.io';
14+
const helmreleaseVersion = 'v2';
2315

24-
if (!isHelmReleasesControllerAvailable) {
25-
return (
26-
<SectionBox sx={{
27-
padding: '1rem',
28-
alignItems: 'center',
29-
margin: '2rem auto',
30-
maxWidth: '600px',
31-
}}>
32-
<h1>Helm Controller is not installed</h1>
33-
<p>
34-
Follow the{' '}
35-
<MuiLink target="_blank" href="https://fluxcd.io/docs/components/helm/">
36-
installation guide
37-
</MuiLink>{' '}
38-
to install Helm Controller on your cluster
39-
</p>
40-
</SectionBox>
41-
);
42-
}
43-
return <HelmReleasesListWrapper />;
16+
return makeCustomResourceClass({
17+
apiInfo: [{ group: helmreleaseGroup, version: helmreleaseVersion }],
18+
isNamespaced: true,
19+
singularName: 'helmrelease',
20+
pluralName: 'helmreleases',
21+
});
4422
}
4523

46-
function HelmReleasesListWrapper() {
47-
const [helmReleases] = K8s.ResourceClasses.CustomResourceDefinition.useGet(
48-
'helmreleases.helm.toolkit.fluxcd.io'
49-
);
24+
function HelmReleasesList() {
25+
const filterFunction = useFilterFunc();
26+
const [resources, setResources] = React.useState(null);
27+
const [error, setError] = React.useState(null);
5028

51-
const helmReleaseResourceClass = React.useMemo(() => {
52-
return helmReleases?.makeCRClass();
53-
}, [helmReleases]);
29+
helmReleaseClass().useApiList(setResources, setError);
5430

55-
const isFluxInstalled = useFluxInstallCheck();
56-
57-
if(isFluxInstalled === null) {
58-
return <Loader />;
59-
}
60-
61-
if(!isFluxInstalled) {
62-
return <Flux404 />;
31+
if (error?.status === 404) {
32+
return <NotSupported typeName="Helm Releases" />
6333
}
6434

65-
return (
66-
<div>
67-
{helmReleaseResourceClass && <HelmReleasesList resourceClass={helmReleaseResourceClass} />}
68-
</div>
69-
);
70-
}
71-
72-
function HelmReleasesList({ resourceClass }) {
73-
const [resource] = resourceClass.useList();
74-
7535
return (
7636
<SectionBox title={<SectionFilterHeader title="Helm Releases" />}>
7737
<Table
78-
data={resource}
38+
data={resources}
7939
defaultSortingColumn={2}
80-
filterFunction={useFilterFunc()}
8140
columns={['name', 'namespace', 'status', 'source', 'revision', 'message', 'lastUpdated']}
41+
filterFunction={filterFunction}
8242
/>
8343
</SectionBox>
8444
);

flux/src/helm-releases/HelmReleaseSingle.tsx

+8-43
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
Table,
88
} from '@kinvolk/headlamp-plugin/lib/components/common';
99
import Event, { KubeEvent } from '@kinvolk/headlamp-plugin/lib/K8s/event';
10-
import { KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster';
1110
import Editor from '@monaco-editor/react';
1211
import React from 'react';
1312
import { useParams } from 'react-router-dom';
@@ -22,66 +21,32 @@ import {
2221
import RemainingTimeDisplay from '../common/RemainingTimeDisplay';
2322
import StatusLabel from '../common/StatusLabel';
2423
import { getSourceNameAndType, ObjectEvents } from '../helpers/index';
25-
import { GetResourcesFromInventory,HELMRELEASE_CRD } from '../inventory';
24+
import { GetResourcesFromInventory } from '../inventory';
25+
import { helmReleaseClass } from './HelmReleaseList';
26+
import { GetSource } from '../sources/Source';
2627

27-
function GetSourceCR(props: {
28-
name: string;
29-
namespace: string;
30-
resource: KubeObject | null;
31-
setSource: (...args) => void;
32-
}) {
33-
const { name, namespace, resource, setSource } = props;
34-
const resourceClass = React.useMemo(() => {
35-
return resource.makeCRClass();
36-
}, [resource]);
37-
38-
resourceClass.useApiGet(setSource, name, namespace);
39-
40-
return null;
41-
}
42-
43-
function GetSource(props: { item: KubeObject | null; setSource: (...args) => void }) {
44-
const { item, setSource } = props;
45-
const namespace = item.jsonData.metadata.namespace;
46-
47-
const { name, type } = getSourceNameAndType(item);
48-
49-
const [resource] = K8s.ResourceClasses.CustomResourceDefinition.useGet(
50-
`${type.split(' ').join('').toLowerCase()}.source.toolkit.fluxcd.io`
51-
);
52-
return (
53-
resource && (
54-
<GetSourceCR name={name} namespace={namespace} resource={resource} setSource={setSource} />
55-
)
56-
);
57-
}
58-
59-
export default function FluxHelmReleaseDetailView() {
28+
export function FluxHelmReleaseDetailView() {
6029
const { namespace, name } = useParams<{ namespace: string; name: string }>();
6130

6231
const [events] = Event?.default.useList({
6332
namespace,
6433
fieldSelector: `involvedObject.name=${name},involvedObject.kind=${'HelmRelease'}`,
6534
});
66-
const [resource] = K8s.ResourceClasses.CustomResourceDefinition.useGet(HELMRELEASE_CRD);
6735

6836
return (
6937
<>
70-
{resource && <CustomResourceDetails resource={resource} name={name} namespace={namespace} />}
38+
<CustomResourceDetails name={name} namespace={namespace} />
7139
<ObjectEvents events={events?.map((event: KubeEvent) => new Event.default(event))} />
7240
</>
7341
);
7442
}
7543

7644
function CustomResourceDetails(props) {
77-
const { name, namespace, resource } = props;
45+
const { name, namespace } = props;
7846
const [cr, setCr] = React.useState(null);
7947
const [source, setSource] = React.useState(null);
80-
const resourceClass = React.useMemo(() => {
81-
return resource.makeCRClass();
82-
}, [resource]);
8348

84-
resourceClass.useApiGet(setCr, name, namespace);
49+
helmReleaseClass().useApiGet(setCr, name, namespace);
8550

8651
function prepareExtraInfo(cr) {
8752
if (!cr) {
@@ -100,7 +65,7 @@ function CustomResourceDetails(props) {
10065
{
10166
name: 'Reconcile Strategy',
10267
value: cr?.jsonData?.spec.chart?.spec?.reconcileStrategy,
103-
}
68+
},
10469
];
10570

10671
if (cr?.jsonData?.spec?.chartRef) {

0 commit comments

Comments
 (0)