Skip to content

Commit

Permalink
add support for egctl x status
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnh2 committed Feb 1, 2024
1 parent 914b48c commit 1680f71
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 20 deletions.
5 changes: 3 additions & 2 deletions internal/cmd/egctl/experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ func newExperimentalCommand() *cobra.Command {
`,
}

experimentalCommand.AddCommand(NewTranslateCommand())
experimentalCommand.AddCommand(statsCommand())
experimentalCommand.AddCommand(newTranslateCommand())
experimentalCommand.AddCommand(newStatsCommand())
experimentalCommand.AddCommand(newStatusCommand())

Check warning on line 27 in internal/cmd/egctl/experimental.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/experimental.go#L25-L27

Added lines #L25 - L27 were not covered by tests

return experimentalCommand
}
2 changes: 1 addition & 1 deletion internal/cmd/egctl/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra"
)

func statsCommand() *cobra.Command {
func newStatsCommand() *cobra.Command {

Check warning on line 12 in internal/cmd/egctl/stats.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/stats.go#L12

Added line #L12 was not covered by tests
c := &cobra.Command{
Use: "stats",
Long: "Retrieve statistics from envoy proxy.",
Expand Down
313 changes: 313 additions & 0 deletions internal/cmd/egctl/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package egctl

import (
"context"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"text/tabwriter"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
)

func newStatusCommand() *cobra.Command {
var (
quiet, verbose, allNamespaces bool
resourceType, namespace string
)

statusCommand := &cobra.Command{
Use: "status",
Short: "Show the summary of the status of resources in Envoy Gateway",
Example: ` # Show the status of gatewayclass resources under default namespace.
egctl x status gatewayclass
# Show the status of gateway resources with less information under default namespace.
egctl x status gateway -q
# Show the status of gateway resources with details under default namespace.
egctl x status gateway -v
# Show the status of httproutes resources with details under a specific namespace.
egctl x status httproutes -v -n foobar
# Show the status of httproutes resources under all namespaces.
egctl x status httproutes -A
`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()

table := tabwriter.NewWriter(os.Stdout, 10, 0, 3, ' ', 0)

if len(args) == 1 {

Check failure on line 55 in internal/cmd/egctl/status.go

View workflow job for this annotation

GitHub Actions / lint

ifElseChain: rewrite if-else to switch statement (gocritic)
resourceType = args[0]
} else if len(args) > 1 {
return fmt.Errorf("unknown args: %s", strings.Join(args[1:], ","))
} else {
return fmt.Errorf("invalid args: must sepecific a resources type")

Check failure on line 60 in internal/cmd/egctl/status.go

View workflow job for this annotation

GitHub Actions / lint

sepecific ==> specific
}

Check warning on line 61 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L26-L61

Added lines #L26 - L61 were not covered by tests

return runStatus(ctx, table, resourceType, namespace, quiet, verbose, allNamespaces)

Check warning on line 63 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L63

Added line #L63 was not covered by tests
},
}

statusCommand.PersistentFlags().BoolVarP(&quiet, "quiet", "q", false, "Show the status of resources only")
statusCommand.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Show the status of resources with details")
statusCommand.PersistentFlags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, "Get resources from all namespaces")
statusCommand.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "Specific a namespace to get resources")

return statusCommand

Check warning on line 72 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L67-L72

Added lines #L67 - L72 were not covered by tests
}

func runStatus(ctx context.Context, table *tabwriter.Writer, resourceType, namespace string, quiet, verbose, allNamespaces bool) error {
k8sClient, err := newK8sClient()
if err != nil {
return err
}

Check warning on line 79 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L75-L79

Added lines #L75 - L79 were not covered by tests

var resourcesList client.ObjectList

if allNamespaces {
namespace = ""
}

Check warning on line 85 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L81-L85

Added lines #L81 - L85 were not covered by tests

switch strings.ToLower(resourceType) {
case "gc", "gcs", "gatewayclass", "gatewayclasses":
gc := gwv1.GatewayClassList{}
if err = k8sClient.List(ctx, &gc, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &gc

Check warning on line 93 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L87-L93

Added lines #L87 - L93 were not covered by tests

case "gtw", "gtws", "gateway", "gateways":
gtw := gwv1.GatewayList{}
if err = k8sClient.List(ctx, &gtw, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &gtw

Check warning on line 100 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L95-L100

Added lines #L95 - L100 were not covered by tests

case "httproute", "httproutes":
httproute := gwv1.HTTPRouteList{}
if err = k8sClient.List(ctx, &httproute, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &httproute

Check warning on line 107 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L102-L107

Added lines #L102 - L107 were not covered by tests

case "grpcroute", "grpcroutes":
grpcroute := gwv1a2.GRPCRouteList{}
if err = k8sClient.List(ctx, &grpcroute, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &grpcroute

Check warning on line 114 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L109-L114

Added lines #L109 - L114 were not covered by tests

case "tcproute", "tcproutes":
tcproute := gwv1a2.TCPRouteList{}
if err = k8sClient.List(ctx, &tcproute, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &tcproute

Check warning on line 121 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L116-L121

Added lines #L116 - L121 were not covered by tests

case "udproute", "udproutes":
udproute := gwv1a2.UDPRouteList{}
if err = k8sClient.List(ctx, &udproute, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &udproute

Check warning on line 128 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L123-L128

Added lines #L123 - L128 were not covered by tests

case "tlsroute", "tlsroutes":
tlsroute := gwv1a2.TLSRouteList{}
if err = k8sClient.List(ctx, &tlsroute, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &tlsroute

Check warning on line 135 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L130-L135

Added lines #L130 - L135 were not covered by tests

case "btlspolicy", "btlspolicies", "backendtlspolicy", "backendtlspolicies":
btlspolicy := gwv1a2.BackendTLSPolicyList{}
if err = k8sClient.List(ctx, &btlspolicy, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &btlspolicy

Check warning on line 142 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L137-L142

Added lines #L137 - L142 were not covered by tests

case "btp", "btps", "backendtrafficpolicy", "backendtrafficpolicies":
btp := egv1a1.BackendTrafficPolicyList{}
if err = k8sClient.List(ctx, &btp, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &btp

Check warning on line 149 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L144-L149

Added lines #L144 - L149 were not covered by tests

case "ctp", "ctps", "clienttrafficpolicy", "clienttrafficpolicies":
ctp := egv1a1.ClientTrafficPolicyList{}
if err = k8sClient.List(ctx, &ctp, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &ctp

Check warning on line 156 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L151-L156

Added lines #L151 - L156 were not covered by tests

case "epp", "epps", "enovypatchpolicy", "envoypatchpolicies":
epp := egv1a1.EnvoyPatchPolicyList{}
if err = k8sClient.List(ctx, &epp, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &epp

Check warning on line 163 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L158-L163

Added lines #L158 - L163 were not covered by tests

case "sp", "sps", "securitypolicy", "secruitypolicies":
sp := egv1a1.SecurityPolicyList{}
if err = k8sClient.List(ctx, &sp, client.InNamespace(namespace)); err != nil {
return err
}
resourcesList = &sp

Check warning on line 170 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L165-L170

Added lines #L165 - L170 were not covered by tests

default:
return fmt.Errorf("unknown resource type: %s", resourceType)

Check warning on line 173 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L172-L173

Added lines #L172 - L173 were not covered by tests
}

namespaced, err := k8sClient.IsObjectNamespaced(resourcesList)
if err != nil {
return err
}

Check warning on line 179 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L176-L179

Added lines #L176 - L179 were not covered by tests

needNamespaceHeader := allNamespaces && namespaced
writeStatusHeaders(table, verbose, needNamespaceHeader)

if err = writeStatusBodies(table, resourcesList, resourceType, quiet, verbose, needNamespaceHeader); err != nil {
return err
}

Check warning on line 186 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L181-L186

Added lines #L181 - L186 were not covered by tests

return table.Flush()

Check warning on line 188 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L188

Added line #L188 was not covered by tests
}

func writeStatusHeaders(table *tabwriter.Writer, verbose, needNamespace bool) {
headers := []string{"NAME", "TYPE", "STATUS", "REASON"}

if needNamespace {
headers = append([]string{"NAMESPACE"}, headers...)
}
if verbose {
headers = append(headers, []string{"MESSAGE", "OBSERVED GENERATION", "LAST TRANSITION TIME"}...)
}

Check warning on line 199 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L191-L199

Added lines #L191 - L199 were not covered by tests

fmt.Fprintln(table, strings.Join(headers, "\t"))

Check warning on line 201 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L201

Added line #L201 was not covered by tests
}

func writeStatusBodies(table *tabwriter.Writer, resourcesList client.ObjectList, resourceType string, quiet, verbose, needNamespace bool) error {
v := reflect.ValueOf(resourcesList).Elem()

itemsField := v.FieldByName("Items")
if !itemsField.IsValid() {
return fmt.Errorf("failed to load `.Items` field from %s", resourceType)
}

Check warning on line 210 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L204-L210

Added lines #L204 - L210 were not covered by tests

for i := 0; i < itemsField.Len(); i++ {
item := itemsField.Index(i)

var name, namespace string
nameField := item.FieldByName("Name")
if !nameField.IsValid() {
return fmt.Errorf("failed to find `.Items[i].Name` field from %s", resourceType)
}
name = nameField.String()

if needNamespace {
namespaceField := item.FieldByName("Namespace")
if !namespaceField.IsValid() {
return fmt.Errorf("failed to find `.Items[i].Namespace` field from %s", resourceType)
}
namespace = namespaceField.String()

Check warning on line 227 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L212-L227

Added lines #L212 - L227 were not covered by tests
}

statusField := item.FieldByName("Status")
if !statusField.IsValid() {
return fmt.Errorf("failed to find `.Items[i].Status` field from %s", resourceType)
}

Check warning on line 233 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L230-L233

Added lines #L230 - L233 were not covered by tests

if strings.Contains(resourceType, "route") {

Check failure on line 235 in internal/cmd/egctl/status.go

View workflow job for this annotation

GitHub Actions / lint

ifElseChain: rewrite if-else to switch statement (gocritic)
// Scrape conditions from `Resource.Status.Parents[i].Conditions` field
parentsField := statusField.FieldByName("Parents")
if !parentsField.IsValid() {
return fmt.Errorf("failed to find `.Items[i].Status.Parents` field from %s", resourceType)
}

Check warning on line 240 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L235-L240

Added lines #L235 - L240 were not covered by tests

for j := 0; j < parentsField.Len(); j++ {
parentItem := parentsField.Index(j)
if err := findAndWriteConditions(table, parentItem, resourceType, name, namespace, quiet, verbose, needNamespace); err != nil {
return err
}

Check warning on line 246 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L242-L246

Added lines #L242 - L246 were not covered by tests
}
} else if strings.Contains(resourceType, "btls") || strings.Contains(resourceType, "backendtls") {
// Scrape conditions from `Resource.Status.Ancestors[i].Conditions` field
ancestorsField := statusField.FieldByName("Ancestors")
if !ancestorsField.IsValid() {
return fmt.Errorf("failed to find `.Items[i].Status.Ancestors` field from %s", resourceType)
}

Check warning on line 253 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L248-L253

Added lines #L248 - L253 were not covered by tests

for j := 0; j < ancestorsField.Len(); j++ {
ancestorItem := ancestorsField.Index(j)
if err := findAndWriteConditions(table, ancestorItem, resourceType, name, namespace, quiet, verbose, needNamespace); err != nil {
return err
}

Check warning on line 259 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L255-L259

Added lines #L255 - L259 were not covered by tests
}
} else {
// Scrape conditions from `Resource.Status.Conditions` field
return findAndWriteConditions(table, statusField, resourceType, name, namespace, quiet, verbose, needNamespace)
}

Check warning on line 264 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L261-L264

Added lines #L261 - L264 were not covered by tests
}

return nil

Check warning on line 267 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L267

Added line #L267 was not covered by tests
}

func findAndWriteConditions(table *tabwriter.Writer, parent reflect.Value, resourceType, name, namespace string, quiet, verbose, needNamespace bool) error {
conditionsField := parent.FieldByName("Conditions")
if !conditionsField.IsValid() {
return fmt.Errorf("failed to find `Conditions` field for %s", resourceType)
}

Check warning on line 274 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L270-L274

Added lines #L270 - L274 were not covered by tests

conditions := conditionsField.Interface().([]metav1.Condition)
writeConditions(table, conditions, name, namespace, quiet, verbose, needNamespace)

return nil

Check warning on line 279 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L276-L279

Added lines #L276 - L279 were not covered by tests
}

func writeConditions(table *tabwriter.Writer, conditions []metav1.Condition, name, namespace string, quiet, verbose, needNamespace bool) {
// Sort in descending order by time of each condition.
for i := len(conditions) - 1; i >= 0; i-- {
if i < len(conditions)-1 {
name, namespace = "", ""
}

Check warning on line 287 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L282-L287

Added lines #L282 - L287 were not covered by tests

writeCondition(table, conditions[i], name, namespace, verbose, needNamespace)

if quiet {
break

Check warning on line 292 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L289-L292

Added lines #L289 - L292 were not covered by tests
}
}
}

// writeCondition writes condition data corresponding to its header.
func writeCondition(table *tabwriter.Writer, condition metav1.Condition, name, namespace string, verbose, needNamespace bool) {
row := []string{name, condition.Type, string(condition.Status), condition.Reason}

if needNamespace {
row = append([]string{namespace}, row...)
}
if verbose {
row = append(row, []string{
condition.Message,
strconv.FormatInt(condition.ObservedGeneration, 10),
condition.LastTransitionTime.String(),
}...)
}

Check warning on line 310 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L298-L310

Added lines #L298 - L310 were not covered by tests

fmt.Fprintln(table, strings.Join(row, "\t"))

Check warning on line 312 in internal/cmd/egctl/status.go

View check run for this annotation

Codecov / codecov/patch

internal/cmd/egctl/status.go#L312

Added line #L312 was not covered by tests
}
2 changes: 1 addition & 1 deletion internal/cmd/egctl/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type TranslationResult struct {
Xds map[string]interface{} `json:"xds,omitempty"`
}

func NewTranslateCommand() *cobra.Command {
func newTranslateCommand() *cobra.Command {
var (
inFile, inType, output, resourceType string
addMissingResources bool
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/egctl/translate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func TestTranslate(t *testing.T) {

t.Run(tc.name+"|"+tc.resourceType, func(t *testing.T) {
b := bytes.NewBufferString("")
root := NewTranslateCommand()
root := newTranslateCommand()
root.SetOut(b)
root.SetErr(b)
args := []string{
Expand Down
Loading

0 comments on commit 1680f71

Please sign in to comment.