sigs.k8s.io/external-dns@v0.14.1/provider/webhook/api/httpapi.go (about)

     1  /*
     2  Copyright 2023 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 api
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"net"
    23  	"net/http"
    24  	"time"
    25  
    26  	"sigs.k8s.io/external-dns/endpoint"
    27  	"sigs.k8s.io/external-dns/plan"
    28  	"sigs.k8s.io/external-dns/provider"
    29  
    30  	log "github.com/sirupsen/logrus"
    31  )
    32  
    33  const (
    34  	MediaTypeFormatAndVersion = "application/external.dns.webhook+json;version=1"
    35  	ContentTypeHeader         = "Content-Type"
    36  )
    37  
    38  type WebhookServer struct {
    39  	Provider provider.Provider
    40  }
    41  
    42  func (p *WebhookServer) RecordsHandler(w http.ResponseWriter, req *http.Request) {
    43  	switch req.Method {
    44  	case http.MethodGet:
    45  		records, err := p.Provider.Records(context.Background())
    46  		if err != nil {
    47  			log.Errorf("Failed to get Records: %v", err)
    48  			w.WriteHeader(http.StatusInternalServerError)
    49  			return
    50  		}
    51  		w.Header().Set(ContentTypeHeader, MediaTypeFormatAndVersion)
    52  		w.WriteHeader(http.StatusOK)
    53  		if err := json.NewEncoder(w).Encode(records); err != nil {
    54  			log.Errorf("Failed to encode records: %v", err)
    55  		}
    56  		return
    57  	case http.MethodPost:
    58  		var changes plan.Changes
    59  		if err := json.NewDecoder(req.Body).Decode(&changes); err != nil {
    60  			log.Errorf("Failed to decode changes: %v", err)
    61  			w.WriteHeader(http.StatusBadRequest)
    62  			return
    63  		}
    64  		err := p.Provider.ApplyChanges(context.Background(), &changes)
    65  		if err != nil {
    66  			log.Errorf("Failed to apply changes: %v", err)
    67  			w.WriteHeader(http.StatusInternalServerError)
    68  			return
    69  		}
    70  		w.WriteHeader(http.StatusNoContent)
    71  		return
    72  	default:
    73  		log.Errorf("Unsupported method %s", req.Method)
    74  		w.WriteHeader(http.StatusBadRequest)
    75  	}
    76  }
    77  
    78  func (p *WebhookServer) AdjustEndpointsHandler(w http.ResponseWriter, req *http.Request) {
    79  	if req.Method != http.MethodPost {
    80  		log.Errorf("Unsupported method %s", req.Method)
    81  		w.WriteHeader(http.StatusBadRequest)
    82  		return
    83  	}
    84  
    85  	pve := []*endpoint.Endpoint{}
    86  	if err := json.NewDecoder(req.Body).Decode(&pve); err != nil {
    87  		log.Errorf("Failed to decode in adjustEndpointsHandler: %v", err)
    88  		w.WriteHeader(http.StatusBadRequest)
    89  		return
    90  	}
    91  	w.Header().Set(ContentTypeHeader, MediaTypeFormatAndVersion)
    92  	pve, err := p.Provider.AdjustEndpoints(pve)
    93  	if err != nil {
    94  		log.Errorf("Failed to call adjust endpoints: %v", err)
    95  		w.WriteHeader(http.StatusInternalServerError)
    96  	}
    97  	if err := json.NewEncoder(w).Encode(&pve); err != nil {
    98  		log.Errorf("Failed to encode in adjustEndpointsHandler: %v", err)
    99  		w.WriteHeader(http.StatusInternalServerError)
   100  		return
   101  	}
   102  }
   103  
   104  func (p *WebhookServer) NegotiateHandler(w http.ResponseWriter, req *http.Request) {
   105  	w.Header().Set(ContentTypeHeader, MediaTypeFormatAndVersion)
   106  	json.NewEncoder(w).Encode(p.Provider.GetDomainFilter())
   107  }
   108  
   109  // StartHTTPApi starts a HTTP server given any provider.
   110  // the function takes an optional channel as input which is used to signal that the server has started.
   111  // The server will listen on port `providerPort`.
   112  // The server will respond to the following endpoints:
   113  // - / (GET): initialization, negotiates headers and returns the domain filter
   114  // - /records (GET): returns the current records
   115  // - /records (POST): applies the changes
   116  // - /adjustendpoints (POST): executes the AdjustEndpoints method
   117  func StartHTTPApi(provider provider.Provider, startedChan chan struct{}, readTimeout, writeTimeout time.Duration, providerPort string) {
   118  	p := WebhookServer{
   119  		Provider: provider,
   120  	}
   121  
   122  	m := http.NewServeMux()
   123  	m.HandleFunc("/", p.NegotiateHandler)
   124  	m.HandleFunc("/records", p.RecordsHandler)
   125  	m.HandleFunc("/adjustendpoints", p.AdjustEndpointsHandler)
   126  
   127  	s := &http.Server{
   128  		Addr:         providerPort,
   129  		Handler:      m,
   130  		ReadTimeout:  readTimeout,
   131  		WriteTimeout: writeTimeout,
   132  	}
   133  
   134  	l, err := net.Listen("tcp", providerPort)
   135  	if err != nil {
   136  		log.Fatal(err)
   137  	}
   138  
   139  	if startedChan != nil {
   140  		startedChan <- struct{}{}
   141  	}
   142  
   143  	if err := s.Serve(l); err != nil {
   144  		log.Fatal(err)
   145  	}
   146  }