Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: charts/redpanda
kind: Deprecated
body: '- `statefulset.sideCars.controllers.createRBAC` is deprecated and no longer respected. In most cases, setting this field to `false` would result in a broken deployment. RBAC may be controlled via `rbac.enabled` or per controller via `statefulset.sideCars.controllers.{pvcUnbinder,brokerDecommissioner}.enabled`.'
time: 2025-10-21T14:38:34.206376-04:00
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: charts/redpanda
kind: Deprecated
body: '`statefulset.sideCars.controllers.run` has been unused for many releases and is now deprecated. Individual controllers may be enabled/disabled by setting their enabled field: `statefulset.sideCars.pvcUnbinder.enabled`, `statefulset.sideCars.brokerDecommissioner.enabled`.'
time: 2025-10-21T14:44:13.331483-04:00
7 changes: 7 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ tasks:
vars:
CLI_ARGS: '--load {{.CLI_ARGS}}'

build:charts:
desc: "Run helm dep build for all charts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: It's not all chart only redpanda, but I think this is the place where we should add any additional helm dep invocation.

Copy link
Contributor Author

@chrisseto chrisseto Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅 redpanda is the only chart any with deps, so technically it is all charts. I named all charts so this gets added onto rather if we ever end up adding new deps. This is exactly what you said.

cmds:
- helm dep build ./charts/redpanda/chart

test:unit:
desc: "Run all unit tests (~5m)"
vars:
Expand All @@ -179,6 +184,7 @@ tasks:
- task: build:image
vars:
CLI_ARGS: '' # Don't forward CLI args to build:image
- task: build:charts
vars:
RUN: '{{ default `"^TestIntegration"` .RUN }}'
cmds:
Expand All @@ -195,6 +201,7 @@ tasks:
- task: build:image
vars:
CLI_ARGS: '' # Don't forward CLI args to build:image
- task: build:charts
vars:
RUN: '{{ default `"^TestAcceptance"` .RUN }}'
GO_TEST_RUNNER: '{{default "go test" .GO_TEST_RUNNER}}'
Expand Down
39 changes: 39 additions & 0 deletions acceptance/features/helm-chart.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@operator:none
Feature: Redpanda Helm Chart

Scenario: Tolerating Node Failure
Given I helm install "redpanda" "../charts/redpanda/chart" with values:
```yaml
nameOverride: foobar
fullnameOverride: bazquux

statefulset:
sideCars:
image:
tag: dev
repository: localhost/redpanda-operator
pvcUnbinder:
enabled: true
unbindAfter: 15s
brokerDecommissioner:
enabled: true
decommissionAfter: 15s
```
When I stop the Node running Pod "bazquux-2"
And Pod "bazquux-2" is eventually Pending
Then Pod "bazquux-2" will eventually be Running
And kubectl exec -it "bazquux-0" "rpk redpanda admin brokers list | sed -E 's/\s+/ /gm' | cut -d ' ' -f 1,6" will eventually output:
```
ID MEMBERSHIP
0 active
1 active
3 active
```
And kubectl exec -it "bazquux-0" "rpk redpanda admin brokers list --include-decommissioned | sed -E 's/\s+/ /gm' | cut -d ' ' -f 1,6" will eventually output:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cut in the redpanda pod doesn't have the ability to split on \s and I refuse to learn awk, so we get sed.

```
ID MEMBERSHIP
0 active
1 active
3 active
2 -
```
2 changes: 1 addition & 1 deletion acceptance/features/migration.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Feature: Helm chart to Redpanda Operator migration

