From b468070945e465019979e6201723bb13a04a266f Mon Sep 17 00:00:00 2001
From: Prabhat Khera <91852476+prabhat-org@users.noreply.github.com>
Date: Mon, 9 Oct 2023 11:20:44 +1300
Subject: [PATCH] feature(helm): move helm charts inside advance deployments
(create from manifest) [EE-5999] (#10395)
---
api/http/handler/handler.go | 1 +
api/http/handler/helm/handler.go | 5 +-
api/http/handler/helm/user_helm_repos.go | 6 +-
api/http/handler/users/handler.go | 6 +
api/http/handler/users/user_helm_repos.go | 196 +++++++++++++
api/http/server.go | 1 +
app/assets/ico/helm.svg | 2 +-
app/kubernetes/__module.js | 2 +-
.../helm-add-repository.controller.js | 39 ---
.../helm-add-repository.html | 72 -----
.../helm-add-repository.js | 11 -
.../helm-templates-list-item.html | 2 +-
.../helm-templates-list.html | 99 +++----
.../helm-templates-list.js | 1 -
.../helm-templates.controller.js | 15 +-
.../helm/helm-templates/helm-templates.html | 260 +++++++-----------
.../helm/helm-templates/helm-templates.js | 2 +
app/kubernetes/helm/rest.js | 7 +-
app/kubernetes/helm/service.js | 4 +-
app/kubernetes/models/deploy.js | 1 +
app/kubernetes/views/deploy/deploy.html | 14 +-
.../views/deploy/deployController.js | 11 +-
app/portainer/__module.js | 11 +
app/portainer/react/components/index.ts | 11 +-
app/portainer/react/views/index.ts | 8 +
app/portainer/views/account/account.html | 12 +-
app/react/components/BadgeIcon/BadgeIcon.tsx | 4 +-
.../BoxSelector/BoxSelectorItem.tsx | 6 +-
app/react/components/BoxSelector/LogoIcon.tsx | 6 +-
.../common-options/build-methods.tsx | 10 +
app/react/components/BoxSelector/types.ts | 1 +
app/react/components/Icon.tsx | 1 +
app/react/portainer/account/.keep | 0
.../HelmRepositoryDatatable.tsx | 57 ++++
.../HelmRepositoryDatatableActions.tsx | 60 ++++
.../HelmRepositoryDatatable/columns/helper.ts | 5 +
.../HelmRepositoryDatatable/columns/index.ts | 3 +
.../HelmRepositoryDatatable/columns/url.tsx | 3 +
.../helm-repositories.service.ts | 108 ++++++++
.../HelmRepositoryDatatable/index.ts | 1 +
.../HelmRepositoryDatatable/types.ts | 20 ++
.../CreateHelmRepositoriesView.tsx | 28 ++
.../CreateHelmRespositoriesForm.tsx | 41 +++
.../CreateHelmRepositoryView/index.ts | 1 +
.../CreateHelmRepositoryForm.validation.ts | 19 ++
.../components/HelmRepositoryForm.tsx | 74 +++++
.../ScreenBannerFieldset.tsx | 2 +-
.../KubeSettingsPanel/HelmSection.tsx | 2 +-
.../KubernetesSidebar/KubernetesSidebar.tsx | 14 -
49 files changed, 877 insertions(+), 388 deletions(-)
create mode 100644 api/http/handler/users/user_helm_repos.go
delete mode 100644 app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.controller.js
delete mode 100644 app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.html
delete mode 100644 app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.js
delete mode 100644 app/react/portainer/account/.keep
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/HelmRepositoryDatatable.tsx
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/HelmRepositoryDatatableActions.tsx
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/columns/helper.ts
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/columns/index.ts
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/columns/url.tsx
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/helm-repositories.service.ts
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/index.ts
create mode 100644 app/react/portainer/account/AccountView/HelmRepositoryDatatable/types.ts
create mode 100644 app/react/portainer/account/help-repositories/CreateHelmRepositoryView/CreateHelmRepositoriesView.tsx
create mode 100644 app/react/portainer/account/help-repositories/CreateHelmRepositoryView/CreateHelmRespositoriesForm.tsx
create mode 100644 app/react/portainer/account/help-repositories/CreateHelmRepositoryView/index.ts
create mode 100644 app/react/portainer/account/help-repositories/components/CreateHelmRepositoryForm.validation.ts
create mode 100644 app/react/portainer/account/help-repositories/components/HelmRepositoryForm.tsx
diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go
index 6972c34df..d3454bc02 100644
--- a/api/http/handler/handler.go
+++ b/api/http/handler/handler.go
@@ -81,6 +81,7 @@ type Handler struct {
UserHandler *users.Handler
WebSocketHandler *websocket.Handler
WebhookHandler *webhooks.Handler
+ UserHelmHandler *helm.Handler
}
// @title PortainerCE API
diff --git a/api/http/handler/helm/handler.go b/api/http/handler/helm/handler.go
index e95263e57..86352c375 100644
--- a/api/http/handler/helm/handler.go
+++ b/api/http/handler/helm/handler.go
@@ -52,10 +52,11 @@ func NewHandler(bouncer security.BouncerService, dataStore dataservices.DataStor
h.Handle("/{id}/kubernetes/helm",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.helmInstall))).Methods(http.MethodPost)
+ // Deprecated
h.Handle("/{id}/kubernetes/helm/repositories",
- bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.userGetHelmRepos))).Methods(http.MethodGet)
+ httperror.LoggerHandler(h.userGetHelmRepos)).Methods(http.MethodGet)
h.Handle("/{id}/kubernetes/helm/repositories",
- bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.userCreateHelmRepo))).Methods(http.MethodPost)
+ httperror.LoggerHandler(h.userCreateHelmRepo)).Methods(http.MethodPost)
return h
}
diff --git a/api/http/handler/helm/user_helm_repos.go b/api/http/handler/helm/user_helm_repos.go
index 1c159955a..886d44530 100644
--- a/api/http/handler/helm/user_helm_repos.go
+++ b/api/http/handler/helm/user_helm_repos.go
@@ -27,7 +27,7 @@ func (p *addHelmRepoUrlPayload) Validate(_ *http.Request) error {
return libhelm.ValidateHelmRepositoryURL(p.URL, nil)
}
-// @id HelmUserRepositoryCreate
+// @id HelmUserRepositoryCreateDeprecated
// @summary Create a user helm repository
// @description Create a user helm repository.
// @description **Access policy**: authenticated
@@ -42,6 +42,7 @@ func (p *addHelmRepoUrlPayload) Validate(_ *http.Request) error {
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 500 "Server error"
+// @deprecated
// @router /endpoints/{id}/kubernetes/helm/repositories [post]
func (handler *Handler) userCreateHelmRepo(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
tokenData, err := security.RetrieveTokenData(r)
@@ -85,7 +86,7 @@ func (handler *Handler) userCreateHelmRepo(w http.ResponseWriter, r *http.Reques
return response.JSON(w, record)
}
-// @id HelmUserRepositoriesList
+// @id HelmUserRepositoriesListDeprecated
// @summary List a users helm repositories
// @description Inspect a user helm repositories.
// @description **Access policy**: authenticated
@@ -98,6 +99,7 @@ func (handler *Handler) userCreateHelmRepo(w http.ResponseWriter, r *http.Reques
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 500 "Server error"
+// @deprecated
// @router /endpoints/{id}/kubernetes/helm/repositories [get]
func (handler *Handler) userGetHelmRepos(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
tokenData, err := security.RetrieveTokenData(r)
diff --git a/api/http/handler/users/handler.go b/api/http/handler/users/handler.go
index dd0bcd372..7c1364736 100644
--- a/api/http/handler/users/handler.go
+++ b/api/http/handler/users/handler.go
@@ -37,6 +37,7 @@ type Handler struct {
CryptoService portainer.CryptoService
passwordStrengthChecker security.PasswordStrengthChecker
AdminCreationDone chan<- struct{}
+ FileService portainer.FileService
}
// NewHandler creates a handler to manage user operations.
@@ -77,5 +78,10 @@ func NewHandler(bouncer security.BouncerService, rateLimiter *security.RateLimit
publicRouter.Handle("/users/admin/check", httperror.LoggerHandler(h.adminCheck)).Methods(http.MethodGet)
publicRouter.Handle("/users/admin/init", httperror.LoggerHandler(h.adminInit)).Methods(http.MethodPost)
+ // Helm repositories
+ authenticatedRouter.Handle("/users/{id}/helm/repositories", httperror.LoggerHandler(h.userGetHelmRepos)).Methods(http.MethodGet)
+ authenticatedRouter.Handle("/users/{id}/helm/repositories", httperror.LoggerHandler(h.userCreateHelmRepo)).Methods(http.MethodPost)
+ authenticatedRouter.Handle("/users/{id}/helm/repositories/{repositoryID}", httperror.LoggerHandler(h.userDeleteHelmRepo)).Methods(http.MethodDelete)
+
return h
}
diff --git a/api/http/handler/users/user_helm_repos.go b/api/http/handler/users/user_helm_repos.go
new file mode 100644
index 000000000..e8490b2e0
--- /dev/null
+++ b/api/http/handler/users/user_helm_repos.go
@@ -0,0 +1,196 @@
+package users
+
+import (
+ "net/http"
+ "strings"
+
+ portainer "github.com/portainer/portainer/api"
+ httperrors "github.com/portainer/portainer/api/http/errors"
+ "github.com/portainer/portainer/api/http/security"
+ "github.com/portainer/portainer/pkg/libhelm"
+ httperror "github.com/portainer/portainer/pkg/libhttp/error"
+ "github.com/portainer/portainer/pkg/libhttp/request"
+ "github.com/portainer/portainer/pkg/libhttp/response"
+
+ "github.com/pkg/errors"
+)
+
+type helmUserRepositoryResponse struct {
+ GlobalRepository string `json:"GlobalRepository"`
+ UserRepositories []portainer.HelmUserRepository `json:"UserRepositories"`
+}
+
+type addHelmRepoUrlPayload struct {
+ URL string `json:"url"`
+}
+
+func (p *addHelmRepoUrlPayload) Validate(_ *http.Request) error {
+ return libhelm.ValidateHelmRepositoryURL(p.URL, nil)
+}
+
+// @id HelmUserRepositoryCreate
+// @summary Create a user helm repository
+// @description Create a user helm repository.
+// @description **Access policy**: authenticated
+// @tags helm
+// @security ApiKeyAuth
+// @security jwt
+// @accept json
+// @produce json
+// @param id path int true "User identifier"
+// @param payload body addHelmRepoUrlPayload true "Helm Repository"
+// @success 200 {object} portainer.HelmUserRepository "Success"
+// @failure 400 "Invalid request"
+// @failure 403 "Permission denied"
+// @failure 500 "Server error"
+// @router /users/{id}/helm/repositories [post]
+func (handler *Handler) userCreateHelmRepo(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ userIDEndpoint, err := request.RetrieveNumericRouteVariableValue(r, "id")
+ if err != nil {
+ return httperror.BadRequest("Invalid user identifier route variable", err)
+ }
+
+ tokenData, err := security.RetrieveTokenData(r)
+ if err != nil {
+ return httperror.InternalServerError("Unable to retrieve user authentication token", err)
+ }
+
+ userID := portainer.UserID(userIDEndpoint)
+ if tokenData.ID != userID {
+ return httperror.Forbidden("Couldn't create Helm repositories for another user", httperrors.ErrUnauthorized)
+ }
+
+ p := new(addHelmRepoUrlPayload)
+ err = request.DecodeAndValidateJSONPayload(r, p)
+ if err != nil {
+ return httperror.BadRequest("Invalid Helm repository URL", err)
+ }
+
+ // lowercase, remove trailing slash
+ p.URL = strings.TrimSuffix(strings.ToLower(p.URL), "/")
+
+ records, err := handler.DataStore.HelmUserRepository().HelmUserRepositoryByUserID(userID)
+ if err != nil {
+ return httperror.InternalServerError("Unable to access the DataStore", err)
+ }
+
+ // check if repo already exists - by doing case insensitive comparison
+ for _, record := range records {
+ if strings.EqualFold(record.URL, p.URL) {
+ errMsg := "Helm repo already registered for user"
+ return httperror.BadRequest(errMsg, errors.New(errMsg))
+ }
+ }
+
+ record := portainer.HelmUserRepository{
+ UserID: userID,
+ URL: p.URL,
+ }
+
+ err = handler.DataStore.HelmUserRepository().Create(&record)
+ if err != nil {
+ return httperror.InternalServerError("Unable to save a user Helm repository URL", err)
+ }
+
+ return response.JSON(w, record)
+}
+
+// @id HelmUserRepositoriesList
+// @summary List a users helm repositories
+// @description Inspect a user helm repositories.
+// @description **Access policy**: authenticated
+// @tags helm
+// @security ApiKeyAuth
+// @security jwt
+// @produce json
+// @param id path int true "User identifier"
+// @success 200 {object} helmUserRepositoryResponse "Success"
+// @failure 400 "Invalid request"
+// @failure 403 "Permission denied"
+// @failure 500 "Server error"
+// @router /users/{id}/helm/repositories [get]
+func (handler *Handler) userGetHelmRepos(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ userIDEndpoint, err := request.RetrieveNumericRouteVariableValue(r, "id")
+ if err != nil {
+ return httperror.BadRequest("Invalid user identifier route variable", err)
+ }
+
+ tokenData, err := security.RetrieveTokenData(r)
+ if err != nil {
+ return httperror.InternalServerError("Unable to retrieve user authentication token", err)
+ }
+
+ userID := portainer.UserID(userIDEndpoint)
+ if tokenData.ID != userID {
+ return httperror.Forbidden("Couldn't create Helm repositories for another user", httperrors.ErrUnauthorized)
+ }
+
+ settings, err := handler.DataStore.Settings().Settings()
+ if err != nil {
+ return httperror.InternalServerError("Unable to retrieve settings from the database", err)
+ }
+
+ userRepos, err := handler.DataStore.HelmUserRepository().HelmUserRepositoryByUserID(userID)
+ if err != nil {
+ return httperror.InternalServerError("Unable to get user Helm repositories", err)
+ }
+
+ resp := helmUserRepositoryResponse{
+ GlobalRepository: settings.HelmRepositoryURL,
+ UserRepositories: userRepos,
+ }
+
+ return response.JSON(w, resp)
+}
+
+// @id HelmUserRepositoryDelete
+// @summary Delete a users helm repositoryies
+// @description **Access policy**: authenticated
+// @tags helm
+// @security ApiKeyAuth
+// @security jwt
+// @produce json
+// @param id path int true "User identifier"
+// @param repositoryID path int true "Repository identifier"
+// @success 204 "Success"
+// @failure 400 "Invalid request"
+// @failure 403 "Permission denied"
+// @failure 500 "Server error"
+// @router /users/{id}/helm/repositories/{repositoryID} [delete]
+func (handler *Handler) userDeleteHelmRepo(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ userIDEndpoint, err := request.RetrieveNumericRouteVariableValue(r, "id")
+ if err != nil {
+ return httperror.BadRequest("Invalid user identifier route variable", err)
+ }
+
+ tokenData, err := security.RetrieveTokenData(r)
+ if err != nil {
+ return httperror.InternalServerError("Unable to retrieve user authentication token", err)
+ }
+
+ userID := portainer.UserID(userIDEndpoint)
+ if tokenData.ID != userID {
+ return httperror.Forbidden("Couldn't create Helm repositories for another user", httperrors.ErrUnauthorized)
+ }
+
+ repositoryID, err := request.RetrieveNumericRouteVariableValue(r, "repositoryID")
+ if err != nil {
+ return httperror.BadRequest("Invalid user identifier route variable", err)
+ }
+
+ userRepos, err := handler.DataStore.HelmUserRepository().HelmUserRepositoryByUserID(userID)
+ if err != nil {
+ return httperror.InternalServerError("Unable to get user Helm repositories", err)
+ }
+
+ for _, repo := range userRepos {
+ if repo.ID == portainer.HelmUserRepositoryID(repositoryID) && repo.UserID == userID {
+ err = handler.DataStore.HelmUserRepository().Delete(portainer.HelmUserRepositoryID(repositoryID))
+ if err != nil {
+ return httperror.InternalServerError("Unable to delete user Helm repository", err)
+ }
+ }
+ }
+
+ return response.JSON(w, nil)
+}
diff --git a/api/http/server.go b/api/http/server.go
index 7800524f1..b14ecaf68 100644
--- a/api/http/server.go
+++ b/api/http/server.go
@@ -282,6 +282,7 @@ func (server *Server) Start() error {
userHandler.DataStore = server.DataStore
userHandler.CryptoService = server.CryptoService
userHandler.AdminCreationDone = server.AdminCreationDone
+ userHandler.FileService = server.FileService
var websocketHandler = websocket.NewHandler(server.KubernetesTokenCacheManager, requestBouncer)
websocketHandler.DataStore = server.DataStore
diff --git a/app/assets/ico/helm.svg b/app/assets/ico/helm.svg
index a6dcc4034..79558ad76 100644
--- a/app/assets/ico/helm.svg
+++ b/app/assets/ico/helm.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/kubernetes/__module.js b/app/kubernetes/__module.js
index a2e19f83c..249001f5d 100644
--- a/app/kubernetes/__module.js
+++ b/app/kubernetes/__module.js
@@ -327,7 +327,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
const deploy = {
name: 'kubernetes.deploy',
- url: '/deploy?templateId&referrer&tab',
+ url: '/deploy?templateId&referrer&tab&buildMethod&chartName',
views: {
'content@': {
component: 'kubernetesDeployView',
diff --git a/app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.controller.js b/app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.controller.js
deleted file mode 100644
index 373bfda48..000000000
--- a/app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.controller.js
+++ /dev/null
@@ -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: '',
- };
- });
- }
-}
diff --git a/app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.html b/app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.html
deleted file mode 100644
index c79ab1c7e..000000000
--- a/app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.html
+++ /dev/null
@@ -1,72 +0,0 @@
-
-