blob: 45c1c3b2cdb39ff7b5656a898bcc92ce99051a32 [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2020 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package rest
18
19import (
20 "fmt"
21 "io"
22 "net/http"
23 "sync"
24
25 "k8s.io/klog/v2"
26
27 "k8s.io/apimachinery/pkg/util/net"
28)
29
30// WarningHandler is an interface for handling warning headers
31type WarningHandler interface {
32 // HandleWarningHeader is called with the warn code, agent, and text when a warning header is countered.
33 HandleWarningHeader(code int, agent string, text string)
34}
35
36var (
37 defaultWarningHandler WarningHandler = WarningLogger{}
38 defaultWarningHandlerLock sync.RWMutex
39)
40
41// SetDefaultWarningHandler sets the default handler client uses when warning headers are encountered.
42// By default, warnings are printed to stderr.
43func SetDefaultWarningHandler(l WarningHandler) {
44 defaultWarningHandlerLock.Lock()
45 defer defaultWarningHandlerLock.Unlock()
46 defaultWarningHandler = l
47}
48func getDefaultWarningHandler() WarningHandler {
49 defaultWarningHandlerLock.RLock()
50 defer defaultWarningHandlerLock.RUnlock()
51 l := defaultWarningHandler
52 return l
53}
54
55// NoWarnings is an implementation of WarningHandler that suppresses warnings.
56type NoWarnings struct{}
57
58func (NoWarnings) HandleWarningHeader(code int, agent string, message string) {}
59
60// WarningLogger is an implementation of WarningHandler that logs code 299 warnings
61type WarningLogger struct{}
62
63func (WarningLogger) HandleWarningHeader(code int, agent string, message string) {
64 if code != 299 || len(message) == 0 {
65 return
66 }
67 klog.Warning(message)
68}
69
70type warningWriter struct {
71 // out is the writer to output warnings to
72 out io.Writer
73 // opts contains options controlling warning output
74 opts WarningWriterOptions
75 // writtenLock guards written and writtenCount
76 writtenLock sync.Mutex
77 writtenCount int
78 written map[string]struct{}
79}
80
81// WarningWriterOptions controls the behavior of a WarningHandler constructed using NewWarningWriter()
82type WarningWriterOptions struct {
83 // Deduplicate indicates a given warning message should only be written once.
84 // Setting this to true in a long-running process handling many warnings can result in increased memory use.
85 Deduplicate bool
86 // Color indicates that warning output can include ANSI color codes
87 Color bool
88}
89
90// NewWarningWriter returns an implementation of WarningHandler that outputs code 299 warnings to the specified writer.
91func NewWarningWriter(out io.Writer, opts WarningWriterOptions) *warningWriter {
92 h := &warningWriter{out: out, opts: opts}
93 if opts.Deduplicate {
94 h.written = map[string]struct{}{}
95 }
96 return h
97}
98
99const (
100 yellowColor = "\u001b[33;1m"
101 resetColor = "\u001b[0m"
102)
103
104// HandleWarningHeader prints warnings with code=299 to the configured writer.
105func (w *warningWriter) HandleWarningHeader(code int, agent string, message string) {
106 if code != 299 || len(message) == 0 {
107 return
108 }
109
110 w.writtenLock.Lock()
111 defer w.writtenLock.Unlock()
112
113 if w.opts.Deduplicate {
114 if _, alreadyWritten := w.written[message]; alreadyWritten {
115 return
116 }
117 w.written[message] = struct{}{}
118 }
119 w.writtenCount++
120
121 if w.opts.Color {
122 fmt.Fprintf(w.out, "%sWarning:%s %s\n", yellowColor, resetColor, message)
123 } else {
124 fmt.Fprintf(w.out, "Warning: %s\n", message)
125 }
126}
127
128func (w *warningWriter) WarningCount() int {
129 w.writtenLock.Lock()
130 defer w.writtenLock.Unlock()
131 return w.writtenCount
132}
133
134func handleWarnings(headers http.Header, handler WarningHandler) []net.WarningHeader {
135 if handler == nil {
136 handler = getDefaultWarningHandler()
137 }
138
139 warnings, _ := net.ParseWarningHeaders(headers["Warning"])
140 for _, warning := range warnings {
141 handler.HandleWarningHeader(warning.Code, warning.Agent, warning.Text)
142 }
143 return warnings
144}