@skip:gke @skip:aks @skip:eks
Scenario: Migrate from a Helm chart release to a Redpanda custom resource
Given a Helm release named "redpanda-migration-example" of the "redpanda/redpanda" helm chart with the values:
Given I helm install "redpanda-migration-example" "../charts/redpanda/chart" with values:
"""
# tag::helm-values[]
fullnameOverride: name-override
Expand Down
44 changes: 2 additions & 42 deletions acceptance/features/operator-upgrades.feature
Original file line number Diff line number Diff line change
@@ -1,48 +1,8 @@
@operator:none @vcluster
Feature: Upgrading the operator
@skip:gke @skip:aks @skip:eks
Scenario: Operator upgrade from 2.4.5
Given I install local CRDs from "../operator/config/crd/bases"
And I install redpanda helm chart version "v2.4.5" with the values:
"""
console:
enabled: false
"""
And I apply Kubernetes manifest:
"""
---
apiVersion: cluster.redpanda.com/v1alpha2
kind: Redpanda
metadata:
name: operator-upgrade
spec:
clusterSpec:
console:
enabled: false
statefulset:
replicas: 1
sideCars:
image:
tag: dev
repository: localhost/redpanda-operator
"""
# use just a Ready status check here since that's all the
# old operator supports
And cluster "operator-upgrade" is available
Then I can upgrade to the latest operator with the values:
"""
image:
tag: dev
repository: localhost/redpanda-operator
crds:
experimental: true
"""
# use the new status as this will eventually get set
And cluster "operator-upgrade" should be stable with 1 nodes

@skip:gke @skip:aks @skip:eks
Scenario: Operator upgrade from 25.1.3
And I install redpanda helm chart version "v25.1.3" with the values:
Given I helm install "redpanda-operator" "redpanda/operator" --version v25.1.3 with values:
"""
crds:
enabled: true
Expand All @@ -68,7 +28,7 @@ Feature: Upgrading the operator
# use just a Ready status check here since that's all the
# old operator supports
And cluster "operator-upgrade" is available
Then I can upgrade to the latest operator with the values:
Then I can helm upgrade "redpanda-operator" "../operator/chart" with values:
"""
image:
tag: dev
Expand Down
4 changes: 2 additions & 2 deletions acceptance/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ var setupSuite = sync.OnceValues(func() (*framework.Suite, error) {
return
}
t.Log("Installing default Redpanda operator chart")
t.InstallLocalHelmChart(ctx, "../operator/chart", helm.InstallOptions{
t.InstallHelmChart(ctx, "../operator/chart", helm.InstallOptions{
Name: "redpanda-operator",
Namespace: namespace,
Values: operatorchart.PartialValues{
Expand Down Expand Up @@ -205,7 +205,7 @@ func OperatorTag(ctx context.Context, t framework.TestingT, args ...string) cont
}

t.Logf("Installing Redpanda operator chart: %q", name)
t.InstallLocalHelmChart(ctx, "../operator/chart", helm.InstallOptions{
t.InstallHelmChart(ctx, "../operator/chart", helm.InstallOptions{
Name: "redpanda-operator",
Namespace: t.Namespace(),
ValuesFile: filepath.Join("operator", fmt.Sprintf("%s.yaml", name)),
Expand Down
19 changes: 19 additions & 0 deletions acceptance/steps/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,25 @@ func shutdownRandomClusterNode(ctx context.Context, t framework.TestingT, cluste
t.ShutdownNode(ctx, pod.Spec.NodeName)
}

func shutdownNodeOfPod(ctx context.Context, t framework.TestingT, podName string) {
t.ResourceKey(podName)

var pod corev1.Pod
require.NoError(t, t.Get(ctx, t.ResourceKey(podName), &pod))

var node corev1.Node
require.NoError(t, t.Get(ctx, t.ResourceKey(pod.Spec.NodeName), &node))

node.Spec.Taints = append(node.Spec.Taints, corev1.Taint{
Key: "node.kubernetes.io/out-of-service",
Effect: corev1.TaintEffectNoExecute,
})

require.NoError(t, t.Update(ctx, &node))

t.ShutdownNode(ctx, pod.Spec.NodeName)
}

func deleteNotReadyKubernetesNodes(ctx context.Context, t framework.TestingT) {
var nodes corev1.NodeList
require.NoError(t, t.List(ctx, &nodes))
Expand Down
35 changes: 20 additions & 15 deletions acceptance/steps/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,45 @@ package steps
import (
"context"
"fmt"
"strings"

"github.com/cucumber/godog"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"sigs.k8s.io/yaml"

framework "github.com/redpanda-data/redpanda-operator/harpoon"
"github.com/redpanda-data/redpanda-operator/pkg/helm"
)

// The unused parameter is meant to specify a Helm chart place (remote or local in the file system).
func iInstallHelmRelease(ctx context.Context, t framework.TestingT, helmReleaseName, _ string, values *godog.DocString) {
func iHelmInstall(ctx context.Context, t framework.TestingT, name, chart, version string, values *godog.DocString) {
// We don't really reference anything other than the redpanda repo, so just
// handle repos as a naive check here.
if strings.HasPrefix(chart, "redpanda/") {
t.AddHelmRepo(ctx, "redpanda", "https://charts.redpanda.com")
}

var valuesMap map[string]any
require.NoError(t, yaml.Unmarshal([]byte(values.Content), &valuesMap))

helmClient, err := helm.New(helm.Options{
KubeConfig: rest.CopyConfig(t.RestConfig()),
t.InstallHelmChart(ctx, chart, helm.InstallOptions{
Name: name,
Version: version,
Values: valuesMap,
Namespace: t.Namespace(),
})
require.NoError(t, err)

require.NoError(t, helmClient.RepoAdd(ctx, "console", "https://charts.redpanda.com"))
}

path := "../charts/redpanda/chart"
require.NoError(t, helmClient.DependencyBuild(ctx, path))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current tests are failing due to removal of this line (since you collapsed this helper, you'll probably want to conditionally run this when passed a chart that has a . prefix?):

Given I helm install "redpanda" "../charts/redpanda/chart" with values: # features/helm-chart.feature:5
--
  | Error:
  | Error Trace:	/work/harpoon/internal/testing/helm.go:37
  | /work/acceptance/steps/helm.go:37
  | /nix/store/5xvi25nqmbrg58aixp4zgczilfnp7pwg-go-1.24.3/share/go/src/reflect/value.go:584
  | /nix/store/5xvi25nqmbrg58aixp4zgczilfnp7pwg-go-1.24.3/share/go/src/reflect/value.go:368
  | /work/harpoon/internal/testing/panic.go:119
  | /nix/store/5xvi25nqmbrg58aixp4zgczilfnp7pwg-go-1.24.3/share/go/src/reflect/value.go:584
  | /nix/store/5xvi25nqmbrg58aixp4zgczilfnp7pwg-go-1.24.3/share/go/src/reflect/value.go:368
  | /work/harpoon/steps.go:58
  | /nix/store/5xvi25nqmbrg58aixp4zgczilfnp7pwg-go-1.24.3/share/go/src/reflect/value.go:584
  | /nix/store/5xvi25nqmbrg58aixp4zgczilfnp7pwg-go-1.24.3/share/go/src/reflect/value.go:368
  | /root/go/pkg/mod/github.com/cucumber/[email protected]/internal/models/stepdef.go:182
  | /root/go/pkg/mod/github.com/cucumber/[email protected]/suite.go:211
  | /root/go/pkg/mod/github.com/cucumber/[email protected]/suite.go:476
  | /root/go/pkg/mod/github.com/cucumber/[email protected]/suite.go:532
  | Error:      	Received unexpected error:
  | stderr: Error: INSTALLATION FAILED: An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: console: exit status 1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm considering that to be a build step and I've added a helm dep build call to the task. Some integration tests should be depending on it too and I think it's an accidental race that makes it work at the moment.

func iHelmUpgrade(ctx context.Context, t framework.TestingT, name, chart, version string, values *godog.DocString) {
var valuesMap map[string]any
require.NoError(t, yaml.Unmarshal([]byte(values.Content), &valuesMap))

t.Logf("installing chart %q", path)
_, err = helmClient.Install(ctx, path, helm.InstallOptions{
Name: helmReleaseName,
Namespace: t.Namespace(),
t.UpgradeHelmChart(ctx, name, chart, helm.UpgradeOptions{
Version: version,
Values: valuesMap,
Namespace: t.Namespace(),
})
require.NoError(t, err)
}

func iDeleteHelmReleaseSecret(ctx context.Context, t framework.TestingT, helmReleaseName string) {
Expand Down
16 changes: 0 additions & 16 deletions acceptance/steps/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ import (
authenticationv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
"k8s.io/utils/ptr"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -651,20 +649,6 @@ func clientsForOperator(ctx context.Context, includeTLS bool, serviceAccountName
}
}

func removeAllFinalizers(ctx context.Context, t framework.TestingT, gvk schema.GroupVersionKind) {
list := &unstructured.UnstructuredList{}
list.SetGroupVersionKind(gvk)

// swallow errors for non-existent crds
if err := t.List(ctx, list); err == nil {
for i := range list.Items {
item := list.Items[i]
item.SetFinalizers(nil)
require.NoError(t, t.Update(ctx, &item))
}
}
}

func getVersion(t framework.TestingT, version string) string {
version = strings.TrimSpace(version)
if version != "" {
Expand Down
52 changes: 45 additions & 7 deletions acceptance/steps/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ import (
// for type assertions
var _ client.Object = (client.Object)(nil)

func podWillEventuallyBeInPhase(ctx context.Context, t framework.TestingT, podName string, phase string) {
require.EventuallyWithT(t, func(c *assert.CollectT) {
var pod corev1.Pod
require.NoError(c, t.Get(ctx, t.ResourceKey(podName), &pod))

require.Equal(c, corev1.PodPhase(phase), pod.Status.Phase)
}, 5*time.Minute, 5*time.Second)
}

func kubernetesObjectHasClusterOwner(ctx context.Context, t framework.TestingT, groupVersionKind, resourceName, clusterName string) {
var cluster redpandav1alpha2.Redpanda

Expand Down Expand Up @@ -163,7 +172,23 @@ func execJSONPath(ctx context.Context, t framework.TestingT, jsonPath, groupVers
return nil
}

func iExecInPodMatching(
func execInPodEventuallyMatches(
ctx context.Context,
t framework.TestingT,
podName string,
cmd string,
expected *godog.DocString,
) {
ctl, err := kube.FromRESTConfig(t.RestConfig())
require.NoError(t, err)

pod, err := kube.Get[corev1.Pod](ctx, ctl, kube.ObjectKey{Namespace: t.Namespace(), Name: podName})
require.NoErrorf(t, err, "Pod with name %q not found", podName)

execInPod(t, ctx, ctl, pod, cmd, expected)
}

func execInPodMatchingEventuallyMatches(
ctx context.Context,
t framework.TestingT,
cmd,
Expand All @@ -181,11 +206,24 @@ func iExecInPodMatching(

require.True(t, len(pods.Items) > 0, "selector %q found no Pods", selector.String())

var stdout bytes.Buffer
require.NoError(t, ctl.Exec(ctx, &pods.Items[0], kube.ExecOptions{
Command: []string{"sh", "-c", cmd},
Stdout: &stdout,
}))
execInPod(t, ctx, ctl, &pods.Items[0], cmd, expected)
}

assert.Equal(t, strings.TrimSpace(expected.Content), strings.TrimSpace(stdout.String()))
func execInPod(
t framework.TestingT,
ctx context.Context,
ctl *kube.Ctl,
pod *corev1.Pod,
cmd string,
expected *godog.DocString,
) {
require.EventuallyWithT(t, func(collect *assert.CollectT) {
var stdout bytes.Buffer
require.NoError(collect, ctl.Exec(ctx, pod, kube.ExecOptions{
Command: []string{"sh", "-c", cmd},
Stdout: &stdout,
}))

assert.Equal(collect, strings.TrimSpace(expected.Content), strings.TrimSpace(stdout.String()))
}, 5*time.Minute, 5*time.Second)
}
Loading