Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
68311c3
Add GetRecordsByNames for batch glue record lookups
poyrazK May 3, 2026
6f360ef
Batch glue record lookups in authority section
poyrazK May 3, 2026
155f13e
Add GetRecordsByNames to test mocks
poyrazK May 3, 2026
7a88470
Fix name format mismatch in GetRecordsByNames and add test
poyrazK May 4, 2026
b95c5bb
fix(ci): add PostgreSQL service to coverage job for integration tests
poyrazK May 4, 2026
2a6dd29
test: add GetRecordsByNames integration tests
poyrazK May 4, 2026
ef41ff5
test: add writeSignCanonicalRData coverage for all record types
poyrazK May 5, 2026
0476c93
test: add fetchDNSKEYFromNetwork tests with mock queryFn
poyrazK May 5, 2026
3d9317f
ci: trigger fresh build for PR #132
poyrazK May 5, 2026
5e470e2
ci: trigger fresh build
poyrazK May 5, 2026
3685e03
fix: disable pgx statement cache in integration tests
poyrazK May 5, 2026
fd07ad8
fix: disable pgx statement cache to prevent stale prepared statement …
poyrazK May 5, 2026
6c4b7d6
fix: use describe_exec mode instead of disabling statement cache
poyrazK May 5, 2026
5a12038
fix: use QueryExecModeSimpleProtocol directly on ConnConfig
poyrazK May 5, 2026
308164f
fix: use QueryExecModeExec instead of simple_protocol
poyrazK May 5, 2026
504cc6f
fix: use DEALLOCATE ALL to clear stale prepared statements
poyrazK May 5, 2026
c864dce
revert: temporarily remove GetRecordsByNames integration tests
poyrazK May 5, 2026
f7323a9
linter: fix outdated actions, markdown, Dockerfile pin, and test config
poyrazK May 6, 2026
7a84230
chore: remove accidentally added cloudMedia workspace file
poyrazK May 6, 2026
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
32 changes: 26 additions & 6 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.26.1"
cache: true
Expand All @@ -35,7 +35,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.26.1"
cache: true
Expand Down Expand Up @@ -104,14 +104,34 @@ jobs:
coverage:
name: Coverage & Thorough Check
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: clouddns
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.26.1"
cache: true


- name: Initialize Database Schema
run: |
sudo apt-get update && sudo apt-get install -y postgresql-client
psql -h localhost -U postgres -d clouddns -f internal/adapters/repository/schema.sql
env:
PGPASSWORD: postgres
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y bc || true

Expand All @@ -135,7 +155,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.26.1"
- name: Install govulncheck
Expand Down
146 changes: 146 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# cloudDNS - Claude Code Context

## Project Overview

**cloudDNS** is a high-performance, authoritative and recursive DNS server written in Go (1.26.1). It implements strict RFC standards with DNSSEC signing/validation, BGP anycast integration, multi-layer caching (L1 in-memory + L2 Redis), DNS over HTTPS (DoH), IXFR zone transfers, and a REST API for management.

## Architecture

### Hexagonal (Ports & Adapters) Architecture

```text
┌─────────────────────────────────────────────────────────────┐
│ cmd/ (Entry Points) │
├─────────────────────────────────────────────────────────────┤
│ internal/adapters/api/ │
│ (REST API HTTP handlers) │
├─────────────────────────────────────────────────────────────┤
│ internal/core/ │
│ ┌─────────┬──────────┬─────────┬──────────┬───────────┐ │
│ │ domain/ │ services/ │ ports/ │ config/ │ utils/ │ │
│ │ (ents) │ (biz log)│ (ifaces)│ (cfg) │ (util) │ │
│ └─────────┴──────────┴─────────┴──────────┴───────────┘ │
├─────────────────────────────────────────────────────────────┤
│ internal/dns/ │
│ ┌─────────┬──────────┬─────────┬──────────┐ │
│ │ packet/ │ server/ │ master/ │ cache/ │ │
│ │ (wire) │ (impl) │ (xfr) │ (l1/l2) │ │
│ └─────────┴──────────┴─────────┴──────────┘ │
├─────────────────────────────────────────────────────────────┤
│ internal/adapters/repository/ │
│ (PostgreSQL implementations) │
├─────────────────────────────────────────────────────────────┤
│ internal/adapters/routing/ │
│ (GoBGP integration) │
└─────────────────────────────────────────────────────────────┘
```

