Skip to content
Open
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
49 changes: 49 additions & 0 deletions resource_customizations/microgateway.airlock.com/_/health.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
local hs = { status = "Progressing", message = "Waiting for " .. (obj.kind or "Policy") .. " status"}

local function is_policy_kind(kind)
return kind ~= nil and string.match(kind, "Policy$") ~= nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't like the idea of matching a kind suffix. If a new kind is added that matches this suffix, there's no reason to believe it'll necessarily follow the same status field conventions. I'd recommend instead hard-coding a static list of kinds that we know this check supports.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi @crenshaw-dev

Thanks for raising these concerns.

For the first point, the decision to avoid using a fixed list of concrete resource kinds was intentional. We want to avoid having to update the HealthCheck implementation every time a new policy kind is introduced, as that would require a separate PR just to register the new kind. The wildcard-based approach allows the HealthCheck to automatically support additional policy resources as they are added to our implementation.

Regarding the tests, the goal was not to avoid thorough test coverage. The additional policies we plan to support are Gateway API direct attached policies, which follow the same status conventions as existing resources such as BackendTLSPolicy. Because the HealthCheck logic is based on those shared conventions rather than kind-specific behavior, we considered it more valuable to test the common behavior than to duplicate equivalent tests for every individual policy kind.

For the second point, we agree that blindly matching arbitrary suffixes would be risky if unrelated resources could satisfy the pattern. In this case, however, the expectation is that future matching resources will be Gateway API policy kinds that share the same status structure and semantics. The intent behind the suffix matching is to make the HealthCheck extensible without requiring code changes whenever a new policy kind is introduced. If we hard-code the supported kinds, we lose that extensibility and would need to keep the list synchronized as our implementation evolves.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are you a maintainer of microgateway? I want to make sure we have solid reason to believe the future CRDs will follow existing conventions. A link to a docs page stating this would be extra valuable. :-)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, I am one of the maintainers of Microgateway.

The standardized Policy resource of GatewayAPI implemented in Microgateway is specified by GEP-713. For the detailed definition of the status field conventions please take a look at the Status reporting section.

We really appreciate your due diligence and are also interested in a robust and low-maintenance solution that benefits everyone.
If you have any further questions or concerns, we are open to discuss them.

end

if not is_policy_kind(obj.kind) then
return { status = "Healthy", message = obj.kind .. " is healthy" }
end

if obj.status ~= nil and obj.status.ancestors ~= nil then
if obj.metadata.generation ~= nil then
for i, ancestor in ipairs(obj.status.ancestors) do
for _, condition in ipairs(ancestor.conditions) do
if condition.observedGeneration ~= nil then
if condition.observedGeneration ~= obj.metadata.generation then
hs.message = "Waiting for Ancestor " .. (ancestor.ancestorRef.name or "") .. " to update " .. (obj.kind or "Policy") .. " status"
return hs
end
end
end
end
end

for i, ancestor in ipairs(obj.status.ancestors) do
for j, condition in ipairs(ancestor.conditions) do
if condition.type == "Accepted" then
if condition.status ~= "True" then
hs.status = "Degraded"
hs.message = "Ancestor " .. (ancestor.ancestorRef.name or "") .. ": " .. condition.message
return hs
else
hs.status = "Healthy"
hs.message = (obj.kind or "Policy") .. " is healthy"
end
end

if condition.type == "ResolvedRefs" then
if condition.status ~= "True" then
hs.status = "Degraded"
hs.message = "Ancestor " .. (ancestor.ancestorRef.name or "") .. ": " .. condition.message
return hs
end
end
end
end
end

return hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
tests:
- healthStatus:
status: Healthy
message: "AccessControlPolicy is healthy"
inputPath: testdata/healthy.yaml
- healthStatus:
status: Degraded
message: "Ancestor example-gateway: ContentSecurityPolicy is conflicting with other policies for this ancestor: [example-contentsecuritypolicy]"
inputPath: testdata/degraded_conflicting.yaml
- healthStatus:
status: Degraded
message: >-
Ancestor example-gateway: Resolving CustomResponsePolicy failed:
Missing referenced CustomResponse 'example'
WARNING: traffic to referenced target(s) will be rejected.
inputPath: testdata/degraded_resolved_refs.yaml
- healthStatus:
status: Progressing
message: "Waiting for EnvoyExtensionPolicy status"
inputPath: testdata/progressing.yaml
- healthStatus:
status: Progressing
message: "Waiting for Ancestor example-gateway to update ICAPPolicy status"
inputPath: testdata/progressing_observed_generation.yaml
- healthStatus:
status: Healthy
message: "DenyRules is healthy"
inputPath: testdata/unknown.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: microgateway.airlock.com/v1alpha1
kind: ContentSecurityPolicy
metadata:
name: example-contentsecuritypolicy-2
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: example-httproute
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: example-gateway
conditions:
- lastTransitionTime: '2026-04-16T14:21:38Z'
message: >-
ContentSecurityPolicy is conflicting with other policies for this ancestor: [example-contentsecuritypolicy]
observedGeneration: 3
reason: Conflicted
status: 'False'
type: Accepted
controllerName: example.com/gatewayclass-controller
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: microgateway.airlock.com/v1alpha1
kind: CustomResponsePolicy
metadata:
name: example-customresponsepolicy
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: example-httproute
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: example-gateway
conditions:
- lastTransitionTime: '2026-04-16T12:20:11Z'
message: >-
Resolving CustomResponsePolicy failed:
Missing referenced CustomResponse 'example'
WARNING: traffic to referenced target(s) will be rejected.
observedGeneration: 3
reason: Invalid
status: 'False'
type: Accepted
controllerName: example.com/gatewayclass-controller
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: microgateway.airlock.com/v1alpha1
kind: AccessControlPolicy
metadata:
name: example-accesscontrolpolicy
namespace: default
generation: 3
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: example-httproute
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: example-gateway
conditions:
- lastTransitionTime: '2026-04-16T12:20:11Z'
message: AccessControlPolicy is accepted
observedGeneration: 3
reason: Accepted
status: 'True'
type: Accepted
controllerName: example.com/gatewayclass-controller
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: microgateway.airlock.com/v1alpha1
kind: EnvoyExtensionPolicy
metadata:
name: example-envoyextensionpolicy
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: example-httproute
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: example-gateway
conditions: []
controllerName: example.com/gatewayclass-controller
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: microgateway.airlock.com/v1alpha1
kind: ICAPPolicy
metadata:
name: example-icappolicy
namespace: default
generation: 3
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: example-httproute
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: example-gateway
conditions:
- lastTransitionTime: '2026-04-16T12:20:11Z'
message: ICAPPolicy is accepted
observedGeneration: 1
reason: Accepted
status: 'True'
type: Accepted
controllerName: example.com/gatewayclass-controller
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: microgateway.airlock.com/v1alpha1
kind: DenyRules
metadata:
name: example-denyrules
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: example-httproute
status:
ancestors:
- ancestorRef:
group: gateway.networking.k8s.io
kind: Gateway
name: example-gateway
conditions: []
controllerName: example.com/gatewayclass-controller
1 change: 1 addition & 0 deletions util/lua/lua_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,7 @@ func Test_getHealthScriptPaths(t *testing.T) {
"_.crossplane.io/_",
"_.upbound.io/_",
"grafana-org-operator.kubitus-project.gitlab.io/_",
"microgateway.airlock.com/_",
"operator.victoriametrics.com/_",
}, paths)
}
Loading