Skip to content
Open
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ endif
OPERATOR_SDK_VERSION ?= v1.33.0
# Image URL to use all building/pushing image targets
#IMG ?= docker.io/layer7api/layer7-operator:v$(VERSION)
IMG ?= $(IMAGE_TAG_BASE):$(VERSION)
# Sanitize VERSION for use as a Docker image tag: slashes (from branch names like
# feature/foo) are not valid in tags and must be replaced with hyphens.
DOCKER_VERSION := $(subst /,-,$(VERSION))
IMG ?= $(IMAGE_TAG_BASE):$(DOCKER_VERSION)
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.30.0

Expand Down Expand Up @@ -218,7 +221,7 @@ build: manifests generate fmt vet ## Build manager binary.

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/main.go --zap-log-level=5
go run ./cmd/main.go --zap-log-level=5 --leader-elect=false

.PHONY: docker-build
docker-build: dockerfile #test ## Build docker image with the manager.
Expand Down
38 changes: 38 additions & 0 deletions api/v1/gateway_types.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved.
/*
Copyright 2021.

Expand Down Expand Up @@ -64,6 +65,20 @@ type GatewayList struct {
Items []Gateway `json:"items"`
}

// MigrationStatus tracks the state of the pre-upgrade database migration job.
// It is persisted in Gateway.Status so that completed migrations survive job
// deletion and are not re-run on daily reconciles or operator restarts.
type MigrationStatus struct {
// SpecHash is a short hash of the migration-relevant spec fields (image,
// effective jdbcUrl, clearLocks). When any of these change the hash changes
// and a fresh migration job is triggered.
SpecHash string `json:"specHash,omitempty"`
// Complete indicates that the migration job succeeded for the current SpecHash.
// Once true, GatewayMigrationJob skips job management entirely and the
// Deployment step is unblocked — regardless of whether the Job still exists.
Complete bool `json:"complete,omitempty"`
}

// GatewayStatus defines the observed state of Gateways
type GatewayStatus struct {
// Host is the Gateway Cluster Hostname
Expand Down Expand Up @@ -119,6 +134,8 @@ type GatewayStatus struct {
LastAppliedExternalCerts map[string][]string `json:"lastAppliedExternalCerts,omitempty"`
// LastAppliedOtkFipsCerts tracks which OTK FIPS user certificates have been applied
LastAppliedOtkFipsCerts map[string][]string `json:"lastAppliedOtkFipsCerts,omitempty"`
// MigrationStatus tracks the state of the pre-upgrade database migration job.
MigrationStatus MigrationStatus `json:"migrationStatus,omitempty"`
}

// GatewayState tracks the status of Gateway Resources
Expand Down Expand Up @@ -688,6 +705,27 @@ type Database struct {
Password string `json:"password,omitempty"`
// LiquibaseLogLevel
LiquibaseLogLevel LiquibaseLogLevel `json:"liquibaseLogLevel,omitempty"`
// MigrationJob for pre-upgrade schema updates
MigrationJob MigrationJob `json:"migrationJob,omitempty"`
}

// MigrationJob configures the pre-upgrade database migration job
type MigrationJob struct {
// Enabled or disabled
Enabled bool `json:"enabled,omitempty"`
// JDBCUrl overrides the main database.jdbcUrl for the migration job (e.g. to bypass a proxy).
// Only applies in diskless mode (disklessConfig.disabled: false, the default).
// In non-diskless mode the entrypoint reads the JDBC URL from the mounted node.properties
// file and this field has no effect — node.properties always wins, consistent with Helm behavior.
JDBCUrl string `json:"jdbcUrl,omitempty"`
// ClearLocks forces release of any stuck Liquibase locks before applying
// schema updates. Use with caution: forcefully releasing a lock while
// another process is actively updating the schema can corrupt the database.
ClearLocks bool `json:"clearLocks,omitempty"`
// ActiveDeadlineSeconds is the max duration each pod attempt is allowed to run.
// The job may retry once (backoffLimit=1), so total elapsed time can be up to
// 2× this value. Defaults to 300 seconds (5 minutes) per attempt.
ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`
}

// Restman is a Gateway Management interface that can be automatically provisioned.
Expand Down
6 changes: 6 additions & 0 deletions api/v1/gateway_webhook.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved.
/*
Copyright 2021.

Expand Down Expand Up @@ -193,6 +194,11 @@ func validateGateway(r *Gateway) (admission.Warnings, error) {
}
}

if r.Spec.App.Management.Database.MigrationJob.Enabled &&
!r.Spec.App.Management.Database.Enabled {
return warnings, fmt.Errorf("migrationJob.enabled requires database.enabled: true")
}

if r.Spec.App.Management.Service.Enabled {
if r.Spec.App.Management.Service.Type != v1.ServiceTypeClusterIP && r.Spec.App.Management.Service.Type != v1.ServiceTypeLoadBalancer && r.Spec.App.Management.Service.Type != v1.ServiceTypeNodePort {
return warnings, fmt.Errorf("please specify a valid management service type, valid types are LoadBalancer, ClusterIP and NodePort")
Expand Down
40 changes: 39 additions & 1 deletion api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions config/crd/bases/security.brcmlabs.com_gateways.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3534,6 +3534,26 @@ spec:
liquibaseLogLevel:
description: LiquibaseLogLevel
type: string
migrationJob:
description: MigrationJob for pre-upgrade schema updates
properties:
activeDeadlineSeconds:
description: ActiveDeadlineSeconds is the max duration
each pod attempt is allowed to...
format: int64
type: integer
clearLocks:
description: ClearLocks forces release of any stuck
Liquibase locks before applying...
type: boolean
enabled:
description: Enabled or disabled
type: boolean
jdbcUrl:
description: JDBCUrl overrides the main database.jdbcUrl
for the migration job (e.g.
type: string
type: object
password:
description: Password MySQL - can be set in management.secretName
type: string
Expand Down Expand Up @@ -6765,6 +6785,19 @@ spec:
description: Management Pod is a Gateway with a special annotation
is used as a...
type: string
migrationStatus:
description: MigrationStatus tracks the state of the pre-upgrade database
migration job.
properties:
complete:
description: Complete indicates that the migration job succeeded
for the current...
type: boolean
specHash:
description: SpecHash is a short hash of the migration-relevant
spec fields (image,...
type: string
type: object
phase:
description: PodPhase is a label for the condition of a pod at the
current time.
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/gateway/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"go.opentelemetry.io/otel/metric"
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
policyv1 "k8s.io/api/policy/v1"
Expand Down Expand Up @@ -100,6 +101,7 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
{reconcile.PodDisruptionBudget, "podDisruptionBudget"},
{reconcile.GatewayStatus, "gatewayStatus"},
{reconcile.ConfigMaps, "configMaps"},
{reconcile.GatewayMigrationJob, "migration job"},
{reconcile.Deployment, "deployment"},
{reconcile.ManagementPod, "management pod"},
{reconcile.HandleEphemeralRestarts, "ephemeral restart detection"},
Expand Down Expand Up @@ -138,7 +140,6 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
}

_ = captureMetrics(ctx, params, start, false, "")

return ctrl.Result{RequeueAfter: 12 * time.Hour}, nil
}

Expand All @@ -163,7 +164,8 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
Owns(&networkingv1.Ingress{}).
Owns(&appsv1.Deployment{}).
Owns(&policyv1.PodDisruptionBudget{}).
Owns(&autoscalingv2.HorizontalPodAutoscaler{})
Owns(&autoscalingv2.HorizontalPodAutoscaler{}).
Owns(&batchv1.Job{})

repo := &metav1.PartialObjectMetadata{}
repo.SetGroupVersionKind(schema.GroupVersionKind{
Expand Down
20 changes: 20 additions & 0 deletions pkg/gateway/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ func NewConfigMap(gw *securityv1.Gateway, name string, opts ...ConfigMapOpts) *c
opt = opts[0]
}
javaArgs := strings.Join(gw.Spec.App.Java.ExtraArgs, " ")

// When the database migration job is enabled, automatically inject skip mode for
// Gateway pods so they bypass Liquibase entirely — the job handles schema updates.
// Only injected if the user has not explicitly set gateway.db.schema-update.mode.
if gw.Spec.App.Management.Database.MigrationJob.Enabled {
hasSchemaMode := false
for _, arg := range gw.Spec.App.Java.ExtraArgs {
if strings.Contains(arg, "gateway.db.schema-update.mode") {
hasSchemaMode = true
break
}
}
if !hasSchemaMode {
if javaArgs != "" {
javaArgs += " "
}
javaArgs += "-Dgateway.db.schema-update.mode=skip"
}
}

data := make(map[string]string)
jvmHeap := setJVMHeapSize(gw, "", gw.Spec.App.Java.JVMHeap.Percentage)
dataCheckSum := ""
Expand Down
11 changes: 1 addition & 10 deletions pkg/gateway/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -938,16 +938,7 @@ func NewDeployment(gw *securityv1.Gateway, platform string) *appsv1.Deployment {
imagePullPolicy = gw.Spec.App.ImagePullPolicy
}

gatewayContainerSecurityContext := corev1.SecurityContext{}
podSecurityContext := corev1.PodSecurityContext{}

if gw.Spec.App.ContainerSecurityContext != (corev1.SecurityContext{}) {
gatewayContainerSecurityContext = gw.Spec.App.ContainerSecurityContext
}

if !reflect.DeepEqual(gw.Spec.App.PodSecurityContext, corev1.PodSecurityContext{}) {
podSecurityContext = gw.Spec.App.PodSecurityContext
}
gatewayContainerSecurityContext, podSecurityContext := gatewaySecurityContexts(gw)
gatewaySecretName := gw.Name
if gw.Spec.App.Management.DisklessConfig.Disabled {
gatewaySecretName = gw.Name + "-node-properties"
Expand Down
Loading
Loading