Skip to content

Commit

Permalink
feat: add diff endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
rainest committed Jul 30, 2024
1 parent fc0fce8 commit cc2b7e1
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 4 deletions.
5 changes: 3 additions & 2 deletions internal/dataplane/sendconfig/dbmode.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (s UpdateStrategyDBMode) Update(ctx context.Context, targetContent ContentW
ctx, cancel := context.WithCancel(ctx)
// TRR this is where db mode update strat handles events. resultchan is the entityaction channel
// TRR targetContent.Hash is the config hash
go s.HandleEvents(ctx, syncer.GetResultChan(), s.diagnostic, string(targetContent.Hash))
go s.HandleEvents(ctx, syncer.GetResultChan(), s.diagnostic, fmt.Sprintf("%x", targetContent.Hash))

_, errs, _ := syncer.Solve(ctx, s.concurrency, false, false)
cancel()
Expand Down Expand Up @@ -165,7 +165,7 @@ func (s *UpdateStrategyDBMode) HandleEvents(
case event := <-events:
if event.Error == nil {
s.logger.V(logging.DebugLevel).Info("updated gateway entity", "action", event.Action, "kind", event.Entity.Kind, "name", event.Entity.Name)
eventDiff := diagnostics.NewEntityDiff(event.Diff, string(event.Action))
eventDiff := diagnostics.NewEntityDiff(event.Diff, string(event.Action), event.Entity)
diff.Entities = append(diff.Entities, eventDiff)
} else {
s.logger.Error(event.Error, "failed updating gateway entity", "action", event.Action, "kind", event.Entity.Kind, "name", event.Entity.Name)
Expand All @@ -192,6 +192,7 @@ func (s *UpdateStrategyDBMode) HandleEvents(
// in some cases.
if diagnostic != nil {
diagnostic.Diffs <- diff
s.logger.V(logging.DebugLevel).Info("recorded database update events and diff", "hash", hash)
}
s.resourceErrorLock.Unlock()
return
Expand Down
10 changes: 10 additions & 0 deletions internal/diagnostics/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,13 @@ type FallbackAffectedObjectMeta struct {
// CausingObjects is the object that triggered this
CausingObjects []string `json:"causingObjects,omitempty"`
}

// DiffResponse is the GET /debug/config/diff response schema.
type DiffResponse struct {
// Message provides explanatory information, if any.
Message string `json:"message,omitempty"`
// ConfigHash is the config hash for the associated diffs.
ConfigHash string `json:"hash"`
// Diffs are the diffs for modified objects.
Diffs []EntityDiff `json:"diffs"`
}
24 changes: 22 additions & 2 deletions internal/diagnostics/diff.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package diagnostics

import (
"fmt"

"github.com/golang-collections/collections/queue"
"github.com/kong/go-database-reconciler/pkg/diff"
)
Expand Down Expand Up @@ -60,7 +62,7 @@ type ConfigDiff struct {
}

type EntityDiff struct {
Source sourceResource `json:"kubernetesResource"`
//Source sourceResource `json:"kubernetesResource"`
Generated generatedEntity `json:"kongEntity"`
Action string `json:"action"`
Diff string `json:"diff,omitempty"`
Expand All @@ -71,7 +73,7 @@ func NewEntityDiff(diff string, action string, entity diff.Entity) EntityDiff {
return EntityDiff{
// TODO this is mostly a stub at present. Need to either derive the source from tags or just omit it for now with
// a nice to have feature issue, or a simpler YAGNI but if someone asks add it TODO here.
Source: sourceResource{},
//Source: sourceResource{},
Generated: generatedEntity{
Name: entity.Name,
Kind: entity.Kind,
Expand Down Expand Up @@ -138,3 +140,21 @@ func (d *diffMap) Update(diff ConfigDiff) {
d.diffs[diff.Hash] = diff
return
}

// Latest returns the newest diff hash.
func (d *diffMap) Latest() string {
return d.hashQueue.Peek().(string)
}

// ByHash returns the diff array matching the given hash.
func (d *diffMap) ByHash(hash string) ([]EntityDiff, error) {
if diff, ok := d.diffs[hash]; ok {
return diff.Entities, nil
}
return []EntityDiff{}, fmt.Errorf("no diff found for hash %s", hash)
}

// Len returns the number of cached diffs.
func (d *diffMap) Len() int {
return len(d.diffs)
}
66 changes: 66 additions & 0 deletions internal/diagnostics/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const (

// diffHistorySize is the number of diffs to keep in history.
diffHistorySize = 5

// diffHashQuery is the query string key for requesting a specific hash's diff.
diffHashQuery = "hash"
)

// Server is an HTTP server running exposing the pprof profiling tool, and processing diagnostic dumps of Kong configurations.
Expand Down Expand Up @@ -212,6 +215,7 @@ func (s *Server) installConfigDebugHandlers(mux *http.ServeMux) {
mux.HandleFunc("/debug/config/failed", s.handleLastFailedConfig)
mux.HandleFunc("/debug/config/fallback", s.handleCurrentFallback)
mux.HandleFunc("/debug/config/raw-error", s.handleLastErrBody)
mux.HandleFunc("/debug/config/diff-report", s.handleDiffReport)
}

// redirectTo redirects request to a certain destination.
Expand Down Expand Up @@ -269,3 +273,65 @@ func (s *Server) handleLastErrBody(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(http.StatusInternalServerError)
}
}

func (s *Server) handleDiffReport(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
s.diffLock.RLock()
defer s.diffLock.RUnlock()

// GDR has no notion of sensitive data, so its raw diffs will include credentials and certificates when they
// change. We could make this fancier by walking through the entity types to exclude them if sensitive is not
// enabled, but would need to maintain a list of such types. Filter would probably happen on the producer (DB
// update strategy) side, since that's where we currently filter for the dump.
if !s.clientDiagnostic.DumpsIncludeSensitive {
if err := json.NewEncoder(rw).Encode(DiffResponse{
Message: "diffs include sensitive data: set CONTROLLER_DUMP_SENSITIVE_CONFIG=true in environment to enable",
}); err == nil {
rw.WriteHeader(http.StatusNotFound)
} else {
rw.WriteHeader(http.StatusInternalServerError)
}
return
}

if s.diffs.Len() == 0 {
if err := json.NewEncoder(rw).Encode(DiffResponse{
Message: "no diffs available",
}); err == nil {
rw.WriteHeader(http.StatusOK)
} else {
rw.WriteHeader(http.StatusInternalServerError)
}
return
}

var requestedHash string
var message string
requestedHashQuery := r.URL.Query()[diffHashQuery]
if len(requestedHashQuery) == 0 {
requestedHash = s.diffs.Latest()
} else {
if len(requestedHashQuery) > 1 {
message = "this endpoint does not support requesting multiple diffs, using the first hash provided"
}
requestedHash = requestedHashQuery[0]
}

diffs, err := s.diffs.ByHash(requestedHash)
if err != nil {
message = err.Error()
rw.WriteHeader(http.StatusNotFound)
}

response := DiffResponse{
Message: message,
ConfigHash: requestedHash,
Diffs: diffs,
}

if err := json.NewEncoder(rw).Encode(response); err == nil {
rw.WriteHeader(http.StatusOK)
} else {
rw.WriteHeader(http.StatusInternalServerError)
}
}

0 comments on commit cc2b7e1

Please sign in to comment.