Skip to content

feat(extra): add external-dns as standalone extra package#1988

Open
mattia-eleuteri wants to merge 1 commit intocozystack:mainfrom
mattia-eleuteri:feature/external-dns-per-tenant
Open

feat(extra): add external-dns as standalone extra package#1988
mattia-eleuteri wants to merge 1 commit intocozystack:mainfrom
mattia-eleuteri:feature/external-dns-per-tenant

Conversation

@mattia-eleuteri
Copy link
Contributor

@mattia-eleuteri mattia-eleuteri commented Feb 5, 2026

Summary

Add external-dns as a standalone self-managed application in packages/extra/external-dns/, allowing tenants to deploy and configure their own DNS management directly from the dashboard.

Motivation

Tenants need the ability to manage their own DNS domains with their own provider. Following the developers guide, this is implemented as an extra package (like ingress and seaweedfs) using the HelmRelease-based pattern, rather than embedding it in the tenant chart.

This enables multi-tenant scenarios where:

  • Tenant A uses Cloudflare for domain-a.com
  • Tenant B uses AWS Route53 for domain-b.com
  • Each tenant deploys and manages external-dns independently from the dashboard

Changes

  • New package: packages/extra/external-dns/ — standalone HelmRelease-based application
  • New PackageSource: packages/core/platform/sources/external-dns-application.yaml — references system/external-dns and extra/external-dns components
  • Cleaned tenant chart: removed the previously embedded externalDns block from packages/apps/tenant/

Features

  • Support for 9 DNS providers: cloudflare, aws, azure, google, digitalocean, linode, ovh, exoscale, godaddy
  • Per-provider credential configuration with full JSON schema validation
  • Domain filtering via domainFilters
  • Configurable sync policy (sync or upsert-only)
  • Namespaced operation (namespaced: true) for tenant isolation
  • Unique txtOwnerId per namespace to prevent DNS record conflicts
  • Resource sizing via presets or explicit CPU/memory

Usage Example

Deploy from the dashboard, or via values:

# Cloudflare
provider: cloudflare
domainFilters:
  - example.com
cloudflare:
  apiToken: "your-cloudflare-api-token"
# AWS Route53
provider: aws
domainFilters:
  - example.org
aws:
  accessKeyId: "AKIAXXXXXXXX"
  secretAccessKey: "your-secret-key"
  region: "us-east-1"

Test plan

  • helm template external-dns packages/extra/external-dns/ --set provider=cloudflare --set cloudflare.apiToken=test renders correctly
  • helm template external-dns packages/extra/external-dns/ fails (provider required)
  • helm template wrong-name packages/extra/external-dns/ --set provider=cloudflare fails (release name check)
  • Deploy external-dns from tenant dashboard
  • Verify HelmRelease is created in tenant namespace with namespaced RBAC
  • Create an Ingress and verify DNS record is created
  • Verify no conflict with global external-dns instance

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an External DNS package for automatic DNS record management.
    • Support for 9 DNS providers: Cloudflare, AWS, Azure, Google, DigitalOcean, Linode, OVH, Exoscale, GoDaddy.
    • Helm-based deployment with namespaced/system variants and release configuration options.
    • Configurable synchronization policies, domain filtering, provider credentials, extra args, and resource presets.
  • Documentation

    • New README and schema-driven values documentation for installation and configuration.

@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Feb 5, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new External DNS package: Helm charts, values/schema, templates, package source, system release descriptors, and an ApplicationDefinition for CozyStack, enabling multi-provider ExternalDNS deployments and packaging metadata.

Changes

