Skip to content

Commit

Permalink
Controller not available fix
Browse files Browse the repository at this point in the history
  • Loading branch information
ashu8912 committed Oct 23, 2024
1 parent 3b99bfa commit 2bfb7ac
Show file tree
Hide file tree
Showing 20 changed files with 286 additions and 152 deletions.
5 changes: 1 addition & 4 deletions flux/src/actions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
ActionButton,
ConfirmDialog,
} from '@kinvolk/headlamp-plugin/lib/components/common';
import { ActionButton, ConfirmDialog } from '@kinvolk/headlamp-plugin/lib/components/common';
import { KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster';
import { useSnackbar } from 'notistack';
import React from 'react';
Expand Down
24 changes: 24 additions & 0 deletions flux/src/checkflux/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,27 @@ export default function CheckIfFluxInstalled() {
);
}
}

export function useFluxControllerAvailableCheck(props: { name: string }) {
const { name } = props;
const [deployments] = K8s.ResourceClasses.Deployment.useList({
labelSelector: 'app.kubernetes.io/part-of=flux',
});

if (deployments === null) {
return null;
}

const isFluxAvailable = deployments?.find(
deployment =>
deployment.jsonData.metadata?.labels['app.kubernetes.io/component'] === 'source-controller'
);

if (!isFluxAvailable) {
return true;
}

return deployments?.find(
deployment => deployment.jsonData.metadata?.labels['app.kubernetes.io/component'] === name
);
}
6 changes: 3 additions & 3 deletions flux/src/common/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Box, Link as MuiLink } from '@mui/material';
import React from 'react';

