mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 05:19:39 +02:00
refactor(app): details widget migration [EE-5352] (#8886)
This commit is contained in:
parent
fdd79cece8
commit
af77e33993
57 changed files with 2046 additions and 1079 deletions
|
@ -1,18 +1,32 @@
|
|||
import { Deployment, DaemonSet, StatefulSet } from 'kubernetes-types/apps/v1';
|
||||
import {
|
||||
Deployment,
|
||||
DaemonSet,
|
||||
StatefulSet,
|
||||
ReplicaSet,
|
||||
ReplicaSetList,
|
||||
ControllerRevisionList,
|
||||
ControllerRevision,
|
||||
} from 'kubernetes-types/apps/v1';
|
||||
import { Pod } from 'kubernetes-types/core/v1';
|
||||
import filesizeParser from 'filesize-parser';
|
||||
|
||||
import { Application } from './types';
|
||||
import { appOwnerLabel } from './constants';
|
||||
import { Application, ApplicationPatch, Revision } from './types';
|
||||
import {
|
||||
appOwnerLabel,
|
||||
defaultDeploymentUniqueLabel,
|
||||
unchangedAnnotationKeysForRollbackPatch,
|
||||
appRevisionAnnotation,
|
||||
} from './constants';
|
||||
|
||||
// naked pods are pods which are not owned by a deployment, daemonset, statefulset or replicaset
|
||||
// https://kubernetes.io/docs/concepts/configuration/overview/#naked-pods-vs-replicasets-deployments-and-jobs
|
||||
// getNakedPods returns an array of naked pods from an array of pods, deployments, daemonsets and statefulsets
|
||||
export function getNakedPods(
|
||||
pods: Pod[],
|
||||
deployments: Deployment[],
|
||||
daemonSets: DaemonSet[],
|
||||
statefulSets: StatefulSet[]
|
||||
) {
|
||||
// naked pods are pods which are not owned by a deployment, daemonset, statefulset or replicaset
|
||||
// https://kubernetes.io/docs/concepts/configuration/overview/#naked-pods-vs-replicasets-deployments-and-jobs
|
||||
const appLabels = [
|
||||
...deployments.map((deployment) => deployment.spec?.selector.matchLabels),
|
||||
...daemonSets.map((daemonSet) => daemonSet.spec?.selector.matchLabels),
|
||||
|
@ -37,7 +51,7 @@ export function getNakedPods(
|
|||
return nakedPods;
|
||||
}
|
||||
|
||||
// type guard to check if an application is a deployment, daemonset statefulset or pod
|
||||
// type guard to check if an application is a deployment, daemonset, statefulset or pod
|
||||
export function applicationIsKind<T extends Application>(
|
||||
appKind: 'Deployment' | 'DaemonSet' | 'StatefulSet' | 'Pod',
|
||||
application?: Application
|
||||
|
@ -165,3 +179,151 @@ export function getResourceLimits(application: Application) {
|
|||
|
||||
return limits;
|
||||
}
|
||||
|
||||
// matchLabelsToLabelSelectorValue converts a map of labels to a label selector value that can be used in the
|
||||
// labelSelector param for the kube api to filter kube resources by labels
|
||||
export function matchLabelsToLabelSelectorValue(obj?: Record<string, string>) {
|
||||
if (!obj) return '';
|
||||
return Object.entries(obj)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
// filterRevisionsByOwnerUid filters a list of revisions to only include revisions that have the given uid in their
|
||||
// ownerReferences
|
||||
export function filterRevisionsByOwnerUid<T extends Revision>(
|
||||
revisions: T[],
|
||||
uid: string
|
||||
) {
|
||||
return revisions.filter((revision) => {
|
||||
const ownerReferencesUids =
|
||||
revision.metadata?.ownerReferences?.map((or) => or.uid) || [];
|
||||
return ownerReferencesUids.includes(uid);
|
||||
});
|
||||
}
|
||||
|
||||
// getRollbackPatchPayload returns the patch payload to rollback a deployment to the previous revision
|
||||
// the patch should be able to update the deployment's template to the previous revision's template
|
||||
export function getRollbackPatchPayload(
|
||||
application: Deployment | StatefulSet | DaemonSet,
|
||||
revisionList: ReplicaSetList | ControllerRevisionList
|
||||
): ApplicationPatch {
|
||||
switch (revisionList.kind) {
|
||||
case 'ControllerRevisionList': {
|
||||
const previousRevision = getPreviousControllerRevision(
|
||||
revisionList.items
|
||||
);
|
||||
if (!previousRevision.data) {
|
||||
throw new Error('No data found in the previous revision.');
|
||||
}
|
||||
return previousRevision.data;
|
||||
}
|
||||
case 'ReplicaSetList': {
|
||||
const previousRevision = getPreviousReplicaSetRevision(
|
||||
revisionList.items
|
||||
);
|
||||
|
||||
// remove hash label before patching back into the deployment
|
||||
const revisionTemplate = previousRevision.spec?.template;
|
||||
if (revisionTemplate?.metadata?.labels) {
|
||||
delete revisionTemplate.metadata.labels[defaultDeploymentUniqueLabel];
|
||||
}
|
||||
|
||||
// build the patch payload for the deployment from the replica set
|
||||
// keep the annotations to skip from the deployment, in the patch
|
||||
const applicationAnnotations = application.metadata?.annotations || {};
|
||||
const applicationAnnotationsInPatch =
|
||||
unchangedAnnotationKeysForRollbackPatch.reduce((acc, annotationKey) => {
|
||||
if (applicationAnnotations[annotationKey]) {
|
||||
acc[annotationKey] = applicationAnnotations[annotationKey];
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
// add any annotations from the target revision that shouldn't be skipped
|
||||
const revisionAnnotations = previousRevision.metadata?.annotations || {};
|
||||
const revisionAnnotationsInPatch = Object.entries(
|
||||
revisionAnnotations
|
||||
).reduce((acc, [annotationKey, annotationValue]) => {
|
||||
if (!unchangedAnnotationKeysForRollbackPatch.includes(annotationKey)) {
|
||||
acc[annotationKey] = annotationValue;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
const patchAnnotations = {
|
||||
...applicationAnnotationsInPatch,
|
||||
...revisionAnnotationsInPatch,
|
||||
};
|
||||
|
||||
// Create a patch of the Deployment that replaces spec.template
|
||||
const deploymentRollbackPatch = [
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/spec/template',
|
||||
value: revisionTemplate,
|
||||
},
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/metadata/annotations',
|
||||
value: patchAnnotations,
|
||||
},
|
||||
].filter((p) => !!p.value); // remove any patch that has no value
|
||||
|
||||
return deploymentRollbackPatch;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown revision list kind ${revisionList.kind}.`);
|
||||
}
|
||||
}
|
||||
|
||||
function getPreviousReplicaSetRevision(replicaSets: ReplicaSet[]) {
|
||||
// sort replicaset(s) using the revision annotation number (old to new).
|
||||
// Kubectl uses the same revision annotation key to determine the previous version
|
||||
// (see the Revision function, and where it's used https://github.com/kubernetes/kubectl/blob/27ec3dafa658d8873b3d9287421d636048b51921/pkg/util/deployment/deployment.go#LL70C11-L70C11)
|
||||
const sortedReplicaSets = replicaSets.sort((a, b) => {
|
||||
const aRevision =
|
||||
Number(a.metadata?.annotations?.[appRevisionAnnotation]) || 0;
|
||||
const bRevision =
|
||||
Number(b.metadata?.annotations?.[appRevisionAnnotation]) || 0;
|
||||
return aRevision - bRevision;
|
||||
});
|
||||
|
||||
// if there are less than 2 revisions, there is no previous revision to rollback to
|
||||
if (sortedReplicaSets.length < 2) {
|
||||
throw new Error(
|
||||
'There are no previous revisions to rollback to. Please check the application revisions.'
|
||||
);
|
||||
}
|
||||
|
||||
// get the second to last revision
|
||||
const previousRevision = sortedReplicaSets[sortedReplicaSets.length - 2];
|
||||
return previousRevision;
|
||||
}
|
||||
|
||||
function getPreviousControllerRevision(
|
||||
controllerRevisions: ControllerRevision[]
|
||||
) {
|
||||
// sort the list of ControllerRevisions by revision, using the creationTimestamp as a tie breaker (old to new)
|
||||
const sortedControllerRevisions = controllerRevisions.sort((a, b) => {
|
||||
if (a.revision === b.revision) {
|
||||
return (
|
||||
new Date(a.metadata?.creationTimestamp || '').getTime() -
|
||||
new Date(b.metadata?.creationTimestamp || '').getTime()
|
||||
);
|
||||
}
|
||||
return a.revision - b.revision;
|
||||
});
|
||||
|
||||
// if there are less than 2 revisions, there is no previous revision to rollback to
|
||||
if (sortedControllerRevisions.length < 2) {
|
||||
throw new Error(
|
||||
'There are no previous revisions to rollback to. Please check the application revisions.'
|
||||
);
|
||||
}
|
||||
|
||||
// get the second to last revision
|
||||
const previousRevision =
|
||||
sortedControllerRevisions[sortedControllerRevisions.length - 2];
|
||||
return previousRevision;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue