sigs.k8s.io/external-dns@v0.14.1/provider/pihole/pihole.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package pihole 18 19 import ( 20 "context" 21 "errors" 22 23 "sigs.k8s.io/external-dns/endpoint" 24 "sigs.k8s.io/external-dns/plan" 25 "sigs.k8s.io/external-dns/provider" 26 ) 27 28 // ErrNoPiholeServer is returned when there is no Pihole server configured 29 // in the environment. 30 var ErrNoPiholeServer = errors.New("no pihole server found in the environment or flags") 31 32 // PiholeProvider is an implementation of Provider for Pi-hole Local DNS. 33 type PiholeProvider struct { 34 provider.BaseProvider 35 api piholeAPI 36 } 37 38 // PiholeConfig is used for configuring a PiholeProvider. 39 type PiholeConfig struct { 40 // The root URL of the Pi-hole server. 41 Server string 42 // An optional password if the server is protected. 43 Password string 44 // Disable verification of TLS certificates. 45 TLSInsecureSkipVerify bool 46 // A filter to apply when looking up and applying records. 47 DomainFilter endpoint.DomainFilter 48 // Do nothing and log what would have changed to stdout. 49 DryRun bool 50 } 51 52 // Helper struct for de-duping DNS entry updates. 53 type piholeEntryKey struct { 54 Target string 55 RecordType string 56 } 57 58 // NewPiholeProvider initializes a new Pi-hole Local DNS based Provider. 59 func NewPiholeProvider(cfg PiholeConfig) (*PiholeProvider, error) { 60 api, err := newPiholeClient(cfg) 61 if err != nil { 62 return nil, err 63 } 64 return &PiholeProvider{api: api}, nil 65 } 66 67 // Records implements Provider, populating a slice of endpoints from 68 // Pi-Hole local DNS. 69 func (p *PiholeProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { 70 aRecords, err := p.api.listRecords(ctx, endpoint.RecordTypeA) 71 if err != nil { 72 return nil, err 73 } 74 cnameRecords, err := p.api.listRecords(ctx, endpoint.RecordTypeCNAME) 75 if err != nil { 76 return nil, err 77 } 78 return append(aRecords, cnameRecords...), nil 79 } 80 81 // ApplyChanges implements Provider, syncing desired state with the Pi-hole server Local DNS. 82 func (p *PiholeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { 83 // Handle pure deletes first. 84 for _, ep := range changes.Delete { 85 if err := p.api.deleteRecord(ctx, ep); err != nil { 86 return err 87 } 88 } 89 90 // Handle updated state - there are no endpoints for updating in place. 91 updateNew := make(map[piholeEntryKey]*endpoint.Endpoint) 92 for _, ep := range changes.UpdateNew { 93 key := piholeEntryKey{ep.DNSName, ep.RecordType} 94 updateNew[key] = ep 95 } 96 97 for _, ep := range changes.UpdateOld { 98 // Check if this existing entry has an exact match for an updated entry and skip it if so. 99 key := piholeEntryKey{ep.DNSName, ep.RecordType} 100 if newRecord := updateNew[key]; newRecord != nil { 101 // PiHole only has a single target; no need to compare other fields. 102 if newRecord.Targets[0] == ep.Targets[0] { 103 delete(updateNew, key) 104 continue 105 } 106 } 107 if err := p.api.deleteRecord(ctx, ep); err != nil { 108 return err 109 } 110 } 111 112 // Handle pure creates before applying new updated state. 113 for _, ep := range changes.Create { 114 if err := p.api.createRecord(ctx, ep); err != nil { 115 return err 116 } 117 } 118 for _, ep := range updateNew { 119 if err := p.api.createRecord(ctx, ep); err != nil { 120 return err 121 } 122 } 123 124 return nil 125 }