Merge pull request #1243 from mantis-toboggan-md/backup-restore-op-deployment

Backup restore op deployment
This commit is contained in:
Vincent Fiduccia 2020-09-21 02:59:04 -07:00 committed by GitHub
commit d99ec480d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 318 additions and 103 deletions

View File

@ -69,7 +69,7 @@ $selected: rgba($primary, .5);
--header-btn-bg : #317DB0;
--header-btn-text : white;
--header-height : 50px;
--nav-width : 230px;
--nav-width : 250px;
--nav-bg : #{$lighter};
--nav-active : #{rgba($primary, 0.3)};
--footer-bg : transparent;

View File

@ -58,7 +58,7 @@ nav:
product:
apps: Apps & Marketplace
backup: Backup Restore Operator
backup: Backup & Restore Operator
cis: CIS Benchmark
ecm: Cluster Manager
explorer: Cluster Explorer
@ -137,7 +137,20 @@ asyncButton:
backupRestoreOperator:
backupFilename: Backup Filename
prune:
deployment:
rancherNamespace: Rancher ResourceSet Namespace
storage:
tip: Configure a storage location where all backups are saved by default. You will have the option to override this with each backup, but will be limited to using an S3-compatible object store.
storageClass: Storage Class
persistentVolume: Persistent Volume
label: Default Storage Location
options:
s3: Use an S3-compatible object store
defaultStorageClass: 'Use the default storage class ({name})'
pickSC: Use an existing storage class
pickPV: Use an existing persistent volume
size: Size
prune:
label: Prune
tip: Delete all resources that match the ResourceSet used, but are not present in the backup. (Recommended)
encryption: Encryption

View File

