Pull in all changes from tech review in EE-943
This commit is contained in:
@@ -1,17 +1,15 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/portainer/libhelm"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/exec/helm"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,14 +25,16 @@ type Handler struct {
|
||||
*mux.Router
|
||||
requestBouncer requestBouncer
|
||||
DataStore portainer.DataStore
|
||||
HelmPackageManager helm.HelmPackageManager
|
||||
kubeConfigService kubernetes.KubeConfigService
|
||||
HelmPackageManager libhelm.HelmPackageManager
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage endpoint group operations.
|
||||
func NewHandler(bouncer requestBouncer) *Handler {
|
||||
func NewHandler(bouncer requestBouncer, kubeConfigService kubernetes.KubeConfigService) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
requestBouncer: bouncer,
|
||||
Router: mux.NewRouter(),
|
||||
requestBouncer: bouncer,
|
||||
kubeConfigService: kubeConfigService,
|
||||
}
|
||||
|
||||
// `helm install [NAME] [CHART] flags`
|
||||
@@ -77,22 +77,3 @@ func (handler *Handler) GetEndpoint(r *http.Request) (*portainer.Endpoint, *http
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// getProxyUrl generates portainer proxy url which acts as proxy to k8s api server
|
||||
func getProxyUrl(r *http.Request, endpointID portainer.EndpointID) string {
|
||||
return fmt.Sprintf("https://%s/api/endpoints/%d/kubernetes", r.Host, endpointID)
|
||||
}
|
||||
|
||||
// extractBearerToken extracts user's portainer bearer token from request auth header
|
||||
func extractBearerToken(r *http.Request) (string, error) {
|
||||
token := ""
|
||||
tokens := r.Header["Authorization"]
|
||||
if len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
}
|
||||
if token == "" {
|
||||
return "", httperrors.ErrUnauthorized
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/libhelm/options"
|
||||
"github.com/portainer/libhelm/release"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/exec/helm"
|
||||
"github.com/portainer/portainer/api/exec/helm/release"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
validation "github.com/portainer/portainer/api/kubernetes/validation"
|
||||
)
|
||||
|
||||
const defaultHelmRepoURL = "https://charts.bitnami.com/bitnami"
|
||||
|
||||
type installChartPayload struct {
|
||||
Namespace string `json:namespace`
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
Chart string `json:"chart"`
|
||||
Values string `json:"values"`
|
||||
}
|
||||
|
||||
var errChartNameInvalid = errors.New("invalid chart name. " +
|
||||
"Chart name must consist of lower case alphanumeric characters, '-' or '.'," +
|
||||
" and must start and end with an alphanumeric character",
|
||||
)
|
||||
|
||||
func (p *installChartPayload) Validate(_ *http.Request) error {
|
||||
var required []string
|
||||
if p.Name == "" {
|
||||
@@ -37,6 +43,11 @@ func (p *installChartPayload) Validate(_ *http.Request) error {
|
||||
if len(required) > 0 {
|
||||
return fmt.Errorf("required field(s) missing: %s", strings.Join(required, ", "))
|
||||
}
|
||||
|
||||
if errs := validation.IsDNS1123Subdomain(p.Name); len(errs) > 0 {
|
||||
return errChartNameInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,7 +81,7 @@ func (handler *Handler) helmInstall(w http.ResponseWriter, r *http.Request) *htt
|
||||
return httperr
|
||||
}
|
||||
|
||||
bearerToken, err := extractBearerToken(r)
|
||||
bearerToken, err := security.ExtractBearerToken(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusUnauthorized, "Unauthorized", err}
|
||||
}
|
||||
@@ -89,7 +100,7 @@ func (handler *Handler) helmInstall(w http.ResponseWriter, r *http.Request) *htt
|
||||
}
|
||||
}
|
||||
|
||||
release, err := handler.installChart(settings.HelmRepositoryURL, endpoint, payload, getProxyUrl(r, endpoint.ID), bearerToken)
|
||||
release, err := handler.installChart(settings.HelmRepositoryURL, endpoint, payload, bearerToken)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
@@ -102,12 +113,18 @@ func (handler *Handler) helmInstall(w http.ResponseWriter, r *http.Request) *htt
|
||||
return response.JSON(w, release)
|
||||
}
|
||||
|
||||
func (handler *Handler) installChart(repo string, endpoint *portainer.Endpoint, p *installChartPayload, serverURL, bearerToken string) (*release.Release, error) {
|
||||
installOpts := helm.InstallOptions{
|
||||
func (handler *Handler) installChart(repo string, endpoint *portainer.Endpoint, p *installChartPayload, bearerToken string) (*release.Release, error) {
|
||||
clusterAccess := handler.kubeConfigService.GetKubeConfigInternal(endpoint.ID, bearerToken)
|
||||
installOpts := options.InstallOptions{
|
||||
Name: p.Name,
|
||||
Chart: p.Chart,
|
||||
Namespace: p.Namespace,
|
||||
Repo: repo,
|
||||
KubernetesClusterAccess: &options.KubernetesClusterAccess{
|
||||
ClusterServerURL: clusterAccess.ClusterServerURL,
|
||||
CertificateAuthorityFile: clusterAccess.CertificateAuthorityFile,
|
||||
AuthToken: clusterAccess.AuthToken,
|
||||
},
|
||||
}
|
||||
|
||||
if p.Values != "" {
|
||||
@@ -128,7 +145,7 @@ func (handler *Handler) installChart(repo string, endpoint *portainer.Endpoint,
|
||||
installOpts.ValuesFile = file.Name()
|
||||
}
|
||||
|
||||
release, err := handler.HelmPackageManager.Install(installOpts, endpoint.ID, bearerToken)
|
||||
release, err := handler.HelmPackageManager.Install(installOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package helm
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/libhelm"
|
||||
"github.com/portainer/libhelm/options"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer/api/exec/helm"
|
||||
)
|
||||
|
||||
// @id HelmRepoSearch
|
||||
@@ -26,11 +27,11 @@ func (handler *Handler) helmRepoSearch(w http.ResponseWriter, r *http.Request) *
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve settings", Err: err}
|
||||
}
|
||||
|
||||
searchOpts := helm.SearchRepoOptions{
|
||||
searchOpts := options.SearchRepoOptions{
|
||||
Repo: settings.HelmRepositoryURL,
|
||||
}
|
||||
|
||||
result, err := handler.HelmPackageManager.SearchRepo(searchOpts)
|
||||
result, err := libhelm.SearchRepo(searchOpts)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
@@ -40,7 +41,7 @@ func (handler *Handler) helmRepoSearch(w http.ResponseWriter, r *http.Request) *
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(result))
|
||||
w.Write(result)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/portainer/libhelm/binary/test"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/exec/helm/test"
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
helper "github.com/portainer/portainer/api/internal/testhelpers"
|
||||
@@ -22,7 +21,7 @@ func Test_helmRepoSearch(t *testing.T) {
|
||||
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
|
||||
}
|
||||
h.DataStore = helper.NewDatastore(helper.WithSettingsService(defaultSettings))
|
||||
h.HelmPackageManager = test.NewMockHelmBinaryPackageManager(kubernetes.NewKubeConfigCAService("", ""), "")
|
||||
h.HelmPackageManager = test.NewMockHelmBinaryPackageManager("")
|
||||
|
||||
t.Run("helmRepoSearch", func(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
@@ -33,8 +32,7 @@ func Test_helmRepoSearch(t *testing.T) {
|
||||
|
||||
is.Equal(rr.Code, http.StatusOK, "Status should be 200 OK")
|
||||
|
||||
body, err := io.ReadAll(rr.Body)
|
||||
_, err := io.ReadAll(rr.Body)
|
||||
is.NoError(err, "ReadAll should not return error")
|
||||
is.EqualValues(string(body), test.MockDataIndex, "Unexpected search response")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/libhelm/options"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/portainer/api/exec/helm"
|
||||
)
|
||||
|
||||
// @id HelmList
|
||||
@@ -43,8 +43,8 @@ func (handler *Handler) helmShow(w http.ResponseWriter, r *http.Request) *httper
|
||||
log.Printf("[DEBUG] [internal,helm] [message: command not provided, defaulting to %s]", cmd)
|
||||
}
|
||||
|
||||
showOptions := helm.ShowOptions{
|
||||
OutputFormat: helm.ShowOutputFormat(cmd),
|
||||
showOptions := options.ShowOptions{
|
||||
OutputFormat: options.ShowOutputFormat(cmd),
|
||||
Chart: chart,
|
||||
Repo: settings.HelmRepositoryURL,
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (handler *Handler) helmShow(w http.ResponseWriter, r *http.Request) *httper
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(result))
|
||||
w.Write(result)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/portainer/libhelm/binary/test"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/exec/helm/test"
|
||||
helper "github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -25,7 +24,7 @@ func Test_helmShow(t *testing.T) {
|
||||
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
|
||||
}
|
||||
h.DataStore = helper.NewDatastore(helper.WithSettingsService(defaultSettings))
|
||||
h.HelmPackageManager = test.NewMockHelmBinaryPackageManager(kubernetes.NewKubeConfigCAService("", ""), "")
|
||||
h.HelmPackageManager = test.NewMockHelmBinaryPackageManager("")
|
||||
|
||||
commands := map[string]string{
|
||||
"values": test.MockDataValues,
|
||||
|
||||
@@ -3,6 +3,8 @@ package kubernetes
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
@@ -10,8 +12,6 @@ import (
|
||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
kcli "github.com/portainer/portainer/api/kubernetes/cli"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @id GetKubernetesConfig
|
||||
|
||||
@@ -206,18 +206,12 @@ func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Han
|
||||
token = r.URL.Query().Get("token")
|
||||
|
||||
// Get token from the Authorization header
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", httperrors.ErrUnauthorized)
|
||||
token, err := ExtractBearerToken(r)
|
||||
if err != nil {
|
||||
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", err)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
tokenData, err = bouncer.jwtService.ParseAndVerifyToken(token)
|
||||
if err != nil {
|
||||
httperror.WriteError(w, http.StatusUnauthorized, "Invalid JWT token", err)
|
||||
@@ -239,6 +233,19 @@ func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Han
|
||||
})
|
||||
}
|
||||
|
||||
func ExtractBearerToken(r *http.Request) (string, error) {
|
||||
token := ""
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
}
|
||||
if token == "" {
|
||||
return "", httperrors.ErrUnauthorized
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// mwSecureHeaders provides secure headers middleware for handlers.
|
||||
func mwSecureHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/libhelm"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/adminmonitor"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/exec/helm"
|
||||
"github.com/portainer/portainer/api/http/handler"
|
||||
"github.com/portainer/portainer/api/http/handler/auth"
|
||||
"github.com/portainer/portainer/api/http/handler/backup"
|
||||
@@ -86,7 +86,7 @@ type Server struct {
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
KubernetesClientFactory *cli.ClientFactory
|
||||
KubernetesDeployer portainer.KubernetesDeployer
|
||||
HelmPackageManager helm.HelmPackageManager
|
||||
HelmPackageManager libhelm.HelmPackageManager
|
||||
Scheduler *scheduler.Scheduler
|
||||
ShutdownCtx context.Context
|
||||
ShutdownTrigger context.CancelFunc
|
||||
@@ -171,7 +171,7 @@ func (server *Server) Start() error {
|
||||
|
||||
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"))
|
||||
|
||||
var endpointHelmHandler = helmhandler.NewHandler(requestBouncer)
|
||||
var endpointHelmHandler = helmhandler.NewHandler(requestBouncer, server.KubeConfigService)
|
||||
endpointHelmHandler.DataStore = server.DataStore
|
||||
endpointHelmHandler.HelmPackageManager = server.HelmPackageManager
|
||||
|
||||
|
||||
Reference in New Issue
Block a user