mirror of https://github.com/portainer/portainer
feat(kubeshell) allow overriding default kubeshell image EE-1756 (#5755)
* feat(kubeshell) allow overriding default kubeshell * Add missing error check and struct tag * Add migrator for kube shell image and add it as a default in the db * Fix file name to match migrator pattern * remove default as it's now coming from the db * remove blank line * - conflict resolution code update - logging migration error on migration failures * - migrateDBVersionTo34 -> migrateDBVersionToDB34 (naming consistency) Co-authored-by: zees-dev <dev.786zshan@gmail.com>pull/5740/merge
parent
7611cc415a
commit
7b72130433
|
@ -47,6 +47,7 @@ func (store *Store) Init() error {
|
||||||
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
|
HelmRepositoryURL: portainer.DefaultHelmRepositoryURL,
|
||||||
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
||||||
KubeconfigExpiry: portainer.DefaultKubeconfigExpiry,
|
KubeconfigExpiry: portainer.DefaultKubeconfigExpiry,
|
||||||
|
KubectlShellImage: portainer.DefaultKubectlShellImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.SettingsService.UpdateSettings(defaultSettings)
|
err = store.SettingsService.UpdateSettings(defaultSettings)
|
||||||
|
|
|
@ -301,9 +301,18 @@ func (m *Migrator) Migrate() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Portainer 2.9.1
|
||||||
if m.currentDBVersion < 33 {
|
if m.currentDBVersion < 33 {
|
||||||
if err := m.migrateDBVersionTo33(); err != nil {
|
err := m.migrateDBVersionToDB33()
|
||||||
return migrationError(err, "migrateDBVersionTo33")
|
if err != nil {
|
||||||
|
return migrationError(err, "migrateDBVersionToDB33")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Portainer 2.10
|
||||||
|
if m.currentDBVersion < 34 {
|
||||||
|
if err := m.migrateDBVersionToDB34(); err != nil {
|
||||||
|
return migrationError(err, "migrateDBVersionToDB34")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package migrator
|
||||||
|
|
||||||
|
import portainer "github.com/portainer/portainer/api"
|
||||||
|
|
||||||
|
func (m *Migrator) migrateDBVersionToDB33() error {
|
||||||
|
if err := m.migrateSettingsToDB33(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Migrator) migrateSettingsToDB33() error {
|
||||||
|
settings, err := m.settingsService.Settings()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.KubectlShellImage = portainer.DefaultKubectlShellImage
|
||||||
|
return m.settingsService.UpdateSettings(settings)
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import (
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *Migrator) migrateDBVersionTo33() error {
|
func (m *Migrator) migrateDBVersionToDB34() error {
|
||||||
err := migrateStackEntryPoint(m.stackService)
|
err := migrateStackEntryPoint(m.stackService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMigrateStackEntryPoint(t *testing.T) {
|
func TestMigrateStackEntryPoint(t *testing.T) {
|
||||||
dbConn, err := bolt.Open(path.Join(t.TempDir(), "portainer-ee-mig-33.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
dbConn, err := bolt.Open(path.Join(t.TempDir(), "portainer-ee-mig-34.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||||
assert.NoError(t, err, "failed to init testing DB connection")
|
assert.NoError(t, err, "failed to init testing DB connection")
|
||||||
defer dbConn.Close()
|
defer dbConn.Close()
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@ type settingsUpdatePayload struct {
|
||||||
EnableTelemetry *bool `example:"false"`
|
EnableTelemetry *bool `example:"false"`
|
||||||
// Helm repository URL
|
// Helm repository URL
|
||||||
HelmRepositoryURL *string `example:"https://charts.bitnami.com/bitnami"`
|
HelmRepositoryURL *string `example:"https://charts.bitnami.com/bitnami"`
|
||||||
|
// Kubectl Shell Image
|
||||||
|
KubectlShellImage *string `example:"portainer/kubectl-shell:latest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -178,6 +180,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
return tlsError
|
return tlsError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.KubectlShellImage != nil {
|
||||||
|
settings.KubectlShellImage = *payload.KubectlShellImage
|
||||||
|
}
|
||||||
|
|
||||||
err = handler.DataStore.Settings().UpdateSettings(settings)
|
err = handler.DataStore.Settings().UpdateSettings(settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist settings changes inside the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist settings changes inside the database", err}
|
||||||
|
|
|
@ -45,7 +45,12 @@ func (handler *Handler) websocketShellPodExec(w http.ResponseWriter, r *http.Req
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find serviceaccount associated with user", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find serviceaccount associated with user", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
shellPod, err := cli.CreateUserShellPod(r.Context(), serviceAccount.Name)
|
settings, err := handler.DataStore.Settings().Settings()
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable read settings", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
shellPod, err := cli.CreateUserShellPod(r.Context(), serviceAccount.Name, settings.KubectlShellImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create user shell", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create user shell", err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,13 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const shellPodImage = "portainer/kubectl-shell"
|
|
||||||
|
|
||||||
// CreateUserShellPod will create a kubectl based shell for the specified user by mounting their respective service account.
|
// CreateUserShellPod will create a kubectl based shell for the specified user by mounting their respective service account.
|
||||||
// The lifecycle of the pod is managed in this function; this entails management of the following pod operations:
|
// The lifecycle of the pod is managed in this function; this entails management of the following pod operations:
|
||||||
// - The shell pod will be scoped to specified service accounts access permissions
|
// - The shell pod will be scoped to specified service accounts access permissions
|
||||||
// - The shell pod will be automatically removed if it's not ready after specified period of time
|
// - The shell pod will be automatically removed if it's not ready after specified period of time
|
||||||
// - The shell pod will be automatically removed after a specified max life (prevent zombie pods)
|
// - The shell pod will be automatically removed after a specified max life (prevent zombie pods)
|
||||||
// - The shell pod will be automatically removed if request is cancelled (or client closes websocket connection)
|
// - The shell pod will be automatically removed if request is cancelled (or client closes websocket connection)
|
||||||
func (kcl *KubeClient) CreateUserShellPod(ctx context.Context, serviceAccountName string) (*portainer.KubernetesShellPod, error) {
|
func (kcl *KubeClient) CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*portainer.KubernetesShellPod, error) {
|
||||||
maxPodKeepAliveSecondsStr := fmt.Sprintf("%d", int(portainer.WebSocketKeepAlive.Seconds()))
|
maxPodKeepAliveSecondsStr := fmt.Sprintf("%d", int(portainer.WebSocketKeepAlive.Seconds()))
|
||||||
|
|
||||||
podPrefix := userShellPodPrefix(serviceAccountName)
|
podPrefix := userShellPodPrefix(serviceAccountName)
|
||||||
|
|
|
@ -714,6 +714,8 @@ type (
|
||||||
EnableTelemetry bool `json:"EnableTelemetry" example:"false"`
|
EnableTelemetry bool `json:"EnableTelemetry" example:"false"`
|
||||||
// Helm repository URL, defaults to "https://charts.bitnami.com/bitnami"
|
// Helm repository URL, defaults to "https://charts.bitnami.com/bitnami"
|
||||||
HelmRepositoryURL string `json:"HelmRepositoryURL" example:"https://charts.bitnami.com/bitnami"`
|
HelmRepositoryURL string `json:"HelmRepositoryURL" example:"https://charts.bitnami.com/bitnami"`
|
||||||
|
// KubectlImage, defaults to portainer/kubectl-shell
|
||||||
|
KubectlShellImage string `json:"KubectlShellImage" example:"portainer/kubectl-shell"`
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
DisplayDonationHeader bool
|
DisplayDonationHeader bool
|
||||||
|
@ -1264,7 +1266,7 @@ type (
|
||||||
SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error
|
SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error
|
||||||
GetServiceAccount(tokendata *TokenData) (*v1.ServiceAccount, error)
|
GetServiceAccount(tokendata *TokenData) (*v1.ServiceAccount, error)
|
||||||
GetServiceAccountBearerToken(userID int) (string, error)
|
GetServiceAccountBearerToken(userID int) (string, error)
|
||||||
CreateUserShellPod(ctx context.Context, serviceAccountName string) (*KubernetesShellPod, error)
|
CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*KubernetesShellPod, error)
|
||||||
StartExecProcess(token string, useAdminToken bool, namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer, errChan chan error)
|
StartExecProcess(token string, useAdminToken bool, namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer, errChan chan error)
|
||||||
NamespaceAccessPoliciesDeleteNamespace(namespace string) error
|
NamespaceAccessPoliciesDeleteNamespace(namespace string) error
|
||||||
GetNodesLimits() (K8sNodesLimits, error)
|
GetNodesLimits() (K8sNodesLimits, error)
|
||||||
|
@ -1498,6 +1500,8 @@ const (
|
||||||
DefaultUserSessionTimeout = "8h"
|
DefaultUserSessionTimeout = "8h"
|
||||||
// DefaultUserSessionTimeout represents the default timeout after which the user session is cleared
|
// DefaultUserSessionTimeout represents the default timeout after which the user session is cleared
|
||||||
DefaultKubeconfigExpiry = "0"
|
DefaultKubeconfigExpiry = "0"
|
||||||
|
// DefaultKubectlShellImage represents the default image and tag for the kubectl shell
|
||||||
|
DefaultKubectlShellImage = "portainer/kubectl-shell"
|
||||||
// WebSocketKeepAlive web socket keep alive for edge environments
|
// WebSocketKeepAlive web socket keep alive for edge environments
|
||||||
WebSocketKeepAlive = 1 * time.Hour
|
WebSocketKeepAlive = 1 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue