From a0dcac59bd3484abb51f34369b37b835d89cc94a Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 10 May 2026 18:59:15 +0800 Subject: [PATCH 1/3] Add CI/CD pipeline --- .drone-manifest.yml | 22 ++++++ .drone.yml | 187 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 .drone-manifest.yml create mode 100644 .drone.yml diff --git a/.drone-manifest.yml b/.drone-manifest.yml new file mode 100644 index 0000000..68ee47f --- /dev/null +++ b/.drone-manifest.yml @@ -0,0 +1,22 @@ +image: happydomain/checker-authoritative-consistency:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} +{{/if}} +manifests: + - image: happydomain/checker-authoritative-consistency:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 + platform: + architecture: amd64 + os: linux + - image: happydomain/checker-authoritative-consistency:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 + platform: + architecture: arm64 + os: linux + variant: v8 + - image: happydomain/checker-authoritative-consistency:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm + platform: + architecture: arm + os: linux + variant: v7 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..612db80 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,187 @@ +--- +kind: pipeline +type: docker +name: build-amd64 + +platform: + os: linux + arch: amd64 + +steps: + - name: checker build + image: golang:1-alpine + commands: + - apk add --no-cache git make + - make + environment: + CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}" + CGO_ENABLED: 0 + when: + event: + exclude: + - tag + + - name: checker build tag + image: golang:1-alpine + commands: + - apk add --no-cache git make + - make + environment: + CHECKER_VERSION: "${DRONE_SEMVER}" + CGO_ENABLED: 0 + when: + event: + - tag + + - name: publish on Docker Hub + image: plugins/docker + settings: + repo: happydomain/checker-authoritative-consistency + auto_tag: true + auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + dockerfile: Dockerfile + build_args: + - CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT} + username: + from_secret: docker_username + password: + from_secret: docker_password + when: + event: + exclude: + - tag + + - name: publish on Docker Hub (tag) + image: plugins/docker + settings: + repo: happydomain/checker-authoritative-consistency + auto_tag: true + auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + dockerfile: Dockerfile + build_args: + - CHECKER_VERSION=${DRONE_SEMVER} + username: + from_secret: docker_username + password: + from_secret: docker_password + when: + event: + - tag + +trigger: + branch: + exclude: + - renovate/* + event: + - cron + - push + - tag + +--- +kind: pipeline +type: docker +name: build-arm64 + +platform: + os: linux + arch: arm64 + +steps: + - name: checker build + image: golang:1-alpine + commands: + - apk add --no-cache git make + - make + environment: + CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}" + CGO_ENABLED: 0 + when: + event: + exclude: + - tag + + - name: checker build tag + image: golang:1-alpine + commands: + - apk add --no-cache git make + - make + environment: + CHECKER_VERSION: "${DRONE_SEMVER}" + CGO_ENABLED: 0 + when: + event: + - tag + + - name: publish on Docker Hub + image: plugins/docker + settings: + repo: happydomain/checker-authoritative-consistency + auto_tag: true + auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + dockerfile: Dockerfile + build_args: + - CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT} + username: + from_secret: docker_username + password: + from_secret: docker_password + when: + event: + exclude: + - tag + + - name: publish on Docker Hub (tag) + image: plugins/docker + settings: + repo: happydomain/checker-authoritative-consistency + auto_tag: true + auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + dockerfile: Dockerfile + build_args: + - CHECKER_VERSION=${DRONE_SEMVER} + username: + from_secret: docker_username + password: + from_secret: docker_password + when: + event: + - tag + +trigger: + event: + - cron + - push + - tag + +--- +kind: pipeline +name: docker-manifest + +platform: + os: linux + arch: arm64 + +steps: + - name: publish on Docker Hub + image: plugins/manifest + settings: + auto_tag: true + ignore_missing: true + spec: .drone-manifest.yml + username: + from_secret: docker_username + password: + from_secret: docker_password + +trigger: + branch: + exclude: + - renovate/* + event: + - cron + - push + - tag + +depends_on: + - build-amd64 + - build-arm64 From 0903357221cdfbb72ec325539c0a587320f25cd2 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 15 May 2026 17:43:21 +0800 Subject: [PATCH 2/3] Enforce prober/evaluator boundary in ObservationData --- checker/types.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/checker/types.go b/checker/types.go index c4d7bdc..444bd2c 100644 --- a/checker/types.go +++ b/checker/types.go @@ -3,6 +3,7 @@ package checker import ( "encoding/json" "fmt" + "slices" "github.com/miekg/dns" ) @@ -77,10 +78,8 @@ type NSResult struct { // Dedupes identical messages and caps the list with a sentinel summary. func (n *NSResult) appendError(format string, args ...any) { msg := fmt.Sprintf(format, args...) - for _, e := range n.Errors { - if e == msg { - return - } + if slices.Contains(n.Errors, msg) { + return } if len(n.Errors) >= maxNSResultErrors { n.suppressedErrors++ @@ -106,9 +105,8 @@ type ObservationData struct { ParentNS []string `json:"parent_ns,omitempty"` ParentQueryError string `json:"parent_query_error,omitempty"` // Union of DeclaredNS and ParentNS, de-duplicated. - Probed []string `json:"probed,omitempty"` - Results map[string]*NSResult `json:"results,omitempty"` - Findings []Finding `json:"findings"` + Probed []string `json:"probed,omitempty"` + Results map[string]*NSResult `json:"results,omitempty"` } // Local mirror of happyDomain's services/abstract.Origin. Duplicated on From 744a75b25d69b958b35f82faa1b55c58b86924d6 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sat, 16 May 2026 21:32:09 +0800 Subject: [PATCH 3/3] checker: resolve relative NS labels using the zone origin NS hostnames stored in happyDomain's abstract.Origin service are encoded relative to the zone apex (e.g. "ns0" instead of "ns0.example.com."). normalizeNSList was calling dns.Fqdn() directly, turning "ns0" into the useless single-label "ns0." rather than the correct FQDN. Expose domain_name in ServiceOpts so happyDomain auto-fills the zone apex at service scope, then use sdk.JoinRelative in normalizeNSList to absolutize relative labels. Absolute FQDNs (trailing dot) are kept as-is so external nameservers round-trip safely. --- checker/collect.go | 10 +++++++--- checker/collect_test.go | 8 +++++--- checker/definition.go | 5 +++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/checker/collect.go b/checker/collect.go index 05134da..c0a1887 100644 --- a/checker/collect.go +++ b/checker/collect.go @@ -32,7 +32,7 @@ func (p *authoritativeConsistencyProvider) Collect(ctx context.Context, opts sdk data := &ObservationData{ Zone: dns.Fqdn(zone), HasSOA: svc.SOA != nil, - DeclaredNS: normalizeNSList(svc.NameServers), + DeclaredNS: normalizeNSList(svc.NameServers, zone), Results: map[string]*NSResult{}, } if svc.SOA != nil { @@ -167,13 +167,17 @@ func loadZone(opts sdk.CheckerOptions, svc *originService) (string, error) { return "", fmt.Errorf("no zone name provided (missing 'domain_name' option and SOA header)") } -func normalizeNSList(ns []*dns.NS) []string { +func normalizeNSList(ns []*dns.NS, origin string) []string { out := make([]string, 0, len(ns)) for _, n := range ns { if n == nil { continue } - out = append(out, strings.ToLower(dns.Fqdn(n.Ns))) + name := n.Ns + if !strings.HasSuffix(name, ".") { + name = sdk.JoinRelative(name, strings.TrimSuffix(origin, ".")) + } + out = append(out, strings.ToLower(dns.Fqdn(name))) } sort.Strings(out) return out diff --git a/checker/collect_test.go b/checker/collect_test.go index 94db089..e9b8bed 100644 --- a/checker/collect_test.go +++ b/checker/collect_test.go @@ -80,13 +80,15 @@ func TestDiffStringSets_Equal(t *testing.T) { } func TestNormalizeNSList(t *testing.T) { + // Relative labels (no trailing dot) are joined with the zone origin. + // Absolute FQDNs (trailing dot) are kept as-is. in := []*dns.NS{ - {Ns: "NS2.Example.COM"}, + {Ns: "ns2"}, nil, {Ns: "ns1.example.com."}, - {Ns: "NS1.example.com"}, + {Ns: "ns1"}, } - got := normalizeNSList(in) + got := normalizeNSList(in, "example.com.") want := []string{"ns1.example.com.", "ns1.example.com.", "ns2.example.com."} if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) diff --git a/checker/definition.go b/checker/definition.go index 32798e9..29a339f 100644 --- a/checker/definition.go +++ b/checker/definition.go @@ -110,6 +110,11 @@ func (p *authoritativeConsistencyProvider) Definition() *sdk.CheckerDefinition { Label: "Origin service", AutoFill: sdk.AutoFillService, }, + { + Id: "domain_name", + Label: "Zone name", + AutoFill: sdk.AutoFillDomainName, + }, }, }, Rules: Rules(),