mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 12:25:22 +02:00
feat(config): separate configmaps and secrets [EE-5078] (#9029)
This commit is contained in:
parent
4a331b71e1
commit
d7fc2046d7
102 changed files with 2845 additions and 665 deletions
|
@ -0,0 +1,124 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'Create ConfigMap'"
|
||||
breadcrumbs="[{ label:'ConfigMaps and Secrets', link:'kubernetes.configurations', linkParams:{ tab: 'configmaps' } }, 'Create a ConfigMap']"
|
||||
reload="true"
|
||||
></page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="kubernetesConfigurationCreationForm" autocomplete="off">
|
||||
<!-- resource-pool -->
|
||||
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
|
||||
<label for="resource-pool-selector" class="col-sm-3 col-lg-2 control-label text-left">Namespace</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="resource-pool-selector"
|
||||
ng-model="ctrl.formValues.ResourcePool"
|
||||
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
||||
ng-change="ctrl.onResourcePoolSelectionChange()"
|
||||
data-cy="k8sConfigCreate-namespaceDropdown"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
This namespace has exhausted its resource capacity and you will not be able to deploy the configuration. Contact your administrator to expand the capacity of the
|
||||
namespace.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
You do not have access to any namespace. Contact your administrator to get access to a namespace.
|
||||
</div>
|
||||
</div>
|
||||
<!-- !resource-pool -->
|
||||
|
||||
<!-- name -->
|
||||
<div class="form-group">
|
||||
<label for="configuration_name" class="col-sm-3 col-lg-2 control-label required text-left">Name</label>
|
||||
<div class="col-sm-8 col-lg-9 mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="configuration_name"
|
||||
ng-model="ctrl.formValues.Name"
|
||||
ng-pattern="/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/"
|
||||
ng-change="ctrl.onChangeName()"
|
||||
placeholder="my-configmap"
|
||||
auto-focus
|
||||
required
|
||||
data-cy="k8sConfigCreate-nameInput"
|
||||
/>
|
||||
<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'" mode="'warning'"></pr-icon> This field is required.</p>
|
||||
<p ng-message="pattern" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" 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'" mode="'warning'"></pr-icon> A configuration with the same name already exists inside the selected namespace.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name -->
|
||||
|
||||
<div class="col-sm-12 !p-0">
|
||||
<annotations-be-teaser></annotations-be-teaser>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.formValues.ResourcePool">
|
||||
<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>
|
||||
|
||||
<!-- summary -->
|
||||
<kubernetes-summary-view
|
||||
ng-if="!(!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress)"
|
||||
form-values="ctrl.formValues"
|
||||
></kubernetes-summary-view>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 10px"> Actions </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm !ml-0"
|
||||
ng-disabled="!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress || !ctrl.formValues.ResourcePool"
|
||||
ng-click="ctrl.createConfiguration()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
data-cy="k8sConfigCreate-CreateConfigButton"
|
||||
>
|
||||
<span ng-hide="ctrl.state.actionInProgress">Create {{ ctrl.formValues.Kind | kubernetesConfigurationKindText }}</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Creation in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesCreateConfigMapView', {
|
||||
templateUrl: './createConfigMap.html',
|
||||
controller: 'KubernetesCreateConfigMapController',
|
||||
controllerAs: 'ctrl',
|
||||
});
|
|
@ -0,0 +1,170 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import { KubernetesConfigurationFormValues, KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { getServiceAccounts } from 'Kubernetes/rest/serviceAccount';
|
||||
import { typeOptions } from '@/react/kubernetes/configs/CreateView/options';
|
||||
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../../validation';
|
||||
|
||||
class KubernetesCreateConfigMapController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $scope, $window, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService, EndpointProvider) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$scope = $scope;
|
||||
this.$window = $window;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.Notifications = Notifications;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
|
||||
|
||||
this.typeOptions = typeOptions;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.createConfigurationAsync = this.createConfigurationAsync.bind(this);
|
||||
this.getConfigurationsAsync = this.getConfigurationsAsync.bind(this);
|
||||
this.onResourcePoolSelectionChangeAsync = this.onResourcePoolSelectionChangeAsync.bind(this);
|
||||
}
|
||||
|
||||
onChangeName() {
|
||||
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;
|
||||
}
|
||||
|
||||
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 to load service accounts');
|
||||
}
|
||||
}
|
||||
onResourcePoolSelectionChange() {
|
||||
this.$async(this.onResourcePoolSelectionChangeAsync);
|
||||
}
|
||||
|
||||
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 [isValid] = isConfigurationFormValid(this.state.alreadyExist, this.state.isDataValid, this.formValues);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
async createConfigurationAsync() {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
this.formValues.ConfigurationOwner = this.Authentication.getUserDetails().username;
|
||||
if (!this.formValues.IsSimple) {
|
||||
this.formValues.Data = KubernetesConfigurationHelper.parseYaml(this.formValues);
|
||||
}
|
||||
|
||||
await this.KubernetesConfigurationService.create(this.formValues);
|
||||
this.Notifications.success('Success', `ConfigMap successfully created`);
|
||||
this.state.isEditorDirty = false;
|
||||
this.$state.go('kubernetes.configurations', { tab: 'configmaps' });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, `Unable to create ConfigMap`);
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
createConfiguration() {
|
||||
return this.$async(this.createConfigurationAsync);
|
||||
}
|
||||
|
||||
async getConfigurationsAsync() {
|
||||
try {
|
||||
this.configurations = await this.KubernetesConfigurationService.get();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve ConfigMaps');
|
||||
}
|
||||
}
|
||||
|
||||
getConfigurations() {
|
||||
return this.$async(this.getConfigurationsAsync);
|
||||
}
|
||||
|
||||
async uiCanExit() {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml && this.state.isEditorDirty) {
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
viewReady: false,
|
||||
alreadyExist: false,
|
||||
isDataValid: true,
|
||||
isEditorDirty: false,
|
||||
isDockerConfig: false,
|
||||
};
|
||||
|
||||
this.formValues = new KubernetesConfigurationFormValues();
|
||||
this.formValues.Kind = KubernetesConfigurationKinds.CONFIGMAP;
|
||||
this.formValues.Data = [new KubernetesConfigurationFormValuesEntry()];
|
||||
|
||||
try {
|
||||
const resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = _.filter(
|
||||
resourcePools,
|
||||
(resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && resourcePool.Namespace.Status === 'Active'
|
||||
);
|
||||
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
if (!this.formValues.ResourcePool) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
|
||||
this.$window.onbeforeunload = () => {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml && this.state.isEditorDirty) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
this.state.isEditorDirty = false;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesCreateConfigMapController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesCreateConfigMapController', KubernetesCreateConfigMapController);
|
Loading…
Add table
Add a link
Reference in a new issue