Cohort / File(s) Summary
Platform Package Source
packages/core/platform/sources/external-dns-application.yaml
Adds a PackageSource manifest referencing cozystack-packages OCIRepository, exposes a default variant, declares cozy-lib library and three components (external-dns-system, external-dns, external-dns-rd) with install settings.
External DNS Chart & Build
packages/extra/external-dns/Chart.yaml, packages/extra/external-dns/Makefile, packages/extra/external-dns/charts/cozy-lib, packages/extra/external-dns/config.json
Introduces chart metadata, Makefile with generate target (cozyvalues-gen + update-crd), local library reference, and codegen config JSON.
External DNS Templates
packages/extra/external-dns/templates/check-release-name.yaml, packages/extra/external-dns/templates/dashboard-resourcemap.yaml, packages/extra/external-dns/templates/external-dns.yaml
Adds Helm templates: release-name enforcement, Role/RoleBinding for dashboard access, and a comprehensive HelmRelease template supporting multiple DNS providers, credential injection, resource handling, and deployment cadence.
Values, Schema & Docs
packages/extra/external-dns/values.yaml, packages/extra/external-dns/values.schema.json, packages/extra/external-dns/README.md
Adds values file, extensive JSON Schema validation (provider credentials, domainFilters, policy, extraArgs, resources/resourcesPreset) and README documenting parameters and provider-specific configuration.
System RD Chart & Packaging
packages/system/external-dns-rd/Chart.yaml, packages/system/external-dns-rd/Makefile, packages/system/external-dns-rd/values.yaml
Adds system chart metadata, Makefile exporting NAME=external-dns-rd and NAMESPACE=cozy-system, and a placeholder values file.
ApplicationDefinition & Template Aggregation
packages/system/external-dns-rd/cozyrds/external-dns.yaml, packages/system/external-dns-rd/templates/cozyrd.yaml
Adds an ApplicationDefinition (cozystack.io/v1alpha1) with OpenAPI schema and dashboard metadata; template inlines files from cozyrds/* separated by ---.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through charts and YAML trees,
packing values, schemas, keys.
Clouds and zones now sing in tune—
records bloom beneath the moon.
CozyDNS, my carrot-fueled glee!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(extra): add external-dns as standalone extra package' directly and clearly reflects the main change: introducing external-dns as a standalone package in the extra section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @mattia-eleuteri, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the multi-tenant capabilities of the platform by enabling individual tenants to manage their own External-DNS instances. Previously, a single cluster-wide External-DNS limited flexibility for tenants requiring different DNS providers or domain management strategies. The changes introduce a robust, isolated, and configurable solution, allowing tenants to integrate their specific DNS infrastructure seamlessly while maintaining security and preventing conflicts.

Highlights

  • Per-Tenant External-DNS Support: Introduced the capability for each tenant to deploy and manage their own dedicated External-DNS instance, allowing for custom DNS providers and configurations.
  • Flexible DNS Provider Configuration: Tenants can now specify their preferred DNS provider (e.g., AWS Route53, Cloudflare, Google DNS) and configure provider-specific settings, including credentials via environment variables or a dedicated secret.
  • Namespaced RBAC and Isolation: Each tenant's External-DNS instance operates with namespaced RBAC (Role/RoleBinding), ensuring tenant isolation and preventing conflicts with a global External-DNS instance or other tenants. A unique txtOwnerId is used per tenant to avoid DNS record conflicts.
  • Configurable Domain Filters: Tenants can define domainFilters to restrict which domains their External-DNS instance is allowed to manage, enhancing security and control.
  • New HelmRelease Template: A new HelmRelease template (external-dns.yaml) has been added to deploy and manage the per-tenant External-DNS instances within the tenant's namespace.
  • Schema and Default Values Updates: The values.schema.json and values.yaml files have been updated to include the new externalDns configuration object, providing clear documentation and default settings for tenant-specific External-DNS deployments.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • packages/apps/tenant/templates/external-dns.yaml
    • Added a new HelmRelease template to deploy external-dns for each tenant.
    • Configured the HelmRelease to use namespaced RBAC and a unique txtOwnerId for tenant isolation.
    • Enabled dynamic configuration of provider name, domain filters, extra arguments, and environment variables based on tenant values.
  • packages/apps/tenant/values.schema.json
    • Extended the schema to include a new externalDns object.
    • Defined properties for externalDns such as enabled, provider, domainFilters, policy, credentialsSecretName, extraArgs, and env with appropriate types and descriptions.
  • packages/apps/tenant/values.yaml
    • Added default values for the new externalDns configuration object.
    • Set externalDns.enabled to false by default, ensuring opt-in for per-tenant External-DNS.
Activity
  • No human activity (comments, reviews) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces per-tenant external-dns support, which is a great feature for multi-tenant clusters. The implementation is solid, using a dedicated HelmRelease per tenant and ensuring DNS record isolation with unique txtOwnerId.

I have a few suggestions to improve the robustness and security of this feature:

  1. Add validation to ensure the DNS provider is specified when external-dns is enabled.
  2. Strengthen the values.schema.json for the env array to provide better validation.
  3. Most importantly, add a strong warning against storing plaintext credentials in the values file, as this is a critical security risk. The current examples could mislead users into insecure configurations.

Comment on lines 94 to 96
"items": {
"type": "object"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The schema for env items is currently a generic object. To provide better validation and guide users towards secure practices (like using valueFrom), it's better to define the expected properties for environment variables. This aligns with the Kubernetes EnvVar structure and allows for static validation of tenant values.

          "items": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "value": { "type": "string" },
              "valueFrom": { "type": "object" }
            },
            "required": ["name"]
          },

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/apps/tenant/templates/external-dns.yaml`:
- Around line 43-44: When external DNS is enabled but no provider is supplied
the chart will render an empty provider name; update the Helm template in
external-dns.yaml to validate .Values.externalDns.provider whenever
.Values.externalDns.enabled is true by adding a conditional check that fails
with a clear message (e.g., use an if .Values.externalDns.enabled -> if not
.Values.externalDns.provider -> fail "externalDns.provider is required when
externalDns.enabled is true") so templates referencing
.Values.externalDns.provider (the provider: {{ .Values.externalDns.provider |
quote }} line) never render an empty value.
🧹 Nitpick comments (2)
packages/apps/tenant/templates/external-dns.yaml (1)

22-28: Infinite retries may mask deployment failures.

Setting retries: -1 for both install and upgrade means Flux will retry indefinitely. While this provides resilience for transient issues, it can also mask persistent configuration problems (e.g., invalid credentials, wrong provider settings). Consider whether a finite retry count with alerting might be more appropriate for tenant-scoped resources.

packages/apps/tenant/values.schema.json (1)

91-97: Consider strengthening the env items schema.

The env array items are typed as generic object, which accepts any structure. For Kubernetes environment variables, you could specify the expected schema to provide better validation and documentation.

♻️ Optional: More specific env items schema
         "env": {
           "description": "Environment variables for provider credentials.",
           "type": "array",
           "items": {
-            "type": "object"
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string",
+                "description": "Environment variable name"
+              },
+              "value": {
+                "type": "string",
+                "description": "Environment variable value"
+              },
+              "valueFrom": {
+                "type": "object",
+                "description": "Source for the environment variable's value"
+              }
+            },
+            "required": ["name"]
           },
           "default": []
         }

Comment on lines 21 to 28
externalDns:
enabled: false
provider: ""
domainFilters: []
policy: upsert-only
credentialsSecretName: ""
extraArgs: []
env: []
Copy link
Member

Choose a reason for hiding this comment

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

Hey @mattia-eleuteri,

Are these parameters expected to be configurable by the tenant themselves, or by the parent tenants?

The problem is that tenants only have visibility into their own namespace, not the parent namespace where the tenant application is installed. Because of that, they can’t modify their tenant application parameters directly.

If this is intended to be tenant-managed, please consider creating a separate additional applicationin packages/extra that would install the HelmRelease from the packages/system.

Also, please check the latest update to the developers guide — it explains this pattern in a bit more detail:
cozystack/website#413

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @kvaps, thanks for the feedback!

You're right — this should be tenant self-managed. I've refactored the implementation following the pattern you described:

  • Removed the externalDns block from packages/apps/tenant/ (template, values, schema)
  • Created packages/extra/external-dns/ as a standalone application using the HelmRelease-based pattern (same approach as ingress and seaweedfs)
  • Added packages/core/platform/sources/external-dns-application.yaml (PackageSource referencing system/external-dns and extra/external-dns)

The extra package now supports per-provider credential configuration (cloudflare, aws, azure, google, digitalocean, linode, ovh, exoscale, godaddy) with full JSON schema validation. Tenants can deploy and configure it directly from the dashboard.

I also reviewed PR #413 on the website repo — the new structure should align with the developers guide.

@kvaps kvaps requested a review from IvanHunters as a code owner February 10, 2026 15:58
@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch from a1b3d43 to 12aff0e Compare February 12, 2026 10:43
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Feb 12, 2026
@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch from 12aff0e to 55afcf1 Compare February 12, 2026 10:46
@mattia-eleuteri mattia-eleuteri changed the title feat(tenant): add per-tenant external-dns support feat(extra): add external-dns as standalone extra package Feb 12, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@packages/extra/external-dns/Chart.yaml`:
- Around line 1-6: Create a new README.md for the external-dns chart referenced
by Chart.yaml (name: external-dns) because the Makefile's cozyvalues-gen step
passes -r README.md and the file is missing; add a minimal markdown README.md
(title, short description, usage placeholder) next to the chart so the generate
step can read it, matching the pattern used by other packages/extra charts.

In `@packages/extra/external-dns/templates/external-dns.yaml`:
- Around line 43-53: The template is emitting an empty "env:" key when provider
is "cloudflare" (and similarly for AWS) if no credential fields are set; change
the template so the "env:" key is rendered only when at least one credential
variable exists by moving the env: line inside the credential checks (use the
same conditional that checks .Values.cloudflare.apiToken or the
.Values.cloudflare.apiKey and .Values.cloudflare.apiEmail) and likewise wrap the
AWS env: under a conditional that checks
.Values.aws.accessKey/.Values.aws.secretKey (or whichever AWS credential fields
you use) so no empty env list is produced when credentials are absent.
- Around line 129-137: The template emits a second extraArgs: when provider ==
"godaddy", which overwrites any user-provided .Values.extraArgs; fix by merging
GoDaddy flags into the same extraArgs block instead of emitting a new key:
remove the separate godaddy extraArgs block and inside the existing extraArgs
rendering (where .Values.extraArgs is iterated) append conditional lines for
--godaddy-api-key={{ .Values.godaddy.apiKey }} and --godaddy-api-secret={{
.Values.godaddy.apiSecret }} only when .Values.provider == "godaddy" and the
values exist, so the final rendered extraArgs contains both user-supplied
entries and GoDaddy flags without duplicate keys.
- Around line 22-31: The HelmRelease currently places provider credentials
inline under values.external-dns (the block containing external-dns, txtOwnerId,
provider.name, sources), so update the HelmRelease to use valuesFrom referencing
the existing secret pattern (e.g., the cozystack-values Secret) instead of
embedding sensitive keys; remove all credential keys currently in the inline
values (CF_API_TOKEN, AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID,
DIGITALOCEAN_TOKEN, GOOGLE_APPLICATION_CREDENTIALS, etc.) and add a valuesFrom
entry that pulls those keys from the Secret (matching the repository pattern
used by seaweedfs/nginx-ingress/tenant apps) so the template no longer exposes
plaintext provider credentials.

Comment on lines +1 to +6
apiVersion: v2
name: external-dns
description: External DNS for automatic DNS record management
icon: /logos/external-dns.svg
type: application
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if other packages/extra charts have a README.md
echo "=== README.md files in packages/extra ==="
fd README.md packages/extra --type f 2>/dev/null || echo "No README.md files found"

echo ""
echo "=== Checking external-dns directory contents ==="
ls -la packages/extra/external-dns/ 2>/dev/null || echo "Directory not found"

echo ""
echo "=== Checking cozyvalues-gen usage in packages/extra Makefiles ==="
rg -n 'cozyvalues-gen' packages/extra/*/Makefile 2>/dev/null | head -30

