From 7b72130433cace72e57a795bb02ab79e4147d889 Mon Sep 17 00:00:00 2001 From: Matt Hook Date: Wed, 29 Sep 2021 11:39:45 +1300 Subject: [PATCH] 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 --- api/bolt/init.go | 1 + api/bolt/migrator/migrate_ce.go | 13 ++++++++++-- api/bolt/migrator/migrate_dbversion32.go | 21 +++++++++++++++++++ api/bolt/migrator/migrate_dbversion33.go | 2 +- api/bolt/migrator/migrate_dbversion33_test.go | 2 +- api/http/handler/settings/settings_update.go | 6 ++++++ api/http/handler/websocket/shell_pod.go | 7 ++++++- api/kubernetes/cli/pod.go | 4 +--- api/portainer.go | 6 +++++- 9 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 api/bolt/migrator/migrate_dbversion32.go diff --git a/api/bolt/init.go b/api/bolt/init.go index 4b9e4559f..7130c9f47 100644 --- a/api/bolt/init.go +++ b/api/bolt/init.go @@ -47,6 +47,7 @@ func (store *Store) Init() error { HelmRepositoryURL: portainer.DefaultHelmRepositoryURL, UserSessionTimeout: portainer.DefaultUserSessionTimeout, KubeconfigExpiry: portainer.DefaultKubeconfigExpiry, + KubectlShellImage: portainer.DefaultKubectlShellImage, } err = store.SettingsService.UpdateSettings(defaultSettings) diff --git a/api/bolt/migrator/migrate_ce.go b/api/bolt/migrator/migrate_ce.go index 15c8885bb..7f2b70586 100644 --- a/api/bolt/migrator/migrate_ce.go +++ b/api/bolt/migrator/migrate_ce.go @@ -301,9 +301,18 @@ func (m *Migrator) Migrate() error { } } + // Portainer 2.9.1 if m.currentDBVersion < 33 { - if err := m.migrateDBVersionTo33(); err != nil { - return migrationError(err, "migrateDBVersionTo33") + err := m.migrateDBVersionToDB33() + if err != nil { + return migrationError(err, "migrateDBVersionToDB33") + } + } + + // Portainer 2.10 + if m.currentDBVersion < 34 { + if err := m.migrateDBVersionToDB34(); err != nil { + return migrationError(err, "migrateDBVersionToDB34") } } diff --git a/api/bolt/migrator/migrate_dbversion32.go b/api/bolt/migrator/migrate_dbversion32.go new file mode 100644 index 000000000..b1639f50f --- /dev/null +++ b/api/bolt/migrator/migrate_dbversion32.go @@ -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) +} diff --git a/api/bolt/migrator/migrate_dbversion33.go b/api/bolt/migrator/migrate_dbversion33.go index d7277ada7..e54ee965e 100644 --- a/api/bolt/migrator/migrate_dbversion33.go +++ b/api/bolt/migrator/migrate_dbversion33.go @@ -4,7 +4,7 @@ import ( portainer "github.com/portainer/portainer/api" ) -func (m *Migrator) migrateDBVersionTo33() error { +func (m *Migrator) migrateDBVersionToDB34() error { err := migrateStackEntryPoint(m.stackService) if err != nil { return err diff --git a/api/bolt/migrator/migrate_dbversion33_test.go b/api/bolt/migrator/migrate_dbversion33_test.go index 256cc121e..7148fcad7 100644 --- a/api/bolt/migrator/migrate_dbversion33_test.go +++ b/api/bolt/migrator/migrate_dbversion33_test.go @@ -14,7 +14,7 @@ import ( ) 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") defer dbConn.Close() diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go index 7d8fde4fc..476b2d70b 100644 --- a/api/http/handler/settings/settings_update.go +++ b/api/http/handler/settings/settings_update.go @@ -40,6 +40,8 @@ type settingsUpdatePayload struct { EnableTelemetry *bool `example:"false"` // Helm repository URL 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 { @@ -178,6 +180,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) * return tlsError } + if payload.KubectlShellImage != nil { + settings.KubectlShellImage = *payload.KubectlShellImage + } + err = handler.DataStore.Settings().UpdateSettings(settings) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist settings changes inside the database", err} diff --git a/api/http/handler/websocket/shell_pod.go b/api/http/handler/websocket/shell_pod.go index f736a7dc7..3aa5c2463 100644 --- a/api/http/handler/websocket/shell_pod.go +++ b/api/http/handler/websocket/shell_pod.go @@ -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} } - 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 { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create user shell", err} } diff --git a/api/kubernetes/cli/pod.go b/api/kubernetes/cli/pod.go index 43d66434c..591d73ab2 100644 --- a/api/kubernetes/cli/pod.go +++ b/api/kubernetes/cli/pod.go @@ -12,15 +12,13 @@ import ( 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. // 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 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 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())) podPrefix := userShellPodPrefix(serviceAccountName) diff --git a/api/portainer.go b/api/portainer.go index 6be88235b..b91413c30 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -714,6 +714,8 @@ type ( EnableTelemetry bool `json:"EnableTelemetry" example:"false"` // Helm repository URL, defaults to "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 DisplayDonationHeader bool @@ -1264,7 +1266,7 @@ type ( SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error GetServiceAccount(tokendata *TokenData) (*v1.ServiceAccount, 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) NamespaceAccessPoliciesDeleteNamespace(namespace string) error GetNodesLimits() (K8sNodesLimits, error) @@ -1498,6 +1500,8 @@ const ( DefaultUserSessionTimeout = "8h" // DefaultUserSessionTimeout represents the default timeout after which the user session is cleared 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 = 1 * time.Hour )