## Key Packages

### `cmd/clouddns/` - Main DNS server
- Entry point: `cmd/clouddns/main.go`
- Server configured via environment variables (no config files)

### `internal/dns/server/` - DNS protocol implementation
- **server.go** (~2100 lines): Core `Server` struct handling UDP/TCP/DoT/DoH
- **cache.go**: L1 (sharded in-memory) and L2 (Redis) caching
- **recursive.go**: Iterative recursive resolution with root hints
- **ratelimit.go**: Token bucket rate limiting (500k req/s, burst 200k)

### `internal/dns/packet/` - DNS wire format
- `DNSPacket` struct: Header, Questions, Answers, Authorities, Resources
- Supports all record types: A, AAAA, MX, TXT, CNAME, NS, SOA, PTR, SRV, CAA, DS, DNSKEY, RRSIG, NSEC, NSEC3, IXFR, AXFR, OPT, TSIG
- EDNS0 support: NSID, Cookie, Padding, EDE (RFC 8914)

### `internal/core/domain/` - Domain entities
- `Zone`: id, name, role (master/slave), vpcid
- `Record`: id, zoneid, name, type, content, ttl, priority/weight/port for MX/SRV
- `UpdateOperation`: ADD, DELETE_RRSET, DELETE_ALL, DELETE_SPECIFIC
- `ZoneChange`: audit trail for zone changes

### `internal/core/services/` - Business logic (10 subdirectories)
- DNSSEC signing and validation
- Recursive resolution
- Zone transfers (AXFR/IXFR)
- Dynamic updates (RFC 2136)

### `internal/adapters/repository/` - PostgreSQL implementations
- Implements `ports.DNSRepository` interface

## Configuration

All configuration via environment variables:
- `DATABASE_URL` - PostgreSQL (default: `postgres://postgres:postgres@localhost:5432/clouddns?sslmode=disable`)
- `REDIS_URL` - Redis cache
- `DNS_ADDR` - DNS bind address (default: `127.0.0.1:1053`; uses 1053 instead of privileged port 53)
- `API_ADDR` - Management API bind (default: `:8080`)
- `LOG_LEVEL`, `LOG_FORMAT`
- `DNSSEC_MODE` - `disabled`, `ad-bit-only`, `strict`
- `ANYCAST_*` / `BGP_*` - Anycast/BGP configuration
- `TRUST_ANCHOR_<zone>` - Base64-encoded DNSSEC trust anchors

## Build & Deploy

### Build
- `go build -o clouddns-bin cmd/clouddns/main.go`
- Docker multi-stage: `golang:1.26-alpine` builder → `alpine:3.20` runtime
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale Go image tag in Build & Deploy section.

Line 86 references golang:1.26-alpine, but the Dockerfile was updated to golang:1.26.1-alpine in this same PR.

📝 Proposed fix
-  - Docker multi-stage: `golang:1.26-alpine` builder → `alpine:3.20` runtime
+  - Docker multi-stage: `golang:1.26.1-alpine` builder → `alpine:3.20` runtime
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 86, Update the stale Docker image tag string
"golang:1.26-alpine" in the Build & Deploy section of CLAUDE.md to match the
Dockerfile change by replacing it with "golang:1.26.1-alpine" so the
documentation reflects the actual builder image used; search for the exact
literal "golang:1.26-alpine" in CLAUDE.md and update that occurrence.

- Statically linked with `CGO_ENABLED=0`

### Test
```bash
go test -short -timeout 5m ./...
go test -v -timeout 10m -coverprofile=coverage.txt $(go list ./... | grep -v "top1m-import")
```
- Coverage threshold: 80% minimum

### Deploy
- ~~GitHub Actions: lint → test → build → push to GCP Artifact Registry → GKE deployment~~
- **Note:** GKE deployment is disabled — we outgrew the gcloud subscription and no longer use deploy workflows
- Ports: 1053/udp, 1053/tcp, 8080/tcp, 853/tcp

## Query Flow

1. Rate limit check
2. Parse packet (`request.FromBuffer()`)
3. Cache check (L1 → L2)
4. EDNS0 processing
5. Zone lookup (traverse domain labels)
6. Record resolution (direct or wildcard)
7. NXDOMAIN → SOA + NSEC/NSEC3 proofs if DNSSEC
8. Recursive fallback (if `RecursionEnabled` and RD bit set)
9. DNSSEC signing (if DO bit set)
10. DNSSEC validation (if validator configured)
11. Padding (RFC 7830/8467)
12. Truncation (if response > maxSize)
13. Cache result
14. Send response

