portainer/portainer

View on GitHub
app/kubernetes/components/kubernetes-configuration-data/kubernetesConfigurationDataController.js

Summary

Maintainability
D
1 day
Test Coverage
import { Buffer } from 'buffer';
import angular from 'angular';
import _ from 'lodash-es';
import chardet from 'chardet';
import { Base64 } from 'js-base64';
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
import { KubernetesConfigurationKinds, KubernetesSecretTypeOptions } from 'Kubernetes/models/configuration/models';

class KubernetesConfigurationDataController {
  /* @ngInject */
  constructor($async, Notifications) {
    Object.assign(this, { $async, Notifications });

    this.editorUpdate = this.editorUpdate.bind(this);
    this.editorUpdateAsync = this.editorUpdateAsync.bind(this);
    this.onFileLoad = this.onFileLoad.bind(this);
    this.onFileLoadAsync = this.onFileLoadAsync.bind(this);
    this.showSimpleMode = this.showSimpleMode.bind(this);
    this.showAdvancedMode = this.showAdvancedMode.bind(this);
    this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
    this.KubernetesSecretTypeOptions = KubernetesSecretTypeOptions;
  }

  onChangeKey(entry) {
    if (entry && entry.Used) {
      return;
    }

    this.onChangeValidation();

    this.state.duplicateKeys = KubernetesFormValidationHelper.getDuplicates(_.map(this.formValues.Data, (data) => data.Key));
    this.state.invalidKeys = KubernetesFormValidationHelper.getInvalidKeys(_.map(this.formValues.Data, (data) => data.Key));
    this.isValid = Object.keys(this.state.duplicateKeys).length === 0 && Object.keys(this.state.invalidKeys).length === 0;
  }

  addEntry() {
    this.formValues.Data.push(new KubernetesConfigurationFormValuesEntry());

    // logic for setting required keys for new entries, based on the secret type
    if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
      const newDataIndex = this.formValues.Data.length - 1;
      switch (this.formValues.Type) {
        case this.KubernetesSecretTypeOptions.DOCKERCFG.value:
          this.addMissingKeys(['dockercfg'], newDataIndex);
          break;
        case this.KubernetesSecretTypeOptions.DOCKERCONFIGJSON.value:
          this.addMissingKeys(['.dockerconfigjson'], newDataIndex);
          break;
        case this.KubernetesSecretTypeOptions.BASICAUTH.value:
          // only add a required key if there is no required key out of username and password
          if (!this.formValues.Data.some((entry) => entry.Key === 'username' || entry.Key === 'password')) {
            this.addMissingKeys(['username', 'password'], newDataIndex);
          }
          break;
        case this.KubernetesSecretTypeOptions.SSHAUTH.value:
          this.addMissingKeys(['ssh-privatekey'], newDataIndex);
          break;
        case this.KubernetesSecretTypeOptions.TLS.value:
          this.addMissingKeys(['tls.crt', 'tls.key'], newDataIndex);
          break;
        case this.KubernetesSecretTypeOptions.BOOTSTRAPTOKEN.value:
          this.addMissingKeys(['token-id', 'token-secret'], newDataIndex);
          break;
        default:
          break;
      }
    }