echo ""
echo "=== Checking cozyvalues-gen usage in packages/apps Makefiles ==="
rg -n 'cozyvalues-gen' packages/apps/*/Makefile 2>/dev/null | head -30

Repository: cozystack/cozystack

Length of output: 3762


Create missing README.md file.

The make generate step fails because the Makefile references README.md via the -r flag in cozyvalues-gen, but the file doesn't exist. All other packages/extra/* charts have this file and use the same pattern. Create an initial README.md file (even if minimal) to resolve the pipeline failure.

🧰 Tools
🪛 GitHub Actions: Pre-Commit Checks

[error] 1-1: make generate failed. README: open README.md: no such file or directory

🤖 Prompt for AI Agents
In `@packages/extra/external-dns/Chart.yaml` around lines 1 - 6, Create a new
README.md for the external-dns chart referenced by Chart.yaml (name:
external-dns) because the Makefile's cozyvalues-gen step passes -r README.md and
the file is missing; add a minimal markdown README.md (title, short description,
usage placeholder) next to the chart so the generate step can read it, matching
the pattern used by other packages/extra charts.

@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch from 93488d9 to 076d890 Compare February 12, 2026 10:59
Copy link
Contributor

@lexfrei lexfrei left a comment

Choose a reason for hiding this comment

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

Consider adding optional Gateway API support as a source.

Cozystack has Gateway API CRDs available in tenant clusters (packages/system/gateway-api-crds/), enabled via addons.gatewayAPI.enabled. When a tenant uses Gateway API (HTTPRoute, etc.), external-dns should be able to pick up hostnames from those resources too.

Suggestion: add a boolean value (e.g. gatewayAPI: false) and conditionally include gateway-httproute in sources:

sources:
  - ingress
  - service
  {{- if .Values.gatewayAPI }}
  - gateway-httproute
  {{- end }}

@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch 2 times, most recently from 7414a3c to a574359 Compare February 12, 2026 11:10
@mattia-eleuteri
Copy link
Contributor Author

Thanks @lexfrei for the suggestion! I've added optional Gateway API support. A new gatewayAPI boolean value (default false) conditionally includes gateway-httproute in the external-dns sources when enabled:

sources:
  - ingress
  - service
  {{- if .Values.gatewayAPI }}
  - gateway-httproute
  {{- end }}

@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch 2 times, most recently from b18fe55 to 699750f Compare February 12, 2026 11:13
@lexfrei
Copy link
Contributor

lexfrei commented Feb 12, 2026

And so two Claudes had a conversation through their human puppets 🤖🤝🤖

@lexfrei
Copy link
Contributor

lexfrei commented Feb 12, 2026

The upstream external-dns chart v1.20.0 added support for annotationPrefix (#5889), which allows customizing the default external-dns.alpha.kubernetes.io prefix. This is essential for running multiple independent external-dns instances — each instance uses its own annotation prefix, so they don't interfere with each other.

Could you expose annotationPrefix as an optional value and bump the system chart dependency to at least 1.20.0?

@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch from 699750f to 2da6688 Compare February 13, 2026 10:15
@dosubot dosubot bot removed the size:XL This PR changes 500-999 lines, ignoring generated files. label Feb 13, 2026
@mattia-eleuteri
Copy link
Contributor Author

Done! Bumped the upstream chart from v1.15.0 to v1.20.0 (appVersion 0.20.0) and exposed annotationPrefix as an optional value.

When set, it's passed through to the HelmRelease; when empty (default), it's omitted so the upstream default (external-dns.alpha.kubernetes.io) is preserved.

Changes:

  • packages/system/external-dns/charts/external-dns/ — bumped via make update
  • packages/extra/external-dns/values.yaml — added annotationPrefix: ""
  • packages/extra/external-dns/templates/external-dns.yaml — conditionally passes annotationPrefix
  • packages/extra/external-dns/values.schema.json + README.md — regenerated
  • packages/system/external-dns-rd/cozyrds/external-dns.yaml — updated openAPISchema + keysOrder

@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 13, 2026
@lexfrei
Copy link
Contributor

lexfrei commented Feb 16, 2026

The ApplicationDefinition is missing required fields based on the Go types in api/v1alpha1/applicationdefinitions_types.go.

spec.application (line 65-74 of the types file) — missing kind, plural, singular:

type ApplicationDefinitionApplication struct {
    Kind          string `json:"kind"`
    OpenAPISchema string `json:"openAPISchema"`
    Plural        string `json:"plural"`
    Singular      string `json:"singular"`
}

All four fields have no omitempty tag, making them structurally required. Every other ApplicationDefinition in the project (ingress, tenant, vpn, monitoring, etc.) includes these fields. Without them, cozystack-api cannot register the dynamic CRD.

Suggested fix:

spec:
  application:
    kind: ExternalDNS
    plural: externaldns
    singular: externaldns
    openAPISchema: ...

spec.dashboard (line 143-177) — missing singular, plural, category:

type ApplicationDefinitionDashboard struct {
    Singular string `json:"singular"`
    Plural   string `json:"plural"`
    Category string `json:"category"`
    // ...
}

Same pattern — no omitempty, required by the struct. Currently the dashboard section only has description, icon, and keysOrder.

Suggested fix:

  dashboard:
    category: Networking
    singular: External DNS
    plural: External DNS
    description: External DNS for automatic DNS record management
    icon: ...

@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch from 2da6688 to 5c65355 Compare February 16, 2026 10:42
@mattia-eleuteri
Copy link
Contributor Author

mattia-eleuteri commented Feb 16, 2026

Thanks for the detailed review!

Missing ApplicationDefinition fields — Fixed. Added kind: ExternalDNS, plural: externaldns, singular: externaldns under spec.application and category: Networking, singular: External DNS, plural: External DNS under spec.dashboard. Good catch — the CRD registration would have failed without these.

@lexfrei
Copy link
Contributor

lexfrei commented Feb 16, 2026

1. provider: "" default breaks schema enum validation

values.yaml defaults provider to "", but the generated schema enum doesn't include "". Helm 3 validates schemas by default, so deploying with defaults will fail.

Suggested fix: either remove the default and make provider required, or handle the empty case in the template with a clear error.

2. policy field is a plain string, should be an enum

Currently:

## @param {string} policy="upsert-only" - How DNS records are synchronized (sync or upsert-only).
policy: "upsert-only"

The upstream chart supports three values: create-only, sync, and upsert-only. Following the same @enum/@value pattern already used for Provider:

## @enum {string} Policy - How DNS records are synchronized.
## @value create-only
## @value sync
## @value upsert-only

## @param {Policy} policy="upsert-only" - How DNS records are synchronized.
policy: "upsert-only"

3. Azure, DigitalOcean, Linode, OVH render credentials unconditionally

Cloudflare has a guard ({{- if .Values.cloudflare.apiToken }}), but Azure, DigitalOcean, Linode, and OVH render all credential fields unconditionally. If a user selects a provider but doesn't fill in credentials, the HelmRelease renders empty strings and external-dns fails at runtime with unclear errors.

Suggested fix: add similar guards, or better — use JSON Schema conditional validation (if/then) to require provider-specific fields when that provider is selected.

4. No e2e test

There is no hack/e2e-apps/external-dns.bats. To make it testable, add inmemory to the Provider enum in values.yaml:

## @enum {string} Provider - DNS provider.
## @value inmemory
## @value cloudflare
...

The inmemory provider requires no credentials and is supported by the upstream chart, making it ideal for e2e testing.

The test should verify that two independent instances can coexist — one with the default annotation prefix, another with a custom annotationPrefix. To validate they work independently, create a Service annotated for each instance (using the respective annotation prefix) pointing to different targets. Verify both external-dns pods are running and each picks up only the Service matching its own annotation prefix.

@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch 3 times, most recently from d2ee231 to 3ee2f9a Compare February 16, 2026 17:42
@mattia-eleuteri
Copy link
Contributor Author

Thanks for the review @lexfrei — all four points have been addressed in the latest force-push:

1. provider: "" default breaks schema enum validation

  • Removed the empty default from values.yaml (provider is now unset by default)
  • Added a {{- fail … }} guard in the template for a clear error message

2. policy should be an enum

  • Added "enum": ["create-only", "sync", "upsert-only"] to both values.schema.json and the ApplicationDefinition openAPISchema

3. Azure, DigitalOcean, Google, Linode, OVH, Exoscale render credentials unconditionally

  • Each provider block now has an inner guard that skips rendering when no credentials are provided (matching the existing Cloudflare/AWS pattern)

4. E2e test

  • Added hack/e2e-apps/external-dns.bats with the inmemory provider — no external credentials needed, the pod starts and runs fully in-memory
  • Two test cases: default setup and custom annotationPrefix for multi-instance isolation
  • Both tests verify the full chain: CR → HelmRelease Ready → Deployment with readyReplicas=1

Add external-dns as a per-tenant extra package with support for multiple
DNS providers (Cloudflare, AWS, Azure, Google, DigitalOcean, Linode, OVH,
Exoscale, GoDaddy).

Features:
- Provider is required (enum-validated, no empty default)
- Policy field constrained to create-only, sync, upsert-only
- Conditional credential rendering to avoid empty string failures
- Custom annotationPrefix support for multi-instance isolation
- Gateway API HTTPRoute source support
- ApplicationDefinition with full openAPISchema
- E2e test with cloudflare provider and fake credentials

Signed-off-by: mattia-eleuteri <mattia@hidora.io>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mattia-eleuteri mattia-eleuteri force-pushed the feature/external-dns-per-tenant branch from 3ee2f9a to 1261ff7 Compare February 16, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants