Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add bootstrap/listener/cluster/route configdump support #1083

Merged
merged 3 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 309 additions & 6 deletions internal/cmd/egctl/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import (
"net/http"

"github.com/spf13/cobra"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/reflect/protoreflect"
"k8s.io/apimachinery/pkg/types"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/yaml"

Xunzhuo marked this conversation as resolved.
Show resolved Hide resolved
adminv3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"

"github.com/envoyproxy/gateway/internal/cmd/options"
kube "github.com/envoyproxy/gateway/internal/kubernetes"
)
Expand Down Expand Up @@ -44,7 +48,7 @@ func NewConfigCommand() *cobra.Command {
options.AddKubeConfigFlags(flags)

cfgCommand.PersistentFlags().StringVarP(&output, "output", "o", "json", "One of 'yaml' or 'json'")
cfgCommand.PersistentFlags().StringVarP(&podNamespace, "namespace", "n", "envoy-gateway", "Namespace where envoy proxy pod are installed.")
cfgCommand.PersistentFlags().StringVarP(&podNamespace, "namespace", "n", "envoy-gateway-system", "Namespace where envoy proxy pod are installed.")

return cfgCommand
}
Expand All @@ -57,13 +61,17 @@ func proxyCommand() *cobra.Command {
}

c.AddCommand(allConfigCmd())
c.AddCommand(bootstrapConfigCmd())
c.AddCommand(clusterConfigCmd())
c.AddCommand(listenerConfigCmd())
c.AddCommand(routeConfigCmd())

return c
}

func allConfigCmd() *cobra.Command {

allConfigCmd := &cobra.Command{
configCmd := &cobra.Command{
Use: "all <pod-name>",
Short: "Retrieves all Envoy xDS resources from the specified pod",
Long: `Retrieves information about all Envoy xDS resources from the Envoy instance in the specified pod.`,
Expand All @@ -81,7 +89,7 @@ func allConfigCmd() *cobra.Command {
},
}

return allConfigCmd
return configCmd
}

func runAllConfig(c *cobra.Command, args []string) error {
Expand Down Expand Up @@ -111,7 +119,288 @@ func runAllConfig(c *cobra.Command, args []string) error {
}
defer fw.Stop()

out, err := extractConfigDump(fw, output)
configDump, err := extractConfigDump(fw)
if err != nil {
return err
}

out, err := marshalEnvoyProxyConfig(configDump, output)
if err != nil {
return err
}

_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

func bootstrapConfigCmd() *cobra.Command {

configCmd := &cobra.Command{
Use: "bootstrap <pod-name>",
Short: "Retrieves bootstrap Envoy xDS resources from the specified pod",
Xunzhuo marked this conversation as resolved.
Show resolved Hide resolved
Long: `Retrieves information about bootstrap Envoy xDS resources from the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about bootstrap configuration for a given pod from Envoy.
egctl config envoy-proxy bootstrap <pod-name> -n <pod-namespace>

# Retrieve full configuration dump as YAML
egctl config envoy-proxy bootstrap <pod-name> -n <pod-namespace> -o yaml

# Retrieve full configuration dump with short syntax
egctl c proxy bootstrap <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runBootstrapConfig(c, args))
},
}

return configCmd
}

func runBootstrapConfig(c *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("pod name is required")
}

podName = args[0]

if podName == "" {
return fmt.Errorf("pod name is required")
}

if podNamespace == "" {
return fmt.Errorf("pod namespace is required")
}

fw, err := portForwarder(types.NamespacedName{
Namespace: podNamespace,
Name: podName,
})
if err != nil {
return err
}
if err := fw.Start(); err != nil {
return err
}
defer fw.Stop()

configDump, err := extractConfigDump(fw)
if err != nil {
return err
}

Xunzhuo marked this conversation as resolved.
Show resolved Hide resolved
bootstrap, err := findXDSResourceFromConfigDump(BootstrapEnvoyConfigType, configDump)
if err != nil {
return err
}

out, err := marshalEnvoyProxyConfig(bootstrap, output)
if err != nil {
return err
}

_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

func clusterConfigCmd() *cobra.Command {

configCmd := &cobra.Command{
Use: "cluster <pod-name>",
Xunzhuo marked this conversation as resolved.
Show resolved Hide resolved
Short: "Retrieves cluster Envoy xDS resources from the specified pod",
Long: `Retrieves information about cluster Envoy xDS resources from the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about cluster configuration for a given pod from Envoy.
egctl config envoy-proxy cluster <pod-name> -n <pod-namespace>

# Retrieve full configuration dump as YAML
egctl config envoy-proxy cluster <pod-name> -n <pod-namespace> -o yaml

# Retrieve full configuration dump with short syntax
egctl c proxy cluster <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runClusterConfig(c, args))
},
}

return configCmd
}

