<commit_before>import ClusterDriver from 'shared/mixins/cluster-driver';
import { equal, alias, or } from '@ember/object/computed';
import {
  get, set, computed, observer, setProperties
} from '@ember/object';
import { maxSatisfying } from 'shared/utils/parse-version';
import { inject as service } from '@ember/service';
import {
  underlineToCamel, removeEmpty, keysToCamel, validateEndpoint, randomStr
} from 'shared/utils/util';
import { validateHostname } from '@rancher/ember-api-store/utils/validate';
import { validateCertWeakly } from 'shared/utils/util';
import C from 'shared/utils/constants';
import YAML from 'yamljs';
import json2yaml from 'json2yaml';
import layout from './template';
import { resolve } from 'rsvp';
import { isEmpty } from '@ember/utils';
import InputTextFile from 'ui/components/input-text-file/component';
import { scheduleOnce } from '@ember/runloop';
import { azure as AzureInfo } from 'shared/components/cru-cloud-provider/cloud-provider-info';
import { gte }  from 'semver';
import { coerceVersion } from 'shared/utils/parse-version';
import moment from 'moment';
import ManageLabels from 'shared/mixins/manage-labels';

const EXCLUDED_KEYS = ['extra_args'];

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 NETWORKCHOICES = [
  {
    label: 'clusterNew.rke.network.flannel',
    value: 'flannel'
  },
  {
    label: 'clusterNew.rke.network.calico',
    value: 'calico'
  },
  {
    label: 'clusterNew.rke.network.canal',
    value: 'canal'
  },
  {
    label: 'clusterNew.rke.network.weave',
    value: 'weave'
  },
];

const FLANNEL              = 'flannel';
const CANAL                = 'canal';
const WEAVE                = 'weave';
const VXLAN                = 'vxlan';
const DEFAULT_BACKEND_TYPE = VXLAN;
const BACKEND_PORT = '4789';
const BACKEND_VNI = '4096';

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'];

