1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

feature(helm): move helm charts inside advance deployments (create from manifest) [EE-5999] (#10395)

This commit is contained in:
Prabhat Khera 2023-10-09 11:20:44 +13:00 committed by GitHub
parent 9885694df6
commit b468070945
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 877 additions and 388 deletions

View file

@ -1,39 +0,0 @@
export default class HelmAddRepositoryController {
/* @ngInject */
constructor($state, $async, HelmService, Notifications) {
this.$state = $state;
this.$async = $async;
this.HelmService = HelmService;
this.Notifications = Notifications;
}
doesRepoExist() {
if (!this.state.repository) {
return false;
}
// lowercase, strip trailing slash and compare
return this.repos.includes(this.state.repository.toLowerCase().replace(/\/$/, ''));
}
async addRepository() {
this.state.isAddingRepo = true;
try {
await this.HelmService.addHelmRepository(this.endpoint.Id, { url: this.state.repository });
this.Notifications.success('Success', 'Helm repository added successfully');
this.$state.reload(this.$state.current);
} catch (err) {
this.Notifications.error('Installation error', err);
} finally {
this.state.isAddingRepo = false;
}
}
$onInit() {
return this.$async(async () => {
this.state = {
isAddingRepo: false,
repository: '',
};
});
}
}

View file

@ -1,72 +0,0 @@
<rd-widget>
<div class="toolBar px-5 pt-5">
<div class="toolBarTitle vertical-center text-[16px] font-medium">
<div class="widget-icon space-right">
<pr-icon icon="'svg-helm'"></pr-icon>
</div>
Additional repositories
</div>
</div>
<rd-widget-body>
<div class="actionBar">
<form class="form-horizontal" name="addUserHelmRepoForm">
<div class="form-group">
<span class="col-sm-12 text-muted small inline-flex gap-1 !align-top">
<div class="icon icon-sm">
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
</div>
<div> Add a Helm repository. All Helm charts in the repository will be added to the list. </div>
</span>
</div>
<div class="form-group mb-2">
<div class="col-sm-12">
<input
type="url"
name="repo"
class="form-control"
ng-model="$ctrl.state.repository"
placeholder="https://charts.bitnami.com/bitnami"
ng-pattern="/^https?:///"
required
/>
</div>
</div>
<div class="form-group nomargin" ng-show="addUserHelmRepoForm.repo.$invalid">
<div class="small">
<div ng-messages="addUserHelmRepoForm.repo.$error">
<p class="vertical-center text-warning" ng-message="pattern"
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> A valid URL beginning with http(s) is required.</p
>
</div>
</div>
</div>
<div class="form-group nomargin" ng-show="$ctrl.doesRepoExist()">
<div class="small">
<div ng-messages="addUserHelmRepoForm.repo.$error">
<p class="vertical-center text-warning"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Helm repository already exists.</p>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm nomargin"
ng-click="$ctrl.addRepository()"
ng-disabled="$ctrl.state.isAddingRepo || addUserHelmRepoForm.repo.$invalid || $ctrl.doesRepoExist()"
analytics-on
analytics-category="kubernetes"
analytics-event="kubernetes-helm-add-repository"
>
Add repository
</button>
</div>
</div>
</form>
</div>
</rd-widget-body>
</rd-widget>

View file

@ -1,11 +0,0 @@
import angular from 'angular';
import controller from './helm-add-repository.controller';
angular.module('portainer.kubernetes').component('helmAddRepository', {
templateUrl: './helm-add-repository.html',
controller,
bindings: {
repos: '<',
endpoint: '<',
},
});

View file

@ -1,5 +1,5 @@
<!-- helm chart -->
<div ng-class="{ 'blocklist-item--selected': $ctrl.model.Selected }" class="blocklist-item template-item mx-[10px]" ng-click="$ctrl.onSelect($ctrl.model)">
<div ng-class="{ 'blocklist-item--selected': $ctrl.model.Selected }" class="blocklist-item template-item mx-0" ng-click="$ctrl.onSelect($ctrl.model)">
<div class="blocklist-item-box">
<!-- helmchart-image -->
<span class="shrink-0">

View file

@ -1,53 +1,54 @@
<div class="datatable">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar vertical-center relative w-full flex-wrap !gap-x-5 !gap-y-1">
<div class="toolBarTitle vertical-center">
<div class="widget-icon space-right">
<pr-icon icon="$ctrl.titleIcon"></pr-icon>
</div>
<div class="toolBar vertical-center relative w-full flex-wrap !gap-x-5 !gap-y-1 !px-0">
<div class="toolBarTitle vertical-center">
{{ $ctrl.titleText }}
</div>
{{ $ctrl.titleText }}
</div>
<div class="searchBar vertical-center !mr-0">
<pr-icon icon="'search'" class="searchIcon"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search for a chart..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="w-1/5">
<por-select
placeholder="'Select a category'"
value="$ctrl.state.selectedCategory"
options="$ctrl.state.categories"
on-change="($ctrl.onCategoryChange)"
is-clearable="true"
bind-to-body="true"
></por-select>
</div>
</div>
<div class="w-full">
<div class="mb-2 small"
>Select the Helm chart to use. Bring further Helm charts into your selection list via <a ui-sref="portainer.account">User settings - Helm repositories</a>.</div
>
<beta-alert
is-html="true"
message="'Beta feature - so far, this functionality has been tested in limited scenarios. For more information, see this <a href=\'https://www.portainer.io/blog/portainer-now-with-helm-support\' target=\'_blank\' class=\'hyperlink\'>blog post on Portainer Helm support</a>.'"
></beta-alert>
</div>
<div class="searchBar vertical-center !mr-0">
<pr-icon icon="'search'" class="searchIcon"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search for a chart..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="w-1/5">
<por-select
placeholder="'Select a category'"
value="$ctrl.state.selectedCategory"
options="$ctrl.state.categories"
on-change="($ctrl.onCategoryChange)"
is-clearable="true"
bind-to-body="true"
></por-select>
</div>
</div>
<div class="blocklist">
<helm-templates-list-item
ng-repeat="chart in $ctrl.charts | filter:$ctrl.state.textFilter | filter: $ctrl.state.selectedCategory"
model="chart"
type-label="helm"
on-select="($ctrl.selectAction)"
>
</helm-templates-list-item>
<div ng-if="$ctrl.loading" class="text-muted text-center">
Loading...
<div class="text-muted text-center"> Initial download of Helm Charts can take a few minutes </div>
</div>
<div ng-if="!$ctrl.loading && $ctrl.charts.length === 0" class="text-muted text-center"> No helm charts available. </div>
</div>
</rd-widget-body>
</rd-widget>
<div class="blocklist !px-0">
<helm-templates-list-item
ng-repeat="chart in $ctrl.charts | filter:$ctrl.state.textFilter | filter: $ctrl.state.selectedCategory"
model="chart"
type-label="helm"
on-select="($ctrl.selectAction)"
>
</helm-templates-list-item>
<div ng-if="$ctrl.loading" class="text-muted text-center">
Loading...
<div class="text-muted text-center"> Initial download of Helm Charts can take a few minutes </div>
</div>
<div ng-if="!$ctrl.loading && $ctrl.charts.length === 0" class="text-muted text-center"> No helm charts available. </div>
</div>
</div>

View file

@ -7,7 +7,6 @@ angular.module('portainer.kubernetes').component('helmTemplatesList', {
bindings: {
loading: '<',
titleText: '@',
titleIcon: '@',
charts: '<',
tableKey: '@',
selectAction: '<',

View file

@ -50,11 +50,11 @@ export default class HelmTemplatesController {
this.state.actionInProgress = true;
try {
const payload = {
Name: this.state.appName,
Name: this.stackName,
Repo: this.state.chart.repo,
Chart: this.state.chart.name,
Values: this.state.values,
Namespace: this.state.resourcePool.Namespace.Name,
Namespace: this.namespace,
};
await this.HelmService.install(this.endpoint.Id, payload);
this.Notifications.success('Success', 'Helm Chart successfully installed');
@ -96,7 +96,7 @@ export default class HelmTemplatesController {
this.state.reposLoading = true;
try {
// fetch globally set helm repo and user helm repos (parallel)
const { GlobalRepository, UserRepositories } = await this.HelmService.getHelmRepositories(this.endpoint.Id);
const { GlobalRepository, UserRepositories } = await this.HelmService.getHelmRepositories(this.user.ID);
this.state.globalRepository = GlobalRepository;
const userHelmReposUrls = UserRepositories.map((repo) => repo.URL);
const uniqueHelmRepos = [...new Set([GlobalRepository, ...userHelmReposUrls])].map((url) => url.toLowerCase()).filter((url) => url); // remove duplicates and blank, to lowercase
@ -155,6 +155,8 @@ export default class HelmTemplatesController {
$onInit() {
return this.$async(async () => {
this.user = this.Authentication.getUserDetails();
this.state = {
appName: '',
chart: null,
@ -178,6 +180,13 @@ export default class HelmTemplatesController {
const helmRepos = await this.getHelmRepoURLs();
await Promise.all([this.getLatestCharts(helmRepos), this.getResourcePools()]);
if (this.state.charts.length > 0 && this.$state.params.chartName) {
const chart = this.state.charts.find((chart) => chart.name === this.$state.params.chartName);
if (chart) {
this.selectHelmChart(chart);
}
}
this.state.viewReady = true;
});
}

View file

@ -1,180 +1,108 @@
<page-header title="'Helm'" breadcrumbs="['Charts']" reload="true"></page-header>
<information-panel title-text="Information" ng-if="!$ctrl.state.chart">
<beta-alert
is-html="true"
message="'Beta feature - initial version of Helm charts functionality, for more information see this <a href=\'https://www.portainer.io/blog/portainer-now-with-helm-support\' target=\'_blank\' class=\'hyperlink\'>blog post</a>.'"
></beta-alert>
<span class="small text-muted">
<p ng-if="$ctrl.state.globalRepository === ''" class="inline-flex items-center">
<pr-icon icon="'info'"></pr-icon>
<span>The Global Helm Repository is not configured.</span>
<a ng-if="$ctrl.state.isAdmin" ui-sref="portainer.settings">Configure Global Helm Repository in Settings</a>.
</p>
</span>
</information-panel>
<div class="row">
<!-- helmchart-form -->
<div class="col-sm-12" ng-if="$ctrl.state.chart">
<div class="col-sm-12 p-0" ng-if="$ctrl.state.chart">
<rd-widget>
<div class="toolBarTitle vertical-center px-5 pt-5 text-[16px] font-medium">
<fallback-image src="$ctrl.state.chart.icon" fallback-icon="'svg-helm'" class-name="'h-8 w-8'" size="'lg'"></fallback-image>
{{ $ctrl.state.chart.name }}
<div class="flex">
<div class="basis-3/4 rounded-[8px] m-2 bg-gray-4 th-highcontrast:bg-black th-highcontrast:text-white th-dark:bg-gray-iron-10 th-dark:text-white">
<div class="vertical-center p-5">
<fallback-image src="$ctrl.state.chart.icon" fallback-icon="'svg-helm'" class-name="'h-16 w-16'" size="'lg'"></fallback-image>
<div class="font-medium ml-4">
<div class="toolBarTitle text-[24px] mb-2">
{{ $ctrl.state.chart.name }}
<span class="space-left text-[14px] vertical-center font-normal">
<pr-icon icon="'svg-helm'" mode="'primary'"></pr-icon>
Helm
</span>
</div>
<div class="text-muted text-xs" ng-bind-html="$ctrl.state.chart.description"></div>
</div>
</div>
</div>
<div class="basis-1/4">
<div class="h-full w-full vertical-center justify-end pr-5">
<button type="button" class="btn btn-sm btn-link !text-gray-8 hover:no-underline th-highcontrast:!text-white th-dark:!text-white" ng-click="$ctrl.state.chart = null">
Clear selection
<pr-icon icon="'x'" class="ml-1"></pr-icon>
</button>
</div>
</div>
</div>
<rd-widget-body classes="padding">
<form class="form-horizontal" name="$ctrl.helmTemplateCreationForm">
<!-- description -->
<div>
<div class="col-sm-12 form-section-title"> Description </div>
<div class="form-group">
<div class="col-sm-12">
<div class="text-muted text-xs" ng-bind-html="$ctrl.state.chart.description"></div>
</div>
</div>
</div>
<!-- !description -->
<div class="col-sm-12 form-section-title"> Configuration </div>
<!-- namespace-input -->
<div class="form-group" ng-if="$ctrl.state.resourcePool">
<label for="resource-pool-selector" class="col-sm-2 control-label text-left">Namespace</label>
<div class="col-sm-10">
<select
class="form-control"
id="resource-pool-selector"
ng-model="$ctrl.state.resourcePool"
ng-options="resourcePool.Namespace.Name for resourcePool in $ctrl.state.resourcePools"
ng-change=""
ng-disabled="$ctrl.state.isEdit"
></select>
</div>
</div>
<div class="form-group" ng-if="!$ctrl.state.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>
<!-- !namespace-input -->
<!-- name-input -->
<div class="form-group mb-2">
<label for="release_name" class="col-sm-2 control-label required text-left">Name</label>
<div class="col-sm-10">
<input
type="text"
name="release_name"
class="form-control"
ng-model="$ctrl.state.appName"
placeholder="e.g. my-app"
required
ng-pattern="/^[a-z]([-a-z0-9]*[a-z0-9])?$/"
/>
</div>
</div>
<div class="form-group" ng-show="$ctrl.helmTemplateCreationForm.release_name.$invalid">
<div class="small">
<div ng-messages="$ctrl.helmTemplateCreationForm.release_name.$error">
<div class="col-sm-2"></div>
<p class="vertical-center col-sm-10 text-warning" ng-message="required">
<pr-icon icon="'alert-triangle'" mode="'warning'" class="vertical-center"></pr-icon>
This field is required.
</p>
<p class="vertical-center col-sm-10 text-warning" ng-message="pattern">
<pr-icon icon="'alert-triangle'" mode="'warning'" class="vertical-center"></pr-icon>
This field must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',
or 'abc-123').
</p>
</div>
</div>
</div>
<!-- !name-input -->
<div class="form-group">
<div class="col-sm-12">
<button
ng-if="!$ctrl.state.showCustomValues && !$ctrl.state.loadingValues"
class="btn btn-xs btn-default vertical-center !ml-0 mr-2"
ng-click="$ctrl.state.showCustomValues = true;"
>
<pr-icon icon="'plus'" class="vertical-center"></pr-icon>
Show custom values
</button>
<span class="small interactive vertical-center" ng-if="$ctrl.state.loadingValues">
<pr-icon icon="'refresh-cw'" class="mr-1"></pr-icon>
Loading values.yaml...
</span>
<button ng-if="$ctrl.state.showCustomValues" class="btn btn-xs btn-default vertical-center !ml-0 mr-2" ng-click="$ctrl.state.showCustomValues = false;">
<pr-icon icon="'minus'" class="vertical-center"></pr-icon>
Hide custom values
</button>
</div>
</div>
<!-- values override -->
<div ng-if="$ctrl.state.showCustomValues">
<!-- web-editor -->
<div>
<div class="form-group">
<div class="col-sm-12">
<web-editor-form
identifier="helm-app-creation-editor"
value="$ctrl.state.values"
on-change="($ctrl.editorUpdate)"
yml="true"
placeholder="Define or paste the content of your values yaml file here"
>
<editor-description class="vertical-center">
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
<span>
You can get more information about Helm values file format in the
<a href="https://helm.sh/docs/chart_template_guide/values_files/" target="_blank" class="text-blue-8 hover:text-blue-8 hover:underline"
>official documentation</a
>.
</span>
</editor-description>
</web-editor-form>
</div>
</div>
</div>
<!-- !web-editor -->
</div>
<!-- !values override -->
<!-- helm actions -->
<div class="col-sm-12 form-section-title"> Actions </div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!($ctrl.state.appName && $ctrl.state.resourcePool && !$ctrl.state.loadingValues && !$ctrl.state.actionInProgress)"
ng-click="$ctrl.installHelmchart()"
button-spinner="$ctrl.state.actionInProgress"
data-cy="helm-install"
>
<span ng-hide="$ctrl.state.actionInProgress">Install</span>
<span ng-hide="!$ctrl.state.actionInProgress">Helm installing in progress</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="$ctrl.state.chart = null">Hide</button>
</div>
</div>
<!-- !helm actions -->
</form>
</rd-widget-body>
</rd-widget>
<form class="form-horizontal" name="$ctrl.helmTemplateCreationForm">
<div class="form-group mt-4">
<div class="col-sm-12">
<button
ng-if="!$ctrl.state.showCustomValues && !$ctrl.state.loadingValues"
class="btn btn-xs btn-default vertical-center !ml-0 mr-2"
ng-click="$ctrl.state.showCustomValues = true;"
>
<pr-icon icon="'plus'" class="vertical-center"></pr-icon>
Show custom values
</button>
<span class="small interactive vertical-center" ng-if="$ctrl.state.loadingValues">
<pr-icon icon="'refresh-cw'" class="mr-1"></pr-icon>
Loading values.yaml...
</span>
<button ng-if="$ctrl.state.showCustomValues" class="btn btn-xs btn-default vertical-center !ml-0 mr-2" ng-click="$ctrl.state.showCustomValues = false;">
<pr-icon icon="'minus'" class="vertical-center"></pr-icon>
Hide custom values
</button>
</div>
</div>
<!-- values override -->
<div ng-if="$ctrl.state.showCustomValues">
<!-- web-editor -->
<div class="form-group">
<div class="col-sm-12">
<web-editor-form
identifier="helm-app-creation-editor"
value="$ctrl.state.values"
on-change="($ctrl.editorUpdate)"
yml="true"
placeholder="Define or paste the content of your values yaml file here"
>
<editor-description class="vertical-center">
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
<span>
You can get more information about Helm values file format in the
<a href="https://helm.sh/docs/chart_template_guide/values_files/" target="_blank" class="hyperlink">official documentation</a>.
</span>
</editor-description>
</web-editor-form>
</div>
</div>
<!-- !web-editor -->
</div>
<!-- !values override -->
<!-- helm actions -->
<div class="col-sm-12 form-section-title"> Actions </div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!($ctrl.stackName && $ctrl.state.resourcePool && !$ctrl.state.loadingValues && !$ctrl.state.actionInProgress)"
ng-click="$ctrl.installHelmchart()"
button-spinner="$ctrl.state.actionInProgress"
data-cy="helm-install"
>
<span ng-hide="$ctrl.state.actionInProgress">Install</span>
<span ng-hide="!$ctrl.state.actionInProgress">Helm installing in progress</span>
</button>
</div>
</div>
<!-- !helm actions -->
</form>
</div>
<!-- helmchart-form -->
</div>
<div class="row">
<div class="col-sm-12">
<helm-add-repository repos="$ctrl.state.repos" endpoint="$ctrl.endpoint"></helm-add-repository>
</div>
</div>
<!-- Helm Charts Component -->
<div class="row">
<div class="col-sm-12">
<div class="row" ng-if="!$ctrl.state.chart">
<div class="col-sm-12 p-0">
<helm-templates-list
title-text="Charts"
title-icon="compass"
title-text="Helm chart"
charts="$ctrl.state.charts"
table-key="$ctrl.state.charts"
select-action="$ctrl.selectHelmChart"

View file

@ -6,5 +6,7 @@ angular.module('portainer.kubernetes').component('helmTemplatesView', {
controller,
bindings: {
endpoint: '<',
namespace: '<',
stackName: '<',
},
});