Skip to content

Commit e52bf04

Browse files
committed
Initial version of postgres cluster and postgres database CRs
Adds PostgresCluster, PostgresClusterClass, and PostgresDatabase CRDs with controllers, RBAC, CRD manifests, and samples. Integrates cloudnative-pg v1.28.0 operator dependency. Bumps controller-runtime to v0.22.4 and k8s deps to v0.34.2 for compatibility.
1 parent 2746807 commit e52bf04

57 files changed

Lines changed: 7357 additions & 310 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.tool-versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
golang 1.25.5

CLAUDE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Claude Code – Project Ground Rules
2+
3+
## Role & Expertise
4+
- You are a **Go expert** and a **Kubernetes controller/operator expert**.
5+
- You write **small, clean, unit-testable functions**.
6+
- Comments explain **why**, not what. Avoid restating the code in prose.
7+
8+
## Code Style
9+
- Keep functions focused and short — each should do one thing.
10+
- Prefer explicit error handling with descriptive context (e.g. `fmt.Errorf("reconciling roles: %w", err)`).
11+
- Avoid deep nesting; use early returns.
12+
13+
## Reconciler / Operator Design
14+
- The **reconciler is the main orchestration flow**. All state modifications are coordinated here.
15+
- We build state **incrementally**: each major step updates state and requeues (`ctrl.Result{RequeueAfter: ...}`).
16+
- Every operation must be **idempotent** — safe to run multiple times with the same outcome.
17+
- Follow **Kubernetes controller best practices**:
18+
- Use `SSA` (Server-Side Apply) where appropriate.
19+
- Emit `Events` for meaningful state transitions.
20+
- Use `Status` conditions to reflect progress and errors.
21+
- Respect finalizers for cleanup logic.
22+
23+
## Testing
24+
- New logic should be accompanied by unit tests.
25+
- Prefer table-driven tests.
26+
- Mock external dependencies (k8s client, DB connections) via interfaces.

PROJECT

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,31 @@ resources:
140140
kind: ObjectStorage
141141
path: github.com/splunk/splunk-operator/api/v4
142142
version: v4
143+
- api:
144+
crdVersion: v1
145+
namespaced: true
146+
controller: true
147+
domain: splunk.com
148+
group: enterprise
149+
kind: PostgresCluster
150+
path: github.com/splunk/splunk-operator/api/v4
151+
version: v4
152+
- api:
153+
crdVersion: v1
154+
namespaced: true
155+
controller: true
156+
domain: splunk.com
157+
group: enterprise
158+
kind: PostgresClusterClass
159+
path: github.com/splunk/splunk-operator/api/v4
160+
version: v4
161+
- api:
162+
crdVersion: v1
163+
namespaced: true
164+
controller: true
165+
domain: splunk.com
166+
group: enterprise
167+
kind: PostgresDatabase
168+
path: github.com/splunk/splunk-operator/api/v4
169+
version: v4
143170
version: "3"

api/v4/common_types.go

