storageclasschange

package
v0.12.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

storage-class-change

Module hook that watches PVCs, Pods, and StorageClasses for a single workload and:

  1. Computes the effective storage class for the workload, layering cluster default → global config → module config → in-cluster PVC.
  2. Stores the result at a well-known internal values path so Helm templates can branch on it.
  3. Evicts pods whose PVCs have been deleted out of band.
  4. Deletes the workload's PVCs and the workload itself (StatefulSet / Deployment / Prometheus) when the storage class actually changed, so the controller recreates everything with the new class.
  5. Exports a d8_emptydir_usage Prometheus metric that is 1 when the module falls back to emptyDir (no storage class).

What it watches

Snapshot Kind Filter
pvcs PersistentVolumeClaim in args.Namespace label args.LabelSelectorKey=args.LabelSelectorValue
pods Pod in args.Namespace same label selector
storageClasses StorageClass (cluster-scoped) none

Each snapshot is JQ-filtered into a small struct with just the fields the hook needs (name, namespace, storageClassName, isDeleted, …).

Effective storage class lookup

For a hook initialised with args, the effective storage class is computed in this order (later wins):

  1. The cluster's default StorageClass (annotation storageclass.kubernetes.io/is-default-class=true or its beta variant).
  2. global.modules.storageClass from config values, if set.
  3. The class currently bound to the workload's PVCs (if any).
  4. <args.ModuleName>.<args.D8ConfigStorageClassParamName | "storageClass"> from config values, if set.

The result is written to <lowerCamel(args.ModuleName)>.internal.<args.InternalValuesSubPath?>.effectiveStorageClass. If the effective class is empty or the literal string "false", the hook stores the boolean false instead and bumps the d8_emptydir_usage metric.

Side effects

When the effective class differs from the currently-bound one:

  • For each existing PVC the hook deletes the PVC (so the controller recreates it).
  • The hook deletes the workload itself. Which objects are deleted depends on args.ObjectName:
args.ObjectName Effect
set Exactly that object is deleted. A delete error other than NotFound is logged, the hook still succeeds.
empty Every object of args.ObjectKind in args.Namespace labelled args.LabelSelectorKey=args.LabelSelectorValue is deleted. A delete error other than NotFound fails the hook; an empty match is only warned about.

Use the second form for modules that manage several workloads in one namespace (e.g. dynamically named StatefulSets) — deleting only one of them makes Helm fail later when it tries to update volumeClaimTemplates of the survivors.

Objects are deleted through the dynamic client, resolved from args.ObjectKind:

args.ObjectKind Resource
StatefulSet apps/v1 statefulsets
Deployment apps/v1 deployments
Prometheus monitoring.coreos.com/v1 prometheuses[^1]
anything else The hook returns unknown object kind <Kind>.

When a PVC has a deletionTimestamp but a Pod still references it, the hook issues a policy/v1 Eviction against the Pod.

Usage

package hooks

import (
    sccc "github.com/deckhouse/module-sdk/common-hooks/storage-class-change"
)

var _ = sccc.RegisterHook(sccc.Args{
    ModuleName:         "myModule",
    Namespace:          "d8-my-module",
    LabelSelectorKey:   "app",
    LabelSelectorValue: "data",
    ObjectKind:         "StatefulSet",
    // Optional: when omitted, every StatefulSet labelled app=data in the
    // namespace is deleted instead of a single named one.
    ObjectName:         "data-set",

    // Optional knobs
    InternalValuesSubPath:         "data",            // → myModule.internal.data.effectiveStorageClass
    D8ConfigStorageClassParamName: "dataStorageClass", // → myModule.dataStorageClass instead of .storageClass
    BeforeHookCheck: func(input *pkg.HookInput) bool {
        // Skip the hook entirely when, e.g., the module is disabled.
        return input.Values.Get("myModule.enabled").Bool()
    },
})

Hook configuration

  • Trigger: OnBeforeHelm with Order: 1.
  • Snapshots: pvcs, pods, storageClasses as described above.

Testing

This hook has both unit and functional coverage:

  • hook_test.go — table-driven JQ-filter tests using testing/helpers.
  • hook_framework_test.go — end-to-end scenarios driven through testing/framework:
    • default StorageClass writes the right effective value;
    • explicit global.modules.storageClass overrides the default;
    • label selector scopes which PVCs participate;
    • BeforeHookCheck short-circuits the hook;
    • deletion by name and by label selector, an empty match, and a failing delete.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterHook

func RegisterHook(args Args) bool

Types

type Args

type Args struct {
	ModuleName         string `json:"moduleName"`
	Namespace          string `json:"namespace"`
	LabelSelectorKey   string `json:"labelSelectorKey"`
	LabelSelectorValue string `json:"labelSelectorValue"`
	ObjectKind         string `json:"objectKind"`
	// ObjectName is optional: when empty, every object of ObjectKind matching
	// LabelSelectorKey=LabelSelectorValue is deleted instead of a single named one.
	ObjectName                    string `json:"objectName"`
	InternalValuesSubPath         string `json:"internalValuesSubPath,omitempty"`
	D8ConfigStorageClassParamName string `json:"d8ConfigStorageClassParamName,omitempty"`

	// if return value is false - hook will stop its execution
	BeforeHookCheck func(input *pkg.HookInput) bool
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL