text: import {
    computed, defineProperty, get, observer, set, setProperties
  } from '@ember/object';
  import { alias, equal, or } from '@ember/object/computed';
  import { on } from '@ember/object/evented';
  import { next, once, scheduleOnce } from '@ember/runloop';
  import { inject as service } from '@ember/service';
  import { isEmpty, typeOf } from '@ember/utils';
  import { validateHostname } from '@rancher/ember-api-store/utils/validate';
  import deepSet from 'ember-deep-set';
  import jsyaml from 'js-yaml';
  import moment from 'moment';
  import { resolve } from 'rsvp';
  import Semver, { major, minor } from 'semver';
  import { azure as AzureInfo } from 'shared/components/cru-cloud-provider/cloud-provider-info';
  import ClusterDriver from 'shared/mixins/cluster-driver';
  import ManageLabels from 'shared/mixins/manage-labels';
  import C from 'shared/utils/constants';
  import { coerceVersion, maxSatisfying } from 'shared/utils/parse-version';
  import {
    keysToCamel, keysToDecamelize, randomStr, removeEmpty, ucFirst, validateCertWeakly
  } from 'shared/utils/util';
  import InputTextFile from 'ui/components/input-text-file/component';
  import layout from './template';
  
  const EXCLUDED_KEYS = ['name'];
  const EXCLUDED_CHILDREN_KEYS = ['extra_args', 'nodelocal', 'dns', 'extraArgs'];
  
  function camelToUnderline(str, split = true) {
    str = (str || '');
    if ( str.indexOf('-') > -1 || str.endsWith('CloudProvider')) {
      return str;
    } else if ( split ) {
      return (str || '').dasherize().split('-').join('_');
    } else {
      return (str || '').dasherize();
    }
  }
  
  const EXCLUED_CLUSTER_OPTIONS = [
    'annotations',
    'labels'
  ];
  
  const AUTHCHOICES = [
    {
      label: 'clusterNew.rke.auth.x509',
      value: 'x509'
    },
  ];
  
  const INGRESSCHOICES = [
    {
      label: 'clusterNew.rke.ingress.nginx',
      value: 'nginx'
    },
    {
      label: 'clusterNew.rke.ingress.none',
      value: 'none'
    },
  ];
  
  const AVAILABLE_STRATEGIES = ['local', 's3'];
  
  const {
    CLUSTER_TEMPLATE_IGNORED_OVERRIDES,
    NETWORK_CONFIG_DEFAULTS: { DEFAULT_BACKEND_TYPE }
  } = C;
  
  const { WEAVE } = C.NETWORK_CONFIG_DEFAULTS;
  
  export default InputTextFile.extend(ManageLabels, ClusterDriver, {
    globalStore:               service(),
    settings:                  service(),
    growl:                     service(),
    intl:                      service(),
    clusterTemplates:          service(),
    access:                    service(),
    router:                    service(),
    cisHelpers:                service(),
    features:                  service(),
  
    layout,
    authChoices:               AUTHCHOICES,
    ingressChoices:            INGRESSCHOICES,
    availableStrategies:       AVAILABLE_STRATEGIES,
    ingornedRkeOverrides:      CLUSTER_TEMPLATE_IGNORED_OVERRIDES,
  
    configField:               'rancherKubernetesEngineConfig',
    registry:                  'default',
    accept:                    '.yml, .yaml',
    backupStrategy:            'local',
    overrideCreatLabel:        null,
    loading:                   false,
    pasteOrUpload:             false,
    model:                     null,
    initialVersion:            null,
    registryUrl:               null,
    registryUser:              null,
    registryPass:              null,
    clusterOptErrors:          null,
  
    existingNodes:             null,
    initialNodeCounts:         null,
    step:                      1,
    token:                     null,
    taints:                    null,
    labels:                    null,
    etcd:                      false,
    controlplane:              false,
    worker:                    true,
    defaultDockerRootDir:      null,
    nodePoolErrors:            null,
  
    windowsEnable:             false,
    isLinux:                   true,
    weaveCustomPassword:       false,
    clusterTemplateCreate:     false,
    clusterTemplateQuestions:  null,
    forceExpandOnInit:         false,
    forceExpandAll:            false,
    applyClusterTemplate:      null,
    useClusterTemplate:        false,
    clusterTemplatesEnforced:  false,
    selectedClusterTemplateId: null,
    upgradeStrategy:           null,
    scheduledClusterScan:      null,
    isWindowsPreferedCluster:  false,
  
    isPostSave: false,
  
    isNew:                        equal('mode', 'new'),
    isEdit:                       equal('mode', 'edit'),
    notView:                      or('isNew', 'isEdit'),
    clusterState:                 alias('model.originalCluster.state'),
    // Custom stuff
    isCustom:                  equal('nodeWhich', 'custom'),
  
    isScheduledClusterScanDisabled: alias('model.cluster.windowsPreferedCluster'),
  
    init() {
      this._super(...arguments);
  
      setProperties(this, {
        upgradeStrategy:           {},
        scheduledClusterScan:      { enabled: false },
      })
  
      this.initNodeCounts();
      if (!this.useClusterTemplate && this.model.cluster.clusterTemplateRevisionId) {
        setProperties(this, {
          useClusterTemplate:        true,
          forceExpandOnInit:         true,
          selectedClusterTemplateId: this.cluster.clusterTemplateId,
        });
      }
  
      if (this.applyClusterTemplate) {
        if (this.useClusterTemplate) {
          this.initTemplateCluster();
        } else {
          this.initNonTemplateCluster();
        }
      } else {
        if (this.model.cluster.clusterTemplateRevisionId && this.router.currentRouteName !== 'global-admin.cluster-templates.new') {
          this.initTemplateCluster();
        } else {
          this.initNonTemplateCluster();
  
          if (this.clusterTemplateCreate && this.clusterTemplateQuestions && this.clusterTemplateQuestions.length > 0) {
            // cloned
            this.clusterTemplateQuestions.forEach((q) => {
              this.send('addOverride', true, { path: q.variable });
            })
          }
        }
      }
      setProperties(this, {
        scheduledClusterScan: {
          enabled:        false,
          scheduleConfig: {
            cronSchedule: '0 0 * * *',
            retention:    24
          },
          scanConfig: {
            cisScanConfig: {
              failuresOnly:             false,
              skip:                     null,
              profile:                  null,
              overrideBenchmarkVersion: null
            }
          }
        },
        upgradeStrategy:           {
          maxUnavailableControlplane: '',
          maxUnavailableWorker:       '',
          drain:                      'false',
          nodeDrainInput:             {}
        },
      });
      // This needs to be scheduled after render in order to wait for initTemplateCluster to setup the rkeconfig defaults.
      scheduleOnce('afterRender', this, this.setupComponent);
    },
  
    didReceiveAttrs() {
      if (this.applyClusterTemplate) {
        if (this.useClusterTemplate) {
          this.initTemplateCluster();
        } else {
          if (!this.clusterTemplateCreate && isEmpty(this.cluster.rancherKubernetesEngineConfig)) {
            this.initNonTemplateCluster();
          }
        }
      }
  
      if (this.useClusterTemplate && this.model.cluster.clusterTemplateRevisionId) {
        set(this, 'forceExpandAll', true);
      }
    },
  
    actions: {
      fileUploaded() {
        let { yamlValue } = this;
  
        next(() => {
          set(this, 'clusterOptErrors', this.checkYamlForRkeConfig(yamlValue));
        });
      },
  
      yamlValUpdated(yamlValue, codeMirror) {
        if (!this.codeMirror) {
          codeMirror.on('paste', this.pastedYaml.bind(this));
        }
        // this fires when we first change over so here is where you can set the paste watcher to validate
        setProperties(this, {
          yamlValue,
          codeMirror
        });
      },
  
      addOverride(enabled, paramsToOverride, hideQuestion = false) {
        let {
          primaryResource,
          clusterTemplateQuestions      = [],
        }                               = this;
        let { path }                    = paramsToOverride;
        let question                    = null;
        let questionsSchemas            = [];
        let { clusterTemplateRevision } = this.model;
  
        if (clusterTemplateRevision && ( this.clusterTemplates.questionsSchemas || []).length > 0) {
          questionsSchemas = this.clusterTemplates.questionsSchemas;
        }
  
        if (enabled) {
          if (!clusterTemplateQuestions.findBy('variable', path)) {
            if (path.startsWith('uiOverride')) {
              question = questionsSchemas.findBy('variable', path);
  
              if (!question) {
                question = this.globalStore.createRecord({
                  type:      'question',
                  variable:  path,
                  primaryResource,
                });
              }
  
              hideQuestion = true;
  
              setProperties(question, {
                type:    'boolean',
                default: true,
                hideQuestion
              });
  
              clusterTemplateQuestions.pushObject(question);
            } else {
              question = questionsSchemas.findBy('variable', path);
  
              if (question) {
                if (question.variable === 'rancherKubernetesEngineConfig.kubernetesVersion') {
                  question = this.parseKubernetesVersionSemVer(primaryResource, question);
                }
  
                if (question.variable.includes('azureCloudProvider')) {
                  let cloudProviderMatch = AzureInfo[(question.variable.split('.') || []).lastObject];
  
                  if (cloudProviderMatch) {
                    let {
                      required = false,
                      type = 'string'
                    } = cloudProviderMatch;
  
                    question = this.globalStore.createRecord({
                      type:      'question',
                      variable:  path,
                    });
  
                    setProperties(question, {
                      required,
                      type,
                    });
  
                    if (required) {
                      set(question, 'forceRequired', true);
                    }
                  }
                }
              } else {
                question = this.globalStore.createRecord({
                  type:      'question',
                  variable:  path,
                });
  
                set(question, 'type', 'string');
              }
  
              setProperties(question, {
                primaryResource,
                isBuiltIn: true,
                hideQuestion
              });
  
              if (path.includes('rancherKubernetesEngineConfig.privateRegistries[0]')) {
                // need to replace the array target with built in first object so the alias works
                path = path.replace('[0]', '.firstObject');
              }
  
              defineProperty(question, 'default', alias(`primaryResource.${ path }`));
  
              clusterTemplateQuestions.pushObject(question);
            }
          } else {
            // we've found the template override this wires up the value displayed in overrides section to the form value
            question = clusterTemplateQuestions.findBy('variable', path);
  
            setProperties(question, {
              primaryResource,
              isBuiltIn: true,
              hideQuestion
            });
  
            defineProperty(question, 'default', alias(`primaryResource.${ path }`));
          }
        } else {
          if (path === 'uiOverrideBackupStrategy') {
            question = clusterTemplateQuestions.findBy('variable', 'rancherKubernetesEngineConfig.services.etcd.backupConfig.s3BackupConfig');
  
            clusterTemplateQuestions.removeObject(question);
          } else {
            question = clusterTemplateQuestions.findBy('variable', path);
  
            clusterTemplateQuestions.removeObject(question);
          }
        }
  
        set(this, 'clusterTemplateQuestions', clusterTemplateQuestions);
      },
  
      setCpFields(configName, cloudProviderConfig) {
        set(this, `config.cloudProvider.${ configName }`, cloudProviderConfig);
      },
  
      cancel() {
        const newOpts = this.getOptsFromYaml();
  
        if (newOpts) {
          if (this.updateFromYaml && this.mode !== 'view') {
            this.updateFromYaml(newOpts);
            setProperties(this, {
              clusterOptErrors: [],
              pasteOrUpload:    false,
            });
          }
        } else {
          if (isEmpty(this.clusterOptErrors)) {
            set(this, 'pasteOrUpload', false);
          }
        }
      },
  
      showPaste() {
        if (!this.configBeforeYaml) {
          // need to store off a raw non-referneced version of the og config
          // if a user switches back to form view and then comes back to yaml to
          // move something to a lower indent or remove we want to only add whats in
          // the orignal config and waht is in the yaml.
          // add the key bloop: true to yaml, sswitch to form, wait no i want bloop nested under something else, comeback and move it, now you have two keys, one at root and dupe at new nest
          set(this, 'configBeforeYaml', this.primaryResource.clone());
        }
        set(this, 'pasteOrUpload', true);
      },
  
      addRegistry(pr) {
        const config = get(this, 'config')
  
        if (( config.privateRegistries || [] ).length <= 0) {
          set(config, 'privateRegistries', [pr]);
        } else {
          config.privateRegistries.pushObject(pr);
        }
      },
  
      removeRegistry(pr) {
        get(this, 'config.privateRegistries').removeObject(pr);
      },
  
      setTaints(taints) {
        set(this, 'taints', taints);
      }
    },
  
    windowsPreferedClusterChanged: observer('model.cluster.windowsPreferedCluster', function() {
      if (get(this, 'model.cluster.windowsPreferedCluster')) {
        set(this, 'scheduledClusterScan.enabled', 'false');
      }
    }),
  
    usingClusterTemplate: observer('useClusterTemplate', function() {
      if (!this.useClusterTemplate && this.model.cluster.clusterTemplateRevisionId) {
        set(this, 'clusterTemplateRevisionId', null);
      }
    }),
  
    driverDidChange: observer('nodeWhich', function() {
      this.createRkeConfigWithDefaults();
    }),
  
    isLinuxChanged: observer('isLinux', function() {
      if (get(this, 'nodeWhich') !== 'custom') {
        return
      }
  
      const isLinux = get(this, 'isLinux')
  
      if (!isLinux) {
        setProperties(this, {
          controlplane: false,
          etcd:         false,
          worker:       true,
        })
      }
    }),
  
    strategyChanged: observer('backupStrategy', function() {
      const { backupStrategy, globalStore } = this;
      const services = this.config.services.clone();
  
      switch (backupStrategy) {
      case 'local':
        if (services) {
          setProperties(services.etcd, {
            backupConfig: globalStore.createRecord({
              type:           'backupConfig',
              s3BackupConfig: null,
              enabled:        get(services, 'etcd.backupConfig.enabled') || true,
            })
          });
        }
        break;
      case 's3':
        if (services) {
          setProperties(services.etcd, {
            backupConfig: globalStore.createRecord({
              type:           'backupConfig',
              s3BackupConfig: globalStore.createRecord({ type: 's3BackupConfig' }),
              enabled:        get(services, 'etcd.backupConfig.enabled') || true,
            })
          });
        }
        break;
      default:
        break;
      }
  
      set(this, 'config.services', services);
    }),
  
    onCisScanBenchmarksUpdated: observer('cisHelpers.cisScanBenchmarks.[]', function() {
      this.initScheduledClusterScan();
    }),
  
  
    enforcementChanged: on('init', observer('settings.clusterTemplateEnforcement', function() {
      let {
        access:   { me: { hasAdmin: globalAdmin = null } },
        settings: { clusterTemplateEnforcement = false }
      } = this;
      let useClusterTemplate = false;
  
      if (!globalAdmin) {
        // setting is string value
        if (clusterTemplateEnforcement === 'true') {
          clusterTemplateEnforcement = true;
        } else {
          clusterTemplateEnforcement = false;
        }
  
        if (this.applyClusterTemplate) {
          if (clusterTemplateEnforcement) {
            useClusterTemplate = true;
          } else if (this.model.cluster.clusterTemplateRevisionId) {
            useClusterTemplate = true;
          }
        } else if (!this.applyClusterTemplate && clusterTemplateEnforcement) {
          useClusterTemplate = true;
        } else {
          if (this.model.cluster.clusterTemplateRevisionId) {
            useClusterTemplate = true;
          }
        }
  
        setProperties(this, {
          useClusterTemplate,
          clusterTemplatesEnforced: clusterTemplateEnforcement
        });
      }
    })),
  
    agentEnvVars: computed('cluster.agentEnvVars.[]', {
      get() {
        if (!this.cluster?.agentEnvVars) {
          // if undefined two way binding doesn't seem to work
          set(this, 'cluster.agentEnvVars', []);
        }
  
        return get(this, 'cluster.agentEnvVars') || [];
      },
      set(_key, value) {
        set(this, 'cluster.agentEnvVars', value);
  
        return value;
      },
    }),
  
    maxUnavailable: computed('upgradeStrategy.maxUnavailableUnit', 'upgradeStrategy.maxUnavailableWorker', function() {
      const value = { value: get(this, 'upgradeStrategy.maxUnavailableWorker') };
  
      const isPercentageUnits = get(this, 'upgradeStrategy.maxUnavailableUnit') === 'percentage';
  
      // Strip off the % at the end of the value, as we will add it again when we format it
      if (isPercentageUnits && (typeof value.value === 'string')) {
        const PERCENT_REGEX = /%+$/;
  
        value.value = value.value.replace(PERCENT_REGEX, '');
      }
  
      return isPercentageUnits
        ? get(this, 'intl').t('clusterNew.rke.upgradeStrategy.maximumWorkersDown.view.percentage', value)
        : get(this, 'intl').t('clusterNew.rke.upgradeStrategy.maximumWorkersDown.view.count', value)
    }),
  
    showUpgradeK8s21Warning: computed('initialVersion', 'config.kubernetesVersion', function() {
      const isLegacyEnabled = get(this, 'features').isFeatureEnabled(C.FEATURES.LEGACY)
  
      if (!isLegacyEnabled) {
        return false;
      }
  
      const selectedVersion = coerceVersion(get(this, 'config.kubernetesVersion'));
  
      return Semver.satisfies(selectedVersion, '>=1.21.0');
    }),
  
    showUpgradeK8sWarning: computed(
      'selectedClusterTemplateId',
      'applyClusterTemplate',
      'initialVersion',
      'model.clusterTemplateRevision.clusterConfig.rancherKubernetesEngineConfig.kubernetesVersion',
      function() {
        const {
          applyClusterTemplate,
          initialVersion,
          isEdit,
        } = this;
        const ctrK8sVersion = get(this, 'model.clusterTemplateRevision.clusterConfig.rancherKubernetesEngineConfig.kubernetesVersion');
  
        try {
          if (isEdit && applyClusterTemplate) {
            if (initialVersion && ctrK8sVersion && Semver.lt(initialVersion, ctrK8sVersion)) {
              return true;
            }
          }
        } catch (err) {
          return false;
        }
  
        return false;
      }),
  
    filteredClusterTemplates: computed('model.clusterTemplates.@each.{id,state,name,members}', function() {
      let { model: { clusterTemplates = [] } } = this;
  
      let mapped = clusterTemplates.map((clusterTemplate) => {
        return {
          name: clusterTemplate.name,
          id:   clusterTemplate.id,
        }
      });
  
      return mapped.sortBy('created').reverse();
    }),
  
    maxUnavailableNodesOptions: computed(function() {
      return [
        {
          name: this.intl.t('clusterNew.rke.upgradeStrategy.maximumWorkersDown.mode.percentage'),
          id:   'percentage'
        },
        {
          name: this.intl.t('clusterNew.rke.upgradeStrategy.maximumWorkersDown.mode.count'),
          id:   'count'
        }
      ];
    }),
  
    filteredTemplateRevisions: computed('cluster.type', 'isEdit', 'model.clusterTemplateRevisions.@each.{id,members,name,state}', 'selectedClusterTemplateId', 'setDefaultRevisionId', function() {
      let {
        selectedClusterTemplateId,
        clusterTemplateRevisionId = null,
        model: {
          clusterTemplateRevisions,
          clusterTemplates,
        },
        originalCluster,
        intl
      } = this;
      let clusterTemplate;
  
      clusterTemplateRevisions = clusterTemplateRevisions.slice().filterBy('enabled').filterBy('clusterTemplateId', selectedClusterTemplateId);
      clusterTemplate          = clusterTemplates.findBy('id', selectedClusterTemplateId)
  
      let mapped = clusterTemplateRevisions.map((clusterTemplateRevision) => {
        let d    = moment(clusterTemplateRevision.created);
        const isDefault = !isEmpty(clusterTemplate) ? clusterTemplate.defaultRevisionId === clusterTemplateRevision.id : false;
  
        let out = {
          id:   clusterTemplateRevision.id,
          name: intl.t(`clusterNew.rke.clustersSelectTemplateRevision.select.${ isDefault ? 'default' : 'other' }`, {
            name: clusterTemplateRevision.name,
            ago:  d.fromNow()
          }),
        };
  
        // editing a cluster not a template
        if (this.isEdit && this.cluster.type === 'cluster') {
          const originalKubeVersion = coerceVersion(get(originalCluster, 'rancherKubernetesEngineConfig.kubernetesVersion'));
          const rawRevisionKubeVersion = clusterTemplateRevision.clusterConfig.rancherKubernetesEngineConfig.kubernetesVersion;
          const revisionKubeVersion = coerceVersion(rawRevisionKubeVersion);
          const validRange = rawRevisionKubeVersion.endsWith('.x') && Semver.validRange(`<=${ rawRevisionKubeVersion }`);
          // Filter revisions with kube versions that are downgrades from the original kube version. We're using satisfies here so
          // that we can support semvers with a .x suffix. When the revision kube version is coerced and ends in .x (which signifies latest patch)
          // it gets coerced into .0 which makes the simple lt check erroenously think the revision version is less than the original version. We
          // still use lt just incase the rawRevisionKubeVersion isn't a valid range and needs to be coereced.
          const isRevisionKubeVersionCompatible =  validRange
            ? Semver.satisfies(originalKubeVersion, validRange)
            : Semver.lte(originalKubeVersion, revisionKubeVersion)
  
          if (!isRevisionKubeVersionCompatible) {
            set(out, 'disabled', true);
          }
        }
  
        return out;
      })
  
      if (clusterTemplate && clusterTemplateRevisionId === null ) {
        once(this, this.setDefaultRevisionId, clusterTemplate);
      }
  
      return mapped.sortBy('created').reverse();
    }),
  
    allTemplates: computed('model.clusterTemplates.[]', 'model.clusterTemplateRevisions.[]', function() {
      const remapped = [];
      let { clusterTemplates, clusterTemplateRevisions } = this.model;
  
      clusterTemplateRevisions = clusterTemplateRevisions.filterBy('enabled');
  
      clusterTemplateRevisions.forEach((rev) => {
        let match = clusterTemplates.findBy('id', get(rev, 'clusterTemplateId'));
  
        if (match) {
          remapped.pushObject({
            clusterTemplateId:           get(match, 'id'),
            clusterTemplateName:         get(match, 'displayName'),
            clusterTemplateRevisionId:   get(rev, 'id'),
            clusterTemplateRevisionName: get(rev, 'name'),
          });
        }
      });
  
      return remapped;
    }),
  
    canEditForm: computed('clusterOptErrors.[]', function() {
      return (this.clusterOptErrors || []).length === 0;
    }),
  
    kubeApiPodSecurityPolicy: computed('config.services.kubeApi.podSecurityPolicy', {
      get() {
        let pspConfig = get(this, 'config.services.kubeApi');
  
        if (typeof  pspConfig === 'undefined') {
          return false;
        }
  
        return get(pspConfig, 'podSecurityPolicy');
      },
      set(key, value) {
        if (typeof get(this, 'config.services') === 'undefined') {
          set(this, 'config.services', get(this, 'globalStore').createRecord({
            type:     'rkeConfigServices',
            kubeApi: get(this, 'globalStore').createRecord({
              type:              'kubeAPIService',
              podSecurityPolicy: value,
            }),
          }));
        } else {
          set(this, 'config.services', { kubeApi: { podSecurityPolicy: value } });
        }
  
        return value;
      }
    }),
  
    monitoringProvider: computed('config.monitoring', {
      get() {
        let monitoringConfig = get(this, 'config.monitoring');
  
        if (typeof  monitoringConfig === 'undefined') {
          return null;
        }
  
        return get(monitoringConfig, 'provider');
      },
      set(key, value) {
        if (typeof get(this, 'config.monitoring') === 'undefined') {
          set(this, 'config.monitoring', get(this, 'globalStore').createRecord({
            type:     'monitoringConfig',
            provider: value
          }));
        } else {
          set(this, 'config.monitoring', { provider: value });
        }
  
        return value;
      }
    }),
  
    nginxIngressProvider: computed('config.ingress', {
      get() {
        let ingressConfig = get(this, 'config.ingress');
  
        if (typeof  ingressConfig === 'undefined') {
          return null;
        }
  
        return get(ingressConfig, 'provider');
      },
      set(key, value) {
        if (typeof get(this, 'config.ingress') === 'undefined') {
          set(this, 'config.ingress', get(this, 'globalStore').createRecord({
            type:     'ingressConfig',
            provider: value
          }));
        } else {
          set(this, 'config.ingress', { provider: value });
        }
  
        return value;
      }
    }),
  
    versionChoices: computed(`settings.${ C.SETTING.VERSIONS_K8S }`, 'clusterTemplateCreate', function() {
      let out = JSON.parse(get(this, `settings.${ C.SETTING.VERSIONS_K8S }`) || '{}');
  
      out = Object.keys(out);
  
      let patchedOut = [];
  
      if (this.clusterTemplateCreate) {
        patchedOut = out.map((version) => {
          return `${ major(version) }.${ minor(version) }.x`;
        });
      }
  
      return [...out, ...patchedOut];
    }),
  
    newNodeCount: computed('initialNodeCounts', 'primaryResource.id', 'existingNodes.@each.clusterId', function() {
      let clusterId = get(this, 'primaryResource.id');
      let orig      = get(this, 'initialNodeCounts')[clusterId] || 0;
      let cur       = get(this, 'existingNodes').filterBy('clusterId', clusterId).length
  
      if ( cur < orig ) {
        orig = cur;
        set(get(this, 'initialNodeCounts'), clusterId, cur)
      }
  
      return cur - orig;
    }),
  
    yamlValue: computed('isEdit', 'pasteOrUpload', 'primaryResource', {
      get() {
        // On edit we should get the cluster fields that are updateable, any fields added during the creation would need the cluster fields at the time
        const intl     = get(this, 'intl');
        let config     = this.isEdit ? this.getSupportedFields(get(this, 'primaryResource'), 'cluster', EXCLUED_CLUSTER_OPTIONS) : this.getClusterFields(this.primaryResource);
        let cpConfig = get(config, 'rancher_kubernetes_engine_config.cloud_provider');
  
        config = removeEmpty(config, EXCLUDED_KEYS, EXCLUDED_CHILDREN_KEYS);
        // get rid of undefined
        config = JSON.parse(JSON.stringify(config));
  
  
        if ( cpConfig ) {
          cpConfig = removeEmpty(cpConfig, EXCLUDED_KEYS, EXCLUDED_CHILDREN_KEYS);
          if (cpConfig.name === 'aws' && !cpConfig.awsCloudProvider) {
            config.rancher_kubernetes_engine_config.cloud_provider = {
              ...config.rancher_kubernetes_engine_config.cloud_provider,
              type:             'cloudProvider',
              awsCloudProvider: { type: 'awsCloudProvider' },
            };
          } else if (cpConfig.azureCloudProvider) {
            // this is a quick and dirty fix for azure only because it is currently the only cp that works
            // this whole process will be recieving updates shortly so this is a temp fix
            // client_id, secret, & subscription_id are all required so ensure they are there on NEW when a user has entered them but switched to yaml or edit
            // removeEmpty works great except for these fields and adding nested paths doesn't work in removeEmpty
            Object.assign(config.rancher_kubernetes_engine_config.cloud_provider.azureCloudProvider, {
              'aad_client_cert_password': cpConfig.azureCloudProvider.aad_client_cert_password || '',
              'aad_client_id':            cpConfig.azureCloudProvider.aad_client_id || '',
              'aad_client_secret':        cpConfig.azureCloudProvider.aad_client_secret || '',
              'tenant_id':                cpConfig.azureCloudProvider.tenant_id || '',
              'subscription_id':          cpConfig.azureCloudProvider.subscription_id || '',
            });
          }
        }
  
  
        let yaml  = jsyaml.safeDump(config, { sortKeys: true });
        let lines = yaml.split('\n');
        let out   = '';
  
        lines.forEach((line, idx) => {
          if ( line.trim() ) {
            if (!this.isEdit) {
              let key          = '';
              let commentLines = '';
  
              if (idx === 0) {
                commentLines = intl.t('').split('\n');
                key = `rkeConfigComment.clusterConfig`
              } else {
                key = `rkeConfigComment.${ line.split(':')[0].trim() }`
              }
  
              if ( intl.exists(key) ) {
                commentLines = intl.t(key).split('\n');
  
                commentLines.forEach((commentLine) => {
                  out += `# ${ commentLine.slice(1, commentLine.length - 1) }\n`;
                });
              }
            }
            out += `${ line.trimEnd() }\n`;
          }
        });
  
        return out;
      },
  
      set(key, value) {
        next(() => {
          set(this, 'clusterOptErrors', this.checkYamlForRkeConfig(value));
        });
  
        return value;
      }
    }),
  
    allErrors: computed('errors.[]', 'clusterErrors.[]', 'otherErrors.[]', 'clusterOptErrors.[]', function() {
      let {
        errors, clusterErrors, clusterOptErrors, otherErrors,
      } = this;
  
      return [...(errors || []), ...(clusterErrors || []), ...(clusterOptErrors || []), ...(otherErrors || [])];
    }),
  
  
    cisProfileDisplay: computed('cisProfile', 'cisScanProfileOptions', function() {
      const profile = get(this, 'cisScanProfileOptions').find((option) => option.value === get(this, 'cisProfile'));
  
      return profile ? profile.label : '';
    }),
  
    cisScanProfileOptions: computed('cisProfile', 'cisHelpers.cisScanProfileOptions', function() {
      const selectedProfile = get(this, 'cisProfile')
      const options = get(this, 'cisHelpers.cisScanProfileOptions');
      const foundProfile = options.find((option) => option.value === selectedProfile);
  
      if (!foundProfile && selectedProfile) {
        const splitProfile = selectedProfile.split(' ');
  
        splitProfile[1] = ucFirst(splitProfile[1]);
        const missingOption = {
          label: splitProfile.join(' '),
          value: splitProfile
        }
  
        return [missingOption, ...options];
      }
  
      return options;
    }),
  
    provider: computed('model.cluster.clusterProvider', 'model.provider', function() {
      return get(this, 'model.provider') ? get(this, 'model.provider') : get(this, 'model.cluster.clusterProvider');
    }),
  
    initScheduledClusterScan() {
      // We need to wait for benchmarks to be available before we can actually create the profiles
      if (this.cisHelpers.cisScanBenchmarks.length === 0) {
        return;
      }
  
      const kubernetesVersion = get(this, 'primaryResource.rancherKubernetesEngineConfig.kubernetesVersion');
      const defaultProfileOption = this.cisHelpers.getDefaultCisScanProfileOption(kubernetesVersion);
      const scheduledClusterScan = get(this, 'primaryResource.scheduledClusterScan') || get(this, 'scheduledClusterScan');
      const scanConfig = get(this, 'primaryResource.scheduledClusterScan.scanConfig') || this.cisHelpers.profileToClusterScanConfig(defaultProfileOption);
      const scheduleConfig = get(this, 'primaryResource.scheduledClusterScan.scheduleConfig') ||  get(this, 'scheduledClusterScan.scheduleConfig');
      const cisProfile =  this.cisHelpers.clusterScanConfigToProfile(scanConfig);
      const enabled = get(this, 'primaryResource.scheduledClusterScan.enabled');
  
      set(this, 'scheduledClusterScan', scheduledClusterScan);
      set(this, 'scheduledClusterScan.enabled', typeof enabled === 'string' ? enabled === 'true' : !!enabled);
      set(this, 'scheduledClusterScan.scanConfig', scanConfig);
      set(this, 'scheduledClusterScan.scheduleConfig', scheduleConfig);
      set(this, 'cisProfile', cisProfile);
    },
  
    getDefaultUpgradeStrategy() {
      return this.globalStore.createRecord({
        type:           'nodeUpgradeStrategy',
        nodeDrainInput: this.globalStore.createRecord({ type: 'nodeDrainInput' })
      });
    },
  
    initUpgradeStrategy() {
      const upgradeStrategy = get(this, 'primaryResource.rancherKubernetesEngineConfig.upgradeStrategy');
      const defaultUpgradeStrategy = this.getDefaultUpgradeStrategy();
      const source = upgradeStrategy
        ? upgradeStrategy
        : defaultUpgradeStrategy;
  
      const maxUnavailableControlplane = get(source, 'maxUnavailableControlplane');
      const maxUnavailableWorker = get(source, 'maxUnavailableWorker') || '';
      const maxUnavailableUnit = maxUnavailableWorker.includes('%') ? 'percentage' : 'count';
      const drainInput = get(source, 'nodeDrainInput');
      const timeout = get(source, 'nodeDrainInput.timeout');
      const drainStatusStr = (get(source, 'drain') || false).toString();
  
      set(this, 'upgradeStrategy.maxUnavailableControlplane', maxUnavailableControlplane);
      set(this, 'upgradeStrategy.maxUnavailableWorker', maxUnavailableWorker.replace('%', ''));
      set(this, 'upgradeStrategy.maxUnavailableUnit', maxUnavailableUnit);
      set(this, 'upgradeStrategy.nodeDrainInput', drainInput ? drainInput : {});
      set(this, 'upgradeStrategy.drain', drainStatusStr);
  
      // This ensures the UI won't initialize the timeout below the minimum.
      if (timeout !== undefined && timeout <= 0) {
        const defaultTimeout = get(defaultUpgradeStrategy, 'nodeDrainInput.timeout');
  
        set(this, 'upgradeStrategy.nodeDrainInput.timeout', defaultTimeout);
      }
    },
  
    getClusterFields(primaryResource) {
      let pojoPr = JSON.parse(JSON.stringify(removeEmpty(primaryResource, EXCLUDED_KEYS, EXCLUDED_CHILDREN_KEYS)));
      let decamelizedObj = {};
  
      decamelizedObj = keysToDecamelize(pojoPr, void (0), ['type', 'azureCloudProvider']);
  
      return decamelizedObj;
    },
  
    checkYamlForRkeConfig(yamlValue) {
      let decamledYaml = this.parseOptsFromYaml(yamlValue);
      let errOut = null;
  
      if (decamledYaml && isEmpty(decamledYaml.rancherKubernetesEngineConfig)) {
        errOut = [`Cluster Options Parse Error: Missing Rancher Kubernetes Engine Config`];
      }
  
      return errOut;
    },
  
    pastedYaml(cm) {
      next(() => {
        set(this, 'clusterOptErrors', this.checkYamlForRkeConfig(cm.doc.getValue()));
      });
    },
  
    parseOptsFromYaml(yamlValue) {
      let yamlConfig;
  
      try {
        yamlConfig = jsyaml.safeLoad(yamlValue);
      } catch ( err ) {
        set(this, 'clusterOptErrors', [`Cluster Options Parse Error: ${ err.snippet } - ${ err.message }`]);
  
        return;
      }
  
      return keysToCamel(yamlConfig);
    },
  
    getOptsFromYaml() {
      let { yamlValue } = this;
      let decamledYaml = this.parseOptsFromYaml(yamlValue);
  
      if (!decamledYaml){
        return;
      }
  
      if (decamledYaml && isEmpty(decamledYaml.rancherKubernetesEngineConfig)) {
        set(this, 'clusterOptErrors', [`Cluster Options Parse Error: Missing rancher_kubernetes_engine_config key`]);
  
        return;
      }
  
      decamledYaml.type = this.primaryResource.type;
  
      return this.globalStore.createRecord(decamledYaml);
    },
  
    buildClusterAnswersFromConfig(cluster, questions) {
      let { backupStrategy } = this;
      let answers            = {};
  
      if (questions && questions.length) {
        questions.forEach((quest) => {
          // ember.object.get doesn't support bracket notation, this provides support for the notation by replace some[0].object with some.0.object
          // which ember does support. I'm putting this here instead of replacing the answer variables because I think it's more intuitive to specify
          // variables with the bracket notation.
          const varaible = quest.variable.replaceAll('[', '.').replaceAll(']', '');
          let match = get(cluster, varaible);
  
          if (match) {
            answers[quest.variable] = match;
          } else {
            if (quest.variable.includes('s3BackupConfig') && backupStrategy === 'local') {
              // we get into this case when a RKE Template creator lets the user override the backup strategy, and they've changed it to local.
              // if we dont send the answers with nulls the s3backupconfig will be created on the backend from its existence on the RKE Template revision
              answers[quest.variable] = null;
            }
          }
        });
      }
  
      return answers;
    },
  
    willSave() {
      const {
        applyClusterTemplate,
        cluster,
        configField: field,
      } = this;
      let ok            = true;
  
      this.checkKubernetesVersionSemVer();
  
      if (get(this, 'upgradeStrategy.maxUnavailableUnit') === 'percentage') {
        set(this, 'upgradeStrategy.maxUnavailableWorker', (`${ get(this, 'upgradeStrategy.maxUnavailableWorker')  }%`));
      }
  
      if (!get(this, 'upgradeStrategy.drain')) {
        set(this, 'upgradeStrategy.nodeDrainInput', null);
      }
      set(cluster, 'rancherKubernetesEngineConfig.upgradeStrategy', get(this, 'upgradeStrategy'));
  
      if (get(this, 'scheduledClusterScan.enabled')) {
        try {
          set(this, 'scheduledClusterScan.scanConfig', this.cisHelpers.profileToClusterScanConfig(get(this, 'cisProfile')));
        } catch (ex) {
          set(this, 'scheduledClusterScan.scanConfig', null);
          console.error('There was a problem attempting to map a profile to a clusterScanConfig', ex, get(this, 'cisProfile'));
        }
      } else {
        set(this, 'scheduledClusterScan.scanConfig', null);
        set(this, 'scheduledClusterScan.scheduleConfig', null);
      }
      set(cluster, 'scheduledClusterScan', get(this, 'scheduledClusterScan'));
  
      set(this, 'isWindowsPreferedCluster', cluster?.windowsPreferedCluster);
  
      if (this.pasteOrUpload && this.mode !== 'view') {
        const newOpts = this.getOptsFromYaml();
  
        if (newOpts) {
          if (this.updateFromYaml) {
            this.updateFromYaml(newOpts);
          }
        } else {
          return false;
        }
      }
  
      if (get(cluster, 'localClusterAuthEndpoint')) {
        if (!get(cluster, 'rancherKubernetesEngineConfig') || isEmpty(get(cluster, 'rancherKubernetesEngineConfig'))) {
          delete cluster.localClusterAuthEndpoint;
        }
      }
  
      set(this, 'errors', null);
  
      ok = this.validate();
  
      if (ok) {
        if (typeOf(cluster.clearProvidersExcept) === 'function' || applyClusterTemplate && typeOf(this.buildClusterAnswersFromConfig) === 'function') {
          if (applyClusterTemplate) {
            let questions = get(this, 'model.clusterTemplateRevision.questions') || [];
            let answers   = [];
            let errors    = null;
  
            answers    = this.buildClusterAnswersFromConfig(cluster, questions);
  
            if (questions.length > 0) {
              errors = this.checkRequiredQuestionsHaveAnswers(questions, answers);
            }
  
            // When creating from an RKE Cluster Template with weave networking, add provider password
            const networkType = get(cluster, C.NETWORK_QUESTIONS.PLUGIN);
  
            if (networkType === WEAVE) {
              const provider = get(cluster, C.NETWORK_QUESTIONS.WEAVE_NETWORK_PROVIDER);
  
              if (provider) {
                answers[C.NETWORK_QUESTIONS.WEAVE_PASSWORD] = randomStr(16, 16, 'password');
              }
            }
  
            if (isEmpty(errors)) {
              set(cluster, 'answers', { values: answers });
  
              this.cluster.clearConfigFieldsForClusterTemplate();
            } else {
              set(this, 'errors', errors);
  
              ok = false;
            }
          } else {
            cluster.clearProvidersExcept(field);
          }
        }
      } else {
        // If validation fails, we stay on the form, so we need to undo the formatting of the max unavailable worker
        // otherwise we keep adding a '%' to the input field
        const maxUnavailableWorker = get(this, 'upgradeStrategy.maxUnavailableWorker');
  
        set(this, 'upgradeStrategy.maxUnavailableWorker', maxUnavailableWorker.replace('%', ''));
      }
  
      return ok;
    },
  
    checkRequiredQuestionsHaveAnswers(questions, answers) {
      const { intl } = this;
      const required = questions.filterBy('required', true);
      const errors   = [];
  
      if (questions.length > 0 && required.length > 0) {
        required.forEach((rq) => {
          if (!answers[rq.variable]) {
            errors.push(intl.t('validation.required', { key: rq.variable }));
          }
        })
      }
  
      return errors;
    },
  
    validate() {
      let { config, intl } = this;
  
      this._super(...arguments);
  
      let errors = this.errors || [];
  
      if (this.clusterTemplateCreate) {
        const revision = this.model.clusterTemplateRevision;
  
        if ( revision ) {
          errors.pushObjects(revision.validationErrors());
  
          const cloudProvider = get(revision, 'clusterConfig.rancherKubernetesEngineConfig.cloudProvider.name');
          const azureProvider = get(revision, 'clusterConfig.rancherKubernetesEngineConfig.cloudProvider.azureCloudProvider') || {};
  
          if ( cloudProvider === 'azure' ) {
            const azureQuestions = (get(revision, 'questions') || []).map((x) => x.variable.replace(/^rancherKubernetesEngineConfig\.cloudProvider\.azureCloudProvider\./, ''));
            const requiredFields = Object.keys(AzureInfo).filter((k) => AzureInfo[k].required);
  
            requiredFields.forEach((key) => {
              if ( !get(azureProvider, key) && !azureQuestions.includes(key)) {
                errors.push(intl.t('validation.requiredOrOverride', { key }));
              }
            });
          }
        }
      } else {
        if ( !get(this, 'isCustom') ) {
          errors.pushObjects(get(this, 'nodePoolErrors'));
        }
  
        if ( get(config, 'cloudProvider.name') === 'azure' && !this.applyClusterTemplate ) {
          Object.keys(AzureInfo).forEach((key) => {
            if ( get(AzureInfo, `${ key }.required`) && !get(config, `cloudProvider.azureCloudProvider.${ key }`)) {
              if ( this.isNew || this.isEdit && key !== 'aadClientSecret' ) {
                errors.push(intl.t('validation.required', { key }));
              }
            }
          });
        }
      }
  
      if ( get(config, 'services.kubeApi.podSecurityPolicy') &&
           !get(this, 'primaryResource.defaultPodSecurityPolicyTemplateId') ) {
        errors.push(intl.t('clusterNew.psp.required'));
      }
  
      if (get(this, 'config.services.etcd.snapshot')) {
        errors = this.validateEtcdService(errors);
      }
  
      if ( get(this, 'primaryResource.localClusterAuthEndpoint.enabled') ) {
        errors = this.validateAuthorizedClusterEndpoint(errors);
      }
  
      set(this, 'errors', errors);
  
      return get(this, 'errors.length') === 0;
    },
  
    validateAuthorizedClusterEndpoint(errors) {
      let { localClusterAuthEndpoint } = get(this, 'primaryResource');
      let { caCerts, fqdn } = localClusterAuthEndpoint;
  
      if (caCerts) {
        if (!validateCertWeakly(caCerts) ) {
          errors.push(this.intl.t('newCertificate.errors.cert.invalidFormat'));
        }
      }
  
      if (fqdn) {
        errors = validateHostname(fqdn, 'FQDN', get(this, 'intl'), { restricted: true }, errors);
      }
  
      return errors;
    },
  
    validateEtcdService(errors) {
      const etcdService             = get(this, 'config.services.etcd') || {};
      const { creation, retention } = etcdService;
      const that = this;
  
      function checkDurationIsValid(duration, type) {
        // exact matching on these inputs
        // patternList = 12h12m12s || 12h12m || 12m12s  || 12h12s || 12h || 12m || 12s
        let patternList = [/^(\d+)(h)(\d+)(m)(\d+)(s)$/, /^(\d+)(h)(\d+)(m)$/, /^(\d+)(m)(\d+)(s)$/, /^(\d+)(h)(\d+)(s)$/, /^(\d+)(h)$/, /^(\d+)(m)$/, /^(\d+)(s)$/];
        let match       = patternList.filter( (p) => p.test(duration) );
  
        if (match.length === 0) {
          durationError(duration, type);
        }
  
        return;
      }
  
      function durationError(entry, type) {
        return errors.push(get(that, 'intl').t('clusterNew.rke.etcd.error', {
          type,
          entry
        }));
      }
  
      checkDurationIsValid(creation, 'Creation');
      checkDurationIsValid(retention, 'Reteintion');
  
  
      return errors;
    },
  
    doneSaving(neu) {
      let { close } = this;
  
      if ( get(this, 'isCustom') ) {
        this.loadToken();
      } else {
        if (close) {
          close(neu);
        }
      }
  
      return resolve();
    },
  
    loadToken() {
      const cluster = get(this, 'primaryResource');
  
      if (cluster.getOrCreateToken) {
        set(this, 'loading', true);
  
        return cluster.getOrCreateToken().then((token) => {
          if ( this.isDestroyed || this.isDestroying ) {
            return;
          }
  
          setProperties(this, {
            token,
            step:    2,
            loading: false
          });
        }).catch((err) => {
          if ( this.isDestroyed || this.isDestroying ) {
            return;
          }
  
          get(this, 'growl').fromError('Error getting command', err);
  
          set(this, 'loading', false);
        });
      } else {
        return;
      }
    },
  
    findExcludedKeys(resourceFields) {
      Object.keys(resourceFields).forEach((key) => {
        const type = resourceFields[key].type;
  
        if ( type.startsWith('map[') ) {
          const t = type.slice(4, type.length - 1);
          const s = get(this, 'globalStore').getById('schema', t.toLowerCase());
  
          if ( s ) {
            const underlineKey = camelToUnderline(key);
  
            if ( EXCLUDED_KEYS.indexOf(underlineKey) === -1 ) {
              EXCLUDED_KEYS.push(underlineKey);
            }
          }
        }
      });
    },
  
    getResourceFields(type) {
      const schema = get(this, 'globalStore').getById('schema', type.toLowerCase());
      let resourceFields = null;
  
      if ( schema ) {
        resourceFields = get(schema, 'resourceFields');
        this.findExcludedKeys(resourceFields);
      }
  
      return resourceFields;
    },
  
    getFieldValue(field, type) {
      let resourceFields;
      const out = {};
  
      if (type === 'map[json]' && get(( field || {} ), 'Resources') && get(( field || {} ), 'kind') === 'EncryptionConfiguration') {
        // this is a one off change to handle rancherKubernetesEngineConfig.services.kubeAPI.secretsEncryptionConfig.customConfig arbitrary JSON problem seeing in #30555.
        return field;
      }
  
      if ( type.startsWith('map[') ) {
        type = type.slice(4, type.length - 1);
  
        resourceFields = this.getResourceFields(type);
  
        if ( resourceFields ) {
          if ( field ) {
            Object.keys(field).forEach((key) => {
              out[camelToUnderline(key)] = this.getFieldValue(field[key], type);
            });
  
            return out;
          } else {
            return null;
          }
        } else {
          if ( field ) {
            Object.keys(field).forEach((key) => {
              out[camelToUnderline(key)] = field[key];
            });
  
            return out;
          } else {
            return null;
          }
        }
      } else if ( type.startsWith('array[') ) {
        type = type.slice(6, type.length - 1);
  
        resourceFields = this.getResourceFields(type);
  
        if ( resourceFields ) {
          return field ? field.map((item) => this.getFieldValue(item, type)) : null;
        } else {
          return field ? field.map((item) => item) : null;
        }
      } else {
        resourceFields = this.getResourceFields(type);
  
        if ( resourceFields ) {
          Object.keys(resourceFields).forEach((key) => {
            if ( !isEmpty(field) && (typeof field !== 'object' || Object.keys(field).length ) ) {
              out[camelToUnderline(key, type !== 'rkeConfigServices')] = this.getFieldValue(field[key], resourceFields[key].type);
            }
          });
  
          return out;
        } else {
          return field;
        }
      }
    },
  
    getSupportedFields(source, targetField, excludeFields = []) {
      const out = {};
      const resourceFields = this.getResourceFields(targetField);
  
      Object.keys(resourceFields).filter((key) => resourceFields[key].update && excludeFields.indexOf(key) === -1).forEach((key) => {
        const field = get(source, key);
        const type = resourceFields[key].type;
        const value = this.getFieldValue(field, type);
  
        out[camelToUnderline(key)] = value;
      });
  
      return out;
    },
  
    initNonTemplateCluster() {
      if ( get(this, 'isNew') ) {
        this.createRkeConfigWithDefaults();
      } else {
        this.initPrivateRegistries();
      }
  
      this.initBackupConfigs();
  
      scheduleOnce('afterRender', this, this.initRootDockerDirectory);
    },
  
    initTemplateCluster() {
      this.initBackupConfigs();
  
      this.initClusterTemplateQuestions();
    },
  
    initNodeCounts() {
      const counts = {};
      let initialVersion = get(this, 'config.kubernetesVersion');
  
      set(this, 'existingNodes', this.globalStore.all('node'));
  
      this.globalStore.findAll('node').then((all) => {
        all.forEach((node) => {
          const id = get(node, 'clusterId');
  
          counts[id] = (counts[id] || 0) + 1;
        });
  
        this.notifyPropertyChange('initialNodeCounts');
      });
  
      if (this.isEdit && !isEmpty(get(this, 'cluster.appliedSpec.rancherKubernetesEngineConfig.kubernetesVersion'))) {
        initialVersion = this.cluster.appliedSpec.rancherKubernetesEngineConfig.kubernetesVersion;
      }
  
      setProperties(this, {
        initialVersion,
        initialNodeCounts: counts,
      })
    },
  
    initPrivateRegistries() {
      const config = get(this, 'config');
  
      if ( get(config, 'privateRegistries.length') > 0 ) {
        const registry = get(config, 'privateRegistries.firstObject');
  
        setProperties(this, {
          registry:     'custom',
          registryUrl:  get(registry, 'url'),
          registryUser: get(registry, 'user'),
          registryPass: get(registry, 'password'),
        });
      }
    },
  
    createRkeConfigWithDefaults() {
      const { globalStore, versionChoices, } = this;
      const defaultVersion                   = get(this, `settings.${ C.SETTING.VERSION_SYSTEM_K8S_DEFAULT_RANGE }`);
      const satisfying                       = maxSatisfying(versionChoices, defaultVersion);
      const out                              = {};
  
      const rkeConfig = globalStore.createRecord({
        type:                'rancherKubernetesEngineConfig',
        ignoreDockerVersion: true,
        kubernetesVersion:   satisfying,
        authentication:      globalStore.createRecord({
          type:     'authnConfig',
          strategy: 'x509',
        }),
        dns: globalStore.createRecord({
          type:      'dnsConfig',
          nodelocal: globalStore.createRecord({
            type:            'nodelocal',
            ip_address:      '',
            node_selector:   null,
            update_strategy: {},
          })
        }),
        network: globalStore.createRecord({
          type:    'networkConfig',
          plugin:  'canal',
          options: { flannel_backend_type: DEFAULT_BACKEND_TYPE, },
        }),
        ingress: globalStore.createRecord({
          type:           'ingressConfig',
          provider:       'nginx',
          defaultBackend: false,
        }),
        monitoring: globalStore.createRecord({
          type:     'monitoringConfig',
          provider: 'metrics-server',
        }),
        services: globalStore.createRecord({
          type:    'rkeConfigServices',
          kubeApi: globalStore.createRecord({
            type:                    'kubeAPIService',
            podSecurityPolicy:       false,
            serviceNodePortRange:    '30000-32767',
            secretsEncryptionConfig: globalStore.createRecord({ type: 'secretsEncryptionConfig' })
          }),
          etcd: globalStore.createRecord({
            type:         'etcdService',
            snapshot:     false,
            backupConfig: globalStore.createRecord({
              type:    'backupConfig',
              enabled: true,
            }),
            extraArgs:    {
              'heartbeat-interval': 500,
              'election-timeout':   5000
            },
          }),
        }),
        upgradeStrategy: this.getDefaultUpgradeStrategy()
      });
  
      setProperties(out, {
        rancherKubernetesEngineConfig: rkeConfig,
        enableNetworkPolicy:           false
      });
  
      if (this.isNew) {
        set(out, 'localClusterAuthEndpoint', globalStore.createRecord({
          type:    'localClusterAuthEndpoint',
          enabled: true
        }));
      }
  
      scheduleOnce('afterRender', this, this.notifyConfigUpdate, out);
    },
  
    notifyConfigUpdate(out) {
      setProperties(this.primaryResource, out);
  
      this.notifyPropertyChange('config');
      console.log('config updated');
    },
  
    migrateLegacyEtcdSnapshotSettings() {
      const { config } = this;
      let {
        retention, creation, backupConfig
      } = config.services.etcd;
  
      let creationMatch = creation.match(/^((\d+)h)?((\d+)m)?((\d+)s)?$/);
      let momentReady   = [creationMatch[2], creationMatch[4], creationMatch[6]];
  
      if (momentReady[1] && momentReady[1] < 30) {
        // round min down since new settting is in intval hours
        momentReady[1] = 0;
      } else  if (momentReady[1] && momentReady[1] >= 30) {
        // round up to the nearest hour
        momentReady[0] = parseInt(momentReady[0], 10) + 1;
        momentReady[1] = 0;
      }
  
      if (( !momentReady[0] || momentReady[0] === 0 ) && momentReady[1] === 0) {
        // if both hours and min are zero set hour to 1;
        momentReady[0] = 1;
      }
  
      let toMoment      = {
        hours:   momentReady[0] ? momentReady[0] : 0,
        minutes: momentReady[1] ? momentReady[1] : 0,
        seconds: momentReady[2] ? momentReady[2] : 0,
      };
  
      const parsedDurationAsHours = moment.duration(toMoment).hours();
  
      setProperties(this, {
        legacyRetention:           retention,
        hasLegacySnapshotSettings: true,
      });
  
      if (backupConfig) {
        setProperties(config.services.etcd, {
          'backupConfig.enabled':       true,
          'backupConfig.intervalHours': parsedDurationAsHours,
          snapshot:                     false,
        });
      } else {
        backupConfig = this.globalStore.createRecord({
          type:          'backupConfig',
          intervalHours: parsedDurationAsHours,
          enabled:       true,
        });
  
        setProperties(config.services.etcd, {
          backupConfig,
          snapshot: false,
        });
      }
    },
  
    initRootDockerDirectory() {
      set(this, 'defaultDockerRootDir', get(this.globalStore.getById('schema', 'cluster').getCreateDefaults(), 'dockerRootDir'))
    },
  
  
    initBackupConfigs() {
      const etcd = get(this, 'config.services.etcd');
  
      if (etcd) {
        if (etcd.snapshot) {
          set(this, 'backupStrategy', 'local');
          this.migrateLegacyEtcdSnapshotSettings();
        } else if (etcd.backupConfig && etcd.backupConfig.s3BackupConfig) {
          set(this, 'backupStrategy', 's3');
        } else if (!etcd.snapshot && !etcd.backupConfig) {
          const backupConfig = get(this, 'globalStore').createRecord({
            enabled: false,
            type:    'backupConfig',
          });
  
          set(etcd, 'backupConfig', backupConfig)
        } else {
          set(this, 'backupStrategy', 'local');
        }
      }
    },
  
    initClusterTemplateQuestions() {
      let { clusterTemplateQuestions } = this;
  
      // Use the existing cluster config values as defaults for the questions
      // because one or more values could already be overridden.
      let primaryResource = get(this, 'config');
  
      if (clusterTemplateQuestions && clusterTemplateQuestions.length > 0 && primaryResource) {
        clusterTemplateQuestions.forEach((question) => {
          let path = question.variable;
  
          if (!question.variable.includes('uiOverride') && question.default) {
            deepSet(primaryResource, path, question.default);
          }
  
          set(question, 'primaryResource', primaryResource);
  
          defineProperty(question, 'default', alias(`primaryResource.${ path }`));
        });
      }
    },
  
    checkKubernetesVersionSemVer() {
      let {
        clusterTemplateCreate = false,
        config : { kubernetesVersion },
        model: { clusterTemplateRevision = {} }
      } = this;
      let questions = [];
  
      if (clusterTemplateRevision && clusterTemplateRevision.questions) {
        questions = clusterTemplateRevision.questions;
      }
  
      const kubernetesVersionOverrideExists = questions.findBy('variable', 'rancherKubernetesEngineConfig.kubernetesVersion');
  
      if (clusterTemplateCreate) {
        if (kubernetesVersionOverrideExists) {
          if (kubernetesVersionOverrideExists.satisfies) {
            // we have a user defined override and this means we dont need satisfies because it can be anything
            delete kubernetesVersionOverrideExists.satisfies;
          }
        } else {
          if (kubernetesVersion.endsWith('x')) {
            this.send('addOverride', true, { path: 'rancherKubernetesEngineConfig.kubernetesVersion' }, true);
          }
        }
      }
    },
  
    parseKubernetesVersionSemVer(cluster, question) {
      let { kubernetesVersion } = cluster.rancherKubernetesEngineConfig;
      let coercedVersion        = Semver.coerce(kubernetesVersion);
      let satisfies             = `>=${ coercedVersion.major }.${ coercedVersion.minor }.${ coercedVersion.patch } <${ coercedVersion.major }.${ coercedVersion.minor + 1 }`
  
      set(question, 'satisfies', satisfies);
  
      return question;
    },
  
    setupComponent() {
      this.initUpgradeStrategy();
      this.initScheduledClusterScan();
    },
  
    setDefaultRevisionId(clusterTemplate) {
      set(this, 'clusterTemplateRevisionId', clusterTemplate.defaultRevisionId);
    }
  });
  