Lines changed: 23 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,12 @@ type Spec struct {
9191
// Image to use for Splunk pod containers (overrides RELATED_IMAGE_SPLUNK_ENTERPRISE environment variables)
9292
Image string `json:"image"`
9393

94-
// Sets pull policy for all images ("Always", "Never", or the default: "IfNotPresent")
95-
// +kubebuilder:validation:Enum=Always;IfNotPresent;Never
96-
// +kubebuilder:default=IfNotPresent
97-
// +optional
98-
ImagePullPolicy string `json:"imagePullPolicy,omitempty"`
94+
// Sets pull policy for all images (either “Always” or the default: “IfNotPresent”)
95+
// +kubebuilder:validation:Enum=Always;IfNotPresent
96+
ImagePullPolicy string `json:"imagePullPolicy"`
9997

10098
// Name of Scheduler to use for pod placement (defaults to “default-scheduler”)
101-
// +optional
102-
SchedulerName string `json:"schedulerName,omitempty"`
99+
SchedulerName string `json:"schedulerName"`
103100

104101
// Kubernetes Affinity rules that control how pods are assigned to particular nodes.
105102
Affinity corev1.Affinity `json:"affinity"`
@@ -140,7 +137,7 @@ const (
140137
// PhaseTerminating means a custom resource is in the process of being removed
141138
PhaseTerminating Phase = "Terminating"
142139

143-
// PhaseError means an error occurred with custom resource management
140+
// PhaseError means an error occured with custom resource management
144141
PhaseError Phase = "Error"
145142
)
146143

@@ -167,16 +164,13 @@ type CommonSplunkSpec struct {
167164
Spec `json:",inline"`
168165

169166
// Storage configuration for /opt/splunk/etc volume
170-
// +optional
171-
EtcVolumeStorageConfig StorageClassSpec `json:"etcVolumeStorageConfig,omitempty"`
167+
EtcVolumeStorageConfig StorageClassSpec `json:"etcVolumeStorageConfig"`
172168

173169
// Storage configuration for /opt/splunk/var volume
174-
// +optional
175-
VarVolumeStorageConfig StorageClassSpec `json:"varVolumeStorageConfig,omitempty"`
170+
VarVolumeStorageConfig StorageClassSpec `json:"varVolumeStorageConfig"`
176171

177172
// List of one or more Kubernetes volumes. These will be mounted in all pod containers as as /mnt/<name>
178-
// +optional
179-
Volumes []corev1.Volume `json:"volumes,omitempty"`
173+
Volumes []corev1.Volume `json:"volumes"`
180174

181175
// Inline map of default.yml overrides used to initialize the environment
182176
Defaults string `json:"defaults"`
@@ -216,12 +210,10 @@ type CommonSplunkSpec struct {
216210
// ServiceAccount is the service account used by the pods deployed by the CRD.
217211
// If not specified uses the default serviceAccount for the namespace as per
218212
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server
219-
// +optional
220-
ServiceAccount string `json:"serviceAccount,omitempty"`
213+
ServiceAccount string `json:"serviceAccount"`
221214

222215
// ExtraEnv refers to extra environment variables to be passed to the Splunk instance containers
223216
// WARNING: Setting environment variables used by Splunk or Ansible will affect Splunk installation and operation
224-
// +optional
225217
ExtraEnv []corev1.EnvVar `json:"extraEnv,omitempty"`
226218

227219
// ReadinessInitialDelaySeconds defines initialDelaySeconds(See https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes) for Readiness probe
@@ -235,64 +227,45 @@ type CommonSplunkSpec struct {
235227
LivenessInitialDelaySeconds int32 `json:"livenessInitialDelaySeconds"`
236228

237229
// LivenessProbe as defined in https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-command
238-
// +optional
239-
// +kubebuilder:default:={"initialDelaySeconds":30,"timeoutSeconds":30,"periodSeconds":30,"failureThreshold":3}
240230
LivenessProbe *Probe `json:"livenessProbe,omitempty"`
241231

242232
// ReadinessProbe as defined in https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes
243-
// +optional
244-
// +kubebuilder:default:={"initialDelaySeconds":10,"timeoutSeconds":5,"periodSeconds":5,"failureThreshold":3}
245233
ReadinessProbe *Probe `json:"readinessProbe,omitempty"`
246234

247235
// StartupProbe as defined in https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-startup-probes
248-
// +optional
249-
// +kubebuilder:default:={"initialDelaySeconds":40,"timeoutSeconds":30,"periodSeconds":30,"failureThreshold":12}
250236
StartupProbe *Probe `json:"startupProbe,omitempty"`
251237

252238
// Sets imagePullSecrets if image is being pulled from a private registry.
253239
// See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
254-
// +optional
255240
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
256241
}
257242

258243
// StorageClassSpec defines storage class configuration
259-
// +kubebuilder:validation:XValidation:rule="!(size(self.storageClassName) > 0 && self.ephemeralStorage == true)",message="storageClassName and ephemeralStorage are mutually exclusive"
260-
// +kubebuilder:validation:XValidation:rule="!(size(self.storageCapacity) > 0 && self.ephemeralStorage == true)",message="storageCapacity and ephemeralStorage are mutually exclusive"
261244
type StorageClassSpec struct {
262245
// Name of StorageClass to use for persistent volume claims
263-
// +optional
264-
StorageClassName string `json:"storageClassName,omitempty"`
246+
StorageClassName string `json:"storageClassName"`
265247

266-
// Storage capacity to request persistent volume claims (default="10Gi" for etc and "100Gi" for var)
267-
// +optional
268-
StorageCapacity string `json:"storageCapacity,omitempty"`
248+
// Storage capacity to request persistent volume claims (default=”10Gi” for etc and "100Gi" for var)
249+
StorageCapacity string `json:"storageCapacity"`
269250

270251
// If true, ephemeral (emptyDir) storage will be used
252+
// default false
271253
// +optional
272-
// +kubebuilder:default=false
273-
EphemeralStorage bool `json:"ephemeralStorage,omitempty"`
254+
EphemeralStorage bool `json:"ephemeralStorage"`
274255
}
275256

276257
// SmartStoreSpec defines Splunk indexes and remote storage volume configuration
277258
type SmartStoreSpec struct {
278259
// List of remote storage volumes
279-
// +optional
280-
// +listType=map
281-
// +listMapKey=name
282260
VolList []VolumeSpec `json:"volumes,omitempty"`
283261

284262
// List of Splunk indexes
285-
// +optional
286-
// +listType=map
287-
// +listMapKey=name
288263
IndexList []IndexSpec `json:"indexes,omitempty"`
289264

290265
// Default configuration for indexes
291-
// +optional
292266
Defaults IndexConfDefaultsSpec `json:"defaults,omitempty"`
293267

294268
// Defines Cache manager settings
295-
// +optional
296269
CacheManagerConf CacheManagerSpec `json:"cacheManager,omitempty"`
297270
}
298271

@@ -301,23 +274,18 @@ type CacheManagerSpec struct {
301274
IndexAndCacheManagerCommonSpec `json:",inline"`
302275

303276
// Eviction policy to use
304-
// +optional
305277
EvictionPolicy string `json:"evictionPolicy,omitempty"`
306278

307279
// Max cache size per partition
308-
// +optional
309280
MaxCacheSizeMB uint `json:"maxCacheSize,omitempty"`
310281

311282
// Additional size beyond 'minFreeSize' before eviction kicks in
312-
// +optional
313283
EvictionPaddingSizeMB uint `json:"evictionPadding,omitempty"`
314284

315285
// Maximum number of buckets that can be downloaded from remote storage in parallel
316-
// +optional
317286
MaxConcurrentDownloads uint `json:"maxConcurrentDownloads,omitempty"`
318287

319288
// Maximum number of buckets that can be uploaded to remote storage in parallel
320-
// +optional
321289
MaxConcurrentUploads uint `json:"maxConcurrentUploads,omitempty"`
322290
}
323291

@@ -327,55 +295,40 @@ type IndexConfDefaultsSpec struct {
327295
}
328296

329297
// VolumeSpec defines remote volume config
330-
// +kubebuilder:validation:XValidation:rule="self.provider != 'aws' || size(self.region) > 0",message="region is required when provider is aws"
331298
type VolumeSpec struct {
332299
// Remote volume name
333-
// +kubebuilder:validation:Required
334-
// +kubebuilder:validation:MinLength=1
335300
Name string `json:"name"`
336301

337302
// Remote volume URI
338-
// +kubebuilder:validation:Required
339-
// +kubebuilder:validation:MinLength=1
340303
Endpoint string `json:"endpoint"`
341304

342305
// Remote volume path
343-
// +kubebuilder:validation:Required
344-
// +kubebuilder:validation:MinLength=1
345306
Path string `json:"path"`
346307

347308
// Secret object name
348-
// +optional
349-
SecretRef string `json:"secretRef,omitempty"`
309+
SecretRef string `json:"secretRef"`
350310

351311
// Remote Storage type. Supported values: s3, blob, gcs. s3 works with aws or minio providers, whereas blob works with azure provider, gcs works for gcp.
352-
// +kubebuilder:validation:Enum=s3;blob;gcs
353312
Type string `json:"storageType"`
354313

355314
// App Package Remote Store provider. Supported values: aws, minio, azure, gcp.
356-
// +optional
357-
// +kubebuilder:validation:Enum=aws;minio;azure;gcp
358-
Provider string `json:"provider,omitempty"`
315+
Provider string `json:"provider"`
359316

360-
// Region of the remote storage volume where apps reside. Required for aws, optional for azure and gcp.
361-
// +optional
362-
Region string `json:"region,omitempty"`
317+
// Region of the remote storage volume where apps reside. Used for aws, if provided. Not used for minio and azure.
318+
Region string `json:"region"`
363319
}
364320

365-
// VolumeAndTypeSpec used to add any custom variables for volume implementation
321+
// VolumeAndTypeSpec used to add any custom varaibles for volume implementation
366322
type VolumeAndTypeSpec struct {
367323
VolumeSpec `json:",inline"`
368324
}
369325

370326
// IndexSpec defines Splunk index name and storage path
371327
type IndexSpec struct {
372328
// Splunk index name
373-
// +kubebuilder:validation:Required
374-
// +kubebuilder:validation:MinLength=1
375329
Name string `json:"name"`
376330

377331
// Index location relative to the remote volume path
378-
// +optional
379332
RemotePath string `json:"remotePath,omitempty"`
380333

381334
IndexAndCacheManagerCommonSpec `json:",inline"`
@@ -387,26 +340,21 @@ type IndexSpec struct {
387340
type IndexAndGlobalCommonSpec struct {
388341

389342
// Remote Volume name
390-
// +optional
391343
VolName string `json:"volumeName,omitempty"`
392344

393345
// MaxGlobalDataSizeMB defines the maximum amount of space for warm and cold buckets of an index
394-
// +optional
395346
MaxGlobalDataSizeMB uint `json:"maxGlobalDataSizeMB,omitempty"`
396347

397348
// MaxGlobalDataSizeMB defines the maximum amount of cumulative space for warm and cold buckets of an index
398-
// +optional
399349
MaxGlobalRawDataSizeMB uint `json:"maxGlobalRawDataSizeMB,omitempty"`
400350
}
401351

402352
// IndexAndCacheManagerCommonSpec defines configurations that can be configured at index level or at server level
403353
type IndexAndCacheManagerCommonSpec struct {
404354
// Time period relative to the bucket's age, during which the bucket is protected from cache eviction
405-
// +optional
406355
HotlistRecencySecs uint `json:"hotlistRecencySecs,omitempty"`
407356

408357
// Time period relative to the bucket's age, during which the bloom filter file is protected from cache eviction
409-
// +optional
410358
HotlistBloomFilterRecencyHours uint `json:"hotlistBloomFilterRecencyHours,omitempty"`
411359
}
412360

@@ -427,9 +375,8 @@ type AppSourceDefaultSpec struct {
427375

428376
// PremiumAppsProps represents properties for premium apps such as ES
429377
type PremiumAppsProps struct {
430-
// Type: enterpriseSecurity for now, can accommodate itsi etc.. later
378+
// Type: enterpriseSecurity for now, can accomodate itsi etc.. later
431379
// +optional
432-
// +kubebuilder:validation:Enum=enterpriseSecurity
433380
Type string `json:"type,omitempty"`
434381

435382
// Enterpreise Security App defaults
@@ -456,13 +403,9 @@ type EsDefaults struct {
456403
// AppSourceSpec defines list of App package (*.spl, *.tgz) locations on remote volumes
457404
type AppSourceSpec struct {
458405
// Logical name for the set of apps placed in this location. Logical name must be unique to the appRepo
459-
// +kubebuilder:validation:Required
460-
// +kubebuilder:validation:MinLength=1
461406
Name string `json:"name"`
462407

463408
// Location relative to the volume path
464-
// +kubebuilder:validation:Required
465-
// +kubebuilder:validation:MinLength=1
466409
Location string `json:"location"`
467410

468411
AppSourceDefaultSpec `json:",inline"`
@@ -480,18 +423,17 @@ type AppFrameworkSpec struct {
480423
// 1. If no value or 0 is specified then it means periodic polling is disabled.
481424
// 2. If anything less than min is specified then we set it to 1 min.
482425
// 3. If anything more than the max value is specified then we set it to 1 day.
483-
// +optional
484426
AppsRepoPollInterval int64 `json:"appsRepoPollIntervalSeconds,omitempty"`
485427

486428
// App installation period within a reconcile. Apps will be installed during this period before the next reconcile is attempted.
487429
// Note: Do not change this setting unless instructed to do so by Splunk Support
488-
// +optional
430+
// +kubebuilder:validation:Optional
489431
// +kubebuilder:validation:Minimum:=30
490432
// +kubebuilder:default:=90
491433
SchedulerYieldInterval uint64 `json:"appInstallPeriodSeconds,omitempty"`
492434

493435
// Maximum number of retries to install Apps
494-
// +optional
436+
// +kubebuilder:validation:Optional
495437
// +kubebuilder:validation:Minimum:=0
496438
// +kubebuilder:default:=2
497439
PhaseMaxRetries uint32 `json:"installMaxRetries,omitempty"`
@@ -500,13 +442,9 @@ type AppFrameworkSpec struct {
500442
VolList []VolumeSpec `json:"volumes,omitempty"`
501443

502444
// List of App sources on remote storage
503-
// +optional
504-
// +listType=map
505-
// +listMapKey=name
506445
AppSources []AppSourceSpec `json:"appSources,omitempty"`
507446

508447
// Maximum number of apps that can be downloaded at same time
509-
// +optional
510448
MaxConcurrentAppDownloads uint64 `json:"maxConcurrentAppDownloads,omitempty"`
511449
}
512450

@@ -545,7 +483,7 @@ type AppSrcDeployInfo struct {
545483
type BundlePushStageType int
546484

547485
const (
548-
// BundlePushUninitialized indicates bundle push never happened
486+
// BundlePushUninitialized indicates bundle push never happend
549487
BundlePushUninitialized BundlePushStageType = iota
550488
// BundlePushPending waiting for all the apps to be copied to the Pod
551489
BundlePushPending

0 commit comments

Comments
 (0)