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
16 changes: 14 additions & 2 deletions api/v1/prefectserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ type PrefectServerSpec struct {
// Image defines the exact image to deploy for the Prefect Server, overriding Version
Image *string `json:"image,omitempty"`

// Host defines the host address to bind the Prefect Server to.
// Defaults to "0.0.0.0" for IPv4 compatibility.
// Use "" (empty string) to bind to all interfaces for IPv6-only or dual-stack environments.
// Note: Prefect does not accept "::" as a valid host value.
// +kubebuilder:validation:Optional
Host *string `json:"host,omitempty"`

// Resources defines the CPU and memory resources for each replica of the Prefect Server
Resources corev1.ResourceRequirements `json:"resources,omitempty"`

Expand Down Expand Up @@ -394,8 +401,13 @@ func (s *PrefectServer) Image() string {
return DEFAULT_PREFECT_IMAGE
}

func (s *PrefectServer) EntrypointArugments() []string {
command := []string{"prefect", "server", "start", "--host", "0.0.0.0"}
func (s *PrefectServer) EntrypointArguments() []string {
host := "0.0.0.0" // Default to IPv4 for backward compatibility
if s.Spec.Host != nil {
host = *s.Spec.Host
}

command := []string{"prefect", "server", "start", "--host", host}
command = append(command, s.Spec.ExtraArgs...)

return command
Expand Down
45 changes: 45 additions & 0 deletions api/v1/prefectserver_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,5 +620,50 @@ var _ = Describe("PrefectServer type", func() {

Expect(envVars).To(ConsistOf(expectedEnvVars))
})

Context("Host binding configuration", func() {
It("should use default host 0.0.0.0 when Host is nil", func() {
server := &PrefectServer{
Spec: PrefectServerSpec{},
}

args := server.EntrypointArguments()
Expect(args).To(Equal([]string{"prefect", "server", "start", "--host", "0.0.0.0"}))
})

It("should use empty string for IPv6/dual-stack when specified", func() {
server := &PrefectServer{
Spec: PrefectServerSpec{
Host: ptr.To(""),
},
}

args := server.EntrypointArguments()
Expect(args).To(Equal([]string{"prefect", "server", "start", "--host", ""}))
})

It("should use custom host with ExtraArgs", func() {
server := &PrefectServer{
Spec: PrefectServerSpec{
Host: ptr.To(""),
ExtraArgs: []string{"--some-arg", "some-value"},
},
}

args := server.EntrypointArguments()
Expect(args).To(Equal([]string{"prefect", "server", "start", "--host", "", "--some-arg", "some-value"}))
})

It("should use specific IPv4 address when specified", func() {
server := &PrefectServer{
Spec: PrefectServerSpec{
Host: ptr.To("127.0.0.1"),
},
}

args := server.EntrypointArguments()
Expect(args).To(Equal([]string{"prefect", "server", "start", "--host", "127.0.0.1"}))
})
})
})
})
7 changes: 5 additions & 2 deletions api/v1/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,13 @@ spec:
- port
type: object
type: array
host:
description: |-
Host defines the host address to bind the Prefect Server to.
Defaults to "0.0.0.0" for IPv4 compatibility.
Use "" (empty string) to bind to all interfaces for IPv6-only or dual-stack environments.
Note: Prefect does not accept "::" as a valid host value.
type: string
image:
description: Image defines the exact image to deploy for the Prefect
Server, overriding Version
Expand Down
13 changes: 13 additions & 0 deletions deploy/samples/v1_prefectserver_dualstack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
apiVersion: prefect.io/v1
kind: PrefectServer
metadata:
name: prefect-dualstack
labels:
app.kubernetes.io/name: prefect-operator
app.kubernetes.io/managed-by: kustomize
spec:
host: "" # Empty string for dual-stack (uvicorn binds to all interfaces)
sqlite:
storageClassName: local-path
size: 1Gi
10 changes: 10 additions & 0 deletions deploy/samples/v1_prefectserver_ipv6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: prefect.io/v1
kind: PrefectServer
metadata:
name: prefect-ipv6
labels:
app.kubernetes.io/name: prefect-operator
app.kubernetes.io/managed-by: kustomize
spec:
host: "" # Empty string binds to all interfaces (works for IPv6-only and dual-stack)
ephemeral: {}
6 changes: 3 additions & 3 deletions internal/controller/prefectserver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ func (r *PrefectServerReconciler) ephemeralDeploymentSpec(server *prefectiov1.Pr
Image: server.Image(),
ImagePullPolicy: corev1.PullIfNotPresent,

Args: server.EntrypointArugments(),
Args: server.EntrypointArguments(),
VolumeMounts: []corev1.VolumeMount{
{
Name: "prefect-data",
Expand Down Expand Up @@ -458,7 +458,7 @@ func (r *PrefectServerReconciler) sqliteDeploymentSpec(server *prefectiov1.Prefe
Image: server.Image(),
ImagePullPolicy: corev1.PullIfNotPresent,

Args: server.EntrypointArugments(),
Args: server.EntrypointArguments(),
VolumeMounts: []corev1.VolumeMount{
{
Name: "prefect-data",
Expand Down Expand Up @@ -514,7 +514,7 @@ func (r *PrefectServerReconciler) postgresDeploymentSpec(server *prefectiov1.Pre
Image: server.Image(),
ImagePullPolicy: corev1.PullIfNotPresent,

Args: server.EntrypointArugments(),
Args: server.EntrypointArguments(),
Env: server.ToEnvVars(),
Ports: []corev1.ContainerPort{
{
Expand Down
35 changes: 35 additions & 0 deletions internal/controller/prefectserver_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,41 @@ var _ = Describe("PrefectServer controller", func() {
Expect(container.Command).To(BeNil())
Expect(container.Args).To(Equal([]string{"prefect", "server", "start", "--host", "0.0.0.0", "--some-arg", "some-value"}))
})

It("should create a Deployment with IPv6/dual-stack host (empty string)", func() {
name := types.NamespacedName{
Namespace: namespaceName,
Name: "prefect-ipv6-server",
}

prefectserver := &prefectiov1.PrefectServer{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespaceName,
Name: "prefect-ipv6-server",
},
Spec: prefectiov1.PrefectServerSpec{
Host: ptr.To(""),
},
}
Expect(k8sClient.Create(ctx, prefectserver)).To(Succeed())

controllerReconciler := &PrefectServerReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: name,
})
Expect(err).NotTo(HaveOccurred())

deployment := &appsv1.Deployment{}
Eventually(func() error {
return k8sClient.Get(ctx, name, deployment)
}).Should(Succeed())

container := deployment.Spec.Template.Spec.Containers[0]
Expect(container.Args).To(Equal([]string{"prefect", "server", "start", "--host", ""}))
})
})

Context("When evaluating changes with any server", func() {
Expand Down
Loading