dashboard/shell/models/namespace.js

303 lines
8.2 KiB
JavaScript

import SYSTEM_NAMESPACES from '@shell/config/system-namespaces';
import {
PROJECT, SYSTEM_NAMESPACE, ISTIO as ISTIO_LABELS, FLEET, RESOURCE_QUOTA
} from '@shell/config/labels-annotations';
import { ISTIO, MANAGEMENT } from '@shell/config/types';
import { get, set } from '@shell/utils/object';
import { insertAt, isArray } from '@shell/utils/array';
import SteveModel from '@shell/plugins/steve/steve-class';
import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
import { hasPSALabels, getPSATooltipsDescription, getPSALabels } from '@shell/utils/pod-security-admission';
import { PSAIconsDisplay, PSALabelsNamespaceVersion } from '@shell/config/pod-security-admission';
/**
* obscure namespaces are reserved and have special meaning if they're also classed as `system`
*
* (by default hidden from user given by default `show dynamic namespaces` preference is disabled and `user namespaces` filter is on)
*/
const OBSCURE_NAMESPACE_PREFIX = [
'c-', // cluster namespace
// Project namespace. When a user creates a project, Rancher creates
// namespaces in the local cluster with the 'p-' prefix which are
// used to manage RBAC for the project. If these namespaces are deleted,
// role bindings can be lost and Rancher may need to be restored from
// backup. Therefore we hide these namespaces unless the developer setting
// is turned on from the user preferences.
'p-',
'user-', // user namespace
'local', // local namespace
];
export default class Namespace extends SteveModel {
applyDefaults() {
set(this, 'disableOpenApiValidation', false);
}
get _availableActions() {
const out = super._availableActions;
insertAt(out, 0, { divider: true });
if (this.istioInstalled) {
insertAt(out, 0, {
action: 'enableAutoInjection',
label: this.t('namespace.enableAutoInjection'),
bulkable: true,
bulkAction: 'enableAutoInjection',
enabled: !this.injectionEnabled,
icon: 'icon icon-plus',
weight: 2
});
insertAt(out, 0, {
action: 'disableAutoInjection',
label: this.t('namespace.disableAutoInjection'),
bulkable: true,
bulkAction: 'disableAutoInjection',
enabled: this.injectionEnabled,
icon: 'icon icon-minus',
weight: 1,
});
}
if (this.$rootGetters['isRancher'] && !this.$rootGetters['isSingleProduct']) {
insertAt(out, 0, {
action: 'move',
label: this.t('namespace.move'),
bulkable: true,
bulkAction: 'move',
enabled: true,
icon: 'icon icon-fork',
weight: 3,
});
}
return out;
}
move(resources = this) {
this.$dispatch('promptModal', {
component: 'MoveNamespaceDialog',
resources: !Array.isArray(resources) ? [resources] : resources,
modalWidth: '440',
height: 'auto',
styles: 'max-height: 100vh;'
});
}
get isSystem() {
if ( this.metadata?.annotations?.[SYSTEM_NAMESPACE] === 'true' ) {
return true;
}
if ( SYSTEM_NAMESPACES.includes(this.metadata.name) ) {
return true;
}
if ( this.metadata.name.startsWith('cattle-') && this.metadata.name.endsWith('-system') ) {
return true;
}
if ( this.project ) {
return this.project.isSystem;
}
return false;
}
get isFleetManaged() {
return get(this, `metadata.labels."${ FLEET.MANAGED }"`) === 'true';
}
// These are namespaces that are created by rancher to serve purposes in the background but the user shouldn't have
// to worry themselves about them.
get isObscure() {
return OBSCURE_NAMESPACE_PREFIX.some((prefix) => this.metadata.name.startsWith(prefix)) && this.isSystem;
}
get projectId() {
const projectAnnotation = this.metadata?.annotations?.[PROJECT] || '';
return projectAnnotation.split(':')[1] || null;
}
get project() {
if ( !this.projectId || !this.$rootGetters['isRancher'] ) {
return null;
}
const clusterId = this.$rootGetters['currentCluster']?.id;
const project = this.$rootGetters['management/byId'](MANAGEMENT.PROJECT, `${ clusterId }/${ this.projectId }`);
return project;
}
get groupById() {
const projectId = this.project?.id;
if ( projectId ) {
return projectId;
} else {
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAProject');
}
}
get projectNameSort() {
return this.project?.nameSort || '';
}
get istioInstalled() {
const schema = this.$rootGetters['cluster/schemaFor'](ISTIO.GATEWAY);
return !!schema;
}
get injectionEnabled() {
return this.labels[ISTIO_LABELS.AUTO_INJECTION] === 'enabled';
}
enableAutoInjection(namespaces = this, enable = true) {
if (!isArray(namespaces)) {
namespaces = [namespaces];
}
namespaces.forEach((ns) => {
if (!enable && ns?.metadata?.labels) {
delete ns.metadata.labels[ISTIO_LABELS.AUTO_INJECTION];
} else {
if (!ns.metadata.labels) {
ns.metadata.labels = {};
}
ns.metadata.labels[ISTIO_LABELS.AUTO_INJECTION] = 'enabled';
}
ns.save();
});
}
disableAutoInjection(namespaces = this) {
this.enableAutoInjection(namespaces, false);
}
get confirmRemove() {
return true;
}
get listLocation() {
const listLocation = { name: this.$rootGetters['isRancher'] ? 'c-cluster-product-projectsnamespaces' : 'c-cluster-product-resource' };
// Harvester uses these resource directly... but has different routes. listLocation covers routes leading back to route
if (this.$rootGetters['currentProduct'].inStore === HARVESTER) {
listLocation.name = `${ HARVESTER }-${ listLocation.name }`.replace('-product', '');
listLocation.params = { resource: 'namespace' };
}
return listLocation;
}
get _detailLocation() {
const _detailLocation = super._detailLocation;
return _detailLocation;
}
get parentLocationOverride() {
return this.listLocation;
}
get doneOverride() {
return this.listLocation;
}
get resourceQuota() {
return JSON.parse(this.metadata.annotations[RESOURCE_QUOTA] || `{"limit":{}}`);
}
set resourceQuota(value) {
this.metadata.annotations[RESOURCE_QUOTA] = JSON.stringify(value);
}
get detailTopTooltips() {
return this.psaTooltipsDescription;
}
get detailTopIcons() {
return PSAIconsDisplay;
}
/**
* Check if resource contains PSA labels
*/
get hasSystemLabels() {
return hasPSALabels(this);
}
get filteredSystemLabels() {
return Object.entries(this.labels).reduce((res, [key, value]) => {
if (!PSALabelsNamespaceVersion.includes(key)) {
res[key] = value;
}
return res;
}, {});
}
/**
* Generate list of present keys which can be filtered based on existing label keys and system keys
*/
get systemLabels() {
return getPSALabels(this);
}
get psaTooltipsDescription() {
return getPSATooltipsDescription(this);
}
// Preserve the project label - ensures we preserve project when cloning a namespace
cleanForNew() {
const project = this.metadata?.labels?.[PROJECT];
super.cleanForNew();
if (project) {
this.metadata = this.metadata || {};
this.metadata.labels = this.metadata.labels || {};
this.metadata.labels[PROJECT] = project;
}
}
get hideDetailLocation() {
return !!this.$rootGetters['currentProduct'].hideNamespaceLocation;
}
get glance() {
const glance = [...this._glance];
const namespaceIndex = glance.findIndex((item) => item.name === 'namespace');
if (namespaceIndex > -1) {
glance.splice(namespaceIndex, 1, this.projectGlance);
}
// projectGlance could be undefined
return glance.filter(Boolean);
}
get projectGlance() {
// Not all namespaces are in a project
if (!this.project) {
return undefined;
}
return {
name: 'project',
label: this.t('component.resource.detail.glance.project'),
formatter: 'Link',
formatterOpts: {
to: this.project.detailLocation, row: {}, options: { internal: true }
},
content: this.project.nameDisplay
};
}
}