mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-09-22 16:41:01 +02:00
* Pi hole V6 impl * Code Review Part One * Fix Go Lint * Regenerate Flags file * Increase code coverage 1/2 * Increase code coverage 2/2 * Fix merge conflict => Provider init move from main.go to execute.go
140 lines
3.9 KiB
Go
140 lines
3.9 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package pihole
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"sigs.k8s.io/external-dns/endpoint"
|
|
"sigs.k8s.io/external-dns/plan"
|
|
"sigs.k8s.io/external-dns/provider"
|
|
)
|
|
|
|
// ErrNoPiholeServer is returned when there is no Pihole server configured
|
|
// in the environment.
|
|
var ErrNoPiholeServer = errors.New("no pihole server found in the environment or flags")
|
|
|
|
// PiholeProvider is an implementation of Provider for Pi-hole Local DNS.
|
|
type PiholeProvider struct {
|
|
provider.BaseProvider
|
|
api piholeAPI
|
|
}
|
|
|
|
// PiholeConfig is used for configuring a PiholeProvider.
|
|
type PiholeConfig struct {
|
|
// The root URL of the Pi-hole server.
|
|
Server string
|
|
// An optional password if the server is protected.
|
|
Password string
|
|
// Disable verification of TLS certificates.
|
|
TLSInsecureSkipVerify bool
|
|
// A filter to apply when looking up and applying records.
|
|
DomainFilter endpoint.DomainFilter
|
|
// Do nothing and log what would have changed to stdout.
|
|
DryRun bool
|
|
// PiHole API version =<5 or >=6, default is 5
|
|
APIVersion string
|
|
}
|
|
|
|
// Helper struct for de-duping DNS entry updates.
|
|
type piholeEntryKey struct {
|
|
Target string
|
|
RecordType string
|
|
}
|
|
|
|
// NewPiholeProvider initializes a new Pi-hole Local DNS based Provider.
|
|
func NewPiholeProvider(cfg PiholeConfig) (*PiholeProvider, error) {
|
|
var api piholeAPI
|
|
var err error
|
|
switch cfg.APIVersion {
|
|
case "6":
|
|
api, err = newPiholeClientV6(cfg)
|
|
default:
|
|
api, err = newPiholeClient(cfg)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &PiholeProvider{api: api}, nil
|
|
}
|
|
|
|
// Records implements Provider, populating a slice of endpoints from
|
|
// Pi-Hole local DNS.
|
|
func (p *PiholeProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
|
aRecords, err := p.api.listRecords(ctx, endpoint.RecordTypeA)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aaaaRecords, err := p.api.listRecords(ctx, endpoint.RecordTypeAAAA)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cnameRecords, err := p.api.listRecords(ctx, endpoint.RecordTypeCNAME)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aRecords = append(aRecords, aaaaRecords...)
|
|
return append(aRecords, cnameRecords...), nil
|
|
}
|
|
|
|
// ApplyChanges implements Provider, syncing desired state with the Pi-hole server Local DNS.
|
|
func (p *PiholeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
|
// Handle pure deletes first.
|
|
for _, ep := range changes.Delete {
|
|
if err := p.api.deleteRecord(ctx, ep); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Handle updated state - there are no endpoints for updating in place.
|
|
updateNew := make(map[piholeEntryKey]*endpoint.Endpoint)
|
|
for _, ep := range changes.UpdateNew {
|
|
key := piholeEntryKey{ep.DNSName, ep.RecordType}
|
|
updateNew[key] = ep
|
|
}
|
|
|
|
for _, ep := range changes.UpdateOld {
|
|
// Check if this existing entry has an exact match for an updated entry and skip it if so.
|
|
key := piholeEntryKey{ep.DNSName, ep.RecordType}
|
|
if newRecord := updateNew[key]; newRecord != nil {
|
|
// PiHole only has a single target; no need to compare other fields.
|
|
if newRecord.Targets[0] == ep.Targets[0] {
|
|
delete(updateNew, key)
|
|
continue
|
|
}
|
|
}
|
|
if err := p.api.deleteRecord(ctx, ep); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Handle pure creates before applying new updated state.
|
|
for _, ep := range changes.Create {
|
|
if err := p.api.createRecord(ctx, ep); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, ep := range updateNew {
|
|
if err := p.api.createRecord(ctx, ep); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|