-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathcommon.go
More file actions
315 lines (271 loc) · 11.2 KB
/
Copy pathcommon.go
File metadata and controls
315 lines (271 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
package resources
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
paperclipv1alpha1 "github.com/paperclipinc/paperclip-operator/api/v1alpha1"
)
const (
// LabelApp is the standard app label key.
LabelApp = "app.kubernetes.io/name"
// LabelInstance is the instance label key.
LabelInstance = "app.kubernetes.io/instance"
// LabelManagedBy is the managed-by label key.
LabelManagedBy = "app.kubernetes.io/managed-by"
// LabelComponent is the component label key.
LabelComponent = "app.kubernetes.io/component"
// AppName is the application name used in labels.
AppName = "paperclip"
// ManagedBy is the manager name used in labels.
ManagedBy = "paperclip-operator"
// ContainerName is the name of the main Paperclip container.
ContainerName = "paperclip"
// DatabaseContainerName is the name of the PostgreSQL container.
DatabaseContainerName = "postgres"
// DefaultPort is the default Paperclip server port.
DefaultPort int32 = 3100
// PostgreSQLPort is the default PostgreSQL port.
PostgreSQLPort int32 = 5432
// DataVolumeName is the name of the Paperclip data volume.
DataVolumeName = "paperclip-data"
// DataMountPath is the mount path for the Paperclip data volume.
DataMountPath = "/paperclip"
// BrandVolumeName is the name of the optional brand-assets volume.
BrandVolumeName = "paperclip-branding"
// BrandMountPath is the read-only mount path for the brand-assets ConfigMap.
// The server serves this directory under /branding (PAPERCLIP_BRAND_DIR).
BrandMountPath = "/etc/paperclip/branding"
// EnvBrandDir is the environment variable pointing the server at the brand dir.
EnvBrandDir = "PAPERCLIP_BRAND_DIR"
// DatabaseVolumeName is the name of the PostgreSQL data volume.
DatabaseVolumeName = "pgdata"
// DatabaseMountPath is the mount path for the PostgreSQL data volume.
DatabaseMountPath = "/var/lib/postgresql/data"
// ModeExternal is the value for external resource modes (database, redis).
ModeExternal = "external"
// HealthPath is the HTTP health check path.
HealthPath = "/api/health"
// DefaultPaperclipEntrypoint is the default Paperclip container entrypoint.
// Used when the operator needs to inject a shell wrapper (e.g., heartbeat leader election).
DefaultPaperclipEntrypoint = `node --import ./server/node_modules/tsx/dist/loader.mjs server/dist/index.js`
// EnvOAuthCredentials is the environment variable for OAuth provider credentials JSON.
EnvOAuthCredentials = "PAPERCLIP_OAUTH_CREDENTIALS" // #nosec G101 -- env var name, not a credential //nolint:gosec
// EnvOAuthProviders is the environment variable for custom OAuth provider definitions.
EnvOAuthProviders = "PAPERCLIP_OAUTH_PROVIDERS"
)
// Ptr returns a pointer to the given value.
func Ptr[T any](v T) *T {
return &v
}
// EffectiveReplicas returns the configured replica count, defaulting to 1.
func EffectiveReplicas(instance *paperclipv1alpha1.Instance) int32 {
if instance.Spec.Availability.Replicas != nil {
return *instance.Spec.Availability.Replicas
}
return 1
}
// WorkloadReplicas returns the replica count for the server workload
// (StatefulSet or Deployment). When the instance is suspended, replicas is
// forced to 0 (scale-to-zero). Otherwise it returns the effective replica
// count. When HPA is enabled the controller preserves the current replica
// count on update so it does not fight the autoscaler.
func WorkloadReplicas(instance *paperclipv1alpha1.Instance) int32 {
if instance.Spec.Suspended {
return 0
}
return EffectiveReplicas(instance)
}
// UseDeploymentWorkload returns true when the server should run as a
// Deployment: explicit spec.workload=Deployment, or auto with no
// persistence and a non-embedded database.
func UseDeploymentWorkload(instance *paperclipv1alpha1.Instance) bool {
switch instance.Spec.Workload {
case "Deployment":
return true
case "auto":
return !PersistenceEnabled(instance) && instance.Spec.Database.Mode != "embedded"
default:
return false
}
}
// PersistenceEnabled reports whether the data PVC is enabled (defaults to
// true when unset).
// NetworkPolicyEnabled resolves the *bool (nil = default true).
func NetworkPolicyEnabled(instance *paperclipv1alpha1.Instance) bool {
if instance.Spec.Security.NetworkPolicy.Enabled == nil {
return true
}
return *instance.Spec.Security.NetworkPolicy.Enabled
}
func PersistenceEnabled(instance *paperclipv1alpha1.Instance) bool {
if instance.Spec.Storage.Persistence.Enabled == nil {
return true
}
return *instance.Spec.Storage.Persistence.Enabled
}
// SchedulerGatingMode resolves spec.heartbeat.schedulerGating to the mode the
// operator actually applies: "ordinal" or "lease". "auto" currently resolves
// to "ordinal" (it will flip to "lease" once the minimum supported app version
// includes lease-based scheduler leadership), and an empty value defaults to
// "ordinal".
func SchedulerGatingMode(instance *paperclipv1alpha1.Instance) string {
if instance.Spec.Heartbeat.SchedulerGating == "lease" {
return "lease"
}
return "ordinal"
}
// EffectiveWorkloadIsDeployment reports whether the server workload the
// controller actually reconciles is a Deployment. It applies the PVC-safety
// override on top of UseDeploymentWorkload: an explicit spec.workload=
// Deployment with persistence enabled falls back to a StatefulSet (the
// ReadWriteOnce data PVC cannot be shared by surging Deployment pods), and the
// HPA scaleTargetRef must follow that fallback.
func EffectiveWorkloadIsDeployment(instance *paperclipv1alpha1.Instance) bool {
return UseDeploymentWorkload(instance) && !PersistenceEnabled(instance)
}
// ServerPort returns the Paperclip server container port: the configured
// service port (spec.networking.service.port) or the default. It is the port
// the container listens on, used by probes and by the operator's own
// /api/health polling for scheduler leader discovery.
func ServerPort(instance *paperclipv1alpha1.Instance) int32 {
if instance.Spec.Networking.Service.Port > 0 {
return instance.Spec.Networking.Service.Port
}
return DefaultPort
}
// UseTCPProbes returns true when probes should use TCP instead of HTTP.
// This is needed in authenticated mode where /api/health returns 403.
func UseTCPProbes(instance *paperclipv1alpha1.Instance) bool {
probeType := instance.Spec.Probes.Type
if probeType == "tcp" {
return true
}
if probeType == "http" {
return false
}
// "auto" or empty: authenticated mode returns 403 from /api/health, so use TCP.
return instance.Spec.Deployment.Mode == "authenticated"
}
// Labels returns the standard labels for a Instance resource.
func Labels(instance *paperclipv1alpha1.Instance) map[string]string {
return map[string]string{
LabelApp: AppName,
LabelInstance: instance.Name,
LabelManagedBy: ManagedBy,
}
}
// LabelsWithComponent returns standard labels plus a component label.
func LabelsWithComponent(instance *paperclipv1alpha1.Instance, component string) map[string]string {
labels := Labels(instance)
labels[LabelComponent] = component
return labels
}
// SelectorLabels returns the minimal labels used for pod selectors.
// Includes component=server to distinguish from database pods.
func SelectorLabels(instance *paperclipv1alpha1.Instance) map[string]string {
return map[string]string{
LabelApp: AppName,
LabelInstance: instance.Name,
LabelComponent: "server",
}
}
// DatabaseSelectorLabels returns the labels used for the database pod selector.
func DatabaseSelectorLabels(instance *paperclipv1alpha1.Instance) map[string]string {
return map[string]string{
LabelApp: AppName,
LabelInstance: instance.Name,
LabelComponent: "database",
}
}
// ObjectMeta returns a standard ObjectMeta for a managed resource.
func ObjectMeta(instance *paperclipv1alpha1.Instance, name string) metav1.ObjectMeta {
return metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
Labels: Labels(instance),
}
}
// --- Naming conventions ---
// StatefulSetName returns the StatefulSet name for a Instance.
func StatefulSetName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// DeploymentName returns the Deployment name for a Instance. It must equal
// StatefulSetName so the server workload keeps its name (and the Service its
// label-based selection) when switching workload kinds.
func DeploymentName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// ServiceName returns the Service name for a Instance.
func ServiceName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// ConfigMapName returns the ConfigMap name for a Instance.
func ConfigMapName(instance *paperclipv1alpha1.Instance) string {
return instance.Name + "-config"
}
// PVCName returns the PVC name for a Instance.
func PVCName(instance *paperclipv1alpha1.Instance) string {
return instance.Name + "-data"
}
// IngressName returns the Ingress name for a Instance.
func IngressName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// HTTPRouteName returns the HTTPRoute name for a Instance.
func HTTPRouteName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// ServiceAccountName returns the ServiceAccount name for a Instance.
func ServiceAccountName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// NetworkPolicyName returns the NetworkPolicy name for a Instance.
func NetworkPolicyName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// DatabaseStatefulSetName returns the database StatefulSet name.
func DatabaseStatefulSetName(instance *paperclipv1alpha1.Instance) string {
return instance.Name + "-db"
}
// DatabaseServiceName returns the database Service name.
func DatabaseServiceName(instance *paperclipv1alpha1.Instance) string {
return instance.Name + "-db"
}
// DatabasePVCName returns the database PVC name.
func DatabasePVCName(instance *paperclipv1alpha1.Instance) string {
return instance.Name + "-db-data"
}
// HPAName returns the HPA name for a Instance.
func HPAName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// PDBName returns the PDB name for a Instance.
func PDBName(instance *paperclipv1alpha1.Instance) string {
return instance.Name
}
// DatabaseSecretName returns the auto-generated database credentials secret name.
func DatabaseSecretName(instance *paperclipv1alpha1.Instance) string {
return instance.Name + "-db-credentials"
}
// SecretsMasterKeySecretName returns the auto-generated secrets master key secret name.
func SecretsMasterKeySecretName(instance *paperclipv1alpha1.Instance) string {
return instance.Name + "-secrets-master-key"
}
// paperclipContainerSecurityContext returns the security context for containers
// running the Paperclip image. If the user has provided a custom security context
// via the CRD, it is used; otherwise the restricted-PSS-compliant default is returned.
func paperclipContainerSecurityContext(instance *paperclipv1alpha1.Instance) *corev1.SecurityContext {
if instance.Spec.Security.ContainerSecurityContext != nil {
return instance.Spec.Security.ContainerSecurityContext
}
return &corev1.SecurityContext{
AllowPrivilegeEscalation: Ptr(false),
RunAsNonRoot: Ptr(true),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
}
}