mirror of https://github.com/rancher/dashboard.git
342 lines
9.7 KiB
JavaScript
342 lines
9.7 KiB
JavaScript
|
|
import { findBy } from '@shell/utils/array';
|
|
import { TARGET_WORKLOADS, UI_MANAGED, HCI as HCI_LABELS_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
import { WORKLOAD_TYPES, SERVICE } from '@shell/config/types';
|
|
import { clone, get } from '@shell/utils/object';
|
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
import { shortenedImage } from '@shell/utils/string';
|
|
|
|
export default class WorkloadService extends SteveModel {
|
|
async getPortsWithServiceType() {
|
|
const ports = [];
|
|
|
|
this.containers.forEach((container) => ports.push(...(container.ports || [])));
|
|
(this.initContainers || []).forEach((container) => ports.push(...(container.ports || [])));
|
|
|
|
// Only get services owned if we can access the service resource
|
|
const canAccessServices = this.$getters['schemaFor'](SERVICE);
|
|
const services = canAccessServices ? await this.getServicesOwned() : [];
|
|
const clusterIPServicePorts = [];
|
|
const loadBalancerServicePorts = [];
|
|
const nodePortServicePorts = [];
|
|
|
|
if (services.length) {
|
|
services.forEach((svc) => {
|
|
switch (svc.spec.type) {
|
|
case 'ClusterIP':
|
|
clusterIPServicePorts.push(...(svc?.spec?.ports || []));
|
|
break;
|
|
case 'LoadBalancer':
|
|
loadBalancerServicePorts.push(...(svc?.spec?.ports || []));
|
|
break;
|
|
case 'NodePort':
|
|
nodePortServicePorts.push(...(svc?.spec?.ports || []));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
ports.forEach((port) => {
|
|
const name = port.name ? port.name : `${ port.containerPort }${ port.protocol.toLowerCase() }${ port.hostPort || port._listeningPort || '' }`;
|
|
|
|
port.name = name;
|
|
|
|
if (port._serviceType && port._serviceType !== '') {
|
|
return;
|
|
}
|
|
|
|
if (loadBalancerServicePorts.length) {
|
|
const portSpec = findBy(loadBalancerServicePorts, 'name', name);
|
|
|
|
if (portSpec) {
|
|
port._listeningPort = portSpec.port;
|
|
|
|
port._serviceType = 'LoadBalancer';
|
|
|
|
return;
|
|
}
|
|
} if (nodePortServicePorts.length) {
|
|
const portSpec = findBy(nodePortServicePorts, 'name', name);
|
|
|
|
if (portSpec) {
|
|
port._listeningPort = portSpec.nodePort;
|
|
|
|
port._serviceType = 'NodePort';
|
|
|
|
return;
|
|
}
|
|
} if (clusterIPServicePorts.length) {
|
|
if (findBy(clusterIPServicePorts, 'name', name)) {
|
|
port._serviceType = 'ClusterIP';
|
|
}
|
|
}
|
|
});
|
|
|
|
return ports;
|
|
}
|
|
|
|
async getServicesOwned(force = false) {
|
|
const normanTypes = {
|
|
[WORKLOAD_TYPES.REPLICA_SET]: 'replicaSet',
|
|
[WORKLOAD_TYPES.DEPLOYMENT]: 'deployment',
|
|
[WORKLOAD_TYPES.STATEFUL_SET]: 'statefulSet',
|
|
[WORKLOAD_TYPES.DAEMON_SET]: 'daemonSet',
|
|
};
|
|
const selectorKey = Object.keys(this.workloadSelector)[0];
|
|
|
|
const normanSelectorValue =
|
|
`${ normanTypes[this._type ? this._type : this.type] }-${
|
|
this.metadata.namespace
|
|
}-${ this.metadata.name }`;
|
|
|
|
const steveSelectorValue = this.workloadSelector[selectorKey];
|
|
const allSvc = await this.$dispatch('cluster/findAll', { type: SERVICE, opt: { force } }, { root: true });
|
|
|
|
return (allSvc || []).filter((svc) => (svc.spec?.selector || {})[selectorKey] === steveSelectorValue || (svc.spec?.selector || {})[selectorKey] === normanSelectorValue );
|
|
}
|
|
|
|
get imageNames() {
|
|
let containers;
|
|
const images = [];
|
|
|
|
if (this.type === WORKLOAD_TYPES.CRON_JOB) {
|
|
containers = get(this, 'spec.jobTemplate.spec.template.spec.containers');
|
|
} else {
|
|
containers = get(this, 'spec.template.spec.containers');
|
|
}
|
|
if (containers) {
|
|
containers.forEach((container) => {
|
|
if (!images.includes(container.image)) {
|
|
images.push(container.image);
|
|
}
|
|
});
|
|
}
|
|
|
|
return images.map(shortenedImage);
|
|
}
|
|
|
|
get containers() {
|
|
if (this.type === WORKLOAD_TYPES.CRON_JOB) {
|
|
// cronjob pod template is nested slightly different than other types
|
|
const { spec: { jobTemplate: { spec: { template: { spec: { containers } } } } } } = this;
|
|
|
|
return containers;
|
|
}
|
|
|
|
if ( this.spec.containers ) {
|
|
return this.spec.containers;
|
|
}
|
|
|
|
const { spec:{ template:{ spec:{ containers } } } } = this;
|
|
|
|
return containers;
|
|
}
|
|
|
|
get initContainers() {
|
|
if (this.type === WORKLOAD_TYPES.CRON_JOB) {
|
|
// cronjob pod template is nested slightly different than other types
|
|
const { spec: { jobTemplate: { spec: { template: { spec: { initContainers } } } } } } = this;
|
|
|
|
return initContainers;
|
|
}
|
|
|
|
if (this.spec.initContainers) {
|
|
return this.spec.initContainers;
|
|
}
|
|
|
|
const { spec:{ template:{ spec:{ initContainers } } } } = this;
|
|
|
|
return initContainers;
|
|
}
|
|
|
|
get workloadSelector() {
|
|
return {
|
|
'workload.user.cattle.io/workloadselector': `${ this._type ? this._type : this.type }-${
|
|
this.metadata.namespace
|
|
}-${ this.metadata.name }`
|
|
};
|
|
}
|
|
|
|
// create clusterip, nodeport, loadbalancer services from container port spec
|
|
async servicesFromContainerPorts(mode, ports) {
|
|
const ownerRef = {
|
|
apiVersion: this.apiVersion,
|
|
controller: true,
|
|
kind: this.kind,
|
|
name: this.metadata.name,
|
|
uid: this.metadata.uid
|
|
};
|
|
|
|
const annotations = { [TARGET_WORKLOADS]: JSON.stringify([`${ this.metadata.namespace }/${ this.metadata.name }`]), [UI_MANAGED]: 'true' };
|
|
|
|
let clusterIP = {
|
|
type: SERVICE,
|
|
spec: {
|
|
ports: [],
|
|
selector: this.workloadSelector,
|
|
type: 'ClusterIP'
|
|
},
|
|
metadata: {
|
|
name: this.metadata.name,
|
|
namespace: this.metadata.namespace,
|
|
annotations,
|
|
ownerReferences: [ownerRef]
|
|
},
|
|
};
|
|
|
|
let nodePort = {
|
|
type: SERVICE,
|
|
spec: {
|
|
ports: [],
|
|
selector: this.workloadSelector,
|
|
type: 'NodePort'
|
|
},
|
|
metadata: {
|
|
name: `${ this.metadata.name }-nodeport`,
|
|
namespace: this.metadata.namespace,
|
|
annotations,
|
|
ownerReferences: [ownerRef]
|
|
},
|
|
};
|
|
|
|
let loadBalancer = {
|
|
type: SERVICE,
|
|
spec: {
|
|
ports: [],
|
|
selector: this.workloadSelector,
|
|
type: 'LoadBalancer',
|
|
externalTrafficPolicy: 'Cluster'
|
|
},
|
|
metadata: {
|
|
name: `${ this.metadata.name }-loadbalancer`,
|
|
namespace: this.metadata.namespace,
|
|
annotations,
|
|
ownerReferences: [ownerRef]
|
|
},
|
|
};
|
|
|
|
const existing = await this.getServicesOwned(this.isFromNorman);
|
|
|
|
if (existing && existing.length) {
|
|
existing.forEach((service) => {
|
|
switch (service.spec.type) {
|
|
case 'ClusterIP':
|
|
clusterIP = service;
|
|
clusterIP.spec.ports = [];
|
|
break;
|
|
case 'NodePort':
|
|
nodePort = service;
|
|
nodePort.spec.ports = [];
|
|
break;
|
|
case 'LoadBalancer':
|
|
loadBalancer = service;
|
|
loadBalancer.spec.ports = [];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
ports.forEach((port) => {
|
|
const portSpec = {
|
|
name: port.name, protocol: port.protocol, port: port.containerPort, targetPort: port.containerPort
|
|
};
|
|
|
|
if (port._serviceType !== '') {
|
|
clusterIP.spec.ports.push(portSpec);
|
|
switch (port._serviceType) {
|
|
case 'NodePort': {
|
|
const npPort = clone(portSpec);
|
|
|
|
if (port._listeningPort) {
|
|
npPort.nodePort = port._listeningPort;
|
|
}
|
|
nodePort.spec.ports.push(npPort);
|
|
break; }
|
|
case 'LoadBalancer': {
|
|
const lbPort = clone(portSpec);
|
|
|
|
if (port._listeningPort) {
|
|
lbPort.port = port._listeningPort;
|
|
}
|
|
loadBalancer.spec.ports.push(lbPort);
|
|
break; }
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
const toSave = [];
|
|
const toRemove = [];
|
|
let clusterIPProxy;
|
|
|
|
if (clusterIP.spec.ports.length > 0) {
|
|
if (clusterIP.id) {
|
|
clusterIPProxy = clusterIP;
|
|
} else {
|
|
clusterIPProxy = await this.$dispatch(`cluster/create`, clusterIP, { root: true });
|
|
}
|
|
toSave.push(clusterIPProxy);
|
|
} else if (clusterIP.id) {
|
|
toRemove.push(clusterIP);
|
|
}
|
|
|
|
if (nodePort.spec.ports.length > 0) {
|
|
let nodePortProxy;
|
|
|
|
// if id is defined it's a preexisting service
|
|
if (nodePort.id) {
|
|
nodePortProxy = nodePort;
|
|
} else {
|
|
nodePortProxy = await this.$dispatch(`cluster/create`, nodePort, { root: true });
|
|
}
|
|
toSave.push(nodePortProxy);
|
|
// if id defined but no ports, the service already exists but should be removed (user has removed all container ports mapping to it)
|
|
} else if (nodePort.id) {
|
|
toRemove.push(nodePort);
|
|
}
|
|
|
|
if (loadBalancer.spec.ports.length > 0) {
|
|
let loadBalancerProxy;
|
|
|
|
if (loadBalancer.id) {
|
|
loadBalancerProxy = loadBalancer;
|
|
} else {
|
|
loadBalancerProxy = await this.$dispatch(`cluster/create`, loadBalancer, { root: true });
|
|
}
|
|
|
|
const portsWithIpam = ports.filter((p) => p._ipam) || [];
|
|
|
|
if (portsWithIpam.length > 0) {
|
|
loadBalancerProxy.metadata.annotations[HCI_LABELS_ANNOTATIONS.CLOUD_PROVIDER_IPAM] = portsWithIpam[0]._ipam;
|
|
}
|
|
|
|
toSave.push(loadBalancerProxy);
|
|
} else if (loadBalancer.id) {
|
|
toRemove.push(loadBalancer);
|
|
}
|
|
|
|
return { toSave, toRemove };
|
|
}
|
|
|
|
cleanForSave(data) {
|
|
const val = super.cleanForSave(data);
|
|
|
|
delete val.__active;
|
|
delete val.type;
|
|
|
|
return val;
|
|
}
|
|
|
|
cleanContainerForSave(container) {
|
|
delete container.__active;
|
|
delete container.active;
|
|
delete container._init;
|
|
delete container.error;
|
|
|
|
return container;
|
|
}
|
|
}
|