dashboard/shell/models/cloudcredential.js

342 lines
8.4 KiB
JavaScript

import { CAPI, CLOUD_CREDENTIALS } from '@shell/config/labels-annotations';
import { fullFields, prefixFields, simplify, suffixFields } from '@shell/store/plugins';
import { isEmpty, set } from '@shell/utils/object';
import { MANAGEMENT, SECRET } from '@shell/config/types';
import { escapeHtml } from '@shell/utils/string';
import NormanModel from '@shell/plugins/steve/norman-class';
import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
import day from 'dayjs';
const harvesterProvider = 'harvester';
const renew = {
[harvesterProvider]: {
renew: ({ cloudCredential, $ctx }) => {
return renew[harvesterProvider].renewBulk(
{ cloudCredentials: [cloudCredential], $ctx }
);
},
renewBulk: async({ cloudCredentials, $ctx }) => {
// A harvester cloud credential (at the moment) is a kubeconfig complete with expiring token
// So to renew we just need to generate a new kubeconfig and save it to the cc (similar to shell/cloud-credential/harvester.vue)
await Promise.all(cloudCredentials.map(async(cc) => {
try {
if (!cc.harvestercredentialConfig?.clusterId) {
throw new Error(`credential has no matching harvester cluster`);
}
const mgmtCluster = $ctx.rootGetters['management/byId'](MANAGEMENT.CLUSTER, cc.harvestercredentialConfig.clusterId);
if (!mgmtCluster) {
throw new Error(`cannot find harvester cluster`);
}
const kubeconfigContent = await mgmtCluster.generateKubeConfig();
cc.setData('kubeconfigContent', kubeconfigContent);
await cc.save();
} catch (error) {
console.error(`Unable to refresh harvester cloud credential '${ cc.id }'`, error); // eslint-disable-line no-console
}
}));
}
}
};
export default class CloudCredential extends NormanModel {
get _availableActions() {
const out = super._availableActions;
out.splice(0, 0, { divider: true });
out.splice(0, 0, {
action: 'renew',
enabled: this.canRenew,
bulkable: this.canBulkRenew,
bulkAction: 'renewBulk',
icon: 'icon icon-fw icon-refresh',
label: this.t('manager.cloudCredentials.renew'),
});
return out;
}
get hasSensitiveData() {
return true;
}
get canCustomEdit() {
return true;
}
get _detailLocation() {
return {
name: `c-cluster-manager-cloudCredential-id`,
params: {
product: this.$rootGetters['productId'],
cluster: this.$rootGetters['clusterId'],
id: this.id,
}
};
}
get parentLocationOverride() {
return {
name: `c-cluster-manager-cloudCredential`,
params: { cluster: this.$rootGetters['clusterId'] }
};
}
get secretName() {
return this.id.replace(':', '/');
}
get secret() {
return this.$rootGetters['management/byId'](SECRET, this.secretName);
}
async getSecret() {
await this.$dispatch('management/find', { type: SECRET, id: this.secretName }, { root: true });
}
get configKey() {
return Object.keys(this).find( (k) => k.endsWith('credentialConfig'));
}
get provider() {
const annotation = this.annotations?.[CAPI.CREDENTIAL_DRIVER];
if ( annotation ) {
return annotation;
}
const configKey = this.configKey;
// Call [amazoneks,amazonec2] -> aws
if ( configKey ) {
const out = this.$rootGetters['plugins/credentialDriverFor'](configKey.replace(/credentialConfig$/, ''));
return out;
}
return null;
}
setProvider(neu) {
this.setAnnotation(CAPI.CREDENTIAL_DRIVER, neu);
Object.keys(this).forEach((k) => {
k = k.toLowerCase();
if ( k.endsWith('config') && k !== `${ neu }config` ) {
set(this, k, null);
}
});
if ( !this[`${ neu }credentialConfig`] ) {
set(this, `${ neu }credentialConfig`, {});
}
}
get decodedData() {
const k = this.configKey;
if ( k ) {
return this[k];
}
return {};
}
setData(key, value) { // or (mapOfNewData)
const isMap = key && typeof key === 'object';
if ( !this[this.configKey] || isMap ) {
set(this, this.configKey, {});
}
let neu;
if ( isMap ) {
neu = key;
} else {
neu = { [key]: value };
}
for ( const k in neu ) {
// The key is quoted so that keys like '.dockerconfigjson' that contain dot don't get parsed into an object path
set(this, `"${ this.configKey }"."${ k }"`, neu[k]);
}
}
get providerDisplay() {
const provider = (this.provider || '').toLowerCase();
return this.$rootGetters['i18n/withFallback'](`cluster.provider."${ provider }"`, null, provider);
}
get publicData() {
let { publicKey, publicMode } = this.$rootGetters['plugins/credentialOptions'](this.provider);
const options = {
full: fullFields,
prefix: prefixFields,
suffix: suffixFields,
};
if ( !publicKey ) {
for ( const k in this.decodedData || {} ) {
if ( publicKey ) {
break;
}
if ( isEmpty(this.decodedData[k]) ) {
continue;
}
for ( const mode in options ) {
if ( options[mode].includes( simplify(k) ) ) {
publicKey = k;
publicMode = mode;
break;
}
}
}
}
if ( !publicKey ) {
return null;
}
let val = this.decodedData[publicKey];
if ( !val ) {
val = this.secret?.decodedData?.[`${ this.provider }credentialConfig-${ publicKey }`];
}
if ( !val ) {
return null;
}
const maxLength = Math.min(8, Math.floor(val.length / 2));
if ( publicMode === 'prefix' ) {
return `${ escapeHtml(val.substr(0, maxLength)) }…`;
} else if ( publicMode === 'suffix' ) {
return `…${ escapeHtml(val.substr(-1 * maxLength)) }`;
} else {
return escapeHtml(val);
}
}
get doneRoute() {
return 'c-cluster-manager-secret';
}
get canRenew() {
return !!renew[this.provider]?.renew && this.expires !== undefined && this.canUpdate;
}
get canBulkRenew() {
return !!renew[this.provider]?.renewBulk;
}
get expiresForSort() {
// Why not just `expires`? Ensures the correct sort order of expired --> expiring --> never expires
// (instead of 'never expired' --> 'expired' --> 'expiring')
return this.expires !== undefined ? this.expires : Number.MAX_SAFE_INTEGER;
}
get expires() {
const expires = this.annotations[CLOUD_CREDENTIALS.EXPIRATION];
if (typeof expires === 'string') {
return parseInt(expires);
} else if (typeof expires === 'number') {
return expires;
}
return undefined; // Weird things happen if this isn't a number
}
get expireData() {
if (typeof this.expiresIn !== 'number') {
return null;
}
const sevenDays = 1000 * 60 * 60 * 24 * 7;
if (this.expiresIn === 0) {
return {
expired: true,
expiring: false,
};
} else if (this.expiresIn < sevenDays) {
return {
expired: false,
expiring: true,
};
} else if (this.expiresIn) {
return {
expired: false,
expiring: false,
};
}
return null;
}
get expiresString() {
if (this.expires === undefined) {
return '';
}
if (this.expireData.expired) {
return this.t('manager.cloudCredentials.expired');
}
const dateFormat = escapeHtml( this.$rootGetters['prefs/get'](DATE_FORMAT));
const timeFormat = escapeHtml( this.$rootGetters['prefs/get'](TIME_FORMAT));
return day(this.expires).format(`${ dateFormat } ${ timeFormat }`);
}
get expiresIn() {
if (this.expires === undefined) {
return null;
}
const timeThen = this.expires;
const timeNow = Date.now();
const expiresIn = timeThen - timeNow;
return expiresIn < 0 ? 0 : expiresIn;
}
renew() {
const renewFn = renew[this.provider]?.renew;
if (!renewFn) {
console.error('No fn renew function for ', this.provider); // eslint-disable-line no-console
}
return renewFn({
cloudCredential: this,
$ctx: this.$ctx
});
}
async renewBulk(cloudCredentials = []) {
const renewBulkFn = renew[this.provider]?.renewBulk;
if (!renewBulkFn) {
console.error('No fn renew bulk function for ', this.provider); // eslint-disable-line no-console
}
return renewBulkFn({
cloudCredentials,
$ctx: this.$ctx
});
}
}