    this.onChangeValidation();
  }

  // addMissingKeys adds the keys in the keys array to the entry at the index provided and stops when the first one is added
  addMissingKeys(keys, newDataIndex) {
    for (let key of keys) {
      if (this.formValues.Data.every((entry) => entry.Key !== key)) {
        this.formValues.Data[newDataIndex].Key = key;
        return;
      }
    }
  }

  isRequiredKey(key) {
    if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
      switch (this.formValues.Type) {
        case this.KubernetesSecretTypeOptions.DOCKERCONFIGJSON.value:
          if (key === '.dockerconfigjson') {
            return true;
          }
          break;
        case this.KubernetesSecretTypeOptions.DOCKERCFG.value:
          if (key === '.dockercfg') {
            return true;
          }
          break;
        case this.KubernetesSecretTypeOptions.SSHAUTH.value:
          if (key === 'ssh-privatekey') {
            return true;
          }
          break;
        case this.KubernetesSecretTypeOptions.TLS.value:
          if (key === 'tls.crt' || key === 'tls.key') {
            return true;
          }
          break;
        case this.KubernetesSecretTypeOptions.BOOTSTRAPTOKEN.value:
          if (key === 'token-id' || key === 'token-secret') {
            return true;
          }
          break;
        default:
          break;
      }
    }
    return false;
  }

  removeEntry(index, entry) {
    if (entry.Used) {
      return;
    }

    this.formValues.Data.splice(index, 1);
    this.onChangeKey();
  }

  async editorUpdateAsync(value) {
    if (this.formValues.DataYaml !== value) {
      this.formValues.DataYaml = value;
      this.isEditorDirty = true;
    }
  }

  editorUpdate(value) {
    return this.$async(this.editorUpdateAsync, value);
  }

  async onFileLoadAsync(event) {
    // exit if the file is too big
    const maximumFileSizeBytes = 1024 * 1024; // 1MB
    if (event.target.result.byteLength > maximumFileSizeBytes) {
      this.Notifications.error('File size is too big', 'File size is too big', 'Select a file that is 1MB or smaller.');
      return;
    }

    const entry = new KubernetesConfigurationFormValuesEntry();
    try {
      const encoding = chardet.detect(Buffer.from(event.target.result));
      const decoder = new TextDecoder(encoding);

      entry.IsBinary = KubernetesConfigurationHelper.isBinary(encoding);

      if (!entry.IsBinary) {
        entry.Value = decoder.decode(event.target.result);
      } else {
        const stringValue = decoder.decode(event.target.result);
        entry.Value = Base64.encode(stringValue);
      }
    } catch (error) {
      this.Notifications.error('Failed to upload file', error, 'Failed to upload file');
      return;
    }

    entry.Key = event.target.fileName;

    if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
      if (this.isDockerConfig) {
        if (this.formValues.Type === this.KubernetesSecretTypeOptions.DOCKERCFG.value) {
          entry.Key = '.dockercfg';
        } else {
          entry.Key = '.dockerconfigjson';
        }
      }

      if (this.formValues.Type === this.KubernetesSecretTypeOptions.TLS.value) {
        const isCrt = entry.Value.indexOf('BEGIN CERTIFICATE') !== -1;
        if (isCrt) {
          entry.Key = 'tls.crt';
        }
        const isKey = entry.Value.indexOf('PRIVATE KEY') !== -1;
        if (isKey) {
          entry.Key = 'tls.key';
        }
      }
    }

    // if this.formValues.Data has a key that matches an existing key, then replace it
    const existingEntryIndex = this.formValues.Data.findIndex((data) => data.Key === entry.Key || (data.Value === '' && data.Key === ''));
    if (existingEntryIndex !== -1) {
      this.formValues.Data[existingEntryIndex] = entry;
    } else {
      this.formValues.Data.push(entry);
    }

    this.onChangeKey();
  }

  isEntryRequired() {
    if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
      if (this.formValues.Data.length === 1) {
        if (this.formValues.Type !== this.KubernetesSecretTypeOptions.SERVICEACCOUNTTOKEN.value) {
          return true;
        }
      }
    }
    return false;
  }

  onFileLoad(event) {
    return this.$async(this.onFileLoadAsync, event);
  }

  addEntryFromFile(file) {
    if (file) {
      const temporaryFileReader = new FileReader();
      temporaryFileReader.fileName = file.name;
      temporaryFileReader.onload = this.onFileLoad;
      temporaryFileReader.readAsArrayBuffer(file);
    }
  }

  showSimpleMode() {
    this.formValues.IsSimple = true;
    this.formValues.Data = KubernetesConfigurationHelper.parseYaml(this.formValues);
    this.onChangeKey();
  }

  showAdvancedMode() {
    this.formValues.IsSimple = false;
    this.formValues.DataYaml = KubernetesConfigurationHelper.parseData(this.formValues);
  }

  $onInit() {
    this.state = {
      duplicateKeys: [],
      invalidKeys: {},
    };
  }
}

export default KubernetesConfigurationDataController;
angular.module('portainer.kubernetes').controller('KubernetesConfigurationDataController', KubernetesConfigurationDataController);