Skip to content

Commit 52f5d42

Browse files
committed
Initial implementation of skupper diagnose
This introduces a framework for diagnostics commands, with an initial implementation of two Kubernetes checks (verifying that the Kubernetes API is accessible, and that the Kubernetes version is supported). The framework supports simple declaration of Cobra commands constructed from individual diagnostics, and dependencies between diagnostics. The kind spinner is copied with some adaptations borrowed from the Submariner project. Signed-off-by: Stephen Kitt <[email protected]>
1 parent d9d37ea commit 52f5d42

File tree

14 files changed

+1133
-5
lines changed

14 files changed

+1133
-5
lines changed

go.mod

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/gorilla/mux v1.8.1
2121
github.com/heimdalr/dag v1.5.0
2222
github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5
23+
github.com/mattn/go-isatty v0.0.20
2324
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0
2425
github.com/oapi-codegen/runtime v1.1.1
2526
github.com/openshift/api v0.0.0-20210428205234-a8389931bee7
@@ -38,6 +39,8 @@ require (
3839
k8s.io/api v0.31.0
3940
k8s.io/apimachinery v0.31.0
4041
k8s.io/client-go v0.31.0
42+
k8s.io/code-generator v0.31.0
43+
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
4144
sigs.k8s.io/yaml v1.4.0
4245
)
4346

