From 85382c59894e67466b274588e17e77f1cf8b46d2 Mon Sep 17 00:00:00 2001 From: igoramf Date: Thu, 21 May 2026 18:17:28 -0300 Subject: [PATCH] feat: rename CRD from RedirectDomain to DecoRedirect Renames the CRD kind from RedirectDomain to DecoRedirect (group deco.sites/v1alpha1, plural decoredict). Updates all Go types, controller, API handlers, tests, RBAC markers, Helm templates, and sample manifests. Regenerated with make generate. Co-Authored-By: Claude Sonnet 4.6 --- ...tdomain_types.go => decoredirect_types.go} | 27 +-- api/v1alpha1/zz_generated.deepcopy.go | 192 +++++++++--------- .../clusterrole-operator-manager-role.yaml | 6 +- ...urcedefinition-decoredict.deco.sites.yaml} | 18 +- chart/values.yaml | 6 +- cmd/main.go | 8 +- ...omains.yaml => deco.sites_decoredict.yaml} | 18 +- config/crd/kustomization.yaml | 2 +- config/rbac/role.yaml | 6 +- ...n_sample.yaml => decoredirect_sample.yaml} | 2 +- hack/test-api.sh | 72 +++++++ internal/api/handlers.go | 12 +- internal/api/server_test.go | 16 +- ...ntroller.go => decoredirect_controller.go} | 24 +-- ...est.go => decoredirect_controller_test.go} | 24 +-- 15 files changed, 253 insertions(+), 180 deletions(-) rename api/v1alpha1/{redirectdomain_types.go => decoredirect_types.go} (68%) rename chart/templates/{customresourcedefinition-redirectdomains.deco.sites.yaml => customresourcedefinition-decoredict.deco.sites.yaml} (92%) rename config/crd/bases/{deco.sites_redirectdomains.yaml => deco.sites_decoredict.yaml} (92%) rename config/samples/{redirectdomain_sample.yaml => decoredirect_sample.yaml} (88%) create mode 100755 hack/test-api.sh rename internal/controller/{redirectdomain_controller.go => decoredirect_controller.go} (85%) rename internal/controller/{redirectdomain_controller_test.go => decoredirect_controller_test.go} (86%) diff --git a/api/v1alpha1/redirectdomain_types.go b/api/v1alpha1/decoredirect_types.go similarity index 68% rename from api/v1alpha1/redirectdomain_types.go rename to api/v1alpha1/decoredirect_types.go index 287c0e5..2aff149 100644 --- a/api/v1alpha1/redirectdomain_types.go +++ b/api/v1alpha1/decoredirect_types.go @@ -6,9 +6,9 @@ const ( ConditionCertificateReady = "CertificateReady" ) -// RedirectDomainSpec defines the desired state of RedirectDomain. +// DecoRedirectSpec defines the desired state of DecoRedirect. // +kubebuilder:validation:XValidation:rule="(self.to+'/').contains('.'+self.from+'/') || (self.to+'/').contains('//'+self.from+'/')",message="redirect target must be within the same domain as 'from' (e.g. from: client.com → to: https://www.client.com)" -type RedirectDomainSpec struct { +type DecoRedirectSpec struct { // From is the apex domain to redirect (e.g. "client.com"). // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength=1 @@ -24,37 +24,38 @@ type RedirectDomainSpec struct { To string `json:"to"` } -// RedirectDomainStatus defines the observed state of RedirectDomain. -type RedirectDomainStatus struct { - // Conditions represent the latest observations of the RedirectDomain's state. +// DecoRedirectStatus defines the observed state of DecoRedirect. +type DecoRedirectStatus struct { + // Conditions represent the latest observations of the DecoRedirect's state. // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status +// +kubebuilder:resource:path=decoredict,singular=decoredirect // +kubebuilder:printcolumn:name="From",type="string",JSONPath=".spec.from" // +kubebuilder:printcolumn:name="To",type="string",JSONPath=".spec.to" // +kubebuilder:printcolumn:name="CertReady",type="string",JSONPath=".status.conditions[?(@.type=='CertificateReady')].status" -// RedirectDomain manages a TLS-terminated apex redirect via cert-manager and nginx Ingress. -type RedirectDomain struct { +// DecoRedirect manages a TLS-terminated apex redirect via cert-manager and nginx Ingress. +type DecoRedirect struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec RedirectDomainSpec `json:"spec,omitempty"` - Status RedirectDomainStatus `json:"status,omitempty"` + Spec DecoRedirectSpec `json:"spec,omitempty"` + Status DecoRedirectStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true -// RedirectDomainList contains a list of RedirectDomain. -type RedirectDomainList struct { +// DecoRedirectList contains a list of DecoRedirect. +type DecoRedirectList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` - Items []RedirectDomain `json:"items"` + Items []DecoRedirect `json:"items"` } func init() { - SchemeBuilder.Register(&RedirectDomain{}, &RedirectDomainList{}) + SchemeBuilder.Register(&DecoRedirect{}, &DecoRedirectList{}) } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 95be6b7..03a1280 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -158,6 +158,102 @@ func (in *DecoPreviewStatus) DeepCopy() *DecoPreviewStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DecoRedirect) DeepCopyInto(out *DecoRedirect) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecoRedirect. +func (in *DecoRedirect) DeepCopy() *DecoRedirect { + if in == nil { + return nil + } + out := new(DecoRedirect) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DecoRedirect) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DecoRedirectList) DeepCopyInto(out *DecoRedirectList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DecoRedirect, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecoRedirectList. +func (in *DecoRedirectList) DeepCopy() *DecoRedirectList { + if in == nil { + return nil + } + out := new(DecoRedirectList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DecoRedirectList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DecoRedirectSpec) DeepCopyInto(out *DecoRedirectSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecoRedirectSpec. +func (in *DecoRedirectSpec) DeepCopy() *DecoRedirectSpec { + if in == nil { + return nil + } + out := new(DecoRedirectSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DecoRedirectStatus) DeepCopyInto(out *DecoRedirectStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecoRedirectStatus. +func (in *DecoRedirectStatus) DeepCopy() *DecoRedirectStatus { + if in == nil { + return nil + } + out := new(DecoRedirectStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DecoSecretRef) DeepCopyInto(out *DecoSecretRef) { *out = *in @@ -478,99 +574,3 @@ func (in *InlineSource) DeepCopy() *InlineSource { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RedirectDomain) DeepCopyInto(out *RedirectDomain) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedirectDomain. -func (in *RedirectDomain) DeepCopy() *RedirectDomain { - if in == nil { - return nil - } - out := new(RedirectDomain) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RedirectDomain) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RedirectDomainList) DeepCopyInto(out *RedirectDomainList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RedirectDomain, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedirectDomainList. -func (in *RedirectDomainList) DeepCopy() *RedirectDomainList { - if in == nil { - return nil - } - out := new(RedirectDomainList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RedirectDomainList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RedirectDomainSpec) DeepCopyInto(out *RedirectDomainSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedirectDomainSpec. -func (in *RedirectDomainSpec) DeepCopy() *RedirectDomainSpec { - if in == nil { - return nil - } - out := new(RedirectDomainSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RedirectDomainStatus) DeepCopyInto(out *RedirectDomainStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedirectDomainStatus. -func (in *RedirectDomainStatus) DeepCopy() *RedirectDomainStatus { - if in == nil { - return nil - } - out := new(RedirectDomainStatus) - in.DeepCopyInto(out) - return out -} diff --git a/chart/templates/clusterrole-operator-manager-role.yaml b/chart/templates/clusterrole-operator-manager-role.yaml index 8eede3b..f0cd39e 100644 --- a/chart/templates/clusterrole-operator-manager-role.yaml +++ b/chart/templates/clusterrole-operator-manager-role.yaml @@ -78,8 +78,8 @@ rules: - deco.sites resources: - decofiles + - decoredict - decos - - redirectdomains verbs: - create - delete @@ -92,16 +92,16 @@ rules: - deco.sites resources: - decofiles/finalizers + - decoredict/finalizers - decos/finalizers - - redirectdomains/finalizers verbs: - update - apiGroups: - deco.sites resources: - decofiles/status + - decoredict/status - decos/status - - redirectdomains/status verbs: - get - patch diff --git a/chart/templates/customresourcedefinition-redirectdomains.deco.sites.yaml b/chart/templates/customresourcedefinition-decoredict.deco.sites.yaml similarity index 92% rename from chart/templates/customresourcedefinition-redirectdomains.deco.sites.yaml rename to chart/templates/customresourcedefinition-decoredict.deco.sites.yaml index ea742f4..cf47501 100644 --- a/chart/templates/customresourcedefinition-redirectdomains.deco.sites.yaml +++ b/chart/templates/customresourcedefinition-decoredict.deco.sites.yaml @@ -3,14 +3,14 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.18.0 - name: redirectdomains.deco.sites + name: decoredict.deco.sites spec: group: deco.sites names: - kind: RedirectDomain - listKind: RedirectDomainList - plural: redirectdomains - singular: redirectdomain + kind: DecoRedirect + listKind: DecoRedirectList + plural: decoredict + singular: decoredirect scope: Namespaced versions: - additionalPrinterColumns: @@ -26,7 +26,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: RedirectDomain manages a TLS-terminated apex redirect via cert-manager + description: DecoRedirect manages a TLS-terminated apex redirect via cert-manager and nginx Ingress. properties: apiVersion: @@ -47,7 +47,7 @@ spec: metadata: type: object spec: - description: RedirectDomainSpec defines the desired state of RedirectDomain. + description: DecoRedirectSpec defines the desired state of DecoRedirect. properties: from: description: From is the apex domain to redirect (e.g. "client.com"). @@ -71,10 +71,10 @@ spec: (e.g. from: client.com → to: https://www.client.com)' rule: (self.to+'/').contains('.'+self.from+'/') || (self.to+'/').contains('//'+self.from+'/') status: - description: RedirectDomainStatus defines the observed state of RedirectDomain. + description: DecoRedirectStatus defines the observed state of DecoRedirect. properties: conditions: - description: Conditions represent the latest observations of the RedirectDomain's + description: Conditions represent the latest observations of the DecoRedirect's state. items: description: Condition contains details for one aspect of the current diff --git a/chart/values.yaml b/chart/values.yaml index 83ae798..430556f 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -117,7 +117,7 @@ build: tolerations: [] # Default tolerations for build pods (overridable per-site via spec.build.tolerations) # ── Operator Management API ────────────────────────────────────────────────── -# General HTTP API for managing operator resources (RedirectDomains, etc.). +# General HTTP API for managing operator resources (DecoRedirects, etc.). # Enabled when username+password (or existingSecret) are set. # Set hostname to auto-create an Ingress+Certificate via the cluster default ingress. operatorApi: @@ -129,10 +129,10 @@ operatorApi: # ── Domain Redirect ────────────────────────────────────────────────────────── # Application-level redirect config — all features are opt-in. -# Set ingressClass to enable RedirectDomain reconciliation. +# Set ingressClass to enable DecoRedirect reconciliation. redirect: namespace: "deco-redirect-system" - ingressClass: "" # set to enable RedirectDomain controller (e.g. "redirect-nginx") + ingressClass: "" # set to enable DecoRedirect controller (e.g. "redirect-nginx") clusterIssuer: enabled: false # set true to create the Let's Encrypt ClusterIssuer name: "" # ClusterIssuer name (e.g. "letsencrypt") diff --git a/cmd/main.go b/cmd/main.go index c2d53b2..06e122b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -125,13 +125,13 @@ func main() { var redirectNamespace string flag.StringVar(&redirectNamespace, "redirect-namespace", getEnvOrDefault("REDIRECT_NAMESPACE", "deco-redirect-system"), - "Default namespace for RedirectDomain resources.") + "Default namespace for DecoRedirect resources.") var redirectIngressClass string var redirectClusterIssuer string flag.StringVar(&redirectIngressClass, "redirect-ingress-class", getEnvOrDefault("REDIRECT_INGRESS_CLASS", "nginx"), - "IngressClass name for RedirectDomain Ingress resources.") + "IngressClass name for DecoRedirect Ingress resources.") flag.StringVar(&redirectClusterIssuer, "redirect-cluster-issuer", getEnvOrDefault("REDIRECT_CLUSTER_ISSUER", "letsencrypt"), "cert-manager ClusterIssuer name (matches redirect.clusterIssuer.name in values).") @@ -366,13 +366,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Deco") os.Exit(1) } - if err := (&controller.RedirectDomainReconciler{ + if err := (&controller.DecoRedirectReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), IngressClass: redirectIngressClass, ClusterIssuer: redirectClusterIssuer, }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "RedirectDomain") + setupLog.Error(err, "unable to create controller", "controller", "DecoRedirect") os.Exit(1) } apiUser := os.Getenv("OPERATOR_API_USER") diff --git a/config/crd/bases/deco.sites_redirectdomains.yaml b/config/crd/bases/deco.sites_decoredict.yaml similarity index 92% rename from config/crd/bases/deco.sites_redirectdomains.yaml rename to config/crd/bases/deco.sites_decoredict.yaml index c435e39..af25b0e 100644 --- a/config/crd/bases/deco.sites_redirectdomains.yaml +++ b/config/crd/bases/deco.sites_decoredict.yaml @@ -4,14 +4,14 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.18.0 - name: redirectdomains.deco.sites + name: decoredict.deco.sites spec: group: deco.sites names: - kind: RedirectDomain - listKind: RedirectDomainList - plural: redirectdomains - singular: redirectdomain + kind: DecoRedirect + listKind: DecoRedirectList + plural: decoredict + singular: decoredirect scope: Namespaced versions: - additionalPrinterColumns: @@ -27,7 +27,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: RedirectDomain manages a TLS-terminated apex redirect via cert-manager + description: DecoRedirect manages a TLS-terminated apex redirect via cert-manager and nginx Ingress. properties: apiVersion: @@ -48,7 +48,7 @@ spec: metadata: type: object spec: - description: RedirectDomainSpec defines the desired state of RedirectDomain. + description: DecoRedirectSpec defines the desired state of DecoRedirect. properties: from: description: From is the apex domain to redirect (e.g. "client.com"). @@ -72,10 +72,10 @@ spec: (e.g. from: client.com → to: https://www.client.com)' rule: (self.to+'/').contains('.'+self.from+'/') || (self.to+'/').contains('//'+self.from+'/') status: - description: RedirectDomainStatus defines the observed state of RedirectDomain. + description: DecoRedirectStatus defines the observed state of DecoRedirect. properties: conditions: - description: Conditions represent the latest observations of the RedirectDomain's + description: Conditions represent the latest observations of the DecoRedirect's state. items: description: Condition contains details for one aspect of the current diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 0848924..6fd7674 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,7 +4,7 @@ resources: - bases/deco.sites_decos.yaml - bases/deco.sites_decofiles.yaml -- bases/deco.sites_redirectdomains.yaml +- bases/deco.sites_decoredict.yaml # +kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index d87068b..84ecaaf 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -79,8 +79,8 @@ rules: - deco.sites resources: - decofiles + - decoredict - decos - - redirectdomains verbs: - create - delete @@ -93,16 +93,16 @@ rules: - deco.sites resources: - decofiles/finalizers + - decoredict/finalizers - decos/finalizers - - redirectdomains/finalizers verbs: - update - apiGroups: - deco.sites resources: - decofiles/status + - decoredict/status - decos/status - - redirectdomains/status verbs: - get - patch diff --git a/config/samples/redirectdomain_sample.yaml b/config/samples/decoredirect_sample.yaml similarity index 88% rename from config/samples/redirectdomain_sample.yaml rename to config/samples/decoredirect_sample.yaml index 65a26f6..10d8150 100644 --- a/config/samples/redirectdomain_sample.yaml +++ b/config/samples/decoredirect_sample.yaml @@ -1,5 +1,5 @@ apiVersion: deco.sites/v1alpha1 -kind: RedirectDomain +kind: DecoRedirect metadata: name: example-redirect namespace: deco-redirect-system diff --git a/hack/test-api.sh b/hack/test-api.sh new file mode 100755 index 0000000..015757e --- /dev/null +++ b/hack/test-api.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE_URL="${OPERATOR_API_URL:-https://operator.infra.deco.cx}" +USER="${OPERATOR_API_USER:-sre@deco.cx}" +PASS="${OPERATOR_API_PASSWORD:-}" +AUTH="-u ${USER}:${PASS}" + +DOMAIN="${1:-test-script.com}" +TO="${2:-https://www.${DOMAIN}}" + +pass() { echo "✓ $1"; } +fail() { echo "✗ $1"; exit 1; } + +echo "=== Operator API Test ===" +echo "URL: $BASE_URL" +echo "Domain: $DOMAIN → $TO" +echo "" + +# 1. Auth check +echo "--- 1. Unauthorized request ---" +code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/redirects") +[ "$code" = "401" ] && pass "401 on missing auth" || fail "expected 401, got $code" + +# 2. List +echo "--- 2. List ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH "$BASE_URL/redirects") +[ "$code" = "200" ] && pass "GET /redirects → 200" || fail "expected 200, got $code" +curl -s $AUTH "$BASE_URL/redirects" | python3 -m json.tool + +# 3. Create +echo "--- 3. Create ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH -X POST "$BASE_URL/redirects" \ + -H "Content-Type: application/json" \ + -d "{\"from\":\"$DOMAIN\",\"to\":\"$TO\"}") +[ "$code" = "201" ] && pass "POST /redirects → 201" || fail "expected 201, got $code" + +# 4. Create duplicate → 409 +echo "--- 4. Duplicate create ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH -X POST "$BASE_URL/redirects" \ + -H "Content-Type: application/json" \ + -d "{\"from\":\"$DOMAIN\",\"to\":\"$TO\"}") +[ "$code" = "409" ] && pass "POST duplicate → 409" || fail "expected 409, got $code" + +# 5. Get +echo "--- 5. Get ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH "$BASE_URL/redirects/$DOMAIN") +[ "$code" = "200" ] && pass "GET /redirects/$DOMAIN → 200" || fail "expected 200, got $code" +curl -s $AUTH "$BASE_URL/redirects/$DOMAIN" | python3 -m json.tool + +# 6. Get not found +echo "--- 6. Get not found ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH "$BASE_URL/redirects/notfound-xyz.com") +[ "$code" = "404" ] && pass "GET /redirects/notfound-xyz.com → 404" || fail "expected 404, got $code" + +# 7. Invalid domain +echo "--- 7. Invalid domain ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH "$BASE_URL/redirects/not_a_domain!") +[ "$code" = "400" ] && pass "GET invalid domain → 400" || fail "expected 400, got $code" + +# 8. Delete +echo "--- 8. Delete ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH -X DELETE "$BASE_URL/redirects/$DOMAIN") +[ "$code" = "204" ] && pass "DELETE /redirects/$DOMAIN → 204" || fail "expected 204, got $code" + +# 9. Get after delete → 404 +echo "--- 9. Get after delete ---" +code=$(curl -s -o /dev/null -w "%{http_code}" $AUTH "$BASE_URL/redirects/$DOMAIN") +[ "$code" = "404" ] && pass "GET after delete → 404" || fail "expected 404, got $code" + +echo "" +echo "=== All tests passed ===" diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 7e96cfe..a0ba8c3 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -40,7 +40,7 @@ type redirectResponse struct { CreatedAt string `json:"createdAt"` } -func toResponse(rd *decositesv1alpha1.RedirectDomain) redirectResponse { +func toResponse(rd *decositesv1alpha1.DecoRedirect) redirectResponse { resp := redirectResponse{ From: rd.Spec.From, To: rd.Spec.To, @@ -75,12 +75,12 @@ func (h *Handlers) create(w http.ResponseWriter, r *http.Request) { } ns := h.nsOrDefault(req.Namespace) - rd := &decositesv1alpha1.RedirectDomain{ + rd := &decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{ Name: domainToName(from), // dots → dashes for k8s name Namespace: ns, }, - Spec: decositesv1alpha1.RedirectDomainSpec{ + Spec: decositesv1alpha1.DecoRedirectSpec{ From: from, // original domain preserved for CEL validation To: req.To, }, @@ -107,7 +107,7 @@ func (h *Handlers) get(w http.ResponseWriter, r *http.Request) { domain := domainToName(rawDomain) ns := h.nsOrDefault(r.URL.Query().Get("namespace")) - rd := &decositesv1alpha1.RedirectDomain{} + rd := &decositesv1alpha1.DecoRedirect{} if err := h.client.Get(r.Context(), client.ObjectKey{Name: domain, Namespace: ns}, rd); err != nil { status := http.StatusInternalServerError if apierrors.IsNotFound(err) { @@ -129,7 +129,7 @@ func (h *Handlers) delete(w http.ResponseWriter, r *http.Request) { domain := domainToName(rawDomain) ns := h.nsOrDefault(r.URL.Query().Get("namespace")) - rd := &decositesv1alpha1.RedirectDomain{ + rd := &decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{Name: domain, Namespace: ns}, } if err := h.client.Delete(r.Context(), rd); err != nil { @@ -146,7 +146,7 @@ func (h *Handlers) delete(w http.ResponseWriter, r *http.Request) { func (h *Handlers) list(w http.ResponseWriter, r *http.Request) { ns := h.nsOrDefault(r.URL.Query().Get("namespace")) - list := &decositesv1alpha1.RedirectDomainList{} + list := &decositesv1alpha1.DecoRedirectList{} if err := h.client.List(r.Context(), list, client.InNamespace(ns)); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/api/server_test.go b/internal/api/server_test.go index 12fe781..809e447 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -51,10 +51,10 @@ func TestCreate_HappyPath(t *testing.T) { t.Fatalf("expected 201, got %d: %s", rec.Code, rec.Body.String()) } - list := &decositesv1alpha1.RedirectDomainList{} + list := &decositesv1alpha1.DecoRedirectList{} _ = fc.List(context.Background(), list) if len(list.Items) != 1 { - t.Fatalf("expected 1 RedirectDomain, got %d", len(list.Items)) + t.Fatalf("expected 1 DecoRedirect, got %d", len(list.Items)) } } @@ -62,9 +62,9 @@ func TestDelete_HappyPath(t *testing.T) { scheme := runtime.NewScheme() _ = clientgoscheme.AddToScheme(scheme) _ = decositesv1alpha1.AddToScheme(scheme) - fc := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&decositesv1alpha1.RedirectDomain{ + fc := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{Name: "example-com", Namespace: "deco-redirect-system"}, - Spec: decositesv1alpha1.RedirectDomainSpec{From: "example.com", To: "https://www.example.com"}, + Spec: decositesv1alpha1.DecoRedirectSpec{From: "example.com", To: "https://www.example.com"}, }).Build() h := api.NewHandlers(fc, "deco-redirect-system") srv := api.NewServer(":0", "user", "pass", h) @@ -82,9 +82,9 @@ func TestGet_HappyPath(t *testing.T) { scheme := runtime.NewScheme() _ = clientgoscheme.AddToScheme(scheme) _ = decositesv1alpha1.AddToScheme(scheme) - fc := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&decositesv1alpha1.RedirectDomain{ + fc := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{Name: "example-com", Namespace: "deco-redirect-system"}, - Spec: decositesv1alpha1.RedirectDomainSpec{From: "example.com", To: "https://www.example.com"}, + Spec: decositesv1alpha1.DecoRedirectSpec{From: "example.com", To: "https://www.example.com"}, }).Build() h := api.NewHandlers(fc, "deco-redirect-system") srv := api.NewServer(":0", "user", "pass", h) @@ -127,9 +127,9 @@ func TestList_HappyPath(t *testing.T) { scheme := runtime.NewScheme() _ = clientgoscheme.AddToScheme(scheme) _ = decositesv1alpha1.AddToScheme(scheme) - fc := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&decositesv1alpha1.RedirectDomain{ + fc := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{Name: "example-com", Namespace: "deco-redirect-system"}, - Spec: decositesv1alpha1.RedirectDomainSpec{From: "example.com", To: "https://www.example.com"}, + Spec: decositesv1alpha1.DecoRedirectSpec{From: "example.com", To: "https://www.example.com"}, }).Build() h := api.NewHandlers(fc, "deco-redirect-system") srv := api.NewServer(":0", "user", "pass", h) diff --git a/internal/controller/redirectdomain_controller.go b/internal/controller/decoredirect_controller.go similarity index 85% rename from internal/controller/redirectdomain_controller.go rename to internal/controller/decoredirect_controller.go index ceb91f0..d6268d6 100644 --- a/internal/controller/redirectdomain_controller.go +++ b/internal/controller/decoredirect_controller.go @@ -23,8 +23,8 @@ import ( decositesv1alpha1 "github.com/deco-sites/decofile-operator/api/v1alpha1" ) -// RedirectDomainReconciler reconciles a RedirectDomain object. -type RedirectDomainReconciler struct { +// DecoRedirectReconciler reconciles a DecoRedirect object. +type DecoRedirectReconciler struct { client.Client Scheme *runtime.Scheme IngressClass string // nginx ingress class name, e.g. "nginx" @@ -35,16 +35,16 @@ type RedirectDomainReconciler struct { // nginx never routes to it because permanent-redirect intercepts first. const dummyBackendName = "redirect-dummy-backend" -// +kubebuilder:rbac:groups=deco.sites,resources=redirectdomains,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=deco.sites,resources=redirectdomains/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=deco.sites,resources=redirectdomains/finalizers,verbs=update +// +kubebuilder:rbac:groups=deco.sites,resources=decoredict,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=deco.sites,resources=decoredict/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=deco.sites,resources=decoredict/finalizers,verbs=update // +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete -func (r *RedirectDomainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *DecoRedirectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := logf.FromContext(ctx) - rd := &decositesv1alpha1.RedirectDomain{} + rd := &decositesv1alpha1.DecoRedirect{} if err := r.Get(ctx, req.NamespacedName, rd); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -72,7 +72,7 @@ func (r *RedirectDomainReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{}, nil } -func (r *RedirectDomainReconciler) reconcileCertificate(ctx context.Context, rd *decositesv1alpha1.RedirectDomain) error { +func (r *DecoRedirectReconciler) reconcileCertificate(ctx context.Context, rd *decositesv1alpha1.DecoRedirect) error { cert := &cmv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: resourceName(rd.Spec.From), @@ -95,7 +95,7 @@ func (r *RedirectDomainReconciler) reconcileCertificate(ctx context.Context, rd return err } -func (r *RedirectDomainReconciler) reconcileIngress(ctx context.Context, rd *decositesv1alpha1.RedirectDomain) error { +func (r *DecoRedirectReconciler) reconcileIngress(ctx context.Context, rd *decositesv1alpha1.DecoRedirect) error { pathType := networkingv1.PathTypePrefix ingress := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ @@ -147,7 +147,7 @@ func (r *RedirectDomainReconciler) reconcileIngress(ctx context.Context, rd *dec return err } -func (r *RedirectDomainReconciler) updateStatus(ctx context.Context, rd *decositesv1alpha1.RedirectDomain) (bool, error) { +func (r *DecoRedirectReconciler) updateStatus(ctx context.Context, rd *decositesv1alpha1.DecoRedirect) (bool, error) { certReady := false cert := &cmv1.Certificate{} if err := r.Get(ctx, types.NamespacedName{Name: resourceName(rd.Spec.From), Namespace: rd.Namespace}, cert); err != nil { @@ -212,9 +212,9 @@ func sanitizeDomain(domain string) string { return strings.NewReplacer(".", "-", "_", "-").Replace(domain) } -func (r *RedirectDomainReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *DecoRedirectReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&decositesv1alpha1.RedirectDomain{}). + For(&decositesv1alpha1.DecoRedirect{}). Owns(&cmv1.Certificate{}). Owns(&networkingv1.Ingress{}). Complete(r) diff --git a/internal/controller/redirectdomain_controller_test.go b/internal/controller/decoredirect_controller_test.go similarity index 86% rename from internal/controller/redirectdomain_controller_test.go rename to internal/controller/decoredirect_controller_test.go index b9f54c4..7ce6c4f 100644 --- a/internal/controller/redirectdomain_controller_test.go +++ b/internal/controller/decoredirect_controller_test.go @@ -16,8 +16,8 @@ import ( decositesv1alpha1 "github.com/deco-sites/decofile-operator/api/v1alpha1" ) -var _ = Describe("RedirectDomain Controller", func() { - Context("When reconciling a RedirectDomain", func() { +var _ = Describe("DecoRedirect Controller", func() { + Context("When reconciling a DecoRedirect", func() { const ( rdName = "test-redirect" rdNS = "default" @@ -28,8 +28,8 @@ var _ = Describe("RedirectDomain Controller", func() { ctx := context.Background() nn := types.NamespacedName{Name: rdName, Namespace: rdNS} - newReconciler := func() *RedirectDomainReconciler { - return &RedirectDomainReconciler{ + newReconciler := func() *DecoRedirectReconciler { + return &DecoRedirectReconciler{ Client: k8sClient, Scheme: k8sClient.Scheme(), IngressClass: "nginx", @@ -38,12 +38,12 @@ var _ = Describe("RedirectDomain Controller", func() { } BeforeEach(func() { - rd := &decositesv1alpha1.RedirectDomain{} + rd := &decositesv1alpha1.DecoRedirect{} err := k8sClient.Get(ctx, nn, rd) if err != nil && errors.IsNotFound(err) { - Expect(k8sClient.Create(ctx, &decositesv1alpha1.RedirectDomain{ + Expect(k8sClient.Create(ctx, &decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{Name: rdName, Namespace: rdNS}, - Spec: decositesv1alpha1.RedirectDomainSpec{ + Spec: decositesv1alpha1.DecoRedirectSpec{ From: fromDomain, To: toDomain, }, @@ -52,7 +52,7 @@ var _ = Describe("RedirectDomain Controller", func() { }) AfterEach(func() { - rd := &decositesv1alpha1.RedirectDomain{} + rd := &decositesv1alpha1.DecoRedirect{} Expect(k8sClient.Get(ctx, nn, rd)).To(Succeed()) Expect(k8sClient.Delete(ctx, rd)).To(Succeed()) }) @@ -91,7 +91,7 @@ var _ = Describe("RedirectDomain Controller", func() { _, err := newReconciler().Reconcile(ctx, reconcile.Request{NamespacedName: nn}) Expect(err).NotTo(HaveOccurred()) - rd := &decositesv1alpha1.RedirectDomain{} + rd := &decositesv1alpha1.DecoRedirect{} Expect(k8sClient.Get(ctx, nn, rd)).To(Succeed()) found := false @@ -105,10 +105,10 @@ var _ = Describe("RedirectDomain Controller", func() { Expect(found).To(BeTrue(), "CertificateReady condition should be present") }) - It("should reject a RedirectDomain whose 'to' is outside the 'from' domain", func() { - err := k8sClient.Create(ctx, &decositesv1alpha1.RedirectDomain{ + It("should reject a DecoRedirect whose 'to' is outside the 'from' domain", func() { + err := k8sClient.Create(ctx, &decositesv1alpha1.DecoRedirect{ ObjectMeta: metav1.ObjectMeta{Name: "invalid-redirect", Namespace: rdNS}, - Spec: decositesv1alpha1.RedirectDomainSpec{ + Spec: decositesv1alpha1.DecoRedirectSpec{ From: "client.com", To: "https://www.other.com", },