export default InputTextFile.extend(ManageLabels, ClusterDriver, {
  globalStore:          service(),
  settings:             service(),
  growl:                service(),
  intl:                 service(),

  layout,
  networkContent:       NETWORKCHOICES,
  authChoices:          AUTHCHOICES,
  ingressChoices:       INGRESSCHOICES,
  availableStrategies:  AVAILABLE_STRATEGIES,

  configField:          'rancherKubernetesEngineConfig',
  registry:             'default',
  accept:               '.yml, .yaml',
  backupStrategy:       'local',
  loading:              false,
  pasteOrUpload:        false,
  model:                null,
  initialVersion:       null,
  registryUrl:          null,
  registryUser:         null,
  registryPass:         null,
  clusterOptErrors:     null,
  nodeNameErrors:       null,

  existingNodes:        null,
  initialNodeCounts:    null,
  step:                 1,
  token:                null,
  labels:               null,
  etcd:                 false,
  controlplane:         false,
  worker:               true,
  defaultDockerRootDir: null,

  windowsEnable:        false,
  isLinux:              true,
  windowsSupport:       false,
  weaveCustomPassword:  false,
  isNew:                equal('mode', 'new'),
  isEdit:               equal('mode', 'edit'),
  notView:              or('isNew', 'isEdit'),
  clusterState:         alias('model.originalCluster.state'),

  // Custom stuff
  isCustom:             equal('nodeWhich', 'custom'),

  init() {
    this._super();

    this.initNodeCounts();

    if ( get(this, 'isNew') ) {
      this.createRkeConfigWithDefaults();
    } else {
      this.initPrivateRegistries();

      this.initBackupConfigs();
    }

    this.setFlannelLables();

    scheduleOnce('afterRender', () => {
      this.initRootDockerDirectory();

      if ( get(this, 'isEdit') && get(this, 'cluster.isWindows') ) {
        set(this, 'windowsSupport', true)
      }
    });
  },

  didReceiveAttrs() {
    if ( get(this, 'isEdit') ) {
      this.loadToken();
    }
  },

  actions: {
    setNodePoolErrors(errors) {
      set(this, 'nodePoolErrors', errors);
    },

    cancel() {
      set(this, 'pasteOrUpload', false);
    },

    showPaste() {
      set(this, 'pasteOrUpload', true);
    },

    addRegistry(pr) {
      const config = get(this, 'cluster.rancherKubernetesEngineConfig')

      if (( config.privateRegistries || [] ).length <= 0) {
        set(config, 'privateRegistries', [pr]);
      } else {
        get(this, 'privateRegistries').pushObject(pr);
      }
    },

    removeRegistry(pr) {
      get(this, 'cluster.rancherKubernetesEngineConfig.privateRegistries').removeObject(pr);
    },
  },

  windowsSupportAvailableDidChange: observer('windowsSupportAvailable', function() {
    if ( !get(this, 'windowsSupportAvailable') ) {
      set(this, 'windowsSupport', false);
    }
  }),

  networkPluginDidChange: observer('cluster.rancherKubernetesEngineConfig.network.plugin', function() {
    let plugin = get(this, 'cluster.rancherKubernetesEngineConfig.network.plugin');

    if (plugin) {
      if (plugin !== CANAL) {
        set(this, 'cluster.enableNetworkPolicy', false);
      }

      if (plugin === WEAVE) {
        set(this, 'cluster.rancherKubernetesEngineConfig.network.weaveNetworkProvider', this.globalStore.createRecord({
          type:     'weaveNetworkProvider',
          password: <PASSWORD>Str(16, 16, 'password')
        }));
      } else if (plugin !== WEAVE && get(this, 'cluster.rancherKubernetesEngineConfig.network.weaveNetworkProvider.password')) {
        set(this, 'cluster.rancherKubernetesEngineConfig.network.weaveNetworkProvider', null);
      }
    }
  }),

  driverDidChange: observer('nodeWhich', function() {
    this.createRkeConfigWithDefaults();
    this.setFlannelLables();
  }),

  windowsSupportChange: observer('windowsSupport', function() {
    if ( get(this, 'isEdit') ) {
      return;
    }

    const windowsSupport = get(this, 'windowsSupport')
    const config = get(this, 'cluster.rancherKubernetesEngineConfig')

    if (windowsSupport) {
      set(config, 'network.options.flannel_backend_type', VXLAN)
    } else {
      set(config, 'network.options.flannel_backend_type', DEFAULT_BACKEND_TYPE)
    }
  }),

  flannelBackendDidChange: observer('windowsSupport', 'config.network.plugin', 'config.network.options.flannel_backend_type', function() {
    const config = get(this, 'config');
    const plugin = get(config, 'network.plugin');
    const flannelBackend = get(config, 'network.options.flannel_backend_type');
    const windowsSupport = get(this, 'windowsSupport');

    if ( flannelBackend === VXLAN && plugin === FLANNEL && windowsSupport ) {
      setProperties(config, {
        'network.options.flannel_backend_port': BACKEND_PORT,
        'network.options.flannel_backend_vni':  BACKEND_VNI
      })
    } else {
      const options = get(config, 'network.options');

      delete options['flannel_backend_port'];
      delete options['flannel_backend_vni'];
    }
  }),

  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.cluster.rancherKubernetesEngineConfig.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, 'cluster.rancherKubernetesEngineConfig.services', services);
  }),

  windowsSupportAvailable: computed('cluster.rancherKubernetesEngineConfig.network.plugin', 'cluster.rancherKubernetesEngineConfig.kubernetesVersion', function() {
    const plugin = get(this, 'cluster.rancherKubernetesEngineConfig.network.plugin');

    const kubernetesVersion = get(this, 'cluster.rancherKubernetesEngineConfig.kubernetesVersion');

    return plugin === FLANNEL && gte(coerceVersion(kubernetesVersion), 'v1.14.1');
  }),

  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 }`, function() {
    const out = JSON.parse(get(this, `settings.${ C.SETTING.VERSIONS_K8S }`) || '{}');

    return Object.keys(out);
  }),

  isNodeNameValid: computed('nodeName', function() {
    const nodeName = (get(this, 'nodeName') || '').toLowerCase();

    if ( get(nodeName, 'length') === 0 ) {
      return true;
    } else {
      const errors = validateHostname(nodeName, 'Node Name', get(this, 'intl'), { restricted: true });

      set(this, 'nodeNameErrors', errors);

      return errors.length === 0;
    }
  }),

  isAddressValid: computed('address', function() {
    return get(this, 'address') === undefined || get(this, 'address.length') === 0 || validateEndpoint(get(this, 'address'));
  }),

  isInternalAddressValid: computed('internalAddress', function() {
    return get(this, 'internalAddress') === undefined || get(this, 'internalAddress.length') === 0 || validateEndpoint(get(this, 'internalAddress'));
  }),

  newNodeCount: computed('initialNodeCounts', 'cluster.id', 'existingNodes.@each.clusterId', function() {
    let clusterId = get(this, 'cluster.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;
  }),

  command: computed('labels', 'token.nodeCommand', 'token.windowsNodeCommand', 'etcd', 'controlplane', 'worker', 'address', 'internalAddress', 'nodeName', 'isLinux', function() {
    let out = get(this, 'token.nodeCommand');

    if ( !out ) {
      return;
    }

    const address         = get(this, 'address');
    const nodeName        = get(this, 'nodeName');
    const internalAddress = get(this, 'internalAddress');
    const roles           = ['etcd', 'controlplane', 'worker'];
    const labels          = get(this, 'labels') || {};

    if ( nodeName ) {
      out += ` --node-name ${ nodeName.toLowerCase() }`;
    }

    if (address) {
      out += ` --address ${ address }`;
    }

    if (internalAddress) {
      out += ` --internal-address ${ internalAddress }`;
    }

    for ( let i = 0, k ; i < roles.length ; i++ ) {
      k = roles[i];

      if ( get(this, k) ) {
        out += ` --${ k }`;
      }
    }

    Object.keys(labels).forEach((key) => {
      out += ` --label ${ key }=${ labels[key] }`;
    });

    const windowsSelected = !get(this, 'isLinux')

    if (windowsSelected) {
      out = get(this, 'token.windowsNodeCommand') || ''
      out = out.replace('--isolation hyperv ', '')
      let addressCmd = ''

      if ( nodeName ) {
        addressCmd += ` -nodeName ${ nodeName.toLowerCase() }`;
      }

      if (address) {
        addressCmd += ` -address ${ address }`
      }

      if (internalAddress) {
        addressCmd += ` -internalAddress ${ internalAddress }`
      }

      const labelKeys = Object.keys(labels);

      if ( get(labelKeys, 'length') > 0 ) {
        addressCmd += ` -label `;
        const labelArray = [];

        labelKeys.forEach((key) => {
          labelArray.push(`${ key }=${ labels[key] }`);
        });
        addressCmd += `${ labelArray.join(',') }`;
      }

      if (addressCmd) {
        out = out.replace(`; if($?)`, `${ addressCmd }; if($?)`)
      }
    }

    return out;
  }),

  yamlValue: computed('pasteOrUpload', {
    get() {
      const intl = get(this, 'intl');

      let config = this.getSupportedFields(get(this, 'cluster.rancherKubernetesEngineConfig'), 'rancherKubernetesEngineConfig');

      if ( !config ) {
        return '';
      }

      let cluster = this.getSupportedFields(get(this, 'cluster'), 'cluster', EXCLUED_CLUSTER_OPTIONS);

      delete cluster.rancher_kubernetes_engine_config
      Object.assign(config, cluster)

      config = removeEmpty(config, EXCLUDED_KEYS);

      while ( JSON.stringify(config) !== JSON.stringify(removeEmpty(config, EXCLUDED_KEYS)) ){
        config = removeEmpty(config, EXCLUDED_KEYS);
      }

      let yaml = json2yaml.stringify(config);
      const lines = yaml.split('\n');

      lines.shift();

      let out = '';

      lines.forEach((line) => {
        if ( line.trim() ) {
          const key = `rkeConfigComment.${ line.split(':')[0].trim() }`

          if ( intl.exists(key) ) {
            const commentLines = intl.t(key).split('\n');

            commentLines.forEach((commentLine) => {
              out += `# ${ commentLine.slice(1, commentLine.length - 1) }\n`;
            });
          }

          out += `${ line.slice(2) }\n`;
        }
      });

      return out;
    },

    set(key, value) {
      let configs;

      try {
        configs = YAML.parse(value);
      } catch ( err ) {
        set(this, 'clusterOptErrors', [`Cluster Options Parse Error: ${ err.snippet } - ${ err.message }`]);

        return value;
      }

      set(this, 'clusterOptErrors', []);

      const validFields = this.getResourceFields('rancherKubernetesEngineConfig');
      const clusterFields = this.getResourceFields('cluster');
      const clusterKeys = [];

      Object.keys(clusterFields).filter((key) => clusterFields[key].update && key !== 'rancher_kubernetes_engine_config').forEach((key) => {
        clusterKeys.push(camelToUnderline(key));
      });

      Object.keys(configs || {}).forEach((key) => {
        if ( validFields[underlineToCamel(key)] || clusterFields[underlineToCamel(key)] ) {
          if (configs[key] === null) {
            return
          }
          const obj = keysToCamel(configs[key]);

          if ( clusterKeys.includes(key) ) {
            set(this, `cluster.${ underlineToCamel(key) }`, obj)

            return
          }

          if ( key === 'services' && obj['kube-api'] ) {
            set(obj, 'kubeApi', obj['kube-api']);
            delete obj['kube-api'];
          }

          if ( key === 'services' && obj['kube-controller'] ) {
            set(obj, 'kubeController', obj['kube-controller']);
            delete obj['kube-controller'];
          }

          set(this, `cluster.rancherKubernetesEngineConfig.${ underlineToCamel(key) }`, obj);
        }
      });

      return value;
    }
  }),

  validate() {
    this._super(...arguments);

    let errors = [];
    let config = get(this, `cluster.rancherKubernetesEngineConfig`);

    if ( !get(this, 'isCustom') ) {
      errors.pushObjects(get(this, 'nodePoolErrors'));
    }

    if ( get(config, 'services.kubeApi.podSecurityPolicy') &&
        !get(this, 'cluster.defaultPodSecurityPolicyTemplateId') ) {
      errors.push(get(this, 'intl').t('clusterNew.psp.required'));
    }

    if (get(this, 'cluster.rancherKubernetesEngineConfig.services.etcd.snapshot')) {
      errors = this.validateEtcdService(errors);
    }

    if ( get(this, 'cluster.localClusterAuthEndpoint.enabled') ) {
      errors = this.validateAuthorizedClusterEndpoint(errors);
    }

    const clusterOptErrors = get(this, 'clusterOptErrors') || [];

    if ( get(config, 'cloudProvider.name') === 'azure' ) {
      const intl = get(this, 'intl');

      Object.keys(AzureInfo).forEach((key) => {
        if ( get(AzureInfo, `${ key }.required`) && !get(config, `cloudProvider.azureCloudProvider.${ key }`) ) {
          errors.push(intl.t('validation.required', { key }));
        }
      });
    }

    set(this, 'errors', errors);

    return errors.length === 0 && clusterOptErrors.length === 0;
  },

  validateAuthorizedClusterEndpoint(errors) {
    let { localClusterAuthEndpoint } = get(this, 'cluster');
    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, 'cluster.rancherKubernetesEngineConfig.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() {
    let { close } = this;

    if ( get(this, 'isCustom') ) {
      if ( get(this, 'isEdit') ) {
        if (close) {
          close();
        }
      } else {
        this.loadToken();
      }
    } else {
      if (close) {
        close();
      }
    }

    return resolve();
  },

  loadToken() {
    const cluster = get(this, 'cluster');

    setProperties(this, {
      step:    2,
      loading: true
    });

    return cluster.getOrCreateToken().then((token) => {
      if ( this.isDestroyed || this.isDestroying ) {
        return;
      }

      setProperties(this, {
        token,
        loading: false
      });
    }).catch((err) => {
      if ( this.isDestroyed || this.isDestroying ) {
        return;
      }

      get(this, 'growl').fromError('Error getting command', err);

      set(this, 'loading', false);
    });
  },

  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.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, tragetField, excludeFields = []) {
    const out = {};
    const resourceFields = this.getResourceFields(tragetField);

    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;
  },

  initNodeCounts() {
    const counts = {};

    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');
    });

    setProperties(this, {
      initialNodeCounts: counts,
      initialVersion:    get(this, 'cluster.rancherKubernetesEngineConfig.kubernetesVersion')
    })
  },

  initPrivateRegistries() {
    const config = get(this, 'cluster.rancherKubernetesEngineConfig');

    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',
      }),
      network: globalStore.createRecord({
        type:    'networkConfig',
        plugin:  'canal',
        options: { flannel_backend_type: DEFAULT_BACKEND_TYPE, },
      }),
      ingress: globalStore.createRecord({
        type:     'ingressConfig',
        provider: 'nginx',
      }),
      monitoring: globalStore.createRecord({
        type:     'monitoringConfig',
        provider: 'metrics-server',
      }),
      services: globalStore.createRecord({
        type:    'rkeConfigServices',
        kubeApi: globalStore.createRecord({
          type:                  'kubeAPIService',
          podSecurityPolicy:     false,
          serviceNodePortRange: '30000-32767',
        }),
        etcd: globalStore.createRecord({
          type:         'etcdService',
          snapshot:     false,
          backupConfig: globalStore.createRecord({
            type:    'backupConfig',
            enabled: true,
          }),
          extraArgs:    {
            'heartbeat-interval': 500,
            'election-timeout':   5000
          },
        }),
      }),
    });

    setProperties(out, {
      rancherKubernetesEngineConfig: rkeConfig,
      enableNetworkPolicy:           false
    });

    if (this.isNew) {
      set(out, 'localClusterAuthEndpoint', globalStore.createRecord({
        type:    'localClusterAuthEndpoint',
        enabled: true
      }));
    }

    scheduleOnce('afterRender', () => setProperties(this.cluster, out));
  },

  setFlannelLables() {
    let flannel = this.networkContent.findBy('value', 'flannel');

    if (get(this, 'isCustom')) {
      set(flannel, 'label', 'clusterNew.rke.network.flannelCustom');
    } else {
      set(flannel, 'label', 'clusterNew.rke.network.flannel');
    }
  },

  migrateLegacyEtcdSnapshotSettings() {
    const { cluster } = this;
    let {
      retention, creation, backupConfig
    } = cluster.rancherKubernetesEngineConfig.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(cluster.rancherKubernetesEngineConfig.services.etcd, {
        'backupConfig.enabled':       true,
        'backupConfig.intervalHours': parsedDurationAsHours,
        snapshot:                     false,
      });
    } else {
      backupConfig = this.globalStore.createRecord({
        type:          'backupConfig',
        intervalHours: parsedDurationAsHours,
        enabled:       true,
      });

      setProperties(cluster.rancherKubernetesEngineConfig.services.etcd, {
        backupConfig,
        snapshot: false,
      });
    }
  },

  initRootDockerDirectory() {
    set(this, 'defaultDockerRootDir', get(this.globalStore.getById('schema', 'cluster').getCreateDefaults(), 'dockerRootDir'))
  },


  initBackupConfigs() {
    const etcd = get(this, 'cluster.rancherKubernetesEngineConfig.services.etcd');

    if (etcd) {
      if (etcd.snapshot) {
        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)
      }
    }
  },
});
<commit_msg>rke private registry push new into correct config object<commit_after>import ClusterDriver from 'shared/mixins/cluster-driver';
import { equal, alias, or } from '@ember/object/computed';
import {
  get, set, computed, observer, setProperties
} from '@ember/object';
import { maxSatisfying } from 'shared/utils/parse-version';
import { inject as service } from '@ember/service';
import {
  underlineToCamel, removeEmpty, keysToCamel, validateEndpoint, randomStr
} from 'shared/utils/util';
import { validateHostname } from '@rancher/ember-api-store/utils/validate';
import { validateCertWeakly } from 'shared/utils/util';
import C from 'shared/utils/constants';
import YAML from 'yamljs';
import json2yaml from 'json2yaml';
import layout from './template';
import { resolve } from 'rsvp';
import { isEmpty } from '@ember/utils';
import InputTextFile from 'ui/components/input-text-file/component';
import { scheduleOnce } from '@ember/runloop';
import { azure as AzureInfo } from 'shared/components/cru-cloud-provider/cloud-provider-info';
import { gte }  from 'semver';
import { coerceVersion } from 'shared/utils/parse-version';
import moment from 'moment';
import ManageLabels from 'shared/mixins/manage-labels';