@ -0,0 +1,88 @@
<script>
import LabeledInput from '@/components/form/LabeledInput';
import Checkbox from '@/components/form/Checkbox';
import FileSelector from '@/components/form/FileSelector';
import LabeledSelect from '@/components/form/LabeledSelect';
import { mapGetters } from 'vuex';
export default {
components: {
LabeledInput,
Checkbox,
FileSelector,
LabeledSelect,
},
props: {
value: {
type: Object,
default: () => {
return {};
}
},
mode: {
type: String,
default: 'create'
},
secrets: {
type: Array,
default: () => []
}
},
computed: {
credentialSecret: {
get() {
const { credentialSecretName, credentialSecretNamespace } = this.value;
return { metadata: { name: credentialSecretName, namespace: credentialSecretNamespace } };
},
set(neu) {
const { name, namespace } = neu.metadata;
this.$set(this.value, 'credentialSecretName', name);
this.$set(this.value, 'credentialSecretNamespace', namespace);
}
},
...mapGetters({ t: 'i18n/t' })
}
};
</script>
<template>
<div>
<div class="row mb-10">
<div class="col span-6">
<LabeledSelect
v-model="credentialSecret"
:get-option-label="opt=>opt.metadata.name || ''"
:mode="mode"
:options="secrets"
:label="t('backupRestoreOperator.s3.credentialSecretName')"
/>
</div>
<div class="col span-6">
<LabeledInput v-model="value.bucketName" :mode="mode" :label="t('backupRestoreOperator.s3.bucketName')" />
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput v-model="value.region" :mode="mode" :label="t('backupRestoreOperator.s3.region')" />
</div>
<div class="col span-6">
<LabeledInput v-model="value.folder" :mode="mode" :label="t('backupRestoreOperator.s3.folder')" />
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput v-model="value.endpoint" :mode="mode" :label="t('backupRestoreOperator.s3.endpoint')" />
<Checkbox v-model="value.insecureTLSSkipVerify" class="mt-10" :mode="mode" :label="t('backupRestoreOperator.s3.insecureTLSSkipVerify')" />
</div>
<div class="col span-6">
<LabeledInput v-model="value.endpointCA" :mode="mode" type="multiline" :label="t('backupRestoreOperator.s3.endpointCA')" />
<FileSelector v-if="mode!=='view'" class="btn btn-sm role-primary mt-5" :mode="mode" :label="t('generic.readFromFile')" @selected="e=>$set(s3, 'endpointCA', e)" />
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,199 @@
<script>
import Tab from '@/components/Tabbed/Tab';
import S3 from '@/chart/backup-restore-operator/S3';
import RadioGroup from '@/components/form/RadioGroup';
import LabeledSelect from '@/components/form/LabeledSelect';
import LabeledInput from '@/components/form/LabeledInput';
import Banner from '@/components/Banner';
import { get } from '@/utils/object';
import { allHash } from '@/utils/promise';
import { STORAGE_CLASS, SECRET, PV } from '@/config/types';
import { mapGetters } from 'vuex';
import { STORAGE } from '@/config/labels-annotations';
export default {
components: {
Tab,
RadioGroup,
S3,
LabeledInput,
LabeledSelect,
Banner
},
hasTabs: true,
props: {
value: {
type: Object,
default: () => {
return {};
}
},
mode: {
type: String,
default: 'create'
}
},
async fetch() {
const hash = await allHash({
storageClasses: this.$store.dispatch('cluster/findAll', { type: STORAGE_CLASS }),
persistentVolumes: this.$store.dispatch('cluster/findAll', { type: PV }),
secrets: this.$store.dispatch('cluster/findAll', { type: SECRET }),
});
this.secrets = hash.secrets;
this.storageClasses = hash.storageClasses;
this.persistentVolumes = hash.persistentVolumes;
},
data() {
const storageSource = this.mode === 'create' ? 's3' : this.getStorageSource(this.value);
return {
storageSource, secrets: [], storageClasses: [], persistentVolumes: []
};
},
computed: {
defaultStorageClass() {
return this.storageClasses.filter(sc => sc.metadata.annotations[STORAGE.DEFAULT_STORAGE_CLASS])[0];
},
storageClassNames() {
return this.storageClasses.reduce((total, each) => {
total.push(each.id);
return total;
}, []);
},
unboundPVs() {
return this.persistentVolumes.reduce((total, each) => {
if (each?.status?.phase !== 'bound') {
total.push(each.id);
}
return total;
}, []);
},
radioOptions() {
const options = ['s3', 'pickSC', 'pickPV'];
const labels = [
this.t('backupRestoreOperator.deployment.storage.options.s3'),
this.t('backupRestoreOperator.deployment.storage.options.pickSC'),
this.t('backupRestoreOperator.deployment.storage.options.pickPV'),
];
if (this.defaultStorageClass) {
options.splice(1, 0, 'defaultSC');
labels.splice(1, 0, this.t('backupRestoreOperator.deployment.storage.options.defaultStorageClass', { name: this.defaultStorageClass.name }));
}
return { options, labels };
},
...mapGetters({ t: 'i18n/t' })
},
watch: {
storageSource(neu) {
switch (neu) {
case 'defaultSC':
this.value.persistence.enabled = true;
this.value.s3.enabled = false;
this.value.persistence.storageClass = this.defaultStorageClass.id;
break;
case 'pickSC':
this.value.persistence.enabled = true;
delete this.value.persistence.volumeName;
this.value.s3.enabled = false;
break;
case 'pickPV':
this.value.persistence.enabled = true;
this.value.persistence.storageClass = '-';
this.value.s3.enabled = false;
break;
case 's3':
this.value.persistence.enabled = false;
this.value.s3.enabled = true;
break;
default:
break;
}
}
},
methods: {
getStorageSource() {
if (get(this.value, 's3.enabled')) {
return 's3';
} if (get(this.value, 'persistence.enabled')) {
if (this.value.persistence.storageClass) {
if (this.value.persistence.storageClass === this.defaultSC.metadata.name) {
return 'defaultSC';
}
return 'pickSC';
}
if (this.value.persistence.volumeName) {
return 'pickPV';
}
}
}
},
};
</script>
<template>
<div>
<Tab label="Chart Options" name="chartOptions">
<Banner color="info" :label="t('backupRestoreOperator.deployment.storage.tip')" />
<RadioGroup
v-model="storageSource"
name="storageSource"
:label="t('backupRestoreOperator.deployment.storage.label')"
class="mb-10"
:options="radioOptions.options"
:labels="radioOptions.labels"
/>
<S3 v-if="storageSource==='s3'" :value="value.s3" :secrets="secrets" :mode="mode" />
<template v-else>
<div class="row">
<div v-if="storageSource === 'pickSC'" class="col span-6">
<LabeledSelect
:key="storageSource"
v-model="value.persistence.storageClass"
:label="t('backupRestoreOperator.deployment.storage.storageClass')"
:mode="mode"
:options="storageClassNames"
/>
</div>
<div v-else-if="storageSource === 'pickPV'" class="col span-6">
<LabeledSelect
:key="storageSource"
:value="value.persistence.volumeName"
:label="t('backupRestoreOperator.deployment.storage.persistentVolume')"
:mode="mode"
:options="unboundPVs"
/>
</div>
<div class="col span-6">
<LabeledInput v-model="value.persistence.size" :mode="mode" :label="t('backupRestoreOperator.deployment.size')" />
</div>
</div>
</template>
</Tab>
</div>
</template>
<style lang='scss' scoped>
::v-deep .radio-group.label>SPAN {
font-size: 1em;
}
</style>