@@ -76,14 +79,12 @@ require (
7679
github.com/json-iterator/go v1.1.12 // indirect
7780
github.com/mailru/easyjson v0.7.7 // indirect
7881
github.com/mattn/go-colorable v0.1.13 // indirect
79-
github.com/mattn/go-isatty v0.0.20 // indirect
8082
github.com/mitchellh/mapstructure v1.4.3 // indirect
8183
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
8284
github.com/modern-go/reflect2 v1.0.2 // indirect
8385
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
8486
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
8587
github.com/oklog/ulid v1.3.1 // indirect
86-
github.com/onsi/gomega v1.33.1 // indirect
8788
github.com/opentracing/opentracing-go v1.2.0 // indirect
8889
github.com/perimeterx/marshmallow v1.1.5 // indirect
8990
github.com/pkg/errors v0.9.1 // indirect
@@ -102,12 +103,9 @@ require (
102103
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
103104
gopkg.in/inf.v0 v0.9.1 // indirect
104105
gopkg.in/yaml.v2 v2.4.0 // indirect
105-
k8s.io/apiextensions-apiserver v0.31.0 // indirect
106-
k8s.io/code-generator v0.31.0 // indirect
107106
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect
108107
k8s.io/klog/v2 v2.130.1 // indirect
109108
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
110-
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
111109
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
112110
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
113111
)
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
Copyright 2019 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 cli
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"io"
23+
"runtime"
24+
"strings"
25+
"sync"
26+
"sync/atomic"
27+
28+
"github.com/skupperproject/skupper/internal/cmd/skupper/diagnose/env"
29+
"github.com/skupperproject/skupper/internal/cmd/skupper/diagnose/log"
30+
)
31+
32+
// Logger is the kind cli's log.Logger implementation.
33+
type Logger struct {
34+
writer io.Writer
35+
bufferPool *bufferPool
36+
writerMu sync.Mutex
37+
verbosity log.Level
38+
// kind special additions
39+
isSmartWriter bool
40+
}
41+
42+
var _ log.Logger = &Logger{}
43+
44+
// NewLogger returns a new Logger with the given verbosity.
45+
func NewLogger(writer io.Writer, verbosity log.Level) *Logger {
46+
l := &Logger{
47+
verbosity: verbosity,
48+
bufferPool: newBufferPool(),
49+
}
50+
l.SetWriter(writer)
51+
52+
return l
53+
}
54+
55+
// SetWriter sets the output writer.
56+
func (l *Logger) SetWriter(w io.Writer) {
57+
l.writerMu.Lock()
58+
defer l.writerMu.Unlock()
59+
l.writer = w
60+
_, isSpinner := w.(*Spinner)
61+
l.isSmartWriter = isSpinner || env.IsSmartTerminal(w)
62+
}
63+
64+
// ColorEnabled returns true if the caller is OK to write colored output.
65+
func (l *Logger) ColorEnabled() bool {
66+
l.writerMu.Lock()
67+
defer l.writerMu.Unlock()
68+
69+
return l.isSmartWriter
70+
}
71+
72+
func (l *Logger) getVerbosity() log.Level {
73+
return log.Level(atomic.LoadInt32((*int32)(&l.verbosity)))
74+
}
75+
76+
// SetVerbosity sets the loggers verbosity.
77+
func (l *Logger) SetVerbosity(verbosity log.Level) {
78+
atomic.StoreInt32((*int32)(&l.verbosity), int32(verbosity))
79+
}
80+
81+
// synchronized write to the inner writer.
82+
func (l *Logger) write(p []byte) (n int, err error) {
83+
l.writerMu.Lock()
84+
defer l.writerMu.Unlock()
85+
86+
return l.writer.Write(p) //nolint:wrapcheck // No need to wrap here
87+
}
88+
89+
// writeBuffer writes buf with write, ensuring there is a trailing newline.
90+
func (l *Logger) writeBuffer(buf *bytes.Buffer) {
91+
// ensure trailing newline
92+
if buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\n' {
93+
buf.WriteByte('\n')
94+
}
95+
96+
// TODO: should we handle this somehow??
97+
// Who logs for the logger? 🤔
98+
_, _ = l.write(buf.Bytes())
99+
}
100+
101+
// print writes a simple string to the log writer.
102+
func (l *Logger) print(message string) {
103+
buf := bytes.NewBufferString(message)
104+
l.writeBuffer(buf)
105+
}
106+
107+
// printf is roughly fmt.Fprintf against the log writer.
108+
func (l *Logger) printf(format string, args ...interface{}) {
109+
buf := l.bufferPool.Get()
110+
fmt.Fprintf(buf, format, args...)
111+
l.writeBuffer(buf)
112+
l.bufferPool.Put(buf)
113+
}
114+
115+
// addDebugHeader inserts the debug line header to buf.
116+
func addDebugHeader(buf *bytes.Buffer) {
117+
_, file, line, ok := runtime.Caller(3)
118+
// lifted from klog
119+
if !ok {
120+
file = "???"
121+
line = 1
122+
} else if slash := strings.LastIndex(file, "/"); slash >= 0 {
123+
path := file
124+
file = path[slash+1:]
125+
126+
if dirsep := strings.LastIndex(path[:slash], "/"); dirsep >= 0 {
127+
file = path[dirsep+1:]
128+
}
129+
}
130+
131+
buf.Grow(len(file) + 11) // we know at least this many bytes are needed.
132+
buf.WriteString("DEBUG: ")
133+
buf.WriteString(file)
134+
buf.WriteByte(':')
135+
fmt.Fprintf(buf, "%d", line)
136+
buf.WriteByte(']')
137+
buf.WriteByte(' ')
138+
}
139+
140+
// debug is like print but with a debug log header.
141+
func (l *Logger) debug(message string) {
142+
buf := l.bufferPool.Get()
143+
addDebugHeader(buf)
144+
buf.WriteString(message)
145+
l.writeBuffer(buf)
146+
l.bufferPool.Put(buf)
147+
}
148+
149+
// debugf is like printf but with a debug log header.
150+
func (l *Logger) debugf(format string, args ...interface{}) {
151+
buf := l.bufferPool.Get()
152+
addDebugHeader(buf)
153+
fmt.Fprintf(buf, format, args...)
154+
l.writeBuffer(buf)
155+
l.bufferPool.Put(buf)
156+
}
157+
158+
// Warn is part of the log.Logger interface.
159+
func (l *Logger) Warn(message string) {
160+
l.print(message)
161+
}
162+
163+
// Warnf is part of the log.Logger interface.
164+
func (l *Logger) Warnf(format string, args ...interface{}) {
165+
l.printf(format, args...)
166+
}
167+
168+
// Error is part of the log.Logger interface.
169+
func (l *Logger) Error(message string) {
170+
l.print(message)
171+
}
172+
173+
// Errorf is part of the log.Logger interface.
174+
func (l *Logger) Errorf(format string, args ...interface{}) {
175+
l.printf(format, args...)
176+
}
177+
178+
// V is part of the log.Logger interface.
179+
func (l *Logger) V(level log.Level) log.InfoLogger {
180+
return infoLogger{
181+
logger: l,
182+
level: level,
183+
enabled: level <= l.getVerbosity(),
184+
}
185+
}
186+
187+
// infoLogger implements log.InfoLogger for Logger.
188+
type infoLogger struct {
189+
logger *Logger
190+
level log.Level
191+
enabled bool
192+
}
193+
194+
// Enabled is part of the log.InfoLogger interface.
195+
func (i infoLogger) Enabled() bool {
196+
return i.enabled
197+
}
198+
199+
// Info is part of the log.InfoLogger interface.
200+
func (i infoLogger) Info(message string) {
201+
if !i.enabled {
202+
return
203+
}
204+
// for > 0, we are writing debug messages, include extra info
205+
if i.level > 0 {
206+
i.logger.debug(message)
207+
} else {
208+
i.logger.print(message)
209+
}
210+
}
211+
212+
// Infof is part of the log.InfoLogger interface.
213+
func (i infoLogger) Infof(format string, args ...interface{}) {
214+
if !i.enabled {
215+
return
216+
}
217+
// for > 0, we are writing debug messages, include extra info
218+
if i.level > 0 {
219+
i.logger.debugf(format, args...)
220+
} else {
221+
i.logger.printf(format, args...)
222+
}
223+
}
224+
225+
// bufferPool is a type safe sync.Pool of *byte.Buffer, guaranteed to be Reset.
226+
type bufferPool struct {
227+
sync.Pool
228+
}
229+
230+
// newBufferPool returns a new bufferPool.
231+
func newBufferPool() *bufferPool {
232+
return &bufferPool{
233+
sync.Pool{
234+
New: func() interface{} {
235+
// The Pool's New function should generally only return pointer
236+
// types, since a pointer can be put into the return interface
237+
// value without an allocation:
238+
return new(bytes.Buffer)
239+
},
240+
},
241+
}
242+
}
243+
244+
// Get obtains a buffer from the pool.
245+
func (b *bufferPool) Get() *bytes.Buffer {
246+
return b.Pool.Get().(*bytes.Buffer)
247+
}
248+
249+
// Put returns a buffer to the pool, resetting it first.
250+
func (b *bufferPool) Put(x *bytes.Buffer) {
251+
// only store small buffers to avoid pointless allocation
252+
// avoid keeping arbitrarily large buffers
253+
if x.Len() > 256 {
254+
return
255+
}
256+
257+
x.Reset()
258+
b.Pool.Put(x)
259+
}

0 commit comments

Comments
 (0)