Skip to content

Commit

Permalink
gwctl: Show Events, Analysis and few other fields for various resourc…
Browse files Browse the repository at this point in the history
…es. (kubernetes-sigs#3051)

* Consolidate all ObjRef types into a single type from the common package

* Start using the Table struct to print table based views for all resources.

* Add Events to all resources in the ResourceModel

* Start using DescriberKV struct to print all describe views and also print events
associated with the resource.

* Add Analysis section to describe views for relevant resources

* fixup! Start using the Table struct to print table based views for all resources.
  • Loading branch information
gauravkghildiyal authored and BobyMCbobs committed Jul 10, 2024
1 parent 359bd0f commit ceb44ec
Show file tree
Hide file tree
Showing 18 changed files with 334 additions and 326 deletions.
2 changes: 1 addition & 1 deletion gwctl/cmd/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func runDescribe(cmd *cobra.Command, args []string, params *utils.CmdParams) {
httpRoutesPrinter := &printer.HTTPRoutesPrinter{Writer: params.Out, Clock: clock.RealClock{}}
gwPrinter := &printer.GatewaysPrinter{Writer: params.Out, Clock: clock.RealClock{}}
gwcPrinter := &printer.GatewayClassesPrinter{Writer: params.Out, Clock: clock.RealClock{}}
backendsPrinter := &printer.BackendsPrinter{Writer: params.Out}
backendsPrinter := &printer.BackendsPrinter{Writer: params.Out, Clock: clock.RealClock{}}
namespacesPrinter := &printer.NamespacesPrinter{Writer: params.Out, Clock: clock.RealClock{}}

switch kind {
Expand Down
8 changes: 5 additions & 3 deletions gwctl/pkg/policymanager/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ limitations under the License.

package policymanager

import "sigs.k8s.io/gateway-api/gwctl/pkg/common"

// ToPolicyRefs returns the Object references of all given policies. Note that
// these are not the value of targetRef within the Policies but rather the
// reference to the Policy object itself.
func ToPolicyRefs(policies []Policy) []ObjRef {
var result []ObjRef
func ToPolicyRefs(policies []Policy) []common.ObjRef {
var result []common.ObjRef
for _, policy := range policies {
result = append(result, ObjRef{
result = append(result, common.ObjRef{
Group: policy.Unstructured().GroupVersionKind().Group,
Kind: policy.Unstructured().GroupVersionKind().Kind,
Name: policy.Unstructured().GetName(),
Expand Down
18 changes: 6 additions & 12 deletions gwctl/pkg/policymanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"k8s.io/client-go/dynamic"

gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
"sigs.k8s.io/gateway-api/gwctl/pkg/common"
)

type PolicyManager struct {
Expand Down Expand Up @@ -78,7 +79,7 @@ func (p *PolicyManager) Init(ctx context.Context) error {
return nil
}

func (p *PolicyManager) PoliciesAttachedTo(objRef ObjRef) []Policy {
func (p *PolicyManager) PoliciesAttachedTo(objRef common.ObjRef) []Policy {
var result []Policy
for _, policy := range p.policies {
if policy.IsAttachedTo(objRef) {
Expand Down Expand Up @@ -218,21 +219,14 @@ type Policy struct {
// targetRef references the target object this policy is attached to. This
// only makes sense in case of a directly-attached-policy, or an
// unmerged-inherited-policy.
targetRef ObjRef
targetRef common.ObjRef
// Indicates whether the policy is supposed to be "inherited" (as opposed to
// "direct").
inherited bool
}

func (p Policy) ClientObject() client.Object { return p.Unstructured() }

type ObjRef struct {
Group string `json:",omitempty"`
Kind string `json:",omitempty"`
Name string `json:",omitempty"`
Namespace string `json:",omitempty"`
}

func PolicyFromUnstructured(u unstructured.Unstructured, policyCRDs map[PolicyCrdID]PolicyCRD) (Policy, error) {
result := Policy{u: u}

Expand All @@ -248,7 +242,7 @@ func PolicyFromUnstructured(u unstructured.Unstructured, policyCRDs map[PolicyCr
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), structuredPolicy); err != nil {
return Policy{}, fmt.Errorf("failed to convert unstructured policy resource to structured: %v", err)
}
result.targetRef = ObjRef{
result.targetRef = common.ObjRef{
Group: string(structuredPolicy.Spec.TargetRef.Group),
Kind: string(structuredPolicy.Spec.TargetRef.Kind),
Name: string(structuredPolicy.Spec.TargetRef.Name),
Expand Down Expand Up @@ -280,7 +274,7 @@ func (p Policy) PolicyCrdID() PolicyCrdID {
return PolicyCrdID(p.u.GetObjectKind().GroupVersionKind().Kind + "." + p.u.GetObjectKind().GroupVersionKind().Group)
}

func (p Policy) TargetRef() ObjRef {
func (p Policy) TargetRef() common.ObjRef {
return p.targetRef
}

Expand All @@ -292,7 +286,7 @@ func (p Policy) IsDirect() bool {
return !p.inherited
}

func (p Policy) IsAttachedTo(objRef ObjRef) bool {
func (p Policy) IsAttachedTo(objRef common.ObjRef) bool {
if p.targetRef.Kind == "Namespace" && p.targetRef.Name == "" {
p.targetRef.Name = "default"
}
Expand Down
4 changes: 3 additions & 1 deletion gwctl/pkg/policymanager/merger.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (

jsonpatch "github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"sigs.k8s.io/gateway-api/gwctl/pkg/common"
)

// MergePoliciesOfSimilarKind will convert a slice a policies to a map of
Expand Down Expand Up @@ -147,7 +149,7 @@ func mergePolicy(parent, child Policy) (Policy, error) {
result.u.SetUnstructuredContent(resultUnstructured)
// Merging two policies means the targetRef no longer makes any sense since
// since they can be conflicting. So we unset the targetRef.
result.targetRef = ObjRef{}
result.targetRef = common.ObjRef{}
return result, nil
}

Expand Down
96 changes: 51 additions & 45 deletions gwctl/pkg/printer/backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@ package printer
import (
"fmt"
"io"
"os"
"strings"
"text/tabwriter"

"golang.org/x/exp/maps"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/utils/clock"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"

"sigs.k8s.io/gateway-api/gwctl/pkg/policymanager"
"sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery"
)

Expand All @@ -39,12 +35,9 @@ type BackendsPrinter struct {
}

func (bp *BackendsPrinter) Print(resourceModel *resourcediscovery.ResourceModel) {
tw := tabwriter.NewWriter(bp, 0, 0, 2, ' ', 0)
row := []string{"NAMESPACE", "NAME", "TYPE", "REFERRED BY ROUTES", "AGE", "POLICIES"}
_, err := tw.Write([]byte(strings.Join(row, "\t") + "\n"))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to write to the tab writer: %v\n", err)
os.Exit(1)
table := &Table{
ColumnNames: []string{"NAMESPACE", "NAME", "TYPE", "REFERRED BY ROUTES", "AGE", "POLICIES"},
UseSeparator: false,
}

backends := maps.Values(resourceModel.Backends)
Expand Down Expand Up @@ -90,58 +83,71 @@ func (bp *BackendsPrinter) Print(resourceModel *resourcediscovery.ResourceModel)
age,
policiesCount,
}
_, err := tw.Write([]byte(strings.Join(row, "\t") + "\n"))
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
table.Rows = append(table.Rows, row)
}

tw.Flush()
}

type backendDescribeView struct {
Group string `json:",omitempty"`
Kind string `json:",omitempty"`
Name string `json:",omitempty"`
Namespace string `json:",omitempty"`
DirectlyAttachedPolicies []policymanager.ObjRef `json:",omitempty"`
EffectivePolicies any `json:",omitempty"`
table.Write(bp, 0)
}

func (bp *BackendsPrinter) PrintDescribeView(resourceModel *resourcediscovery.ResourceModel) {
index := 0
for _, backendNode := range resourceModel.Backends {
index++

views := []backendDescribeView{
{
Group: backendNode.Backend.GroupVersionKind().Group,
Kind: backendNode.Backend.GroupVersionKind().Kind,
Name: backendNode.Backend.GetName(),
Namespace: backendNode.Backend.GetNamespace(),
},
backend := backendNode.Backend.DeepCopy()
backend.SetLabels(nil)
backend.SetAnnotations(nil)

pairs := []*DescriberKV{
{Key: "Name", Value: backendNode.Backend.GetName()},
{Key: "Namespace", Value: backendNode.Backend.GetNamespace()},
{Key: "Labels", Value: backendNode.Backend.GetLabels()},
{Key: "Annotations", Value: backendNode.Backend.GetAnnotations()},
{Key: "Backend", Value: backend},
}
if policyRefs := resourcediscovery.ConvertPoliciesMapToPolicyRefs(backendNode.Policies); len(policyRefs) != 0 {
views = append(views, backendDescribeView{
DirectlyAttachedPolicies: policyRefs,
})

// ReferencedByRoutes
routes := &Table{
ColumnNames: []string{"Kind", "Name"},
UseSeparator: true,
}
for _, httpRouteNode := range backendNode.HTTPRoutes {
row := []string{
httpRouteNode.HTTPRoute.Kind, // Kind
fmt.Sprintf("%v/%v", httpRouteNode.HTTPRoute.Namespace, httpRouteNode.HTTPRoute.Name), // Name
}
routes.Rows = append(routes.Rows, row)
}
pairs = append(pairs, &DescriberKV{Key: "ReferencedByRoutes", Value: routes})

// DirectlyAttachedPolicies
policyRefs := resourcediscovery.ConvertPoliciesMapToPolicyRefs(backendNode.Policies)
pairs = append(pairs, &DescriberKV{Key: "DirectlyAttachedPolicies", Value: convertPolicyRefsToTable(policyRefs)})

// EffectivePolicies
if len(backendNode.EffectivePolicies) != 0 {
views = append(views, backendDescribeView{
EffectivePolicies: backendNode.EffectivePolicies,
})
pairs = append(pairs, &DescriberKV{Key: "EffectivePolicies", Value: backendNode.EffectivePolicies})
}

for _, view := range views {
b, err := yaml.Marshal(view)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal to yaml: %v\n", err)
os.Exit(1)
// ReferenceGrants
if len(backendNode.ReferenceGrants) != 0 {
var names []string
for _, refGrantNode := range backendNode.ReferenceGrants {
names = append(names, refGrantNode.ReferenceGrant.Name)
}
fmt.Fprint(bp, string(b))
pairs = append(pairs, &DescriberKV{Key: "ReferenceGrants", Value: names})
}

// Analysis
if len(backendNode.Errors) != 0 {
pairs = append(pairs, &DescriberKV{Key: "Analysis", Value: convertErrorsToString(backendNode.Errors)})
}

// Events
pairs = append(pairs, &DescriberKV{Key: "Events", Value: convertEventsSliceToTable(backendNode.Events, bp.Clock)})

Describe(bp, pairs)

if index+1 <= len(resourceModel.Backends) {
fmt.Fprintf(bp, "\n\n")
}
Expand Down
35 changes: 32 additions & 3 deletions gwctl/pkg/printer/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (
"k8s.io/utils/clock"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"

"sigs.k8s.io/gateway-api/gwctl/pkg/common"
)

// DescriberKV stores key-value pairs that are used with Describing a resource.
Expand All @@ -52,7 +54,7 @@ func Describe(w io.Writer, pairs []*DescriberKV) {
fmt.Fprintf(w, "%v: <none>\n", pair.Key)
} else {
fmt.Fprintf(w, "%v:\n", pair.Key)
table.writeTable(w, defaultDescribeTableIndentSpaces)
table.Write(w, defaultDescribeTableIndentSpaces)
}
continue
}
Expand All @@ -76,9 +78,9 @@ type Table struct {
UseSeparator bool
}

// writeTable will write a formatted table to the writer. indent controls the
// Write will write a formatted table to the writer. indent controls the
// number of spaces at the beginning of each row.
func (t *Table) writeTable(w io.Writer, indent int) {
func (t *Table) Write(w io.Writer, indent int) {
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)

// Print column names.
Expand Down Expand Up @@ -151,6 +153,33 @@ func convertEventsSliceToTable(events []corev1.Event, clock clock.Clock) *Table
return table
}

func convertPolicyRefsToTable(policyRefs []common.ObjRef) *Table {
table := &Table{
ColumnNames: []string{"Type", "Name"},
UseSeparator: true,
}
for _, policyRef := range policyRefs {
name := policyRef.Name
if policyRef.Namespace != "" {
name = fmt.Sprintf("%v/%v", policyRef.Namespace, name)
}
row := []string{
fmt.Sprintf("%v.%v", policyRef.Kind, policyRef.Group), // Type
name, // Name
}
table.Rows = append(table.Rows, row)
}
return table
}

func convertErrorsToString(errors []error) []string {
var result []string
for _, err := range errors {
result = append(result, err.Error())
}
return result
}

type NodeResource interface {
ClientObject() client.Object
}
Expand Down
2 changes: 1 addition & 1 deletion gwctl/pkg/printer/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ TCPRoute ns2/my-tcproute
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
writable := &bytes.Buffer{}
tc.table.writeTable(writable, tc.indent)
tc.table.Write(writable, tc.indent)

got := writable.String()
if diff := cmp.Diff(common.YamlString(tc.want), common.YamlString(got), common.YamlStringTransformer); diff != "" {
Expand Down
Loading

0 comments on commit ceb44ec

Please sign in to comment.