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  }