## Important Files

- `internal/dns/server/server.go` - Main server implementation
- `internal/dns/packet/packet.go` - DNS packet parsing
- `internal/dns/server/cache.go` - Multi-layer cache
- `internal/dns/server/recursive.go` - Recursive resolver
- `internal/core/ports/ports.go` - Repository interface definition
- `internal/core/domain/dns.go` - Domain entities
- `infra/k8s/deployment.yaml` - Kubernetes deployment
- `.github/workflows/go.yml` - CI pipeline

## Documentation

- `README.md` - Project overview
- `features.md` - Feature list
- `docs/dnssec.md` - DNSSEC documentation
- `docs/decisions/` - Architecture Decision Records (ADRs)

## Design Decisions (ADRs)

1. **0001** - Hexagonal architecture
2. **0002** - Anycast/BGP integration
3. **0003** - Distributed cache invalidation
4. **0004** - API authentication and RBAC
5. **0005** - Smart engine GSLB health checks
6. **0006** - Incremental zone transfer (IXFR)
7. **0007** - CAA record support
8. **0008** - DNSSEC validation
9. **0009** - Multi-algorithm DNSSEC
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM golang:1.26-alpine AS builder
FROM golang:1.26.1-alpine AS builder

# Install build dependencies for CGO (though we plan to disable it)
RUN apk add --no-cache git gcc musl-dev
Expand Down
78 changes: 78 additions & 0 deletions internal/adapters/repository/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,84 @@ func (r *PostgresRepository) GetRecords(ctx context.Context, name string, qType
return records, nil
}