func runClusterConfig(c *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("pod name is required")
}

podName = args[0]

if podName == "" {
return fmt.Errorf("pod name is required")
}

if podNamespace == "" {
return fmt.Errorf("pod namespace is required")
}

fw, err := portForwarder(types.NamespacedName{
Namespace: podNamespace,
Name: podName,
})
if err != nil {
return err
}
if err := fw.Start(); err != nil {
return err
}
defer fw.Stop()

configDump, err := extractConfigDump(fw)
if err != nil {
return err
}

cluster, err := findXDSResourceFromConfigDump(ClusterEnvoyConfigType, configDump)
if err != nil {
return err
}

out, err := marshalEnvoyProxyConfig(cluster, output)
if err != nil {
return err
}

_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

func listenerConfigCmd() *cobra.Command {

configCmd := &cobra.Command{
Use: "listener <pod-name>",
Short: "Retrieves listener Envoy xDS resources from the specified pod",
Xunzhuo marked this conversation as resolved.
Show resolved Hide resolved
Long: `Retrieves information about listener Envoy xDS resources from the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about listener configuration for a given pod from Envoy.
egctl config envoy-proxy listener <pod-name> -n <pod-namespace>

# Retrieve full configuration dump as YAML
egctl config envoy-proxy listener <pod-name> -n <pod-namespace> -o yaml

# Retrieve full configuration dump with short syntax
egctl c proxy listener <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runListenerConfig(c, args))
},
}

return configCmd
}

func runListenerConfig(c *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("pod name is required")
}

podName = args[0]

if podName == "" {
return fmt.Errorf("pod name is required")
}

if podNamespace == "" {
return fmt.Errorf("pod namespace is required")
}

fw, err := portForwarder(types.NamespacedName{
Namespace: podNamespace,
Name: podName,
})
if err != nil {
return err
}
if err := fw.Start(); err != nil {
return err
}
defer fw.Stop()

configDump, err := extractConfigDump(fw)
if err != nil {
return err
}

listener, err := findXDSResourceFromConfigDump(ListenerEnvoyConfigType, configDump)
if err != nil {
return err
}

out, err := marshalEnvoyProxyConfig(listener, output)
if err != nil {
return err
}

_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

func routeConfigCmd() *cobra.Command {

configCmd := &cobra.Command{
Use: "route <pod-name>",
Xunzhuo marked this conversation as resolved.
Show resolved Hide resolved
Short: "Retrieves route Envoy xDS resources from the specified pod",
Long: `Retrieves information about route Envoy xDS resources from the Envoy instance in the specified pod.`,
Example: ` # Retrieve summary about route configuration for a given pod from Envoy.
egctl config envoy-proxy route <pod-name> -n <pod-namespace>

# Retrieve full configuration dump as YAML
egctl config envoy-proxy route <pod-name> -n <pod-namespace> -o yaml

# Retrieve full configuration dump with short syntax
egctl c proxy route <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runRouteConfig(c, args))
},
}

return configCmd
}

func runRouteConfig(c *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("pod name is required")
}

podName = args[0]

if podName == "" {
return fmt.Errorf("pod name is required")
}

if podNamespace == "" {
return fmt.Errorf("pod namespace is required")
}

fw, err := portForwarder(types.NamespacedName{
Namespace: podNamespace,
Name: podName,
})
if err != nil {
return err
}
if err := fw.Start(); err != nil {
return err
}
defer fw.Stop()

configDump, err := extractConfigDump(fw)
if err != nil {
return err
}

route, err := findXDSResourceFromConfigDump(RouteEnvoyConfigType, configDump)
if err != nil {
return err
}

out, err := marshalEnvoyProxyConfig(route, output)
if err != nil {
return err
}
Expand Down Expand Up @@ -142,8 +431,8 @@ func portForwarder(nn types.NamespacedName) (kube.PortForwarder, error) {
return fw, nil
}

func extractConfigDump(fw kube.PortForwarder, output string) ([]byte, error) {
out, err := configDumpRequest(fw.Address())
func marshalEnvoyProxyConfig(configDump protoreflect.ProtoMessage, output string) ([]byte, error) {
out, err := protojson.Marshal(configDump)
if err != nil {
return nil, err
}
Expand All @@ -158,6 +447,20 @@ func extractConfigDump(fw kube.PortForwarder, output string) ([]byte, error) {
return out, nil
}

func extractConfigDump(fw kube.PortForwarder) (*adminv3.ConfigDump, error) {
out, err := configDumpRequest(fw.Address())
if err != nil {
return nil, err
}

configDump := &adminv3.ConfigDump{}
if err := protojson.Unmarshal(out, configDump); err != nil {
return nil, err
}

return configDump, nil
}

func configDumpRequest(address string) ([]byte, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/config_dump", address), nil)
if err != nil {
Expand Down
Loading