Skip to content

Commit

Permalink
Added args for kubeconfig, kubecontext, and namespace, added comments,
Browse files Browse the repository at this point in the history
changed command name to `LogLevelCommand`
  • Loading branch information
jm96441n authored and david-yu committed Feb 14, 2023
1 parent 6811241 commit 68859ed
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 32 deletions.
136 changes: 109 additions & 27 deletions cli/cmd/proxy/loglevel/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ import (
"github.com/hashicorp/consul-k8s/cli/common"
"github.com/hashicorp/consul-k8s/cli/common/flag"
"github.com/hashicorp/consul-k8s/cli/common/terminal"
"github.com/posener/complete"
helmCLI "helm.sh/helm/v3/pkg/cli"
"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

const defaultAdminPort int = 19000
const (
defaultAdminPort int = 19000
flagNameNamespace = "namespace"
flagNameKubeConfig = "kubeconfig"
flagNameKubeContext = "context"
)

type LoggerConfig map[string]string

Expand All @@ -35,34 +42,66 @@ var levelToColor = map[string]string{
"off": "",
}

type LogCommand struct {
type LogLevelCommand struct {
*common.BaseCommand

kubernetes kubernetes.Interface
set *flag.Sets

// Command Flags
podName string
podName string
namespace string
kubeConfig string
kubeContext string

once sync.Once
help string
restConfig *rest.Config
logLevelFetcher func(context.Context, common.PortForwarder) (LoggerConfig, error)
}

func (l *LogCommand) init() {
func (l *LogLevelCommand) init() {
l.set = flag.NewSets()
f := l.set.NewSet("Command Options")
f.StringVar(&flag.StringVar{
Name: flagNameNamespace,
Target: &l.namespace,
Usage: "The namespace where the target Pod can be found.",
Aliases: []string{"n"},
})

f = l.set.NewSet("Global Options")
f.StringVar(&flag.StringVar{
Name: flagNameKubeConfig,
Aliases: []string{"c"},
Target: &l.kubeConfig,
Usage: "Set the path to kubeconfig file.",
})
f.StringVar(&flag.StringVar{
Name: flagNameKubeContext,
Target: &l.kubeContext,
Usage: "Set the Kubernetes context to use.",
})

l.help = l.set.Help()
}

func (l *LogCommand) Run(args []string) int {
func (l *LogLevelCommand) Run(args []string) int {
l.once.Do(l.init)
l.Log.ResetNamed("loglevel")
defer common.CloseWithError(l.BaseCommand)

err := l.parseFlags(args)
if err != nil {
fmt.Println(err)
l.UI.Output(err.Error(), terminal.WithErrorStyle())
l.UI.Output(fmt.Sprintf("\n%s", l.Help()))
return 1
}

err = l.validateFlags()
if err != nil {
l.UI.Output(err.Error(), terminal.WithErrorStyle())
l.UI.Output(fmt.Sprintf("\n%s", l.Help()))
return 1
}

Expand All @@ -72,26 +111,29 @@ func (l *LogCommand) Run(args []string) int {

err = l.initKubernetes()
if err != nil {
fmt.Println(err)
l.UI.Output(err.Error(), terminal.WithErrorStyle())
l.UI.Output(fmt.Sprintf("\n%s", l.Help()))
return 1
}

adminPorts, err := l.fetchAdminPorts()
if err != nil {
fmt.Println(err)
l.UI.Output(err.Error(), terminal.WithErrorStyle())
l.UI.Output(fmt.Sprintf("\n%s", l.Help()))
return 1
}

logLevels, err := l.fetchLogLevels(adminPorts)
if err != nil {
fmt.Println(err)
l.UI.Output(err.Error(), terminal.WithErrorStyle())
l.UI.Output(fmt.Sprintf("\n%s", l.Help()))
return 1
}
l.outputLevels(logLevels)
return 0
}

func (l *LogCommand) parseFlags(args []string) error {
func (l *LogLevelCommand) parseFlags(args []string) error {
positional := []string{}
// Separate positional args from keyed args
for _, arg := range args {
Expand All @@ -115,10 +157,25 @@ func (l *LogCommand) parseFlags(args []string) error {
return nil
}

func (l *LogCommand) initKubernetes() error {
func (l *LogLevelCommand) validateFlags() error {
if errs := validation.ValidateNamespaceName(l.namespace, false); l.namespace != "" && len(errs) > 0 {
return fmt.Errorf("invalid namespace name passed for -namespace/-n: %v", strings.Join(errs, "; "))
}
return nil
}

func (l *LogLevelCommand) initKubernetes() error {
settings := helmCLI.New()
var err error

if l.kubeConfig != "" {
settings.KubeConfig = l.kubeConfig
}

if l.kubeContext != "" {
settings.KubeContext = l.kubeContext
}

if l.restConfig == nil {
l.restConfig, err = settings.RESTClientGetter().ToRESTConfig()
if err != nil {
Expand All @@ -133,13 +190,17 @@ func (l *LogCommand) initKubernetes() error {
return fmt.Errorf("error creating Kubernetes client %v", err)
}
}
if l.namespace == "" {
l.namespace = settings.Namespace()
}

return nil
}

func (l *LogCommand) fetchAdminPorts() (map[string]int, error) {
// fetchAdminPorts retrieves all admin ports for Envoy Proxies running in a pod given namespace
func (l *LogLevelCommand) fetchAdminPorts() (map[string]int, error) {
adminPorts := make(map[string]int, 0)
// TODO: support different namespaces
pod, err := l.kubernetes.CoreV1().Pods("default").Get(l.Ctx, l.podName, metav1.GetOptions{})
pod, err := l.kubernetes.CoreV1().Pods(l.namespace).Get(l.Ctx, l.podName, metav1.GetOptions{})
if err != nil {
return adminPorts, err
}
Expand All @@ -159,12 +220,12 @@ func (l *LogCommand) fetchAdminPorts() (map[string]int, error) {
return adminPorts, nil
}

func (l *LogCommand) fetchLogLevels(adminPorts map[string]int) (map[string]LoggerConfig, error) {
func (l *LogLevelCommand) fetchLogLevels(adminPorts map[string]int) (map[string]LoggerConfig, error) {
loggers := make(map[string]LoggerConfig, 0)

for name, port := range adminPorts {
pf := common.PortForward{
Namespace: "default", // TODO: change this to use the configurable namespace
Namespace: l.namespace,
PodName: l.podName,
RemotePort: port,
KubeClient: l.kubernetes,
Expand All @@ -180,6 +241,8 @@ func (l *LogCommand) fetchLogLevels(adminPorts map[string]int) (map[string]Logge
return loggers, nil
}

// FetchLogLevel requests the logging endpoint from Envoy Admin Interface for a given port
// more can be read about that endpoint https://www.envoyproxy.io/docs/envoy/latest/operations/admin#post--logging
func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (LoggerConfig, error) {
endpoint, err := portForward.Open(ctx)
if err != nil {
Expand All @@ -188,7 +251,8 @@ func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (Logge

defer portForward.Close()

response, err := http.Post(fmt.Sprintf("http://%s/logging", endpoint), "application/json", bytes.NewBuffer([]byte{}))
// this endpoint does not support returning json, so we've gotta parse the plain text
response, err := http.Post(fmt.Sprintf("http://%s/logging", endpoint), "text/plain", bytes.NewBuffer([]byte{}))
if err != nil {
return nil, err
}
Expand All @@ -210,16 +274,7 @@ func FetchLogLevel(ctx context.Context, portForward common.PortForwarder) (Logge
return logLevels, nil
}

func (l *LogCommand) Help() string {
l.once.Do(l.init)
return fmt.Sprintf("%s\n\nUsage: consul-k8s proxy log <pod-name> [flags]\n\n%s", l.Synopsis(), l.help)
}

func (l *LogCommand) Synopsis() string {
return "Inspect and Modify the Envoy Log configuration for a given Pod."
}

func (l *LogCommand) outputLevels(logLevels map[string]LoggerConfig) {
func (l *LogLevelCommand) outputLevels(logLevels map[string]LoggerConfig) {
l.UI.Output(fmt.Sprintf("Envoy log configuration for %s in namespace default:", l.podName))
for n, levels := range logLevels {
l.UI.Output(fmt.Sprintf("Log Levels for %s", n), terminal.WithHeaderStyle())
Expand All @@ -231,3 +286,30 @@ func (l *LogCommand) outputLevels(logLevels map[string]LoggerConfig) {
l.UI.Output("")
}
}

func (l *LogLevelCommand) Help() string {
l.once.Do(l.init)
return fmt.Sprintf("%s\n\nUsage: consul-k8s proxy log <pod-name> [flags]\n\n%s", l.Synopsis(), l.help)
}

func (l *LogLevelCommand) Synopsis() string {
return "Inspect and Modify the Envoy Log configuration for a given Pod."
}

// AutocompleteFlags returns a mapping of supported flags and autocomplete
// options for this command. The map key for the Flags map should be the
// complete flag such as "-foo" or "--foo".
func (l *LogLevelCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
fmt.Sprintf("-%s", flagNameNamespace): complete.PredictNothing,
fmt.Sprintf("-%s", flagNameKubeConfig): complete.PredictFiles("*"),
fmt.Sprintf("-%s", flagNameKubeContext): complete.PredictNothing,
}
}

// AutocompleteArgs returns the argument predictor for this command.
// Since argument completion is not supported, this will return
// complete.PredictNothing.
func (l *LogLevelCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
57 changes: 53 additions & 4 deletions cli/cmd/proxy/loglevel/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
)

func TestFlagParsing(t *testing.T) {
func TestFlagParsingFails(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
args []string
Expand All @@ -31,9 +31,17 @@ func TestFlagParsing(t *testing.T) {
args: []string{},
out: 1,
},
"With pod name": {
args: []string{"now-this-is-pod-racing"},
out: 0,
"Multiple podnames passed": {
args: []string{"podname", "podname2"},
out: 1,
},
"Nonexistent flag passed, -foo bar": {
args: []string{"podName", "-foo", "bar"},
out: 1,
},
"Invalid argument passed, -namespace YOLO": {
args: []string{"podName", "-namespace", "YOLO"},
out: 1,
},
}
podName := "now-this-is-pod-racing"
Expand All @@ -58,6 +66,47 @@ func TestFlagParsing(t *testing.T) {
}
}

func TestFlagParsingSucceeds(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
args []string
podNamespace string
out int
}{
"With single pod name": {
args: []string{"now-this-is-pod-racing"},
podNamespace: "default",
out: 0,
},
"With single pod name and namespace": {
args: []string{"now-this-is-pod-racing", "-n", "another"},
podNamespace: "another",
out: 0,
},
}
podName := "now-this-is-pod-racing"

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
fakePod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: tc.podNamespace,
},
}

c := setupCommand(bytes.NewBuffer([]byte{}))
c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{fakePod}})
c.logLevelFetcher = func(context.Context, common.PortForwarder) (LoggerConfig, error) {
return testLogConfig, nil
}

out := c.Run(tc.args)
require.Equal(t, tc.out, out)
})
}
}

func TestOutputForGettingLogLevel(t *testing.T) {
t.Parallel()
podName := "now-this-is-pod-racing"
Expand Down
2 changes: 1 addition & 1 deletion cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm
}, nil
},
"proxy log": func() (cli.Command, error) {
return &loglevel.LogCommand{
return &loglevel.LogLevelCommand{
BaseCommand: baseCommand,
}, nil
},
Expand Down

0 comments on commit 68859ed

Please sign in to comment.