const EXCLUDED_KEYS = ['extra_args'];

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 NETWORKCHOICES = [
  {
    label: 'clusterNew.rke.network.flannel',
    value: 'flannel'
  },
  {
    label: 'clusterNew.rke.network.calico',
    value: 'calico'
  },
  {
    label: 'clusterNew.rke.network.canal',
    value: 'canal'
  },
  {
    label: 'clusterNew.rke.network.weave',
    value: 'weave'
  },
];

const FLANNEL              = 'flannel';
const CANAL                = 'canal';
const WEAVE                = 'weave';
const VXLAN                = 'vxlan';
const DEFAULT_BACKEND_TYPE = VXLAN;
const BACKEND_PORT = '4789';
const BACKEND_VNI = '4096';

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'];

export default InputTextFile.extend(ManageLabels, ClusterDriver, {
  globalStore:          service(),
  settings:             service(),
  growl:                service(),
  intl:                 service(),

  layout,
  networkContent:       NETWORKCHOICES,
  authChoices:          AUTHCHOICES,
  ingressChoices:       INGRESSCHOICES,
  availableStrategies:  AVAILABLE_STRATEGIES,

  configField:          'rancherKubernetesEngineConfig',
  registry:             'default',
  accept:               '.yml, .yaml',
  backupStrategy:       'local',
  loading:              false,
  pasteOrUpload:        false,
  model:                null,
  initialVersion:       null,
  registryUrl:          null,
  registryUser:         null,
  registryPass:         null,
  clusterOptErrors:     null,
  nodeNameErrors:       null,

  existingNodes:        null,
  initialNodeCounts:    null,
  step:                 1,
  token:                null,
  labels:               null,
  etcd:                 false,
  controlplane:         false,
  worker:               true,
  defaultDockerRootDir: null,

  windowsEnable:        false,
  isLinux:              true,
  windowsSupport:       false,
  weaveCustomPassword:  false,
  isNew:                equal('mode', 'new'),
  isEdit:               equal('mode', 'edit'),
  notView:              or('isNew', 'isEdit'),
  clusterState:         alias('model.originalCluster.state'),

  // Custom stuff
  isCustom:             equal('nodeWhich', 'custom'),

  init() {
    this._super();

    this.initNodeCounts();

    if ( get(this, 'isNew') ) {
      this.createRkeConfigWithDefaults();
    } else {
      this.initPrivateRegistries();

      this.initBackupConfigs();
    }

    this.setFlannelLables();

    scheduleOnce('afterRender', () => {
      this.initRootDockerDirectory();

      if ( get(this, 'isEdit') && get(this, 'cluster.isWindows') ) {
        set(this, 'windowsSupport', true)
      }
    });
  },

  didReceiveAttrs() {
    if ( get(this, 'isEdit') ) {
      this.loadToken();
    }
  },

  actions: {
    setNodePoolErrors(errors) {
      set(this, 'nodePoolErrors', errors);
    },

    cancel() {
      set(this, 'pasteOrUpload', false);
    },

    showPaste() {
      set(this, 'pasteOrUpload', true);
    },

    addRegistry(pr) {
      const config = get(this, 'cluster.rancherKubernetesEngineConfig')

      if (( config.privateRegistries || [] ).length <= 0) {
        set(config, 'privateRegistries', [pr]);
      } else {
        config.privateRegistries.pushObject(pr);
      }
    },

    removeRegistry(pr) {
      get(this, 'cluster.rancherKubernetesEngineConfig.privateRegistries').removeObject(pr);
    },
  },

  windowsSupportAvailableDidChange: observer('windowsSupportAvailable', function() {
    if ( !get(this, 'windowsSupportAvailable') ) {
      set(this, 'windowsSupport', false);
    }
  }),

  networkPluginDidChange: observer('cluster.rancherKubernetesEngineConfig.network.plugin', function() {
    let plugin = get(this, 'cluster.rancherKubernetesEngineConfig.network.plugin');

    if (plugin) {
      if (plugin !== CANAL) {
        set(this, 'cluster.enableNetworkPolicy', false);
      }

      if (plugin === WEAVE) {
        set(this, 'cluster.rancherKubernetesEngineConfig.network.weaveNetworkProvider', this.globalStore.createRecord({
          type:     'weaveNetworkProvider',
          password: <PASSWORD>(16, 16, 'password')
        }));
      } else if (plugin !== WEAVE && get(this, 'cluster.rancherKubernetesEngineConfig.network.weaveNetworkProvider.password')) {
        set(this, 'cluster.rancherKubernetesEngineConfig.network.weaveNetworkProvider', null);
      }
    }
  }),

  driverDidChange: observer('nodeWhich', function() {
    this.createRkeConfigWithDefaults();
    this.setFlannelLables();
  }),

  windowsSupportChange: observer('windowsSupport', function() {
    if ( get(this, 'isEdit') ) {
      return;
    }

    const windowsSupport = get(this, 'windowsSupport')
    const config = get(this, 'cluster.rancherKubernetesEngineConfig')

    if (windowsSupport) {
      set(config, 'network.options.flannel_backend_type', VXLAN)
    } else {
      set(config, 'network.options.flannel_backend_type', DEFAULT_BACKEND_TYPE)
    }
  }),

  flannelBackendDidChange: observer('windowsSupport', 'config.network.plugin', 'config.network.options.flannel_backend_type', function() {
    const config = get(this, 'config');
    const plugin = get(config, 'network.plugin');
    const flannelBackend = get(config, 'network.options.flannel_backend_type');
    const windowsSupport = get(this, 'windowsSupport');

    if ( flannelBackend === VXLAN && plugin === FLANNEL && windowsSupport ) {
      setProperties(config, {
        'network.options.flannel_backend_port': BACKEND_PORT,
        'network.options.flannel_backend_vni':  BACKEND_VNI
      })
    } else {
      const options = get(config, 'network.options');

      delete options['flannel_backend_port'];
      delete options['flannel_backend_vni'];
    }
  }),

  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.cluster.rancherKubernetesEngineConfig.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, 'cluster.rancherKubernetesEngineConfig.services', services);
  }),

  windowsSupportAvailable: computed('cluster.rancherKubernetesEngineConfig.network.plugin', 'cluster.rancherKubernetesEngineConfig.kubernetesVersion', function() {
    const plugin = get(this, 'cluster.rancherKubernetesEngineConfig.network.plugin');

    const kubernetesVersion = get(this, 'cluster.rancherKubernetesEngineConfig.kubernetesVersion');

    return plugin === FLANNEL && gte(coerceVersion(kubernetesVersion), 'v1.14.1');
  }),

  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 }`, function() {
    const out = JSON.parse(get(this, `settings.${ C.SETTING.VERSIONS_K8S }`) || '{}');

    return Object.keys(out);
  }),

  isNodeNameValid: computed('nodeName', function() {
    const nodeName = (get(this, 'nodeName') || '').toLowerCase();

    if ( get(nodeName, 'length') === 0 ) {
      return true;
    } else {
      const errors = validateHostname(nodeName, 'Node Name', get(this, 'intl'), { restricted: true });

      set(this, 'nodeNameErrors', errors);

      return errors.length === 0;
    }
  }),

  isAddressValid: computed('address', function() {
    return get(this, 'address') === undefined || get(this, 'address.length') === 0 || validateEndpoint(get(this, 'address'));
  }),

  isInternalAddressValid: computed('internalAddress', function() {
    return get(this, 'internalAddress') === undefined || get(this, 'internalAddress.length') === 0 || validateEndpoint(get(this, 'internalAddress'));
  }),

  newNodeCount: computed('initialNodeCounts', 'cluster.id', 'existingNodes.@each.clusterId', function() {
    let clusterId = get(this, 'cluster.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;
  }),

  command: computed('labels', 'token.nodeCommand', 'token.windowsNodeCommand', 'etcd', 'controlplane', 'worker', 'address', 'internalAddress', 'nodeName', 'isLinux', function() {
    let out = get(this, 'token.nodeCommand');

    if ( !out ) {
      return;
    }

    const address         = get(this, 'address');
    const nodeName        = get(this, 'nodeName');
    const internalAddress = get(this, 'internalAddress');
    const roles           = ['etcd', 'controlplane', 'worker'];
    const labels          = get(this, 'labels') || {};

    if ( nodeName ) {
      out += ` --node-name ${ nodeName.toLowerCase() }`;
    }

    if (address) {
      out += ` --address ${ address }`;
    }

    if (internalAddress) {
      out += ` --internal-address ${ internalAddress }`;
    }

    for ( let i = 0, k ; i < roles.length ; i++ ) {
      k = roles[i];

      if ( get(this, k) ) {
        out += ` --${ k }`;
      }
    }

    Object.keys(labels).forEach((key) => {
      out += ` --label ${ key }=${ labels[key] }`;
    });

    const windowsSelected = !get(this, 'isLinux')

    if (windowsSelected) {
      out = get(this, 'token.windowsNodeCommand') || ''
      out = out.replace('--isolation hyperv ', '')
      let addressCmd = ''

      if ( nodeName ) {
        addressCmd += ` -nodeName ${ nodeName.toLowerCase() }`;
      }

      if (address) {
        addressCmd += ` -address ${ address }`
      }

      if (internalAddress) {
        addressCmd += ` -internalAddress ${ internalAddress }`
      }

      const labelKeys = Object.keys(labels);

      if ( get(labelKeys, 'length') > 0 ) {
        addressCmd += ` -label `;
        const labelArray = [];

        labelKeys.forEach((key) => {
          labelArray.push(`${ key }=${ labels[key] }`);
        });
        addressCmd += `${ labelArray.join(',') }`;
      }

      if (addressCmd) {
        out = out.replace(`; if($?)`, `${ addressCmd }; if($?)`)
      }
    }

    return out;
  }),

  yamlValue: computed('pasteOrUpload', {
    get() {
      const intl = get(this, 'intl');

      let config = this.getSupportedFields(get(this, 'cluster.rancherKubernetesEngineConfig'), 'rancherKubernetesEngineConfig');

      if ( !config ) {
        return '';
      }

      let cluster = this.getSupportedFields(get(this, 'cluster'), 'cluster', EXCLUED_CLUSTER_OPTIONS);

      delete cluster.rancher_kubernetes_engine_config
      Object.assign(config, cluster)

      config = removeEmpty(config, EXCLUDED_KEYS);

      while ( JSON.stringify(config) !== JSON.stringify(removeEmpty(config, EXCLUDED_KEYS)) ){
        config = removeEmpty(config, EXCLUDED_KEYS);
      }

      let yaml = json2yaml.stringify(config);
      const lines = yaml.split('\n');

      lines.shift();

      let out = '';

      lines.forEach((line) => {
        if ( line.trim() ) {
          const key = `rkeConfigComment.${ line.split(':')[0].trim() }`

          if ( intl.exists(key) ) {
            const commentLines = intl.t(key).split('\n');

            commentLines.forEach((commentLine) => {
              out += `# ${ commentLine.slice(1, commentLine.length - 1) }\n`;
            });
          }

          out += `${ line.slice(2) }\n`;
        }
      });

      return out;
    },

    set(key, value) {
      let configs;

      try {
        configs = YAML.parse(value);
      } catch ( err ) {
        set(this, 'clusterOptErrors', [`Cluster Options Parse Error: ${ err.snippet } - ${ err.message }`]);

        return value;
      }

      set(this, 'clusterOptErrors', []);

      const validFields = this.getResourceFields('rancherKubernetesEngineConfig');
      const clusterFields = this.getResourceFields('cluster');
      const clusterKeys = [];

      Object.keys(clusterFields).filter((key) => clusterFields[key].update && key !== 'rancher_kubernetes_engine_config').forEach((key) => {
        clusterKeys.push(camelToUnderline(key));
      });

      Object.keys(configs || {}).forEach((key) => {
        if ( validFields[underlineToCamel(key)] || clusterFields[underlineToCamel(key)] ) {
          if (configs[key] === null) {
            return
          }
          const obj = keysToCamel(configs[key]);

          if ( clusterKeys.includes(key) ) {
            set(this, `cluster.${ underlineToCamel(key) }`, obj)

            return
          }

          if ( key === 'services' && obj['kube-api'] ) {
            set(obj, 'kubeApi', obj['kube-api']);
            delete obj['kube-api'];
          }

          if ( key === 'services' && obj['kube-controller'] ) {
            set(obj, 'kubeController', obj['kube-controller']);
            delete obj['kube-controller'];
          }

          set(this, `cluster.rancherKubernetesEngineConfig.${ underlineToCamel(key) }`, obj);
        }
      });

      return value;
    }
  }),

  validate() {
    this._super(...arguments);

    let errors = [];
    let config = get(this, `cluster.rancherKubernetesEngineConfig`);

    if ( !get(this, 'isCustom') ) {
      errors.pushObjects(get(this, 'nodePoolErrors'));
    }

    if ( get(config, 'services.kubeApi.podSecurityPolicy') &&
        !get(this, 'cluster.defaultPodSecurityPolicyTemplateId') ) {
      errors.push(get(this, 'intl').t('clusterNew.psp.required'));
    }

    if (get(this, 'cluster.rancherKubernetesEngineConfig.services.etcd.snapshot')) {
      errors = this.validateEtcdService(errors);
    }

    if ( get(this, 'cluster.localClusterAuthEndpoint.enabled') ) {
      errors = this.validateAuthorizedClusterEndpoint(errors);
    }

    const clusterOptErrors = get(this, 'clusterOptErrors') || [];

    if ( get(config, 'cloudProvider.name') === 'azure' ) {
      const intl = get(this, 'intl');

      Object.keys(AzureInfo).forEach((key) => {
        if ( get(AzureInfo, `${ key }.required`) && !get(config, `cloudProvider.azureCloudProvider.${ key }`) ) {
          errors.push(intl.t('validation.required', { key }));
        }
      });
    }

    set(this, 'errors', errors);

    return errors.length === 0 && clusterOptErrors.length === 0;
  },

  validateAuthorizedClusterEndpoint(errors) {
    let { localClusterAuthEndpoint } = get(this, 'cluster');
    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, 'cluster.rancherKubernetesEngineConfig.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() {
    let { close } = this;

    if ( get(this, 'isCustom') ) {
      if ( get(this, 'isEdit') ) {
        if (close) {
          close();
        }
      } else {
        this.loadToken();
      }
    } else {
      if (close) {
        close();
      }
    }

    return resolve();
  },

  loadToken() {
    const cluster = get(this, 'cluster');

    setProperties(this, {
      step:    2,
      loading: true
    });

    return cluster.getOrCreateToken().then((token) => {
      if ( this.isDestroyed || this.isDestroying ) {
        return;
      }

      setProperties(this, {
        token,
        loading: false
      });
    }).catch((err) => {
      if ( this.isDestroyed || this.isDestroying ) {
        return;
      }

      get(this, 'growl').fromError('Error getting command', err);

      set(this, 'loading', false);
    });
  },

  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.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, tragetField, excludeFields = []) {
    const out = {};
    const resourceFields = this.getResourceFields(tragetField);

    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;
  },

  initNodeCounts() {
    const counts = {};

    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');
    });

    setProperties(this, {
      initialNodeCounts: counts,
      initialVersion:    get(this, 'cluster.rancherKubernetesEngineConfig.kubernetesVersion')
    })
  },

  initPrivateRegistries() {
    const config = get(this, 'cluster.rancherKubernetesEngineConfig');

    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',
      }),
      network: globalStore.createRecord({
        type:    'networkConfig',
        plugin:  'canal',
        options: { flannel_backend_type: DEFAULT_BACKEND_TYPE, },
      }),
      ingress: globalStore.createRecord({
        type:     'ingressConfig',
        provider: 'nginx',
      }),
      monitoring: globalStore.createRecord({
        type:     'monitoringConfig',
        provider: 'metrics-server',
      }),
      services: globalStore.createRecord({
        type:    'rkeConfigServices',
        kubeApi: globalStore.createRecord({
          type:                  'kubeAPIService',
          podSecurityPolicy:     false,
          serviceNodePortRange: '30000-32767',
        }),
        etcd: globalStore.createRecord({
          type:         'etcdService',
          snapshot:     false,
          backupConfig: globalStore.createRecord({
            type:    'backupConfig',
            enabled: true,
          }),
          extraArgs:    {
            'heartbeat-interval': 500,
            'election-timeout':   5000
          },
        }),
      }),
    });

    setProperties(out, {
      rancherKubernetesEngineConfig: rkeConfig,
      enableNetworkPolicy:           false
    });

    if (this.isNew) {
      set(out, 'localClusterAuthEndpoint', globalStore.createRecord({
        type:    'localClusterAuthEndpoint',
        enabled: true
      }));
    }

    scheduleOnce('afterRender', () => setProperties(this.cluster, out));
  },

  setFlannelLables() {
    let flannel = this.networkContent.findBy('value', 'flannel');

    if (get(this, 'isCustom')) {
      set(flannel, 'label', 'clusterNew.rke.network.flannelCustom');
    } else {
      set(flannel, 'label', 'clusterNew.rke.network.flannel');
    }
  },

  migrateLegacyEtcdSnapshotSettings() {
    const { cluster } = this;
    let {
      retention, creation, backupConfig
    } = cluster.rancherKubernetesEngineConfig.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(cluster.rancherKubernetesEngineConfig.services.etcd, {
        'backupConfig.enabled':       true,
        'backupConfig.intervalHours': parsedDurationAsHours,
        snapshot:                     false,
      });
    } else {
      backupConfig = this.globalStore.createRecord({
        type:          'backupConfig',
        intervalHours: parsedDurationAsHours,
        enabled:       true,
      });

      setProperties(cluster.rancherKubernetesEngineConfig.services.etcd, {
        backupConfig,
        snapshot: false,
      });
    }
  },

  initRootDockerDirectory() {
    set(this, 'defaultDockerRootDir', get(this.globalStore.getById('schema', 'cluster').getCreateDefaults(), 'dockerRootDir'))
  },


  initBackupConfigs() {
    const etcd = get(this, 'cluster.rancherKubernetesEngineConfig.services.etcd');

    if (etcd) {
      if (etcd.snapshot) {
        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)
      }
    }
  },
});

{'fraction_non_alphanumeric': 0.1497663551401869, 'fraction_numerical': 0.0048397863818424565, 'mean_word_length': 2.517935771737216, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6720588', 'n_tokens_mistral': 18643, 'n_tokens_neox': 18044, 'n_words': 3395}