Skip to content

Commit

Permalink
Merge pull request #8268 from tinyspeck/am_vtadmin_debug
Browse files Browse the repository at this point in the history
[vtadmin] Add debug endpoints
  • Loading branch information
ajm188 authored Jun 7, 2021
2 parents f051584 + 100a9db commit c489dc0
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 1 deletion.
4 changes: 4 additions & 0 deletions go/cmd/vtadmin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"vitess.io/vitess/go/vt/vtadmin/cluster"
"vitess.io/vitess/go/vt/vtadmin/grpcserver"
vtadminhttp "vitess.io/vitess/go/vt/vtadmin/http"
"vitess.io/vitess/go/vt/vtadmin/http/debug"
)

var (
Expand Down Expand Up @@ -129,6 +130,9 @@ func main() {
rootCmd.Flags().BoolVar(&httpOpts.EnableTracing, "http-tracing", false, "whether to enable tracing on the HTTP server")

rootCmd.Flags().BoolVar(&httpOpts.DisableCompression, "http-no-compress", false, "whether to disable compression of HTTP API responses")
rootCmd.Flags().BoolVar(&httpOpts.DisableDebug, "http-no-debug", false, "whether to disable /debug/pprof/* and /debug/env HTTP endpoints")
rootCmd.Flags().Var(&debug.OmitEnv, "http-debug-omit-env", "name of an environment variable to omit from /debug/env, if http debug endpoints are enabled. specify multiple times to omit multiple env vars")
rootCmd.Flags().Var(&debug.SanitizeEnv, "http-debug-sanitize-env", "name of an environment variable to sanitize in /debug/env, if http debug endpoints are enabled. specify multiple times to sanitize multiple env vars")
rootCmd.Flags().StringSliceVar(&httpOpts.CORSOrigins, "http-origin", []string{}, "repeated, comma-separated flag of allowed CORS origins. omit to disable CORS")
rootCmd.Flags().StringVar(&httpOpts.ExperimentalOptions.TabletURLTmpl,
"http-tablet-url-tmpl",
Expand Down
80 changes: 80 additions & 0 deletions go/flagutil/sets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2021 The Vitess Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package flagutil

import (
"flag"
"strings"

"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/sets"
)

var (
_ flag.Value = (*StringSetFlag)(nil)
_ pflag.Value = (*StringSetFlag)(nil)
)

// StringSetFlag can be used to collect multiple instances of a flag into a set
// of values.
//
// For example, defining the following:
//
// var x flagutil.StringSetFlag
// flag.Var(&x, "foo", "")
//
// And then specifying "-foo x -foo y -foo x", will result in a set of {x, y}.
//
// In addition to implemnting the standard flag.Value interface, it also
// provides an implementation of pflag.Value, so it is usable in libraries like
// cobra.
type StringSetFlag struct {
set sets.String
}

// ToSet returns the underlying string set, or an empty set if the underlying
// set is nil.
func (set *StringSetFlag) ToSet() sets.String {
if set.set == nil {
set.set = sets.NewString()
}

return set.set
}

// Set is part of the pflag.Value and flag.Value interfaces.
func (set *StringSetFlag) Set(s string) error {
if set.set == nil {
set.set = sets.NewString(s)
return nil
}

set.set.Insert(s)
return nil
}

// String is part of the pflag.Value and flag.Value interfaces.
func (set *StringSetFlag) String() string {
if set.set == nil {
return ""
}

return strings.Join(set.set.List(), ", ")
}

// Type is part of the pflag.Value interface.
func (set *StringSetFlag) Type() string { return "StringSetFlag" }
15 changes: 15 additions & 0 deletions go/vt/vtadmin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
stderrors "errors"
"fmt"
"net/http"
"net/http/pprof"
stdsort "sort"
"strings"
"sync"
Expand All @@ -40,6 +41,7 @@ import (
"vitess.io/vitess/go/vt/vtadmin/errors"
"vitess.io/vitess/go/vt/vtadmin/grpcserver"
vtadminhttp "vitess.io/vitess/go/vt/vtadmin/http"
"vitess.io/vitess/go/vt/vtadmin/http/debug"
"vitess.io/vitess/go/vt/vtadmin/http/experimental"
vthandlers "vitess.io/vitess/go/vt/vtadmin/http/handlers"
"vitess.io/vitess/go/vt/vtadmin/sort"
Expand Down Expand Up @@ -126,6 +128,19 @@ func NewAPI(clusters []*cluster.Cluster, opts grpcserver.Options, httpOpts vtadm
experimentalRouter := router.PathPrefix("/experimental").Subrouter()
experimentalRouter.HandleFunc("/tablet/{tablet}/debug/vars", httpAPI.Adapt(experimental.TabletDebugVarsPassthrough)).Name("API.TabletDebugVarsPassthrough")

if !httpOpts.DisableDebug {
// Due to the way net/http/pprof insists on registering its handlers, we
// have to put these on the root router, and not on the /debug prefixed
// subrouter, which would make way more sense, but alas. Additional
// debug routes should still go on the /debug subrouter, though.
serv.Router().HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
serv.Router().HandleFunc("/debug/pprof/profile", pprof.Profile)
serv.Router().HandleFunc("/debug/pprof/symbol", pprof.Symbol)
serv.Router().PathPrefix("/debug/pprof").HandlerFunc(pprof.Index)
debugRouter := serv.Router().PathPrefix("/debug").Subrouter()
debugRouter.HandleFunc("/env", debug.Env)
}

// Middlewares are executed in order of addition. Our ordering (all
// middlewares being optional) is:
// 1. CORS. CORS is a special case and is applied globally, the rest are applied only to the subrouter.
Expand Down
5 changes: 4 additions & 1 deletion go/vt/vtadmin/http/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ type Options struct {
// DisableCompression specifies whether to turn off gzip compression for API
// endpoints. It is named as the negative (as opposed to EnableTracing) so
// the zero value has compression enabled.
DisableCompression bool
DisableCompression bool
// DisableDebug specifies whether to omit the /debug/pprof/* and /debug/env
// routes.
DisableDebug bool
ExperimentalOptions struct {
TabletURLTmpl string
}
Expand Down
102 changes: 102 additions & 0 deletions go/vt/vtadmin/http/debug/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright 2021 The Vitess Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package debug

import (
"fmt"
"net/http"
"os"
"sort"
"strings"

"vitess.io/vitess/go/flagutil"
)

var (
// SanitizeEnv is the set of environment variables to sanitize their values
// in the Env http handler.
SanitizeEnv flagutil.StringSetFlag
// OmitEnv is the set of environment variables to omit entirely in the Env
// http handler.
OmitEnv flagutil.StringSetFlag
)

const sanitized = "********"

// Env responds with a plaintext listing of key=value pairs of the environment
// variables, sorted by key name.
//
// If a variable appears in OmitEnv, it is excluded entirely. If a variable
// appears in SanitizeEnv, its value is replaced with a sanitized string,
// including if there was no value set in the environment.
func Env(w http.ResponseWriter, r *http.Request) {
vars := readEnv()

msg := &strings.Builder{}
for i, kv := range vars {
msg.WriteString(fmt.Sprintf("%s=%s", kv[0], kv[1]))
if i < len(vars)-1 {
msg.WriteByte('\n')
}
}

w.Write([]byte(msg.String()))
}

func readEnv() [][2]string {
env := os.Environ()
vars := make([][2]string, 0, len(env))

var key, value string
for _, ev := range env {
parts := strings.SplitN(ev, "=", 2)
switch len(parts) {
case 0:
key = ev
case 1:
key = parts[0]
default:
key = parts[0]
value = parts[1]
}

if key == "" {
continue
}

if OmitEnv.ToSet().Has(key) {
continue
}

if SanitizeEnv.ToSet().Has(key) {
value = sanitized
}

vars = append(vars, [2]string{
key,
value,
})
}

// Sort by env var name, ascending.
sort.SliceStable(vars, func(i, j int) bool {
left, right := vars[i], vars[j]
return left[0] < right[0]
})

return vars
}

0 comments on commit c489dc0

Please sign in to comment.