View File

@ -200,7 +200,7 @@ export default {
:options="options"
:placeholder="placeholder"
:reduce="x => reduce(x)"
:value="value ? value : ' '"
:value="value ? value : ''"
@input="e=>$emit('input', e)"
@search:blur="onBlur"
@search:focus="onFocus"

View File

@ -103,7 +103,7 @@ export default {
<template>
<div>
<div v-if="label" class="radio-group label" :class="{'view':isView}">
<div v-if="label" class="radio-group label">
<span class="text-label">
{{ label }}
</span>
@ -154,7 +154,7 @@ export default {
margin-right: 10px;
}
}
.radio-group.label.view{
color: var(--input-label);
.radio-group.label{
font-size: 1.2em;
}
</style>

View File

@ -15,6 +15,8 @@ export const RIO = { STACK: 'rio.cattle.io/stack' };
export const CERTMANAGER = { ISSUER: 'cert-manager.io/issuer-name' };
export const STORAGE = { DEFAULT_STORAGE_CLASS: 'storageclass.kubernetes.io/is-default-class' };
export const NODE_ROLES = {
CONTROL_PLANE: 'node-role.kubernetes.io/controlplane',
WORKER: 'node-role.kubernetes.io/worker',

View File

@ -3,13 +3,13 @@ import CruResource from '@/components/CruResource';
import createEditView from '@/mixins/create-edit-view';
import LabeledInput from '@/components/form/LabeledInput';
import UnitInput from '@/components/form/UnitInput';
import Checkbox from '@/components/form/Checkbox';
import FileSelector from '@/components/form/FileSelector';
import LabeledSelect from '@/components/form/LabeledSelect';
import Banner from '@/components/Banner';
import RadioGroup from '@/components/form/RadioGroup';
import NameNsDescription from '@/components/form/NameNsDescription';
import Loading from '@/components/Loading';
import S3 from '@/chart/backup-restore-operator/S3';
import { mapGetters } from 'vuex';
import { SECRET, BACKUP_RESTORE, CATALOG } from '@/config/types';
import { allHash } from '@/utils/promise';
@ -22,13 +22,12 @@ export default {
CruResource,
UnitInput,
LabeledInput,
Checkbox,
FileSelector,
LabeledSelect,
RadioGroup,
NameNsDescription,
Banner,
Loading
Loading,
S3
},
mixins: [createEditView],
@ -77,20 +76,6 @@ export default {
return BRORelease ? BRORelease.spec.namespace : '';
},
credentialSecret: {
get() {
const { credentialSecretName, credentialSecretNamespace } = this.s3;
return { metadata: { name: credentialSecretName, namespace: credentialSecretNamespace } };
},
set(neu) {
const { name, namespace } = neu.metadata;
this.$set(this.s3, 'credentialSecretName', name);
this.$set(this.s3, 'credentialSecretNamespace', namespace);
}
},
encryptionSecretNames() {
return this.allSecrets.filter(secret => !!secret.data['encryption-provider-config.yaml'] && secret.metadata.namespace === this.chartNamespace).map(secret => secret.metadata.name);
},
@ -137,7 +122,7 @@ export default {
if (neu === 'useDefault') {
delete this.value.spec.storageLocation;
} else {
this.$set(this.value.spec, 'storageLocation', this.s3);
this.$set(this.value.spec, 'storageLocation', { s3: this.s3 });
}
},
@ -224,32 +209,7 @@ export default {
</div>
<template v-if="storageSource !== 'useDefault'">
<div class="row mb-10">
<div class="col span-6">
<LabeledSelect v-model="credentialSecret" :get-option-label="opt=>opt.metadata.name || ''" :mode="mode" :options="allSecrets" :label="t('backupRestoreOperator.s3.credentialSecretName')" />
</div>
<div class="col span-6">
<LabeledInput v-model="s3.bucketName" :mode="mode" :label="t('backupRestoreOperator.s3.bucketName')" />
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput v-model="s3.region" :mode="mode" :label="t('backupRestoreOperator.s3.region')" />
</div>
<div class="col span-6">
<LabeledInput v-model="s3.folder" :mode="mode" :label="t('backupRestoreOperator.s3.folder')" />
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput v-model="s3.endpoint" :mode="mode" :label="t('backupRestoreOperator.s3.endpoint')" />
<Checkbox v-model="s3.insecureTLSSkipVerify" class="mt-10" :mode="mode" :label="t('backupRestoreOperator.s3.insecureTLSSkipVerify')" />
</div>
<div class="col span-6">
<LabeledInput v-model="s3.endpointCA" :mode="mode" type="multiline" :label="t('backupRestoreOperator.s3.endpointCA')" />
<FileSelector v-if="mode!=='view'" class="btn btn-sm role-primary mt-5" :mode="mode" :label="t('generic.readFromFile')" @selected="e=>$set(s3, 'endpointCA', e)" />
</div>
</div>
<S3 :value="s3" :secrets="allSecrets" :mode="mode" />
</template>
</template>
<Banner v-else color="error">

View File

@ -4,10 +4,10 @@ import createEditView from '@/mixins/create-edit-view';
import LabeledInput from '@/components/form/LabeledInput';
import UnitInput from '@/components/form/UnitInput';
import Checkbox from '@/components/form/Checkbox';
import FileSelector from '@/components/form/FileSelector';
import LabeledSelect from '@/components/form/LabeledSelect';
import Loading from '@/components/Loading';
import RadioGroup from '@/components/form/RadioGroup';
import S3 from '@/chart/backup-restore-operator/S3';
import { mapGetters } from 'vuex';
import { SECRET, BACKUP_RESTORE, CATALOG } from '@/config/types';
import { allHash } from '@/utils/promise';
@ -19,7 +19,7 @@ export default {
UnitInput,
LabeledInput,
Checkbox,
FileSelector,
S3,
LabeledSelect,
Loading,
RadioGroup
@ -79,21 +79,6 @@ export default {
return BRORelease ? BRORelease.spec.namespace : '';
},
credentialSecret: {
get() {
const { credentialSecretName, credentialSecretNamespace } = this.s3;
return { metadata: { name: credentialSecretName, namespace: credentialSecretNamespace } };
},
set(neu) {
const { name, namespace } = neu.metadata;
this.$set(this.s3, 'credentialSecretName', name);
this.$set(this.s3, 'credentialSecretNamespace', namespace);
}
},
encryptionSecretNames() {
return this.allSecrets.filter(secret => !!secret.data['encryption-provider-config.yaml'] && secret.metadata.namespace === this.chartNamespace).map(secret => secret.metadata.name);
},
@ -177,38 +162,7 @@ export default {
</div>
</div>
<template v-if="storageSource === 'configureS3'">
<div class="row mb-10">
<div class="col span-6">
<LabeledSelect
v-model="credentialSecret"
:get-option-label="opt=>opt.metadata.name || ''"
:mode="mode"
:options="allSecrets"
:label="t('backupRestoreOperator.s3.credentialSecretName')"
/>
</div>
<div class="col span-6">
<LabeledInput v-model="s3.bucketName" :mode="mode" :label="t('backupRestoreOperator.s3.bucketName')" />
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput v-model="s3.region" :mode="mode" :label="t('backupRestoreOperator.s3.region')" />
</div>
<div class="col span-6">
<LabeledInput v-model="s3.folder" :mode="mode" :label="t('backupRestoreOperator.s3.folder')" />
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput v-model="s3.endpoint" :mode="mode" :label="t('backupRestoreOperator.s3.endpoint')" />
<Checkbox v-model="s3.insecureTLSSkipVerify" class="mt-10" :mode="mode" :label="t('backupRestoreOperator.s3.insecureTLSSkipVerify')" />
</div>
<div class="col span-6">
<LabeledInput v-model="s3.endpointCA" :mode="mode" type="multiline" :label="t('backupRestoreOperator.s3.endpointCA')" />
<FileSelector v-if="mode!=='view'" class="btn btn-sm role-primary mt-5" :mode="mode" :label="t('generic.readFromFile')" @selected="e=>$set(s3, 'endpointCA', e)" />
</div>
</div>
<S3 v-model="s3" :mode="mode" :secrets="allSecrets" />
</template>
<div v-else-if="storageSource==='useBackup'" class="row mb-10">
<div class="col span-6">

View File

@ -507,7 +507,6 @@ export default {
return;
}
const res = await this.repo.doAction((isUpgrade ? 'upgrade' : 'install'), input);
this.operation = await this.$store.dispatch('cluster/find', {