fix(policy): pod security constraints - develop [R8S-808] (#1758)
Co-authored-by: Phil Calder <4473109+predlac@users.noreply.github.com> Co-authored-by: Viktor Pettersson <viktor.pettersson@portainer.io> Co-authored-by: Yajith Dayarathna <yajith.dayarathna@portainer.io> Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com> Co-authored-by: nickl-portainer <nicholas.loomans@portainer.io>
This commit is contained in:
@@ -598,7 +598,7 @@ type (
|
||||
|
||||
// RestoreSettings contains instructions for restoring environment-level settings
|
||||
RestoreSettings struct {
|
||||
Manifest string `json:"manifest"` // Base64-encoded Kubernetes YAML manifest
|
||||
Manifest string `json:"manifest,omitempty"` // Base64-encoded Kubernetes YAML manifest
|
||||
}
|
||||
|
||||
// RestoreSettingsBundle maps restore type to restoration instructions
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package options
|
||||
|
||||
import "time"
|
||||
|
||||
// UninstallOptions are portainer supported options for `helm uninstall`
|
||||
type UninstallOptions struct {
|
||||
Name string
|
||||
Namespace string
|
||||
KubernetesClusterAccess *KubernetesClusterAccess
|
||||
|
||||
// Wait blocks until all resources are deleted before returning (helm uninstall --wait).
|
||||
// Use when a restore will be applied immediately after uninstall so resources are gone first.
|
||||
Wait bool
|
||||
// Timeout is how long to wait for resources to be deleted when Wait is true (default 15m).
|
||||
Timeout time.Duration
|
||||
|
||||
Env []string
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||
"github.com/rs/zerolog/log"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
)
|
||||
|
||||
// Uninstall implements the HelmPackageManager interface by using the Helm SDK to uninstall a release.
|
||||
@@ -34,6 +37,12 @@ func (hspm *HelmSDKPackageManager) Uninstall(uninstallOpts options.UninstallOpti
|
||||
uninstallClient := action.NewUninstall(actionConfig)
|
||||
// 'foreground' means the parent object remains in a "terminating" state until all of its children are deleted. This ensures that all dependent resources are completely removed before finalizing the deletion of the parent resource.
|
||||
uninstallClient.DeletionPropagation = "foreground" // "background" or "orphan"
|
||||
uninstallClient.Wait = uninstallOpts.Wait
|
||||
if uninstallOpts.Timeout == 0 {
|
||||
uninstallClient.Timeout = 15 * time.Minute
|
||||
} else {
|
||||
uninstallClient.Timeout = uninstallOpts.Timeout
|
||||
}
|
||||
|
||||
// Run the uninstallation
|
||||
log.Info().
|
||||
@@ -63,3 +72,53 @@ func (hspm *HelmSDKPackageManager) Uninstall(uninstallOpts options.UninstallOpti
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceRemoveRelease removes all release history (Helm secrets) without attempting
|
||||
// to delete Kubernetes resources. This is a last-resort recovery mechanism for when
|
||||
// a standard Uninstall fails because CRDs are missing and Helm can't build kubernetes
|
||||
// objects for deletion, leaving the release stuck with no way to recover.
|
||||
func (hspm *HelmSDKPackageManager) ForceRemoveRelease(uninstallOpts options.UninstallOptions) error {
|
||||
if uninstallOpts.Name == "" {
|
||||
return errors.New("release name is required")
|
||||
}
|
||||
|
||||
log.Warn().
|
||||
Str("context", "HelmClient").
|
||||
Str("release", uninstallOpts.Name).
|
||||
Str("namespace", uninstallOpts.Namespace).
|
||||
Msg("Force-removing release history (skipping resource deletion)")
|
||||
|
||||
actionConfig := new(action.Configuration)
|
||||
err := hspm.initActionConfig(actionConfig, uninstallOpts.Namespace, uninstallOpts.KubernetesClusterAccess)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize helm configuration for force-remove")
|
||||
}
|
||||
|
||||
// Get all release versions from Helm's storage (Kubernetes secrets)
|
||||
versions, err := actionConfig.Releases.History(uninstallOpts.Name)
|
||||
if err != nil {
|
||||
if errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
log.Debug().
|
||||
Str("context", "HelmClient").
|
||||
Str("release", uninstallOpts.Name).
|
||||
Msg("Release not found in storage, nothing to force-remove")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "failed to get release history for force-remove")
|
||||
}
|
||||
|
||||
// Delete each release version from storage
|
||||
for _, v := range versions {
|
||||
if _, err := actionConfig.Releases.Delete(v.Name, v.Version); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete release version %d for force-remove", v.Version)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("context", "HelmClient").
|
||||
Str("release", uninstallOpts.Name).
|
||||
Int("versions_removed", len(versions)).
|
||||
Msg("Successfully force-removed all release history")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -110,6 +110,11 @@ func (hpm helmMockPackageManager) Uninstall(uninstallOpts options.UninstallOptio
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceRemoveRelease removes release history without deleting resources (not thread safe)
|
||||
func (hpm helmMockPackageManager) ForceRemoveRelease(uninstallOpts options.UninstallOptions) error {
|
||||
return hpm.Uninstall(uninstallOpts)
|
||||
}
|
||||
|
||||
// List a helm chart (not thread safe)
|
||||
func (hpm helmMockPackageManager) List(listOpts options.ListOptions) ([]release.ReleaseElement, error) {
|
||||
return mockCharts, nil
|
||||
|
||||
@@ -14,6 +14,10 @@ type HelmPackageManager interface {
|
||||
List(listOpts options.ListOptions) ([]release.ReleaseElement, error)
|
||||
Upgrade(upgradeOpts options.InstallOptions) (*release.Release, error)
|
||||
Uninstall(uninstallOpts options.UninstallOptions) error
|
||||
// ForceRemoveRelease removes all release history (Helm secrets) without attempting
|
||||
// to delete Kubernetes resources. Use as a last resort when Uninstall fails because
|
||||
// CRDs are missing and Helm can't build kubernetes objects for deletion.
|
||||
ForceRemoveRelease(uninstallOpts options.UninstallOptions) error
|
||||
Get(getOpts options.GetOptions) (*release.Release, error)
|
||||
GetHistory(historyOpts options.HistoryOptions) ([]*release.Release, error)
|
||||
Rollback(rollbackOpts options.RollbackOptions) (*release.Release, error)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -160,8 +161,9 @@ func (c *Client) applyResource(ctx context.Context, dynamicClient dynamic.Interf
|
||||
return "", fmt.Errorf("failed to marshal object to JSON: %w", err)
|
||||
}
|
||||
|
||||
// Apply using Server-Side Apply
|
||||
// This is more efficient and handles field ownership better than traditional apply
|
||||
// Apply using Server-Side Apply (Patch). If the resource does not exist (404),
|
||||
// fall back to Create so restoration can create Deployments and other resources
|
||||
// that were removed (e.g. by Helm uninstall).
|
||||
patchOptions := metav1.PatchOptions{
|
||||
FieldManager: "portainer",
|
||||
Force: boolPtr(true),
|
||||
@@ -175,7 +177,14 @@ func (c *Client) applyResource(ctx context.Context, dynamicClient dynamic.Interf
|
||||
patchOptions,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to apply %s %s/%s: %w", gvk.Kind, namespace, name, err)
|
||||
if apierrors.IsNotFound(err) {
|
||||
_, createErr := resourceClient.Create(ctx, obj, metav1.CreateOptions{})
|
||||
if createErr != nil {
|
||||
return "", fmt.Errorf("failed to create %s %s/%s: %w", gvk.Kind, namespace, name, createErr)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("failed to apply %s %s/%s: %w", gvk.Kind, namespace, name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Format output message
|
||||
|
||||
Reference in New Issue
Block a user