export default function Link(props: {url: string, wrap?: boolean}) {
const { url, wrap=false} = props;
export default function Link(props: { url: string; wrap?: boolean }) {
const { url, wrap = false } = props;

const href = React.useMemo(() => {
if (url.startsWith('http')) {
Expand Down Expand Up @@ -48,4 +48,4 @@ export default function Link(props: {url: string, wrap?: boolean}) {
}

return <LinkComponent />;
}
}
61 changes: 35 additions & 26 deletions flux/src/common/RemainingTimeDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { HoverInfoLabel } from '@kinvolk/headlamp-plugin/lib/components/common';
import { KubeCondition, KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster';
import { localeDate, timeAgo } from '@kinvolk/headlamp-plugin/lib/Utils';
import { Box, Typography } from '@mui/material';
import React from 'react';
import { parseDuration } from '../helpers';
import { HoverInfoLabel } from '@kinvolk/headlamp-plugin/lib/components/common';

export interface RemainingTimeDisplayProps {
item: KubeObject;
}


function getNextReconciliationAttempt(item: KubeObject) {
const interval = item?.jsonData.spec?.interval;
const lastReadyCondition = item?.jsonData?.status?.conditions?.find(condition => condition.type === 'Ready');
const lastReadyCondition = item?.jsonData?.status?.conditions?.find(
condition => condition.type === 'Ready'
);

if (!interval) {
return -1;
Expand All @@ -27,13 +28,13 @@ function getNextReconciliationAttempt(item: KubeObject) {
let lastHandledReconcile = item?.jsonData?.status?.lastHandledReconcileAt;

if (!lastHandledReconcile) {
lastHandledReconcile = item?.jsonData?.metadata?.annotations?.['reconcile.fluxcd.io/requestedAt'];
lastHandledReconcile =
item?.jsonData?.metadata?.annotations?.['reconcile.fluxcd.io/requestedAt'];
}

if (lastHandledReconcile) {
lastRecordedCheck = new Date(lastHandledReconcile);
}

}

// If there is no way to retrieve a last reconcile time, return -1
Expand All @@ -45,8 +46,9 @@ function getNextReconciliationAttempt(item: KubeObject) {

// Calculate the last reconciliation attempt assuming there was an attempt at every interval,
// starting from the last ready state.
const nowAndLastReadyDiff = (new Date()).getTime() - lastRecordedCheck.getTime();
const lastReconciliationAttempt = lastRecordedCheck.getTime() + Math.floor(nowAndLastReadyDiff / intervalInMs) * intervalInMs;
const nowAndLastReadyDiff = new Date().getTime() - lastRecordedCheck.getTime();
const lastReconciliationAttempt =
lastRecordedCheck.getTime() + Math.floor(nowAndLastReadyDiff / intervalInMs) * intervalInMs;
const expectedTime = new Date(new Date(lastReconciliationAttempt).getTime() + intervalInMs);

return expectedTime.getTime();
Expand All @@ -57,34 +59,42 @@ export default function RemainingTimeDisplay(props: RemainingTimeDisplayProps) {
const timeoutRef = React.useRef<number>();
const [timeRemaining, setTimeRemaining] = React.useState<number>();

const {nextAttempt, isReconciling, lastReconcileUnknown} = React.useMemo(() => {
const { nextAttempt, isReconciling, lastReconcileUnknown } = React.useMemo(() => {
function isReconcilingCondition(condition: KubeCondition) {
return (condition.type === 'Ready' && condition.status === 'Unknown') ||
(condition.type === 'Reconciling' && condition.status === 'True');
return (
(condition.type === 'Ready' && condition.status === 'Unknown') ||
(condition.type === 'Reconciling' && condition.status === 'True')
);
}

const isReconciling = item?.jsonData?.status?.conditions?.find(condition => isReconcilingCondition(condition)) !== undefined;
const isReconciling =
item?.jsonData?.status?.conditions?.find(condition => isReconcilingCondition(condition)) !==
undefined;

const nextAttempt = getNextReconciliationAttempt(item);
return {
nextAttempt,
isReconciling,
lastReconcileUnknown: nextAttempt === -1
lastReconcileUnknown: nextAttempt === -1,
};
}, [item]);

React.useEffect(() => {
if (lastReconcileUnknown || nextAttempt === 0 || (new Date(nextAttempt)).getTime() < (new Date()).getTime()) {
if (
lastReconcileUnknown ||
nextAttempt === 0 ||
new Date(nextAttempt).getTime() < new Date().getTime()
) {
return;
}

const newTimeRemaining = (new Date(nextAttempt)).getTime() - (new Date()).getTime()
const newTimeRemaining = new Date(nextAttempt).getTime() - new Date().getTime();

if (timeRemaining === undefined) {
setTimeRemaining(newTimeRemaining);
}

let remainingTimeSecs = Math.floor(newTimeRemaining / 1000);
const remainingTimeSecs = Math.floor(newTimeRemaining / 1000);

// By default, we will update the time every second
let timeoutTrigger = 1;
Expand Down Expand Up @@ -123,18 +133,17 @@ export default function RemainingTimeDisplay(props: RemainingTimeDisplayProps) {

return (
<Box>
{lastReconcileUnknown ?
{lastReconcileUnknown ? (
<Typography>Unable to calculate</Typography>
:
(isReconciling || timeRemaining <= 0) ?
<Typography>In progress…</Typography>
:
<HoverInfoLabel
label={`In ${timeAgo(nextAttempt)}`}
hoverInfo={localeDate(new Date(nextAttempt))}
icon="mdi:calendar"
/>
}
) : isReconciling || timeRemaining <= 0 ? (
<Typography>In progress…</Typography>
) : (
<HoverInfoLabel
label={`In ${timeAgo(nextAttempt)}`}
hoverInfo={localeDate(new Date(nextAttempt))}
icon="mdi:calendar"
/>
)}
</Box>
);
}
35 changes: 21 additions & 14 deletions flux/src/common/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { K8s } from '@kinvolk/headlamp-plugin/lib';
import {
DateLabel,
Link,
ShowHideLabel,
StatusLabel,
Table as HTable,
TableProps as HTableProps,
TableColumn,
ShowHideLabel,
StatusLabel
TableProps as HTableProps,
} from '@kinvolk/headlamp-plugin/lib/components/common';
import { KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster';
import { KubeCRD } from '@kinvolk/headlamp-plugin/lib/lib/k8s/crd';
import React from 'react';
import { getSourceNameAndType } from '../helpers';

type CommonColumnType = 'namespace' | 'name' | 'lastUpdated' | 'age' | 'source' | 'message' | 'status' | 'revision';
type CommonColumnType =
| 'namespace'
| 'name'
| 'lastUpdated'
| 'age'
| 'source'
| 'message'
| 'status'
| 'revision';

interface TableCol {
header: string;
Expand Down Expand Up @@ -117,10 +124,7 @@ export function Table(props: TableProps) {
},
Cell: ({ row }) =>
row.original && (
<DateLabel
date={row.original.metadata.creationTimestamp}
format="mini"
/>
<DateLabel date={row.original.metadata.creationTimestamp} format="mini" />
),
};
case 'source':
Expand Down Expand Up @@ -151,7 +155,9 @@ export function Table(props: TableProps) {
accessorFn: item => {
const reference = item.jsonData.status?.lastAttemptedRevision;
return (
<ShowHideLabel labelId={`${item?.metadata?.uid}-revision`}>{reference ?? ''}</ShowHideLabel>
<ShowHideLabel labelId={`${item?.metadata?.uid}-revision`}>
{reference ?? ''}
</ShowHideLabel>
);
},
};
Expand All @@ -163,7 +169,9 @@ export function Table(props: TableProps) {
c => c.type === 'Ready'
)?.message;
return (
<ShowHideLabel labelId={`${item?.metadata?.uid}-message`}>{message ?? ''}</ShowHideLabel>
<ShowHideLabel labelId={`${item?.metadata?.uid}-message`}>
{message ?? ''}
</ShowHideLabel>
);
},
};
Expand All @@ -178,10 +186,9 @@ export function Table(props: TableProps) {

return column;
});
},
[columns]);
}, [columns]);

return <HTable data={data} loading={data === null} {...otherProps} columns={processedColumns} />;
}

export default Table;
export default Table;
41 changes: 30 additions & 11 deletions flux/src/helm-releases/HelmReleaseList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
import { K8s } from '@kinvolk/headlamp-plugin/lib';
import { SectionBox } from '@kinvolk/headlamp-plugin/lib/components/common';
import { Loader, SectionBox } from '@kinvolk/headlamp-plugin/lib/components/common';
import { Link as MuiLink } from '@mui/material';
import React from 'react';
import CheckIfFluxInstalled from '../checkflux';
import CheckIfFluxInstalled, { useFluxControllerAvailableCheck } from '../checkflux';
import Table from '../common/Table';

export function HelmReleases() {
const isHelmReleasesControllerAvailable = useFluxControllerAvailableCheck({
name: 'helm-controller',
});

if (isHelmReleasesControllerAvailable === null) {
return <Loader />;
}

if (!isHelmReleasesControllerAvailable) {
return (
<SectionBox>
<h1>Helm Controller is not installed</h1>
<p>
Follow the{' '}
<MuiLink target="_blank" href="https://fluxcd.io/docs/components/helm/">
installation guide
</MuiLink>{' '}
to install Helm Controller on your cluster
</p>
</SectionBox>
);
}
return <HelmReleasesListWrapper />;
}

function HelmReleasesListWrapper() {
const [helmReleases] = K8s.ResourceClasses.CustomResourceDefinition.useGet(
'helmreleases.helm.toolkit.fluxcd.io'
);
Expand All @@ -29,15 +56,7 @@ function HelmReleasesList({ resourceClass }) {
<Table
data={resource}
defaultSortingColumn={2}
columns={[
'name',
'namespace',
'status',
'source',
'revision',
'message',
'lastUpdated',
]}
columns={['name', 'namespace', 'status', 'source', 'revision', 'message', 'lastUpdated']}
/>
</SectionBox>
);
Expand Down
6 changes: 3 additions & 3 deletions flux/src/helm-releases/HelmReleaseSingle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import {
SyncWithoutSourceAction,
SyncWithSourceAction,
} from '../actions/index';
import { getSourceNameAndType, ObjectEvents } from '../helpers/index';
import { HELMRELEASE_CRD, GetResourcesFromInventory } from '../inventory';
import RemainingTimeDisplay from '../common/RemainingTimeDisplay';
import { getSourceNameAndType, ObjectEvents } from '../helpers/index';
import { GetResourcesFromInventory,HELMRELEASE_CRD } from '../inventory';

function GetSourceCR(props: {
name: string;
Expand Down Expand Up @@ -55,7 +55,7 @@ function GetSource(props: { item: KubeObject | null; setSource: (...args) => voi
}

export default function FluxHelmReleaseDetailView() {
const { namespace, name } = useParams<{ namespace: string, name: string }>();
const { namespace, name } = useParams<{ namespace: string; name: string }>();

const [events] = Event?.default.useList({
namespace,
Expand Down
31 changes: 15 additions & 16 deletions flux/src/helpers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export function getSourceNameAndType(item: KubeObject) {
type = kindToSourceType[item.jsonData.spec.sourceRef.kind] ?? '';
name = item.jsonData.spec?.sourceRef?.name;
} else if (itemKind === 'HelmRelease') {
const refToCheck = item?.jsonData?.spec?.chartRef ?? item?.jsonData?.spec?.chart?.spec?.sourceRef;
const refToCheck =
item?.jsonData?.spec?.chartRef ?? item?.jsonData?.spec?.chart?.spec?.sourceRef;
if (refToCheck) {
type = kindToSourceType[refToCheck.kind] ?? '';
name = refToCheck.name;
Expand Down Expand Up @@ -133,23 +134,21 @@ export function ObjectEvents(props: { events: any }) {
export function prepareNameLink(item) {
const kind = item.kind;
console.log('item', item);
if (
kind === 'Kustomization' ||
kind === 'HelmRelease' ||
kind in kindToSourceType
) {
if (kind === 'Kustomization' || kind === 'HelmRelease' || kind in kindToSourceType) {
const { name, type } = getSourceNameAndType(item);
if (!!name && !!type) {
return <Link
routeName={`/flux/sources/:type/:namespace/:name`}
params={{
name: item.metadata.name,
namespace: item.metadata.namespace,
type,
}}
>
{name}
</Link>;
return (
<Link
routeName={`/flux/sources/:type/:namespace/:name`}
params={{
name: item.metadata.name,
namespace: item.metadata.namespace,
type,
}}
>
{name}
</Link>
);
}
}

Expand Down
Loading

0 comments on commit 2bfb7ac

Please sign in to comment.