// GetRecordsByNames returns records for multiple names with a single query.
// Used for batch-fetching glue records to avoid N+1 queries.
func (r *PostgresRepository) GetRecordsByNames(ctx context.Context, names []string, qType domain.RecordType, clientIP string) (map[string][]domain.Record, error) {
if len(names) == 0 {
return nil, nil
}

// Build query: WHERE LOWER(r.name) IN (LOWER($1), LOWER($2), ...)
placeholders := make([]string, len(names))
args := make([]interface{}, len(names)+2)
args[0] = clientIP
for i, name := range names {
placeholders[i] = fmt.Sprintf("LOWER($%d)", i+2)
args[i+1] = name
}

query := fmt.Sprintf(`SELECT r.id, r.zone_id, r.name, r.type, r.content, r.ttl, r.priority, r.weight, r.port, r.network,
r.health_check_type, r.health_check_target, COALESCE(h.status, 'UNKNOWN')
FROM dns_records r
LEFT JOIN record_health h ON r.id = h.record_id
WHERE LOWER(r.name) IN (%s) AND (r.network IS NULL OR $1::inet <<= r.network)`,
strings.Join(placeholders, ","))

if qType != "" {
query += fmt.Sprintf(` AND r.type = $%d`, len(names)+2)
args = append(args, string(qType))
Comment on lines +115 to +131
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix the placeholder/argument indexing before calling QueryContext.

Line 115 preallocates one extra element in args, so the no-qType path sends an extra nil argument. In the qType path, AND r.type = $N binds that preallocated nil, while the appended type becomes an unused extra parameter. This makes the new batch lookup fail in both branches.

Proposed fix
-	placeholders := make([]string, len(names))
-	args := make([]interface{}, len(names)+2)
-	args[0] = clientIP
+	placeholders := make([]string, len(names))
+	args := make([]interface{}, 0, len(names)+2)
+	args = append(args, clientIP)
 	for i, name := range names {
 		placeholders[i] = fmt.Sprintf("LOWER($%d)", i+2)
-		args[i+1] = name
+		args = append(args, name)
 	}
@@
 	if qType != "" {
-		query += fmt.Sprintf(` AND r.type = $%d`, len(names)+2)
+		query += fmt.Sprintf(` AND r.type = $%d`, len(args)+1)
 		args = append(args, string(qType))
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
args := make([]interface{}, len(names)+2)
args[0] = clientIP
for i, name := range names {
placeholders[i] = fmt.Sprintf("LOWER($%d)", i+2)
args[i+1] = name
}
query := fmt.Sprintf(`SELECT r.id, r.zone_id, r.name, r.type, r.content, r.ttl, r.priority, r.weight, r.port, r.network,
r.health_check_type, r.health_check_target, COALESCE(h.status, 'UNKNOWN')
FROM dns_records r
LEFT JOIN record_health h ON r.id = h.record_id
WHERE LOWER(r.name) IN (%s) AND (r.network IS NULL OR $1::inet <<= r.network)`,
strings.Join(placeholders, ","))
if qType != "" {
query += fmt.Sprintf(` AND r.type = $%d`, len(names)+2)
args = append(args, string(qType))
placeholders := make([]string, len(names))
args := make([]interface{}, 0, len(names)+2)
args = append(args, clientIP)
for i, name := range names {
placeholders[i] = fmt.Sprintf("LOWER($%d)", i+2)
args = append(args, name)
}
query := fmt.Sprintf(`SELECT r.id, r.zone_id, r.name, r.type, r.content, r.ttl, r.priority, r.weight, r.port, r.network,
r.health_check_type, r.health_check_target, COALESCE(h.status, 'UNKNOWN')
FROM dns_records r
LEFT JOIN record_health h ON r.id = h.record_id
WHERE LOWER(r.name) IN (%s) AND (r.network IS NULL OR $1::inet <<= r.network)`,
strings.Join(placeholders, ","))
if qType != "" {
query += fmt.Sprintf(` AND r.type = $%d`, len(args)+1)
args = append(args, string(qType))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/adapters/repository/postgres.go` around lines 115 - 131, The args
slice is mis-sized causing an extra nil argument and mismatched placeholders:
adjust how args and placeholders are built so args[0] is clientIP and the
subsequent name args occupy args[1:len(names)+1] (i.e. allocate args with
len(names)+1 or build args by appending rather than preallocating len(names)+2),
ensure placeholders use $2..$N accordingly, and when qType is present append the
type argument so the placeholder AND r.type = $M matches the appended value;
update the code around placeholders, args, and the QueryContext call (referenced
symbols: args, placeholders, clientIP, names, qType, QueryContext) so no extra
nil is passed and all placeholders bind the intended arguments.

}

rows, errQuery := r.db.QueryContext(ctx, query, args...)
if errQuery != nil {
return nil, errQuery
}
defer func() {
if errClose := rows.Close(); errClose != nil {
log.Printf("failed to close rows: %v", errClose)
}
}()

result := make(map[string][]domain.Record)
for rows.Next() {
var rec domain.Record
var priority, weight, port sql.NullInt32
var hcType, hcTarget, hStatus sql.NullString
if errScan := rows.Scan(&rec.ID, &rec.ZoneID, &rec.Name, &rec.Type, &rec.Content, &rec.TTL, &priority, &weight, &port, &rec.Network, &hcType, &hcTarget, &hStatus); errScan != nil {
return nil, errScan
}
if priority.Valid {
p := int(priority.Int32)
rec.Priority = &p
}
if weight.Valid {
w := int(weight.Int32)
rec.Weight = &w
}
if port.Valid {
p := int(port.Int32)
rec.Port = &p
}
if hcType.Valid {
rec.HealthCheckType = domain.HealthCheckType(hcType.String)
}
if hcTarget.Valid {
rec.HealthCheckTarget = hcTarget.String
}
if hStatus.Valid {
rec.HealthStatus = domain.HealthStatus(hStatus.String)
}
// Normalize key with trailing dot to match ConvertDomainToPacketRecord behavior
key := rec.Name
if !strings.HasSuffix(key, ".") {
key += "."
}
result[key] = append(result[key], rec)
Comment on lines +173 to +178
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Key the result map by the requested/canonical name, not rec.Name.

The SQL match is case-insensitive, but Lines 174-178 use the stored record name as the lookup key. If the caller asks for NS1.EXAMPLE.COM. and the A record is stored as ns1.example.com, the query matches but the caller’s map lookup misses and the glue record is dropped. The mock in internal/dns/server/server_test.go:131-146 avoids this by matching on normalized names and storing under the requested key.

Proposed fix
+	requestedKeys := make(map[string]string, len(names))
+	for _, name := range names {
+		key := strings.TrimSuffix(strings.ToLower(name), ".") + "."
+		if _, exists := requestedKeys[key]; !exists {
+			requestedKeys[key] = key
+		}
+	}
+
 	result := make(map[string][]domain.Record)
 	for rows.Next() {
@@
-		key := rec.Name
-		if !strings.HasSuffix(key, ".") {
-			key += "."
-		}
+		key := strings.TrimSuffix(strings.ToLower(rec.Name), ".") + "."
+		if requestedKey, ok := requestedKeys[key]; ok {
+			key = requestedKey
+		}
 		result[key] = append(result[key], rec)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/adapters/repository/postgres.go` around lines 173 - 178, The result
map is currently keyed by rec.Name which preserves storage casing and causes
lookups to miss the caller's requested/canonical name; change the key to the
requested/canonical name used for the SQL match (the variable representing the
query name/normalized name) instead of rec.Name, and normalize it to match
ConvertDomainToPacketRecord behavior (ensure trailing dot and consistent
case/normalization) before using it as the map key so matches like
"NS1.EXAMPLE.COM." correctly map to stored records.

}

return result, rows.Err()
}

// GetIPsForName implements ports.DNSRepository.
func (r *PostgresRepository) GetIPsForName(ctx context.Context, name string, clientIP string) ([]string, error) {
// Optimized query returning only content for Type A
Expand Down
9 changes: 6 additions & 3 deletions internal/adapters/repository/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import (
"testing"
"time"

_ "github.com/jackc/pgx/v5/stdlib"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/stdlib"
"github.com/poyrazK/cloudDNS/internal/core/domain"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
Expand Down Expand Up @@ -42,18 +43,20 @@ func setupTestDB(t *testing.T) (*sql.DB, func()) {
return
}

connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable", "default_query_exec_mode=describe_exec")
if err != nil {
containerErr = err
return
}

db, err := sql.Open("pgx", connStr)
connConfig, err := pgx.ParseConfig(connStr)
if err != nil {
containerErr = err
return
}

db := stdlib.OpenDB(*connConfig)

schemaPath := filepath.Join(".", "schema.sql")
schema, err := os.ReadFile(schemaPath) // #nosec G304
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/core/ports/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type RecordIterator interface {
// DNSRepository defines the interface for DNS data persistence.
type DNSRepository interface {
GetRecords(ctx context.Context, name string, qType domain.RecordType, clientIP string) ([]domain.Record, error)
GetRecordsByNames(ctx context.Context, names []string, qType domain.RecordType, clientIP string) (map[string][]domain.Record, error)
GetIPsForName(ctx context.Context, name string, clientIP string) ([]string, error)
GetZone(ctx context.Context, name string) (*domain.Zone, error)
GetZoneLongestMatch(ctx context.Context, qName string) (*domain.Zone, error)
Expand Down
15 changes: 15 additions & 0 deletions internal/core/services/dns_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ func (m *mockRepo) GetRecords(_ context.Context, name string, qType domain.Recor
return res, nil
}

func (m *mockRepo) GetRecordsByNames(_ context.Context, names []string, qType domain.RecordType, _ string) (map[string][]domain.Record, error) {
if m.err != nil {
return nil, m.err
}
result := make(map[string][]domain.Record)
for _, name := range names {
for _, r := range m.records {
if r.Name == name && (qType == "" || r.Type == qType) {
result[name] = append(result[name], r)
}
}
}
return result, nil
}

func (m *mockRepo) GetIPsForName(_ context.Context, name string, _ string) ([]string, error) {
if m.err != nil {
return nil, m.err
Expand Down
3 changes: 3 additions & 0 deletions internal/core/services/dnssec_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ func (m *mockDNSSECRepo) GetRecords(_ context.Context, _ string, _ domain.Record
func (m *mockDNSSECRepo) GetIPsForName(_ context.Context, _ string, _ string) ([]string, error) {
return nil, nil
}
func (m *mockDNSSECRepo) GetRecordsByNames(_ context.Context, _ []string, _ domain.RecordType, _ string) (map[string][]domain.Record, error) {
return nil, nil
}
func (m *mockDNSSECRepo) GetZone(_ context.Context, _ string) (*domain.Zone, error) { return nil, nil }
func (m *mockDNSSECRepo) GetZoneLongestMatch(_ context.Context, _ string) (*domain.Zone, error) { return nil, nil }
func (m *mockDNSSECRepo) GetRecord(_ context.Context, _ string, _ string, _ string) (*domain.Record, error) {
Expand Down
Loading
Loading