mirror of https://github.com/portainer/portainer
				
				
				
			feat(secrets): allow creating secrets beyond opaque [EE-2625] (#7709)
							parent
							
								
									3b2f0ff9eb
								
							
						
					
					
						commit
						4e20d70a99
					
				| 
						 | 
				
			
			@ -1,9 +1,9 @@
 | 
			
		|||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
 | 
			
		||||
export default class {
 | 
			
		||||
  $onInit() {
 | 
			
		||||
    const secrets = (this.configurations || [])
 | 
			
		||||
      .filter((config) => config.Data && config.Type === KubernetesConfigurationTypes.SECRET)
 | 
			
		||||
      .filter((config) => config.Data && config.Type === KubernetesConfigurationKinds.SECRET)
 | 
			
		||||
      .flatMap((config) => Object.entries(config.Data))
 | 
			
		||||
      .map(([key, value]) => ({ key, value }));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ import _ from 'lodash-es';
 | 
			
		|||
import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
 | 
			
		||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
 | 
			
		||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
 | 
			
		||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
 | 
			
		||||
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
 | 
			
		||||
  '$scope',
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +112,7 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    this.hasConfigurationSecrets = function (item) {
 | 
			
		||||
      return item.Configurations && item.Configurations.some((config) => config.Data && config.Type === KubernetesConfigurationTypes.SECRET);
 | 
			
		||||
      return item.Configurations && item.Configurations.some((config) => config.Data && config.Type === KubernetesConfigurationKinds.SECRET);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
    <rd-widget-body classes="no-padding">
 | 
			
		||||
      <!-- table title and action menu -->
 | 
			
		||||
      <div class="toolBar !flex-col gap-1">
 | 
			
		||||
        <div class="toolBar vertical-center !gap-x-5 !gap-y-1 flex-wrap !p-0 w-full">
 | 
			
		||||
        <div class="toolBar vertical-center !gap-x-5 !gap-y-1 flex-wrap !px-0 !py-1 w-full">
 | 
			
		||||
          <div class="toolBarTitle">
 | 
			
		||||
            <div class="widget-icon space-right">
 | 
			
		||||
              <pr-icon icon="'lock'" feather="true"></pr-icon>
 | 
			
		||||
| 
						 | 
				
			
			@ -125,11 +125,11 @@
 | 
			
		|||
              </th>
 | 
			
		||||
              <th>
 | 
			
		||||
                <table-column-header
 | 
			
		||||
                  col-title="'Type'"
 | 
			
		||||
                  col-title="'Kind'"
 | 
			
		||||
                  can-sort="true"
 | 
			
		||||
                  is-sorted="$ctrl.state.orderBy === 'Type'"
 | 
			
		||||
                  is-sorted-desc="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"
 | 
			
		||||
                  ng-click="$ctrl.changeOrderBy('Type')"
 | 
			
		||||
                  is-sorted="$ctrl.state.orderBy === 'Kind'"
 | 
			
		||||
                  is-sorted-desc="$ctrl.state.orderBy === 'Kind' && $ctrl.state.reverseOrder"
 | 
			
		||||
                  ng-click="$ctrl.changeOrderBy('Kind')"
 | 
			
		||||
                ></table-column-header>
 | 
			
		||||
              </th>
 | 
			
		||||
              <th>
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +160,7 @@
 | 
			
		|||
              <td>
 | 
			
		||||
                <a ui-sref="kubernetes.resourcePools.resourcePool({ id: item.Namespace })">{{ item.Namespace }}</a>
 | 
			
		||||
              </td>
 | 
			
		||||
              <td>{{ item.Type | kubernetesConfigurationTypeText }}</td>
 | 
			
		||||
              <td>{{ item.Kind | kubernetesConfigurationKindText }}</td>
 | 
			
		||||
              <td>{{ item.CreationDate | getisodate }} {{ item.ConfigurationOwner ? 'by ' + item.ConfigurationOwner : '' }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr ng-if="!$ctrl.dataset">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,33 +21,70 @@
 | 
			
		|||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="form-group" ng-if="$ctrl.formValues.IsSimple">
 | 
			
		||||
    <div class="col-sm-12">
 | 
			
		||||
    <div class="col-sm-12 vertical-center">
 | 
			
		||||
      <button type="button" class="btn btn-sm btn-default" style="margin-left: 0" ng-click="$ctrl.addEntry()" data-cy="k8sConfigCreate-createEntryButton">
 | 
			
		||||
        <pr-icon class="vertical-center" icon="'plus'" feather="true"></pr-icon> Create entry
 | 
			
		||||
      </button>
 | 
			
		||||
      <button type="button" class="btn btn-sm btn-default" ngf-select="$ctrl.addEntryFromFile($file)" style="margin-left: 0" data-cy="k8sConfigCreate-createConfigsFromFileButton">
 | 
			
		||||
        <pr-icon class="vertical-center" icon="'upload'" feather="true"></pr-icon> Create key/value from file
 | 
			
		||||
      <button
 | 
			
		||||
        ng-if="
 | 
			
		||||
          !(
 | 
			
		||||
            ($ctrl.isDockerConfig || $ctrl.formValues.Type.name === $ctrl.KubernetesSecretTypes.TLS.name || $ctrl.formValues.Type === $ctrl.KubernetesSecretTypes.TLS.value) &&
 | 
			
		||||
            $ctrl.formValues.Kind === $ctrl.KubernetesConfigurationKinds.SECRET
 | 
			
		||||
          )
 | 
			
		||||
        "
 | 
			
		||||
        type="button"
 | 
			
		||||
        class="btn btn-sm btn-default ml-0"
 | 
			
		||||
        ngf-select="$ctrl.addEntryFromFile($file)"
 | 
			
		||||
        data-cy="k8sConfigCreate-createConfigsFromFileButton"
 | 
			
		||||
      >
 | 
			
		||||
        <pr-icon icon="'upload'" feather="true" class="vertical-center"></pr-icon> Create key/value from file
 | 
			
		||||
      </button>
 | 
			
		||||
      <button
 | 
			
		||||
        ng-if="$ctrl.isDockerConfig && $ctrl.formValues.Kind === $ctrl.KubernetesConfigurationKinds.SECRET"
 | 
			
		||||
        type="button"
 | 
			
		||||
        class="btn btn-sm btn-default ml-0"
 | 
			
		||||
        ngf-select="$ctrl.addEntryFromFile($file)"
 | 
			
		||||
        ngf-accept="'.json'"
 | 
			
		||||
        data-cy="k8sConfigCreate-createConfigsFromFileButton"
 | 
			
		||||
      >
 | 
			
		||||
        <pr-icon icon="'upload'" feather="true" class="vertical-center"></pr-icon> Upload docker config file
 | 
			
		||||
      </button>
 | 
			
		||||
      <button
 | 
			
		||||
        ng-if="
 | 
			
		||||
          ($ctrl.formValues.Type.name === $ctrl.KubernetesSecretTypes.TLS.name || $ctrl.formValues.Type === $ctrl.KubernetesSecretTypes.TLS.value) &&
 | 
			
		||||
          $ctrl.formValues.Kind === $ctrl.KubernetesConfigurationKinds.SECRET
 | 
			
		||||
        "
 | 
			
		||||
        type="button"
 | 
			
		||||
        class="btn btn-sm btn-default ml-0"
 | 
			
		||||
        ngf-select="$ctrl.addEntryFromFile($file)"
 | 
			
		||||
        data-cy="k8sConfigCreate-createConfigsFromFileButton"
 | 
			
		||||
      >
 | 
			
		||||
        <pr-icon icon="'upload'" feather="true" class="vertical-center"></pr-icon> Upload TLS key/cert file
 | 
			
		||||
      </button>
 | 
			
		||||
      <portainer-tooltip message="'Maximum upload file size is 1MB'"></portainer-tooltip>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div ng-repeat="(index, entry) in $ctrl.formValues.Data" ng-if="$ctrl.formValues.IsSimple">
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
      <label for="configuration_data_key_{{ index }}" class="col-sm-3 col-lg-2 control-label text-left required">Key</label>
 | 
			
		||||
      <label for="configuration_data_key_{{ index }}" class="col-sm-3 col-lg-2 control-label text-left required"
 | 
			
		||||
        >Key
 | 
			
		||||
        <portainer-tooltip message="'The key must consist of alphanumeric characters, \'-\', \'_\' or \'.\' and be up to 253 characters in length.'"></portainer-tooltip>
 | 
			
		||||
      </label>
 | 
			
		||||
      <div class="col-sm-8 col-lg-9">
 | 
			
		||||
        <input
 | 
			
		||||
          type="text"
 | 
			
		||||
          class="form-control"
 | 
			
		||||
          maxlength="253"
 | 
			
		||||
          id="configuration_data_key_{{ index }}"
 | 
			
		||||
          name="configuration_data_key_{{ index }}"
 | 
			
		||||
          ng-model="$ctrl.formValues.Data[index].Key"
 | 
			
		||||
          ng-disabled="entry.Used"
 | 
			
		||||
          ng-disabled="entry.Used || $ctrl.isRequiredKey(entry.Key)"
 | 
			
		||||
          required
 | 
			
		||||
          ng-change="$ctrl.onChangeKey(entry)"
 | 
			
		||||
        />
 | 
			
		||||
        <div
 | 
			
		||||
          class="small text-warning"
 | 
			
		||||
          style="margin-top: 5px"
 | 
			
		||||
          class="small text-warning mt-1"
 | 
			
		||||
          ng-show="
 | 
			
		||||
            kubernetesConfigurationDataCreationForm['configuration_data_key_' + index].$invalid ||
 | 
			
		||||
            (!entry.Used && $ctrl.state.duplicateKeys[index] !== undefined) ||
 | 
			
		||||
| 
						 | 
				
			
			@ -55,18 +92,16 @@
 | 
			
		|||
          "
 | 
			
		||||
        >
 | 
			
		||||
          <ng-messages for="kubernetesConfigurationDataCreationForm['configuration_data_key_' + index].$error">
 | 
			
		||||
            <p ng-message="required" class="vertical-center">
 | 
			
		||||
              <pr-icon class="vertical-center" icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required.
 | 
			
		||||
            </p>
 | 
			
		||||
            <p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required. </p>
 | 
			
		||||
          </ng-messages>
 | 
			
		||||
          <div>
 | 
			
		||||
            <p ng-if="$ctrl.state.duplicateKeys[index] !== undefined" class="vertical-center">
 | 
			
		||||
              <pr-icon class="vertical-center" icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon>This key is already defined.
 | 
			
		||||
              <pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon>This key is already defined.
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
          <p ng-if="$ctrl.state.invalidKeys[index]" class="vertical-center">
 | 
			
		||||
            <pr-icon class="vertical-center" icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon> This key is invalid. A valid key must
 | 
			
		||||
            consist of alphanumeric characters, '-', '_' or '.'
 | 
			
		||||
            <pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon> This key is invalid. A valid key must consist of alphanumeric
 | 
			
		||||
            characters, '-', '_' or '.'
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -106,10 +141,11 @@
 | 
			
		|||
      <div class="col-sm-3 col-lg-2"></div>
 | 
			
		||||
      <div class="col-sm-8">
 | 
			
		||||
        <button
 | 
			
		||||
          ng-if="!$ctrl.isRequiredKey(entry.Key) || $ctrl.state.duplicateKeys[index] !== undefined"
 | 
			
		||||
          type="button"
 | 
			
		||||
          class="btn btn-sm btn-dangerlight !ml-0"
 | 
			
		||||
          style="margin-left: 0"
 | 
			
		||||
          ng-disabled="entry.Used"
 | 
			
		||||
          ng-disabled="entry.Used || $ctrl.isEntryRequired()"
 | 
			
		||||
          ng-click="$ctrl.removeEntry(index, entry)"
 | 
			
		||||
          data-cy="k8sConfigDetail-removeEntryButton{{ index }}"
 | 
			
		||||
        >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@ angular.module('portainer.kubernetes').component('kubernetesConfigurationData',
 | 
			
		|||
  controller: 'KubernetesConfigurationDataController',
 | 
			
		||||
  bindings: {
 | 
			
		||||
    formValues: '=',
 | 
			
		||||
    isDockerConfig: '=',
 | 
			
		||||
    onChangeValidation: '&',
 | 
			
		||||
    isValid: '=',
 | 
			
		||||
    isCreation: '=',
 | 
			
		||||
    isEditorDirty: '=',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,11 +6,12 @@ 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, KubernetesSecretTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
 | 
			
		||||
class KubernetesConfigurationDataController {
 | 
			
		||||
  /* @ngInject */
 | 
			
		||||
  constructor($async) {
 | 
			
		||||
    this.$async = $async;
 | 
			
		||||
  constructor($async, Notifications) {
 | 
			
		||||
    Object.assign(this, { $async, Notifications });
 | 
			
		||||
 | 
			
		||||
    this.editorUpdate = this.editorUpdate.bind(this);
 | 
			
		||||
    this.editorUpdateAsync = this.editorUpdateAsync.bind(this);
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +19,8 @@ class KubernetesConfigurationDataController {
 | 
			
		|||
    this.onFileLoadAsync = this.onFileLoadAsync.bind(this);
 | 
			
		||||
    this.showSimpleMode = this.showSimpleMode.bind(this);
 | 
			
		||||
    this.showAdvancedMode = this.showAdvancedMode.bind(this);
 | 
			
		||||
    this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
 | 
			
		||||
    this.KubernetesSecretTypes = KubernetesSecretTypes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChangeKey(entry) {
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +28,8 @@ class KubernetesConfigurationDataController {
 | 
			
		|||
      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;
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +37,85 @@ class KubernetesConfigurationDataController {
 | 
			
		|||
 | 
			
		||||
  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;
 | 
			
		||||
      const typeValue = typeof this.formValues.Type === 'string' ? this.formValues.Type : this.formValues.Type.value;
 | 
			
		||||
      switch (typeValue) {
 | 
			
		||||
        case this.KubernetesSecretTypes.DOCKERCFG.value:
 | 
			
		||||
          this.addMissingKeys(['dockercfg'], newDataIndex);
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.DOCKERCONFIGJSON.value:
 | 
			
		||||
          this.addMissingKeys(['.dockerconfigjson'], newDataIndex);
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.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.KubernetesSecretTypes.SSHAUTH.value:
 | 
			
		||||
          this.addMissingKeys(['ssh-privatekey'], newDataIndex);
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.TLS.value:
 | 
			
		||||
          this.addMissingKeys(['tls.crt', 'tls.key'], newDataIndex);
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.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) {
 | 
			
		||||
      const secretTypeValue = typeof this.formValues.Type === 'string' ? this.formValues.Type : this.formValues.Type.value;
 | 
			
		||||
      switch (secretTypeValue) {
 | 
			
		||||
        case this.KubernetesSecretTypes.DOCKERCONFIGJSON.value:
 | 
			
		||||
          if (key === '.dockerconfigjson') {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.DOCKERCFG.value:
 | 
			
		||||
          if (key === '.dockercfg') {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.SSHAUTH.value:
 | 
			
		||||
          if (key === 'ssh-privatekey') {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.TLS.value:
 | 
			
		||||
          if (key === 'tls.crt' || key === 'tls.key') {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case this.KubernetesSecretTypes.BOOTSTRAPTOKEN.value:
 | 
			
		||||
          if (key === 'token-id' || key === 'token-secret') {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeEntry(index, entry) {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,24 +139,77 @@ class KubernetesConfigurationDataController {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  async onFileLoadAsync(event) {
 | 
			
		||||
    const entry = new KubernetesConfigurationFormValuesEntry();
 | 
			
		||||
    const encoding = chardet.detect(Buffer.from(event.target.result));
 | 
			
		||||
    const decoder = new TextDecoder(encoding);
 | 
			
		||||
 | 
			
		||||
    entry.Key = event.target.fileName;
 | 
			
		||||
    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);
 | 
			
		||||
    // 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.name === this.KubernetesSecretTypes.DOCKERCFG.name) {
 | 
			
		||||
          entry.Key = '.dockercfg';
 | 
			
		||||
        } else {
 | 
			
		||||
          entry.Key = '.dockerconfigjson';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.formValues.Type.name === this.KubernetesSecretTypes.TLS.name) {
 | 
			
		||||
        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.formValues.Data.push(entry);
 | 
			
		||||
    this.onChangeKey();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isEntryRequired() {
 | 
			
		||||
    if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
 | 
			
		||||
      const typeValue = typeof this.formValues.Type === 'string' ? this.formValues.Type : this.formValues.Type.value;
 | 
			
		||||
      if (this.formValues.Data.length === 1) {
 | 
			
		||||
        if (typeValue !== this.KubernetesSecretTypes.SERVICEACCOUNTTOKEN.value) {
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onFileLoad(event) {
 | 
			
		||||
    return this.$async(this.onFileLoadAsync, event);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,13 @@
 | 
			
		|||
import _ from 'lodash-es';
 | 
			
		||||
import { KubernetesConfiguration, KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfiguration, KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
 | 
			
		||||
class KubernetesConfigurationConverter {
 | 
			
		||||
  static secretToConfiguration(secret) {
 | 
			
		||||
    const res = new KubernetesConfiguration();
 | 
			
		||||
    res.Type = KubernetesConfigurationTypes.SECRET;
 | 
			
		||||
    res.Kind = KubernetesConfigurationKinds.SECRET;
 | 
			
		||||
    res.Id = secret.Id;
 | 
			
		||||
    res.Name = secret.Name;
 | 
			
		||||
    res.Type = secret.Type;
 | 
			
		||||
    res.Namespace = secret.Namespace;
 | 
			
		||||
    res.CreationDate = secret.CreationDate;
 | 
			
		||||
    res.Yaml = secret.Yaml;
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +22,7 @@ class KubernetesConfigurationConverter {
 | 
			
		|||
 | 
			
		||||
  static configMapToConfiguration(configMap) {
 | 
			
		||||
    const res = new KubernetesConfiguration();
 | 
			
		||||
    res.Type = KubernetesConfigurationTypes.CONFIGMAP;
 | 
			
		||||
    res.Kind = KubernetesConfigurationKinds.CONFIGMAP;
 | 
			
		||||
    res.Id = configMap.Id;
 | 
			
		||||
    res.Name = configMap.Name;
 | 
			
		||||
    res.Namespace = configMap.Namespace;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,12 +4,13 @@ import { KubernetesApplicationSecret } from 'Kubernetes/models/secret/models';
 | 
			
		|||
import { KubernetesPortainerConfigurationDataAnnotation } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesPortainerConfigurationOwnerLabel } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
 | 
			
		||||
 | 
			
		||||
import { KubernetesSecretTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
class KubernetesSecretConverter {
 | 
			
		||||
  static createPayload(secret) {
 | 
			
		||||
    const res = new KubernetesSecretCreatePayload();
 | 
			
		||||
    res.metadata.name = secret.Name;
 | 
			
		||||
    res.metadata.namespace = secret.Namespace;
 | 
			
		||||
    res.type = secret.Type.value;
 | 
			
		||||
    const configurationOwner = _.truncate(secret.ConfigurationOwner, { length: 63, omission: '' });
 | 
			
		||||
    res.metadata.labels[KubernetesPortainerConfigurationOwnerLabel] = configurationOwner;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +26,11 @@ class KubernetesSecretConverter {
 | 
			
		|||
    if (annotation !== '') {
 | 
			
		||||
      res.metadata.annotations[KubernetesPortainerConfigurationDataAnnotation] = annotation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _.forEach(secret.Annotations, (entry) => {
 | 
			
		||||
      res.metadata.annotations[entry.name] = entry.value;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +38,7 @@ class KubernetesSecretConverter {
 | 
			
		|||
    const res = new KubernetesSecretUpdatePayload();
 | 
			
		||||
    res.metadata.name = secret.Name;
 | 
			
		||||
    res.metadata.namespace = secret.Namespace;
 | 
			
		||||
    res.type = secret.Type;
 | 
			
		||||
    res.metadata.labels[KubernetesPortainerConfigurationOwnerLabel] = secret.ConfigurationOwner;
 | 
			
		||||
 | 
			
		||||
    let annotation = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +61,7 @@ class KubernetesSecretConverter {
 | 
			
		|||
    res.Id = payload.metadata.uid;
 | 
			
		||||
    res.Name = payload.metadata.name;
 | 
			
		||||
    res.Namespace = payload.metadata.namespace;
 | 
			
		||||
    res.Type = payload.type;
 | 
			
		||||
    res.ConfigurationOwner = payload.metadata.labels ? payload.metadata.labels[KubernetesPortainerConfigurationOwnerLabel] : '';
 | 
			
		||||
    res.CreationDate = payload.metadata.creationTimestamp;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -84,8 +92,19 @@ class KubernetesSecretConverter {
 | 
			
		|||
    const res = new KubernetesApplicationSecret();
 | 
			
		||||
    res.Name = formValues.Name;
 | 
			
		||||
    res.Namespace = formValues.ResourcePool.Namespace.Name;
 | 
			
		||||
    res.Type = formValues.Type;
 | 
			
		||||
    res.ConfigurationOwner = formValues.ConfigurationOwner;
 | 
			
		||||
    res.Data = formValues.Data;
 | 
			
		||||
 | 
			
		||||
    switch (formValues.Type) {
 | 
			
		||||
      case KubernetesSecretTypes.CUSTOM:
 | 
			
		||||
        res.Type.value = formValues.customType;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case KubernetesSecretTypes.SERVICEACCOUNTTOKEN:
 | 
			
		||||
        res.Annotations = [{ name: 'kubernetes.io/service-account.name', value: formValues.ServiceAccountName }];
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
 | 
			
		||||
angular.module('portainer.kubernetes').filter('kubernetesConfigurationTypeText', function () {
 | 
			
		||||
angular.module('portainer.kubernetes').filter('kubernetesConfigurationKindText', function () {
 | 
			
		||||
  'use strict';
 | 
			
		||||
  return function (type) {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case KubernetesConfigurationTypes.SECRET:
 | 
			
		||||
      case KubernetesConfigurationKinds.SECRET:
 | 
			
		||||
        return 'Secret';
 | 
			
		||||
      case KubernetesConfigurationTypes.CONFIGMAP:
 | 
			
		||||
      case KubernetesConfigurationKinds.CONFIGMAP:
 | 
			
		||||
        return 'ConfigMap';
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import _ from 'lodash-es';
 | 
			
		||||
import { KubernetesPortMapping, KubernetesPortMappingPort } from 'Kubernetes/models/port/models';
 | 
			
		||||
import { KubernetesService, KubernetesServicePort, KubernetesServiceTypes } from 'Kubernetes/models/service/models';
 | 
			
		||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import {
 | 
			
		||||
  KubernetesApplicationAutoScalerFormValue,
 | 
			
		||||
  KubernetesApplicationConfigurationFormValue,
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +147,7 @@ class KubernetesApplicationHelper {
 | 
			
		|||
  /* #region  CONFIGURATIONS FV <> ENV & VOLUMES */
 | 
			
		||||
  static generateConfigurationFormValuesFromEnvAndVolumes(env, volumes, configurations) {
 | 
			
		||||
    const finalRes = _.flatMap(configurations, (cfg) => {
 | 
			
		||||
      const filterCondition = cfg.Type === KubernetesConfigurationTypes.CONFIGMAP ? 'valueFrom.configMapKeyRef.name' : 'valueFrom.secretKeyRef.name';
 | 
			
		||||
      const filterCondition = cfg.Type === KubernetesConfigurationKinds.CONFIGMAP ? 'valueFrom.configMapKeyRef.name' : 'valueFrom.secretKeyRef.name';
 | 
			
		||||
 | 
			
		||||
      const cfgEnv = _.filter(env, [filterCondition, cfg.Name]);
 | 
			
		||||
      const cfgVol = _.filter(volumes, { configurationName: cfg.Name });
 | 
			
		||||
| 
						 | 
				
			
			@ -207,7 +207,7 @@ class KubernetesApplicationHelper {
 | 
			
		|||
    let finalMounts = [];
 | 
			
		||||
 | 
			
		||||
    _.forEach(configurations, (config) => {
 | 
			
		||||
      const isBasic = config.SelectedConfiguration.Type === KubernetesConfigurationTypes.CONFIGMAP;
 | 
			
		||||
      const isBasic = config.SelectedConfiguration.Type === KubernetesConfigurationKinds.CONFIGMAP;
 | 
			
		||||
 | 
			
		||||
      if (!config.Overriden) {
 | 
			
		||||
        const envKeys = _.keys(config.SelectedConfiguration.Data);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import _ from 'lodash-es';
 | 
			
		||||
import YAML from 'yaml';
 | 
			
		||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
 | 
			
		||||
 | 
			
		||||
class KubernetesConfigurationHelper {
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ class KubernetesConfigurationHelper {
 | 
			
		|||
    return _.filter(applications, (app) => {
 | 
			
		||||
      let envFind;
 | 
			
		||||
      let volumeFind;
 | 
			
		||||
      if (config.Type === KubernetesConfigurationTypes.CONFIGMAP) {
 | 
			
		||||
      if (config.Type === KubernetesConfigurationKinds.CONFIGMAP) {
 | 
			
		||||
        envFind = _.find(app.Env, { valueFrom: { configMapKeyRef: { name: config.Name } } });
 | 
			
		||||
        volumeFind = _.find(app.Volumes, { configMap: { name: config.Name } });
 | 
			
		||||
      } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { KubernetesConfigurationTypes } from './models';
 | 
			
		||||
import { KubernetesConfigurationKinds, KubernetesSecretTypes } from './models';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * KubernetesConfigurationFormValues Model
 | 
			
		||||
| 
						 | 
				
			
			@ -8,15 +8,17 @@ const _KubernetesConfigurationFormValues = Object.freeze({
 | 
			
		|||
  ResourcePool: '',
 | 
			
		||||
  Name: '',
 | 
			
		||||
  ConfigurationOwner: '',
 | 
			
		||||
  Type: KubernetesConfigurationTypes.CONFIGMAP,
 | 
			
		||||
  Kind: KubernetesConfigurationKinds.CONFIGMAP,
 | 
			
		||||
  Data: [],
 | 
			
		||||
  DataYaml: '',
 | 
			
		||||
  IsSimple: true,
 | 
			
		||||
  ServiceAccountName: '',
 | 
			
		||||
  Type: KubernetesSecretTypes.OPAQUE,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export class KubernetesConfigurationFormValues {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    Object.assign(this, JSON.parse(JSON.stringify(_KubernetesConfigurationFormValues)));
 | 
			
		||||
    Object.assign(this, _KubernetesConfigurationFormValues);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ export const KubernetesPortainerConfigurationDataAnnotation = 'io.portainer.kube
 | 
			
		|||
const _KubernetesConfiguration = Object.freeze({
 | 
			
		||||
  Id: 0,
 | 
			
		||||
  Name: '',
 | 
			
		||||
  Type: '',
 | 
			
		||||
  Kind: '',
 | 
			
		||||
  Namespace: '',
 | 
			
		||||
  CreationDate: '',
 | 
			
		||||
  ConfigurationOwner: '',
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +23,19 @@ export class KubernetesConfiguration {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const KubernetesConfigurationTypes = Object.freeze({
 | 
			
		||||
export const KubernetesConfigurationKinds = Object.freeze({
 | 
			
		||||
  CONFIGMAP: 1,
 | 
			
		||||
  SECRET: 2,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const KubernetesSecretTypes = Object.freeze({
 | 
			
		||||
  OPAQUE: { name: 'Opaque', value: 'Opaque' },
 | 
			
		||||
  SERVICEACCOUNTTOKEN: { name: 'Service account token', value: 'kubernetes.io/service-account-token' },
 | 
			
		||||
  DOCKERCFG: { name: 'Dockercfg', value: 'kubernetes.io/dockercfg' },
 | 
			
		||||
  DOCKERCONFIGJSON: { name: 'Dockerconfigjson', value: 'kubernetes.io/dockerconfigjson' },
 | 
			
		||||
  BASICAUTH: { name: 'Basic auth', value: 'kubernetes.io/basic-auth' },
 | 
			
		||||
  SSHAUTH: { name: 'SSH auth', value: 'kubernetes.io/ssh-auth' },
 | 
			
		||||
  TLS: { name: 'TLS', value: 'kubernetes.io/tls' },
 | 
			
		||||
  BOOTSTRAPTOKEN: { name: 'Bootstrap token', value: 'bootstrap.kubernetes.io/token' },
 | 
			
		||||
  CUSTOM: { name: 'Custom', value: 'Custom' },
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,13 @@ const _KubernetesApplicationSecret = Object.freeze({
 | 
			
		|||
  Id: 0,
 | 
			
		||||
  Name: '',
 | 
			
		||||
  Namespace: '',
 | 
			
		||||
  Type: '',
 | 
			
		||||
  CreationDate: '',
 | 
			
		||||
  ConfigurationOwner: '',
 | 
			
		||||
  Yaml: '',
 | 
			
		||||
  Data: [],
 | 
			
		||||
  SecretType: '',
 | 
			
		||||
  Annotations: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export class KubernetesApplicationSecret {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import { KubernetesCommonMetadataPayload } from 'Kubernetes/models/common/payloa
 | 
			
		|||
 */
 | 
			
		||||
const _KubernetesSecretCreatePayload = Object.freeze({
 | 
			
		||||
  metadata: new KubernetesCommonMetadataPayload(),
 | 
			
		||||
  type: 'Opaque',
 | 
			
		||||
  type: '',
 | 
			
		||||
  data: {},
 | 
			
		||||
  stringData: {},
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ export class KubernetesSecretCreatePayload {
 | 
			
		|||
 */
 | 
			
		||||
const _KubernetesSecretUpdatePayload = Object.freeze({
 | 
			
		||||
  metadata: new KubernetesCommonMetadataPayload(),
 | 
			
		||||
  type: 'Opaque',
 | 
			
		||||
  type: '',
 | 
			
		||||
  data: {},
 | 
			
		||||
  stringData: {},
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
import axios, { parseAxiosError } from '@/portainer/services/axios';
 | 
			
		||||
 | 
			
		||||
export async function getServiceAccounts(environmentId, namespaceId) {
 | 
			
		||||
  try {
 | 
			
		||||
    const {
 | 
			
		||||
      data: { items },
 | 
			
		||||
    } = await axios.get(urlBuilder(environmentId, namespaceId));
 | 
			
		||||
    return items;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    throw parseAxiosError(error);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function urlBuilder(environmentId, namespaceId) {
 | 
			
		||||
  return `endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespaceId}/serviceaccounts`;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import _ from 'lodash-es';
 | 
			
		|||
import KubernetesConfigurationConverter from 'Kubernetes/converters/configuration';
 | 
			
		||||
import KubernetesConfigMapConverter from 'Kubernetes/converters/configMap';
 | 
			
		||||
import KubernetesSecretConverter from 'Kubernetes/converters/secret';
 | 
			
		||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import KubernetesCommonHelper from 'Kubernetes/helpers/commonHelper';
 | 
			
		||||
 | 
			
		||||
class KubernetesConfigurationService {
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +62,7 @@ class KubernetesConfigurationService {
 | 
			
		|||
  async createAsync(formValues) {
 | 
			
		||||
    formValues.ConfigurationOwner = KubernetesCommonHelper.ownerToLabel(formValues.ConfigurationOwner);
 | 
			
		||||
 | 
			
		||||
    if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
 | 
			
		||||
    if (formValues.Kind === KubernetesConfigurationKinds.CONFIGMAP) {
 | 
			
		||||
      const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
 | 
			
		||||
      await this.KubernetesConfigMapService.create(configMap);
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +79,7 @@ class KubernetesConfigurationService {
 | 
			
		|||
   * UPDATE
 | 
			
		||||
   */
 | 
			
		||||
  async updateAsync(formValues, configuration) {
 | 
			
		||||
    if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
 | 
			
		||||
    if (formValues.Kind === KubernetesConfigurationKinds.CONFIGMAP) {
 | 
			
		||||
      const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
 | 
			
		||||
      configMap.ConfigurationOwner = configuration.ConfigurationOwner;
 | 
			
		||||
      await this.KubernetesConfigMapService.update(configMap);
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +98,7 @@ class KubernetesConfigurationService {
 | 
			
		|||
   * DELETE
 | 
			
		||||
   */
 | 
			
		||||
  async deleteAsync(config) {
 | 
			
		||||
    if (config.Type === KubernetesConfigurationTypes.CONFIGMAP) {
 | 
			
		||||
    if (config.Kind == KubernetesConfigurationKinds.CONFIGMAP) {
 | 
			
		||||
      await this.KubernetesConfigMapService.delete(config);
 | 
			
		||||
    } else {
 | 
			
		||||
      await this.KubernetesSecretService.delete(config);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,9 +14,9 @@
 | 
			
		|||
        <rd-widget-body>
 | 
			
		||||
          <form class="form-horizontal" name="kubernetesConfigurationCreationForm" autocomplete="off">
 | 
			
		||||
            <!-- name -->
 | 
			
		||||
            <div class="form-group">
 | 
			
		||||
            <div class="form-group mb-0">
 | 
			
		||||
              <label for="configuration_name" class="col-sm-3 col-lg-2 control-label text-left required">Name</label>
 | 
			
		||||
              <div class="col-sm-8 col-lg-9">
 | 
			
		||||
              <div class="col-sm-8 col-lg-9 mb-0">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  class="form-control"
 | 
			
		||||
| 
						 | 
				
			
			@ -29,21 +29,21 @@
 | 
			
		|||
                  required
 | 
			
		||||
                  data-cy="k8sConfigCreate-nameInput"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-group" ng-show="kubernetesConfigurationCreationForm.configuration_name.$invalid || ctrl.state.alreadyExist">
 | 
			
		||||
              <div class="col-sm-3 col-lg-2"></div>
 | 
			
		||||
              <div class="col-sm-8 col-lg-9 small text-warning">
 | 
			
		||||
                <div ng-messages="kubernetesConfigurationCreationForm.configuration_name.$error">
 | 
			
		||||
                  <p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required.</p>
 | 
			
		||||
                  <p ng-message="pattern" class="vertical-center"
 | 
			
		||||
                    ><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric
 | 
			
		||||
                    characters, '-' or '.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
 | 
			
		||||
                  >
 | 
			
		||||
                <div ng-show="kubernetesConfigurationCreationForm.configuration_name.$invalid || ctrl.state.alreadyExist">
 | 
			
		||||
                  <div class="help-block small text-warning">
 | 
			
		||||
                    <div ng-messages="kubernetesConfigurationCreationForm.configuration_name.$error">
 | 
			
		||||
                      <p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required.</p>
 | 
			
		||||
                      <p ng-message="pattern" class="vertical-center"
 | 
			
		||||
                        ><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric
 | 
			
		||||
                        characters, '-' or '.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
 | 
			
		||||
                      >
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p ng-if="ctrl.state.alreadyExist" class="vertical-center"
 | 
			
		||||
                      ><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> A configuration with the same name already exists inside the selected
 | 
			
		||||
                      namespace.</p
 | 
			
		||||
                    >
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <p ng-if="ctrl.state.alreadyExist" class="vertical-center"
 | 
			
		||||
                  ><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> A configuration with the same name already exists inside the selected namespace.</p
 | 
			
		||||
                >
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <!-- !name -->
 | 
			
		||||
| 
						 | 
				
			
			@ -80,56 +80,155 @@
 | 
			
		|||
            <!-- !resource-pool -->
 | 
			
		||||
 | 
			
		||||
            <div ng-if="ctrl.formValues.ResourcePool">
 | 
			
		||||
              <div class="col-sm-12 form-section-title"> Configuration type </div>
 | 
			
		||||
              <div class="col-sm-12 form-section-title"> Configuration kind </div>
 | 
			
		||||
 | 
			
		||||
              <div class="form-group">
 | 
			
		||||
                <div class="col-sm-12 small text-muted"> Select the type of data that you want to save in the configuration. </div>
 | 
			
		||||
                <div class="col-sm-12 small text-muted"> Select the kind of data that you want to save in the configuration. </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- type options -->
 | 
			
		||||
              <div class="form-group">
 | 
			
		||||
                <div class="col-sm-12">
 | 
			
		||||
                  <div class="boxselector_wrapper">
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" />
 | 
			
		||||
                      <label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton">
 | 
			
		||||
                        <div class="boxselector_header">
 | 
			
		||||
                          <pr-icon icon="'svg-filecode'"></pr-icon>
 | 
			
		||||
                          ConfigMap
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <p>This configuration holds non-sensitive information</p>
 | 
			
		||||
                      </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <input type="radio" id="type_secret" ng-value="ctrl.KubernetesConfigurationTypes.SECRET" ng-model="ctrl.formValues.Type" />
 | 
			
		||||
                      <label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton">
 | 
			
		||||
                        <div class="boxselector_header">
 | 
			
		||||
                          <pr-icon icon="'lock'" feather="true"></pr-icon>
 | 
			
		||||
                          Secret
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <p>This configuration holds sensitive information</p>
 | 
			
		||||
                      </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
              <div class="form-group px-[15px] mb-0">
 | 
			
		||||
                <div class="boxselector_wrapper">
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationKinds.CONFIGMAP" ng-model="ctrl.formValues.Kind" ng-change="ctrl.onChangeKind()" />
 | 
			
		||||
                    <label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton">
 | 
			
		||||
                      <div class="boxselector_header">
 | 
			
		||||
                        <pr-icon icon="'svg-filecode'"></pr-icon>
 | 
			
		||||
                        ConfigMap
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <p>This configuration holds non-sensitive information</p>
 | 
			
		||||
                    </label>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <input type="radio" id="type_secret" ng-value="ctrl.KubernetesConfigurationKinds.SECRET" ng-model="ctrl.formValues.Kind" ng-change="ctrl.onChangeKind()" />
 | 
			
		||||
                    <label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton">
 | 
			
		||||
                      <div class="boxselector_header">
 | 
			
		||||
                        <pr-icon icon="'lock'" feather="true"></pr-icon>
 | 
			
		||||
                        Secret
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <p>This configuration holds sensitive information</p>
 | 
			
		||||
                    </label>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <!-- !type options -->
 | 
			
		||||
 | 
			
		||||
              <div class="col-sm-12 form-section-title" ng-if="ctrl.formValues.Type == ctrl.KubernetesConfigurationTypes.SECRET"> Information </div>
 | 
			
		||||
              <div class="form-group" ng-if="ctrl.formValues.Type == ctrl.KubernetesConfigurationTypes.SECRET">
 | 
			
		||||
                <div class="col-sm-12 small text-muted">
 | 
			
		||||
                  Creating a sensitive configuration will create a Kubernetes Secret of type <code>Opaque</code>. You can find more information about this in the
 | 
			
		||||
                  <a href="https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" target="_blank">official documentation</a>.
 | 
			
		||||
              <div ng-if="ctrl.formValues.Kind === ctrl.KubernetesConfigurationKinds.SECRET">
 | 
			
		||||
                <div class="col-sm-12 form-section-title"> Information </div>
 | 
			
		||||
                <div class="form-group">
 | 
			
		||||
                  <div class="col-sm-12 small text-muted vertical-center">
 | 
			
		||||
                    <pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
 | 
			
		||||
                    <span>
 | 
			
		||||
                      More information about types of secret can be found in the official
 | 
			
		||||
                      <a class="hyperlink" href="https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" target="_blank">kubernetes documentation</a>.
 | 
			
		||||
                    </span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="form-group">
 | 
			
		||||
                  <label for="configuration_data_type" class="col-sm-3 col-lg-2 control-label text-left">Type</label>
 | 
			
		||||
                  <div class="col-sm-8 col-lg-9">
 | 
			
		||||
                    <select
 | 
			
		||||
                      class="form-control"
 | 
			
		||||
                      id="configuration_data_type"
 | 
			
		||||
                      ng-model="ctrl.formValues.Type"
 | 
			
		||||
                      ng-options="value.name for (name, value) in ctrl.KubernetesSecretTypes"
 | 
			
		||||
                      ng-change="ctrl.onSecretTypeChange()"
 | 
			
		||||
                    ></select>
 | 
			
		||||
 | 
			
		||||
                    <div class="col-sm-3 col-lg-2"></div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypes.SERVICEACCOUNTTOKEN" class="col-sm-12 small text-warning vertical-center pt-5">
 | 
			
		||||
                    <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
 | 
			
		||||
                    <span
 | 
			
		||||
                      >You should only create a service account token Secret object if you can't use the TokenRequest API to obtain a token, and the security exposure of persisting
 | 
			
		||||
                      a non-expiring token credential in a readable API object is acceptable to you. <br />See
 | 
			
		||||
                      <a href="https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets" target="_blank">service account token secrets</a> in the
 | 
			
		||||
                      kubernetes documentation.</span
 | 
			
		||||
                    >
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypes.DOCKERCFG" class="col-sm-12 small text-muted vertical-center pt-5">
 | 
			
		||||
                    <pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
 | 
			
		||||
                    <span>Ensure the Secret data field contains a <code>.dockercfg</code> key whose value is content of a legacy <code>~/.dockercfg</code> file.</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypes.DOCKERCONFIGJSON" class="col-sm-12 small text-muted vertical-center pt-5">
 | 
			
		||||
                    <pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
 | 
			
		||||
                    <span>Ensure the Secret data field contains a <code>.dockerconfigjson</code> key whose value is content of a <code>~/.docker/config.json</code> file.</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypes.TLS" class="col-sm-12 small text-muted vertical-center pt-5">
 | 
			
		||||
                    <pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
 | 
			
		||||
                    <span>Ensure the Secret data field contains a <code>tls.key</code> key and a <code>tls.crt</code> key.</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypes.BOOTSTRAPTOKEN" class="col-sm-12 small text-muted vertical-center pt-5">
 | 
			
		||||
                    <pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
 | 
			
		||||
                    <span
 | 
			
		||||
                      >Ensure the Secret data field contains a <code>token-id</code> key and a <code>token-secret</code> key. See
 | 
			
		||||
                      <a href="https://kubernetes.io/docs/concepts/configuration/secret/#bootstrap-token-secrets" target="_blank">bootstrap token secrets</a> in the kubernetes
 | 
			
		||||
                      documentation for optional keys.</span
 | 
			
		||||
                    >
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group" ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypes.CUSTOM">
 | 
			
		||||
                  <label for="configuration_data_customtype" class="col-sm-3 col-lg-2 control-label text-left required">Custom Type</label>
 | 
			
		||||
                  <div class="col-sm-8 col-lg-9">
 | 
			
		||||
                    <input
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      name="custom_type"
 | 
			
		||||
                      class="form-control"
 | 
			
		||||
                      id="configuration_data_customtype"
 | 
			
		||||
                      ng-model="ctrl.formValues.customType"
 | 
			
		||||
                      ng-pattern="/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/"
 | 
			
		||||
                      required
 | 
			
		||||
                    />
 | 
			
		||||
                    <div ng-show="kubernetesConfigurationCreationForm.custom_type.$invalid">
 | 
			
		||||
                      <div class="help-block small text-warning">
 | 
			
		||||
                        <div ng-messages="kubernetesConfigurationCreationForm.custom_type.$error">
 | 
			
		||||
                          <p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required.</p>
 | 
			
		||||
                          <p ng-message="pattern" class="vertical-center"
 | 
			
		||||
                            ><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric
 | 
			
		||||
                            characters, '-' or '.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
 | 
			
		||||
                          >
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group" ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypes.SERVICEACCOUNTTOKEN">
 | 
			
		||||
                  <label for="service_account" class="col-sm-3 col-lg-2 control-label text-left required">Service Account</label>
 | 
			
		||||
                  <div class="col-sm-8 col-lg-9">
 | 
			
		||||
                    <select
 | 
			
		||||
                      class="form-control"
 | 
			
		||||
                      id="service_account"
 | 
			
		||||
                      ng-selected="$first"
 | 
			
		||||
                      ng-model="ctrl.formValues.ServiceAccountName"
 | 
			
		||||
                      ng-options="value.metadata.name as value.metadata.name for (name, value) in ctrl.availableServiceAccounts"
 | 
			
		||||
                      data-cy="k8sConfigCreate-serviceAccountDropdown"
 | 
			
		||||
                      ng-change="ctrl.onChangeServiceAccount()"
 | 
			
		||||
                      required
 | 
			
		||||
                    ></select>
 | 
			
		||||
                    <div class="help-block small text-warning" ng-messages="kubernetesConfigurationCreationForm.service_account.$error">
 | 
			
		||||
                      <p class="vertical-center" ng-message="required"> <pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>This field is required.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <kubernetes-configuration-data
 | 
			
		||||
                ng-if="ctrl.formValues"
 | 
			
		||||
                form-values="ctrl.formValues"
 | 
			
		||||
                is-docker-config="ctrl.state.isDockerConfig"
 | 
			
		||||
                is-valid="ctrl.state.isDataValid"
 | 
			
		||||
                on-change-validation="ctrl.isFormValid()"
 | 
			
		||||
                is-creation="true"
 | 
			
		||||
                is-editor-dirty="ctrl.state.isEditorDirty"
 | 
			
		||||
              ></kubernetes-configuration-data>
 | 
			
		||||
 | 
			
		||||
              <div class="form-group" ng-if="ctrl.state.secretWarningMessage">
 | 
			
		||||
                <div class="col-sm-12 small text-warning vertical-center pt-5">
 | 
			
		||||
                  <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
 | 
			
		||||
                  <span>{{ ctrl.state.secretWarningMessage }}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- summary -->
 | 
			
		||||
| 
						 | 
				
			
			@ -150,7 +249,7 @@
 | 
			
		|||
                  button-spinner="ctrl.state.actionInProgress"
 | 
			
		||||
                  data-cy="k8sConfigCreate-CreateConfigButton"
 | 
			
		||||
                >
 | 
			
		||||
                  <span ng-hide="ctrl.state.actionInProgress">Create {{ ctrl.formValues.Type | kubernetesConfigurationTypeText }}</span>
 | 
			
		||||
                  <span ng-hide="ctrl.state.actionInProgress">Create {{ ctrl.formValues.Kind | kubernetesConfigurationKindText }}</span>
 | 
			
		||||
                  <span ng-show="ctrl.state.actionInProgress">Creation in progress...</span>
 | 
			
		||||
                </button>
 | 
			
		||||
              </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,43 +1,134 @@
 | 
			
		|||
import angular from 'angular';
 | 
			
		||||
import _ from 'lodash-es';
 | 
			
		||||
import { KubernetesConfigurationFormValues, KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
 | 
			
		||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds, KubernetesSecretTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
 | 
			
		||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
 | 
			
		||||
import { getServiceAccounts } from 'Kubernetes/rest/serviceAccount';
 | 
			
		||||
 | 
			
		||||
import { isConfigurationFormValid } from '../validation';
 | 
			
		||||
 | 
			
		||||
class KubernetesCreateConfigurationController {
 | 
			
		||||
  /* @ngInject */
 | 
			
		||||
  constructor($async, $state, $window, ModalService, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService) {
 | 
			
		||||
  constructor($async, $state, $window, ModalService, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService, EndpointProvider) {
 | 
			
		||||
    this.$async = $async;
 | 
			
		||||
    this.$state = $state;
 | 
			
		||||
    this.$window = $window;
 | 
			
		||||
    this.EndpointProvider = EndpointProvider;
 | 
			
		||||
    this.ModalService = ModalService;
 | 
			
		||||
    this.Notifications = Notifications;
 | 
			
		||||
    this.Authentication = Authentication;
 | 
			
		||||
    this.KubernetesConfigurationService = KubernetesConfigurationService;
 | 
			
		||||
    this.KubernetesResourcePoolService = KubernetesResourcePoolService;
 | 
			
		||||
    this.KubernetesConfigurationTypes = KubernetesConfigurationTypes;
 | 
			
		||||
    this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
 | 
			
		||||
    this.KubernetesSecretTypes = KubernetesSecretTypes;
 | 
			
		||||
 | 
			
		||||
    this.onInit = this.onInit.bind(this);
 | 
			
		||||
    this.createConfigurationAsync = this.createConfigurationAsync.bind(this);
 | 
			
		||||
    this.getConfigurationsAsync = this.getConfigurationsAsync.bind(this);
 | 
			
		||||
    this.onResourcePoolSelectionChangeAsync = this.onResourcePoolSelectionChangeAsync.bind(this);
 | 
			
		||||
    this.onSecretTypeChange = this.onSecretTypeChange.bind(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChangeName() {
 | 
			
		||||
    const filteredConfigurations = _.filter(this.configurations, (config) => config.Namespace === this.formValues.ResourcePool.Namespace.Name);
 | 
			
		||||
    const filteredConfigurations = _.filter(
 | 
			
		||||
      this.configurations,
 | 
			
		||||
      (config) => config.Namespace === this.formValues.ResourcePool.Namespace.Name && config.Kind === this.formValues.Kind
 | 
			
		||||
    );
 | 
			
		||||
    this.state.alreadyExist = _.find(filteredConfigurations, (config) => config.Name === this.formValues.Name) !== undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onResourcePoolSelectionChange() {
 | 
			
		||||
  onChangeKind() {
 | 
			
		||||
    this.onChangeName();
 | 
			
		||||
    // if there is no data field, add one
 | 
			
		||||
    if (this.formValues.Data.length === 0) {
 | 
			
		||||
      this.formValues.Data.push(new KubernetesConfigurationFormValuesEntry());
 | 
			
		||||
    }
 | 
			
		||||
    // if changing back to a secret, that is a service account token, remove the data field
 | 
			
		||||
    if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
 | 
			
		||||
      this.onSecretTypeChange();
 | 
			
		||||
    } else {
 | 
			
		||||
      this.isDockerConfig = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async onResourcePoolSelectionChangeAsync() {
 | 
			
		||||
    try {
 | 
			
		||||
      this.onChangeName();
 | 
			
		||||
      this.availableServiceAccounts = await getServiceAccounts(this.environmentId, this.formValues.ResourcePool.Namespace.Name);
 | 
			
		||||
      this.formValues.ServiceAccountName = this.availableServiceAccounts.length > 0 ? this.availableServiceAccounts[0].metadata.name : '';
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      this.Notifications.error('Failure', err, 'Unable load service accounts');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  onResourcePoolSelectionChange() {
 | 
			
		||||
    this.$async(this.onResourcePoolSelectionChangeAsync);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSecretTypeChange() {
 | 
			
		||||
    switch (this.formValues.Type.value) {
 | 
			
		||||
      case KubernetesSecretTypes.OPAQUE.value:
 | 
			
		||||
      case KubernetesSecretTypes.CUSTOM.value:
 | 
			
		||||
        this.formValues.Data = this.formValues.Data.filter((entry) => entry.Value !== '');
 | 
			
		||||
        if (this.formValues.Data.length === 0) {
 | 
			
		||||
          this.addRequiredKeysToForm(['']);
 | 
			
		||||
        }
 | 
			
		||||
        this.state.isDockerConfig = false;
 | 
			
		||||
        break;
 | 
			
		||||
      case KubernetesSecretTypes.SERVICEACCOUNTTOKEN.value:
 | 
			
		||||
        // data isn't required for service account tokens, so remove the data fields if they are empty
 | 
			
		||||
        this.addRequiredKeysToForm([]);
 | 
			
		||||
        this.state.isDockerConfig = false;
 | 
			
		||||
        break;
 | 
			
		||||
      case KubernetesSecretTypes.DOCKERCONFIGJSON.value:
 | 
			
		||||
        this.addRequiredKeysToForm(['.dockerconfigjson']);
 | 
			
		||||
        this.state.isDockerConfig = true;
 | 
			
		||||
        break;
 | 
			
		||||
      case KubernetesSecretTypes.DOCKERCFG.value:
 | 
			
		||||
        this.addRequiredKeysToForm(['.dockercfg']);
 | 
			
		||||
        this.state.isDockerConfig = true;
 | 
			
		||||
        break;
 | 
			
		||||
      case KubernetesSecretTypes.BASICAUTH.value:
 | 
			
		||||
        this.addRequiredKeysToForm(['username', 'password']);
 | 
			
		||||
        this.state.isDockerConfig = false;
 | 
			
		||||
        break;
 | 
			
		||||
      case KubernetesSecretTypes.SSHAUTH.value:
 | 
			
		||||
        this.addRequiredKeysToForm(['ssh-privatekey']);
 | 
			
		||||
        this.state.isDockerConfig = false;
 | 
			
		||||
        break;
 | 
			
		||||
      case KubernetesSecretTypes.TLS.value:
 | 
			
		||||
        this.addRequiredKeysToForm(['tls.crt', 'tls.key']);
 | 
			
		||||
        this.state.isDockerConfig = false;
 | 
			
		||||
        break;
 | 
			
		||||
      case KubernetesSecretTypes.BOOTSTRAPTOKEN.value:
 | 
			
		||||
        this.addRequiredKeysToForm(['token-id', 'token-secret']);
 | 
			
		||||
        this.state.isDockerConfig = false;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        this.state.isDockerConfig = false;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    this.isFormValid();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addRequiredKeysToForm(keys) {
 | 
			
		||||
    // remove data entries that have an empty value
 | 
			
		||||
    this.formValues.Data = this.formValues.Data.filter((entry) => entry.Value);
 | 
			
		||||
 | 
			
		||||
    keys.forEach((key) => {
 | 
			
		||||
      // if the key doesn't exist on the form, add a new formValues.Data entry
 | 
			
		||||
      if (!this.formValues.Data.some((data) => data.Key === key)) {
 | 
			
		||||
        this.formValues.Data.push(new KubernetesConfigurationFormValuesEntry());
 | 
			
		||||
        const index = this.formValues.Data.length - 1;
 | 
			
		||||
        this.formValues.Data[index].Key = key;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isFormValid() {
 | 
			
		||||
    const uniqueCheck = !this.state.alreadyExist && this.state.isDataValid;
 | 
			
		||||
    if (this.formValues.IsSimple) {
 | 
			
		||||
      return this.formValues.Data.length > 0 && uniqueCheck;
 | 
			
		||||
    }
 | 
			
		||||
    return uniqueCheck;
 | 
			
		||||
    const [isValid, warningMessage] = isConfigurationFormValid(this.state.alreadyExist, this.state.isDataValid, this.formValues);
 | 
			
		||||
    this.state.secretWarningMessage = warningMessage;
 | 
			
		||||
    return isValid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async createConfigurationAsync() {
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +138,7 @@ class KubernetesCreateConfigurationController {
 | 
			
		|||
      if (!this.formValues.IsSimple) {
 | 
			
		||||
        this.formValues.Data = KubernetesConfigurationHelper.parseYaml(this.formValues);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await this.KubernetesConfigurationService.create(this.formValues);
 | 
			
		||||
      this.Notifications.success('Success', 'Configuration succesfully created');
 | 
			
		||||
      this.state.isEditorDirty = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -87,10 +179,12 @@ class KubernetesCreateConfigurationController {
 | 
			
		|||
      alreadyExist: false,
 | 
			
		||||
      isDataValid: true,
 | 
			
		||||
      isEditorDirty: false,
 | 
			
		||||
      isDockerConfig: false,
 | 
			
		||||
      secretWarningMessage: '',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.formValues = new KubernetesConfigurationFormValues();
 | 
			
		||||
    this.formValues.Data.push(new KubernetesConfigurationFormValuesEntry());
 | 
			
		||||
    this.formValues.Data = [new KubernetesConfigurationFormValuesEntry()];
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const resourcePools = await this.KubernetesResourcePoolService.get();
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +192,10 @@ class KubernetesCreateConfigurationController {
 | 
			
		|||
 | 
			
		||||
      this.formValues.ResourcePool = this.resourcePools[0];
 | 
			
		||||
      await this.getConfigurations();
 | 
			
		||||
 | 
			
		||||
      this.environmentId = this.EndpointProvider.endpointID();
 | 
			
		||||
      this.availableServiceAccounts = await getServiceAccounts(this.environmentId, this.resourcePools[0].Namespace.Name);
 | 
			
		||||
      this.formValues.ServiceAccountName = this.availableServiceAccounts.length > 0 ? this.availableServiceAccounts[0].metadata.name : '';
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      this.Notifications.error('Failure', err, 'Unable to load view data');
 | 
			
		||||
    } finally {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,9 +46,15 @@
 | 
			
		|||
                      </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                      <td class="!pl-0">Configuration type</td>
 | 
			
		||||
                      <td class="!pl-0">Configuration kind</td>
 | 
			
		||||
                      <td>
 | 
			
		||||
                        {{ ctrl.configuration.Type | kubernetesConfigurationTypeText }}
 | 
			
		||||
                        {{ ctrl.configuration.Kind | kubernetesConfigurationKindText }}
 | 
			
		||||
                      </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr ng-if="ctrl.secretTypeName">
 | 
			
		||||
                      <td class="!pl-0">Secret Type</td>
 | 
			
		||||
                      <td>
 | 
			
		||||
                        {{ ctrl.secretTypeName }}
 | 
			
		||||
                      </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                  </tbody>
 | 
			
		||||
| 
						 | 
				
			
			@ -99,11 +105,20 @@
 | 
			
		|||
            <kubernetes-configuration-data
 | 
			
		||||
              ng-if="ctrl.formValues"
 | 
			
		||||
              form-values="ctrl.formValues"
 | 
			
		||||
              is-docker-config="ctrl.state.isDockerConfig"
 | 
			
		||||
              is-valid="ctrl.state.isDataValid"
 | 
			
		||||
              on-change-validation="ctrl.isFormValid()"
 | 
			
		||||
              is-creation="false"
 | 
			
		||||
              is-editor-dirty="ctrl.state.isEditorDirty"
 | 
			
		||||
            ></kubernetes-configuration-data>
 | 
			
		||||
 | 
			
		||||
            <div class="form-group" ng-if="ctrl.state.secretWarningMessage">
 | 
			
		||||
              <div class="col-sm-12 small text-warning vertical-center pt-5">
 | 
			
		||||
                <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
 | 
			
		||||
                <span>{{ ctrl.state.secretWarningMessage }}</span>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- summary -->
 | 
			
		||||
            <kubernetes-summary-view
 | 
			
		||||
              ng-if="!(!ctrl.isFormValid() || !kubernetesConfigurationCreationForm.$valid || ctrl.state.actionInProgress)"
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +137,7 @@
 | 
			
		|||
                  button-spinner="ctrl.state.actionInProgress"
 | 
			
		||||
                  data-cy="k8sConfigDetail-updateConfig"
 | 
			
		||||
                >
 | 
			
		||||
                  <span ng-hide="ctrl.state.actionInProgress">Update {{ ctrl.configuration.Type | kubernetesConfigurationTypeText }}</span>
 | 
			
		||||
                  <span ng-hide="ctrl.state.actionInProgress">Update {{ ctrl.configuration.Kind | kubernetesConfigurationKindText }}</span>
 | 
			
		||||
                  <span ng-show="ctrl.state.actionInProgress">Update in progress...</span>
 | 
			
		||||
                </button>
 | 
			
		||||
              </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,12 +2,14 @@ import angular from 'angular';
 | 
			
		|||
import _ from 'lodash-es';
 | 
			
		||||
 | 
			
		||||
import { KubernetesConfigurationFormValues } from 'Kubernetes/models/configuration/formvalues';
 | 
			
		||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds, KubernetesSecretTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
 | 
			
		||||
import KubernetesConfigurationConverter from 'Kubernetes/converters/configuration';
 | 
			
		||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
 | 
			
		||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
 | 
			
		||||
 | 
			
		||||
import { isConfigurationFormValid } from '../validation';
 | 
			
		||||
 | 
			
		||||
class KubernetesConfigurationController {
 | 
			
		||||
  /* @ngInject */
 | 
			
		||||
  constructor(
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +38,8 @@ class KubernetesConfigurationController {
 | 
			
		|||
    this.KubernetesResourcePoolService = KubernetesResourcePoolService;
 | 
			
		||||
    this.KubernetesApplicationService = KubernetesApplicationService;
 | 
			
		||||
    this.KubernetesEventService = KubernetesEventService;
 | 
			
		||||
    this.KubernetesConfigurationTypes = KubernetesConfigurationTypes;
 | 
			
		||||
    this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
 | 
			
		||||
    this.KubernetesSecretTypes = KubernetesSecretTypes;
 | 
			
		||||
    this.KubernetesConfigMapService = KubernetesConfigMapService;
 | 
			
		||||
    this.KubernetesSecretService = KubernetesSecretService;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -76,10 +79,9 @@ class KubernetesConfigurationController {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  isFormValid() {
 | 
			
		||||
    if (this.formValues.IsSimple) {
 | 
			
		||||
      return this.formValues.Data.length > 0 && this.state.isDataValid;
 | 
			
		||||
    }
 | 
			
		||||
    return this.state.isDataValid;
 | 
			
		||||
    const [isValid, warningMessage] = isConfigurationFormValid(this.state.alreadyExist, this.state.isDataValid, this.formValues);
 | 
			
		||||
    this.state.secretWarningMessage = warningMessage;
 | 
			
		||||
    return isValid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: refactor
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +91,7 @@ class KubernetesConfigurationController {
 | 
			
		|||
    try {
 | 
			
		||||
      this.state.actionInProgress = true;
 | 
			
		||||
      if (
 | 
			
		||||
        this.formValues.Type !== this.configuration.Type ||
 | 
			
		||||
        this.formValues.Kind !== this.configuration.Kind ||
 | 
			
		||||
        this.formValues.ResourcePool.Namespace.Name !== this.configuration.Namespace ||
 | 
			
		||||
        this.formValues.Name !== this.configuration.Name
 | 
			
		||||
      ) {
 | 
			
		||||
| 
						 | 
				
			
			@ -153,6 +155,7 @@ class KubernetesConfigurationController {
 | 
			
		|||
      this.formValues.Id = this.configuration.Id;
 | 
			
		||||
      this.formValues.Name = this.configuration.Name;
 | 
			
		||||
      this.formValues.Type = this.configuration.Type;
 | 
			
		||||
      this.formValues.Kind = this.configuration.Kind;
 | 
			
		||||
      this.oldDataYaml = this.formValues.DataYaml;
 | 
			
		||||
 | 
			
		||||
      return this.configuration;
 | 
			
		||||
| 
						 | 
				
			
			@ -254,6 +257,8 @@ class KubernetesConfigurationController {
 | 
			
		|||
        currentName: this.$state.$current.name,
 | 
			
		||||
        isDataValid: true,
 | 
			
		||||
        isEditorDirty: false,
 | 
			
		||||
        isDockerConfig: false,
 | 
			
		||||
        secretWarningMessage: '',
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      this.state.activeTab = this.LocalStorage.getActiveTab('configuration');
 | 
			
		||||
| 
						 | 
				
			
			@ -267,6 +272,23 @@ class KubernetesConfigurationController {
 | 
			
		|||
        await this.getEvents(this.configuration.Namespace);
 | 
			
		||||
        await this.getConfigurations();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // after loading the configuration, check if it is a docker config secret type
 | 
			
		||||
      if (
 | 
			
		||||
        this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET &&
 | 
			
		||||
        (this.formValues.Type === this.KubernetesSecretTypes.DOCKERCONFIGJSON.value || this.formValues.Type === this.KubernetesSecretTypes.DOCKERCFG.value)
 | 
			
		||||
      ) {
 | 
			
		||||
        this.state.isDockerConfig = true;
 | 
			
		||||
      }
 | 
			
		||||
      // convert the secret type to a human readable value
 | 
			
		||||
      if (this.formValues.Type) {
 | 
			
		||||
        const secretTypeValues = Object.values(this.KubernetesSecretTypes);
 | 
			
		||||
        const secretType = secretTypeValues.find((secretType) => secretType.value === this.formValues.Type);
 | 
			
		||||
        this.secretTypeName = secretType ? secretType.name : this.formValues.Type;
 | 
			
		||||
      } else {
 | 
			
		||||
        this.secretTypeName = '';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.tagUsedDataKeys();
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      this.Notifications.error('Failure', err, 'Unable to load view data');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
import { KubernetesSecretTypes } from '@/kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from '@/kubernetes/models/configuration/models';
 | 
			
		||||
 | 
			
		||||
export function isConfigurationFormValid(alreadyExist, isDataValid, formValues) {
 | 
			
		||||
  const uniqueCheck = !alreadyExist && isDataValid;
 | 
			
		||||
  let secretWarningMessage = '';
 | 
			
		||||
  let isFormValid = false;
 | 
			
		||||
 | 
			
		||||
  if (formValues.IsSimple) {
 | 
			
		||||
    if (formValues.Kind === KubernetesConfigurationKinds.SECRET) {
 | 
			
		||||
      let isSecretDataValid = true;
 | 
			
		||||
      const secretTypeValue = typeof formValues.Type === 'string' ? formValues.Type : formValues.Type.value;
 | 
			
		||||
 | 
			
		||||
      switch (secretTypeValue) {
 | 
			
		||||
        case KubernetesSecretTypes.SERVICEACCOUNTTOKEN.value:
 | 
			
		||||
          // data isn't required for service account tokens
 | 
			
		||||
          isFormValid = uniqueCheck && formValues.ResourcePool;
 | 
			
		||||
          return [isFormValid, ''];
 | 
			
		||||
        case KubernetesSecretTypes.DOCKERCFG.value:
 | 
			
		||||
          // needs to contain a .dockercfg key
 | 
			
		||||
          isSecretDataValid = formValues.Data.some((entry) => entry.Key === '.dockercfg');
 | 
			
		||||
          secretWarningMessage = isSecretDataValid ? '' : 'A data entry with a .dockercfg key is required.';
 | 
			
		||||
          break;
 | 
			
		||||
        case KubernetesSecretTypes.DOCKERCONFIGJSON.value:
 | 
			
		||||
          // needs to contain a .dockerconfigjson key
 | 
			
		||||
          isSecretDataValid = formValues.Data.some((entry) => entry.Key === '.dockerconfigjson');
 | 
			
		||||
          secretWarningMessage = isSecretDataValid ? '' : 'A data entry with a .dockerconfigjson key. is required.';
 | 
			
		||||
          break;
 | 
			
		||||
        case KubernetesSecretTypes.BASICAUTH.value:
 | 
			
		||||
          isSecretDataValid = formValues.Data.some((entry) => entry.Key === 'username' || entry.Key === 'password');
 | 
			
		||||
          secretWarningMessage = isSecretDataValid ? '' : 'A data entry with a username or password key is required.';
 | 
			
		||||
          break;
 | 
			
		||||
        case KubernetesSecretTypes.SSHAUTH.value:
 | 
			
		||||
          isSecretDataValid = formValues.Data.some((entry) => entry.Key === 'ssh-privatekey');
 | 
			
		||||
          secretWarningMessage = isSecretDataValid ? '' : `A data entry with a 'ssh-privatekey' key is required.`;
 | 
			
		||||
          break;
 | 
			
		||||
        case KubernetesSecretTypes.TLS.value:
 | 
			
		||||
          isSecretDataValid = formValues.Data.some((entry) => entry.Key === 'tls.crt') && formValues.Data.some((entry) => entry.Key === 'tls.key');
 | 
			
		||||
          secretWarningMessage = isSecretDataValid ? '' : `Data entries containing a 'tls.crt' key and a 'tls.key' key are required.`;
 | 
			
		||||
          break;
 | 
			
		||||
        case KubernetesSecretTypes.BOOTSTRAPTOKEN.value:
 | 
			
		||||
          isSecretDataValid = formValues.Data.some((entry) => entry.Key === 'token-id') && formValues.Data.some((entry) => entry.Key === 'token-secret');
 | 
			
		||||
          secretWarningMessage = isSecretDataValid ? '' : `Data entries containing a 'token-id' key and a 'token-secret' key are required.`;
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      isFormValid = uniqueCheck && formValues.ResourcePool && formValues.Data.length >= 1 && isSecretDataValid;
 | 
			
		||||
      return [isFormValid, secretWarningMessage];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isFormValid = formValues.Data.length > 0 && uniqueCheck && formValues.ResourcePool;
 | 
			
		||||
    return [isFormValid, secretWarningMessage];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isFormValid = uniqueCheck && formValues.ResourcePool;
 | 
			
		||||
  return [isFormValid, secretWarningMessage];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +1,17 @@
 | 
			
		|||
import { KubernetesResourceTypes, KubernetesResourceActions } from 'Kubernetes/models/resource-types/models';
 | 
			
		||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
 | 
			
		||||
 | 
			
		||||
const { CREATE, UPDATE } = KubernetesResourceActions;
 | 
			
		||||
 | 
			
		||||
export default function (formValues) {
 | 
			
		||||
  const action = formValues.Id ? UPDATE : CREATE;
 | 
			
		||||
  if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
 | 
			
		||||
  if (formValues.Kind === KubernetesConfigurationKinds.CONFIGMAP) {
 | 
			
		||||
    return [{ action, kind: KubernetesResourceTypes.CONFIGMAP, name: formValues.Name }];
 | 
			
		||||
  } else if (formValues.Type === KubernetesConfigurationTypes.SECRET) {
 | 
			
		||||
    return [{ action, kind: KubernetesResourceTypes.SECRET, name: formValues.Name }];
 | 
			
		||||
  } else if (formValues.Kind === KubernetesConfigurationKinds.SECRET) {
 | 
			
		||||
    let type = typeof formValues.Type === 'string' ? formValues.Type : formValues.Type.name;
 | 
			
		||||
    if (formValues.customType) {
 | 
			
		||||
      type = formValues.customType;
 | 
			
		||||
    }
 | 
			
		||||
    return [{ action, kind: KubernetesResourceTypes.SECRET, name: formValues.Name, type }];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue