diff --git a/app/authenticated/cluster/edit/route.js b/app/authenticated/cluster/edit/route.js
index 144875391..27291d618 100644
--- a/app/authenticated/cluster/edit/route.js
+++ b/app/authenticated/cluster/edit/route.js
@@ -9,6 +9,7 @@ import { scheduleOnce } from '@ember/runloop';
export default Route.extend({
access: service(),
globalStore: service(),
+ clusterStore: service(),
releaseVersions: service(),
clusterTemplateService: service('clusterTemplates'),
roleTemplateService: service('roleTemplate'),
@@ -21,11 +22,13 @@ export default Route.extend({
originalCluster: cluster,
cluster: cluster.clone(),
cloudCredentials: globalStore.findAll('cloudcredential'),
+ configMaps: this.configMaps(),
kontainerDrivers: globalStore.findAll('kontainerDriver'),
nodeTemplates: globalStore.findAll('nodeTemplate'),
nodeDrivers: globalStore.findAll('nodeDriver'),
psps: globalStore.findAll('podSecurityPolicyTemplate'),
roleTemplates: get(this, 'roleTemplateService').get('allFilteredRoleTemplates'),
+ secrets: this.secrets(),
users: globalStore.findAll('user'),
clusterRoleTemplateBinding: globalStore.findAll('clusterRoleTemplateBinding'),
me: get(this, 'access.principal'),
@@ -157,6 +160,28 @@ export default Route.extend({
}
},
+ async configMaps() {
+ const projects = await this.projects();
+ const configMapPromises = projects.map((project) => project.hasLink('configMaps') ? project.followLink('configMaps') : Promise.resolve([]));
+ const nestedConfigMaps = await Promise.all(configMapPromises) || [];
+
+ return nestedConfigMaps.flatMap((maps) => maps.content);
+ },
+
+ async secrets() {
+ const projects = await this.projects();
+ const secretPromises = projects.map((project) => project.hasLink('secrets') ? project.followLink('secrets') : Promise.resolve([]));
+ const nestedSecrets = await Promise.all(secretPromises) || [];
+
+ return nestedSecrets.flatMap((maps) => maps.content);
+ },
+
+ async projects() {
+ const cluster = this.modelFor('authenticated.cluster');
+
+ return (await cluster.followLink('projects')).content;
+ },
+
queryParams: {
provider: { refreshModel: true },
clusterTemplateRevision: { refreshModel: true }
diff --git a/lib/shared/addon/components/cluster-driver/driver-rke/component.js b/lib/shared/addon/components/cluster-driver/driver-rke/component.js
index b46a3fcbd..08d8f7ca5 100644
--- a/lib/shared/addon/components/cluster-driver/driver-rke/component.js
+++ b/lib/shared/addon/components/cluster-driver/driver-rke/component.js
@@ -140,6 +140,8 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
scheduledClusterScan: { enabled: false },
})
+ this.initAgentEnvVars();
+
this.initNodeCounts();
if (!this.useClusterTemplate && this.model.cluster.clusterTemplateRevisionId) {
@@ -875,6 +877,10 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
return options;
}),
+ showCustomConfigMapsAndSecrets: computed('model.provider', function() {
+ return get(this, 'model.provider') === 'custom';
+ }),
+
initScheduledClusterScan() {
// We need to wait for benchmarks to be available before we can actually create the profiles
if (this.cisHelpers.cisScanBenchmarks.length === 0) {
@@ -1428,6 +1434,10 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
})
},
+ initAgentEnvVars() {
+ setProperties(this.cluster, { agentEnvVars: get(this, 'cluster.agentEnvVars') || [] });
+ },
+
initPrivateRegistries() {
const config = get(this, 'config');
diff --git a/lib/shared/addon/components/cluster-driver/driver-rke/template.hbs b/lib/shared/addon/components/cluster-driver/driver-rke/template.hbs
index 6b023f3b7..993854233 100644
--- a/lib/shared/addon/components/cluster-driver/driver-rke/template.hbs
+++ b/lib/shared/addon/components/cluster-driver/driver-rke/template.hbs
@@ -1253,6 +1253,21 @@
{{drain-node selection=upgradeStrategy.nodeDrainInput clusterTemplateCreate=clusterTemplateCreate editable=notView applyClusterTemplate=applyClusterTemplate clusterTemplateRevision=model.clusterTemplateRevision.clusterConfig questions=model.clusterTemplateRevision.questions addOverride=(action "addOverride")}}
{{/if}}
+ {{#if (not clusterTemplateCreate)}}
+
+
+
+
+ {{/if}}
{{/accordion-list-item}}
{{#accordion-list-item
diff --git a/lib/shared/addon/components/form-agent-env-var/component.js b/lib/shared/addon/components/form-agent-env-var/component.js
new file mode 100644
index 000000000..60725e143
--- /dev/null
+++ b/lib/shared/addon/components/form-agent-env-var/component.js
@@ -0,0 +1,254 @@
+import Component from '@ember/component';
+import { inject as service } from '@ember/service';
+import layout from './template';
+import { computed, get, set } from '@ember/object';
+
+export default Component.extend({
+ globalStore: service(),
+ intl: service(),
+ layout,
+
+ model: null,
+ editable: true,
+
+ statusClass: null,
+ status: null,
+ editing: true,
+ showCustomConfigMaps: false,
+ showCustomSecrets: false,
+ typeChoices: [],
+ configMaps: [],
+ secrets: [],
+ value: [],
+ resourceKeyChoices: [],
+ init() {
+ this._super(...arguments);
+
+ const configMapsEnabled = this.showCustomConfigMaps || this.configMaps?.length > 0;
+ const secretsEnabled = this.showCustomSecrets || this.secrets?.length > 0;
+
+ const typeChoices = [
+ {
+ label: this.intl.t('formAgentEnvVar.typeChoices.keyValue'),
+ value: 'keyValue'
+ },
+ {
+ label: this.intl.t('formAgentEnvVar.typeChoices.resource'),
+ value: 'resource'
+ },
+ {
+ label: configMapsEnabled ? this.intl.t('formAgentEnvVar.typeChoices.configMap') : this.intl.t('formAgentEnvVar.typeChoices.configMapNone'),
+ disabled: !configMapsEnabled,
+ value: 'configMap'
+ },
+ {
+ label: secretsEnabled ? this.intl.t('formAgentEnvVar.typeChoices.secret') : this.intl.t('formAgentEnvVar.typeChoices.secretNone'),
+ disabled: !secretsEnabled,
+ value: 'secret'
+ },
+ {
+ label: this.intl.t('formAgentEnvVar.typeChoices.podField'),
+ value: 'podfield'
+ }
+ ];
+
+ this.set('typeChoices', typeChoices);
+
+ this.set('resourcekeyChoices', [
+ {
+ label: 'limits.cpu',
+ value: 'limits.cpu'
+ },
+ {
+ label: 'limits.ephemeral-storage',
+ value: 'limits.ephemeral-storage'
+ },
+ {
+ label: 'limits.memory',
+ value: 'limits.memory'
+ },
+ {
+ label: 'requests.cpu',
+ value: 'requests.cpu'
+ },
+ {
+ label: 'requests.ephemeral-storage',
+ value: 'requests.ephemeral-storage'
+ },
+ {
+ label: 'requests.memory',
+ value: 'requests.memory'
+ },
+ ]);
+
+ this.set('value', this.value || []);
+
+
+ this.value.forEach((envVar) => this.inferTypeAndSetHelpers(envVar));
+ },
+
+ actions: {
+ add() {
+ this.value.pushObject(this.createEnvVar('keyValue'));
+ },
+ updateType(index, event) {
+ const type = event.target.value;
+
+ this.value.replace(index, 1, [this.createEnvVar(type)]);
+ },
+ updateSecret(index, event) {
+ const secretName = event.target.value;
+
+ set(this.value[index], 'secretKeyChoices', this.keyChoices('secrets', secretName));
+ set(this.value[index], 'valueFrom.secretKeyRef.name', secretName);
+ set(this.value[index], 'valueFrom.secretKeyRef.key', this.value[index].secretKeyChoices[0].value);
+ },
+ updateConfigMap(index, event) {
+ const configMapName = event.target.value;
+
+ set(this.value[index], 'configMapKeyChoices', this.keyChoices('configMaps', configMapName));
+ set(this.value[index], 'valueFrom.configMapKeyRef.name', configMapName);
+ set(this.value[index], 'valueFrom.configMapKeyRef.key', this.value[index].configMapKeyChoices[0].value);
+ },
+ remove(index) {
+ this.value.removeAt(index, 1);
+ },
+ },
+
+ configMapChoices: computed('configMaps', function() {
+ const configMaps = get(this, 'configMaps') || [];
+ const choices = configMaps.map((configMap) => ({
+ label: configMap.displayName,
+ value: configMap.name
+ }));
+
+ return choices
+ .uniqBy('label')
+ .sortBy('label');
+ }),
+
+ secretChoices: computed('secrets', function() {
+ const secrets = get(this, 'secrets') || [];
+ const choices = secrets.map((secret) => ({
+ label: secret.displayName,
+ value: secret.name
+ }));
+
+ return choices
+ .uniqBy('label')
+ .sortBy('label');
+ }),
+
+ inferTypeAndSetHelpers(envVar) {
+ if (this.isValueFromSet(envVar, 'resourceFieldRef', ['resource', 'containerName', 'divisor'])) {
+ return this.setEnvVarHelpers(envVar, 'resource');
+ }
+
+ if (this.isValueFromSet(envVar, 'configMapKeyRef', ['name', 'key'])) {
+ return this.setEnvVarHelpers(envVar, 'configMap');
+ }
+
+ if (this.isValueFromSet(envVar, 'secretKeyRef', ['name', 'key'])) {
+ return this.setEnvVarHelpers(envVar, 'secret');
+ }
+
+ if (this.isValueFromSet(envVar, 'fieldRef', ['fieldPath'])) {
+ return this.setEnvVarHelpers(envVar, 'podfield');
+ }
+
+ return this.setEnvVarHelpers(envVar, 'keyValue');
+ },
+
+ isValueFromSet(envVar, refKey, fields) {
+ return fields.some((field) => get(envVar, `valueFrom.${ refKey }.${ field }`));
+ },
+
+ keyChoices(resource, selectedName) {
+ const resources = get(this, resource) || [];
+ const data = resources.find((r) => r.name === selectedName)?.data || {};
+
+ return Object.keys(data).map((key) => ({
+ label: key,
+ value: key
+ }))
+ },
+
+ showContainer(type) {
+ return ['resource'].includes(type);
+ },
+
+ showKey(type) {
+ return ['resource', 'configMap', 'secret', 'podfield'].includes(type);
+ },
+
+ showValue(type) {
+ return ['keyValue'].includes(type);
+ },
+
+ showConfigMap(type) {
+ return ['configMap'].includes(type);
+ },
+
+ showSecret(type) {
+ return ['secret'].includes(type);
+ },
+
+ showPodField(type) {
+ return ['podfield'].includes(type);
+ },
+ createEnvVar(type) {
+ const envVar = {};
+
+ this.setEnvVarHelpers(envVar, type);
+ this.setDefaults(envVar, type);
+
+ return envVar;
+ },
+
+ setEnvVarHelpers(envVar, type) {
+ Object.assign(envVar, {
+ type,
+ showContainer: this.showContainer(type),
+ showKey: this.showKey(type),
+ showValue: this.showValue(type),
+ showConfigMap: this.showConfigMap(type),
+ showSecret: this.showSecret(type),
+ showPodField: this.showPodField(type),
+ secretKeyChoices: envVar.valueFrom?.secretKeyRef ? this.keyChoices('secrets', envVar.valueFrom.secretKeyRef.name) : null,
+ configMapKeyChoices: envVar.valueFrom?.configMapKeyRef ? this.keyChoices('configMaps', envVar.valueFrom.configMapKeyRef.name) : null,
+ });
+ },
+
+ setDefaults(envVar, type) {
+ if (type !== 'keyValue') {
+ set(envVar, 'valueFrom', envVar.valueFrom || {});
+ }
+
+ if (type === 'configMap') {
+ set(envVar, 'valueFrom.configMapKeyRef', envVar.valueFrom.configMapKeyRef || {});
+ const configMapName = this.configMapChoices[0]?.value;
+
+ set(envVar, 'configMapKeyChoices', this.keyChoices('configMaps', configMapName));
+ set(envVar, 'valueFrom.configMapKeyRef', envVar.valueFrom.configMapKeyRef || {
+ name: configMapName,
+ key: envVar.configMapKeyChoices[0]?.value
+ });
+ }
+ if (type === 'resource') {
+ set(envVar, 'valueFrom.resourceFieldRef', envVar.valueFrom.resourceFieldRef || {});
+ }
+ if (type === 'podfield') {
+ set(envVar, 'valueFrom.fieldRef', envVar.valueFrom.fieldRef || {});
+ }
+
+ if (type === 'secret') {
+ const secretName = this.secretChoices[0]?.value;
+
+ set(envVar, 'secretKeyChoices', this.keyChoices('secrets', secretName));
+ set(envVar, 'valueFrom.secretKeyRef', envVar.valueFrom.secretKeyRef || {
+ name: secretName,
+ key: envVar.secretKeyChoices[0]?.value
+ });
+ }
+ }
+});
diff --git a/lib/shared/addon/components/form-agent-env-var/template.hbs b/lib/shared/addon/components/form-agent-env-var/template.hbs
new file mode 100644
index 000000000..1e309bea0
--- /dev/null
+++ b/lib/shared/addon/components/form-agent-env-var/template.hbs
@@ -0,0 +1,212 @@
+
+
+
+ {{#each value as |envVar index|}}
+
+ |
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.type
+ }}
+ {{new-select
+ class="form-control"
+ content=typeChoices
+ value=envVar.type
+ onChange=(action 'updateType' index)
+ }}
+ {{/input-or-display}}
+ |
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.name
+ }}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.name
+ placeholder=(t 'formAgentEnvVar.placeholders.foo')
+ }}
+ {{/input-or-display}}
+ |
+ {{#if envVar.showValue}}
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.value
+ }}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.value
+ placeholder=(t 'formAgentEnvVar.placeholders.bar')
+ }}
+ {{/input-or-display}}
+ |
+ {{/if}}
+ {{#if envVar.showContainer}}
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.valueFrom.resourceFieldRef.containerName
+ }}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.valueFrom.resourceFieldRef.containerName
+ placeholder=(t 'formAgentEnvVar.placeholders.container')
+ }}
+ {{/input-or-display}}
+ |
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.valueFrom.resourceFieldRef.resource
+ }}
+ {{new-select
+ class="form-control"
+ content=resourcekeyChoices
+ value=envVar.valueFrom.resourceFieldRef.resource
+ }}
+ {{/input-or-display}}
+ |
+ {{/if}}
+ {{#if envVar.showConfigMap}}
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.valueFrom.configMapKeyRef.name
+ }}
+ {{#if showCustomConfigMaps}}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.valueFrom.configMapKeyRef.name
+ }}
+ {{else}}
+ {{new-select
+ class="form-control"
+ content=configMapChoices
+ value=envVar.valueFrom.configMapKeyRef.name
+ onChange=(action 'updateConfigMap' index)
+ }}
+ {{/if}}
+ {{/input-or-display}}
+ |
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.valueFrom.configMapKeyRef.key
+ }}
+ {{#if showCustomConfigMaps}}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.valueFrom.configMapKeyRef.key
+ }}
+ {{else}}
+ {{new-select
+ class="form-control"
+ content=envVar.configMapKeyChoices
+ value=envVar.valueFrom.configMapKeyRef.key
+ disabled=(not envVar.configMapKeyChoices.length)
+ }}
+ {{/if}}
+ {{/input-or-display}}
+ |
+ {{/if}}
+ {{#if envVar.showSecret}}
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.valueFrom.secretKeyRef.name
+ }}
+ {{#if showCustomSecrets}}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.valueFrom.secretKeyRef.name
+ }}
+ {{else}}
+ {{new-select
+ class="form-control"
+ content=secretChoices
+ value=envVar.valueFrom.secretKeyRef.name
+ onChange=(action 'updateSecret' index)
+ }}
+ {{/if}}
+ {{/input-or-display}}
+ |
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.valueFrom.secretKeyRef.key
+ }}
+ {{#if showCustomSecrets}}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.valueFrom.secretKeyRef.key
+ }}
+ {{else}}
+ {{new-select
+ class="form-control"
+ content=envVar.secretKeyChoices
+ value=envVar.valueFrom.secretKeyRef.key
+ disabled=(not envVar.secretKeyChoices.length)
+ }}
+ {{/if}}
+ {{/input-or-display}}
+ |
+ {{/if}}
+ {{#if envVar.showPodField}}
+
+
+ {{#input-or-display
+ editable=editable
+ value=envVar.valueFrom.fieldRef.fieldPath
+ }}
+ {{input
+ class="form-control"
+ type="text"
+ value=envVar.valueFrom.fieldRef.fieldPath
+ placeholder=(t 'formAgentEnvVar.placeholders.key')
+ }}
+ {{/input-or-display}}
+ |
+ {{/if}}
+
+
+ {{#if editable}}
+
+
+
+ {{/if}}
+ |
+
+ {{/each}}
+
+
+ {{#if editable}}
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/lib/shared/addon/components/new-select/component.js b/lib/shared/addon/components/new-select/component.js
index 56b0bc16a..abbf12362 100644
--- a/lib/shared/addon/components/new-select/component.js
+++ b/lib/shared/addon/components/new-select/component.js
@@ -14,6 +14,7 @@ export default Component.extend({
optionLabelPath: 'label',
optionGroupPath: 'group',
optionDisabledPath: 'disabled',
+ onChange: null,
value: null,
useContentForDefaultValue: false,
@@ -49,6 +50,13 @@ export default Component.extend({
this.off('change', this, this._change);
},
+ actions: {
+ onChange() {
+ if (this.onChange) {
+ this.onChange(...arguments);
+ }
+ }
+ },
setDefaultValueObserver: observer('asyncContent.value', function() {
const content = get(this, 'asyncContent.value');
diff --git a/lib/shared/addon/components/new-select/template.hbs b/lib/shared/addon/components/new-select/template.hbs
index 637585e0e..f361ebb29 100644
--- a/lib/shared/addon/components/new-select/template.hbs
+++ b/lib/shared/addon/components/new-select/template.hbs
@@ -1,4 +1,4 @@
-