diff --git a/gwctl/pkg/cmd/get/get.go b/gwctl/pkg/cmd/get/get.go index 050df4f947..219d9cac22 100644 --- a/gwctl/pkg/cmd/get/get.go +++ b/gwctl/pkg/cmd/get/get.go @@ -36,7 +36,7 @@ func NewGetCommand(params *utils.CmdParams) *cobra.Command { flags := &getFlags{} cmd := &cobra.Command{ - Use: "get {policies|policycrds|httproutes}", + Use: "get {gateways|policies|policycrds|httproutes}", Short: "Display one or many resources", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { @@ -56,10 +56,26 @@ func runGet(args []string, params *utils.CmdParams, flags *getFlags) { ns = "" } + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + gwPrinter := &printer.GatewaysPrinter{Out: params.Out} policiesPrinter := &printer.PoliciesPrinter{Out: params.Out} httpRoutesPrinter := &printer.HTTPRoutesPrinter{Out: params.Out} switch kind { + case "gateway", "gateways": + filter := resourcediscovery.Filter{Namespace: ns} + if len(args) > 1 { + filter.Name = args[1] + } + resourceModel, err := discoverer.DiscoverResourcesForGateway(filter) + if err != nil { + panic(err) + } + gwPrinter.Print(resourceModel) + case "policy", "policies": list := params.PolicyManager.GetPolicies() policiesPrinter.Print(list) @@ -69,10 +85,6 @@ func runGet(args []string, params *utils.CmdParams, flags *getFlags) { policiesPrinter.PrintCRDs(list) case "httproute", "httproutes": - discoverer := resourcediscovery.Discoverer{ - K8sClients: params.K8sClients, - PolicyManager: params.PolicyManager, - } filter := resourcediscovery.Filter{Namespace: ns} if len(args) > 1 { filter.Name = args[1] diff --git a/gwctl/pkg/printer/gateways.go b/gwctl/pkg/printer/gateways.go index a398ce8da1..247c572100 100644 --- a/gwctl/pkg/printer/gateways.go +++ b/gwctl/pkg/printer/gateways.go @@ -19,11 +19,17 @@ package printer import ( "fmt" "io" + "strconv" + "strings" + "text/tabwriter" + "time" "sigs.k8s.io/yaml" "sigs.k8s.io/gateway-api/gwctl/pkg/policymanager" "sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery" + + "k8s.io/apimachinery/pkg/util/duration" ) type GatewaysPrinter struct { @@ -40,6 +46,50 @@ type gatewayDescribeView struct { EffectivePolicies map[policymanager.PolicyCrdID]policymanager.Policy `json:",omitempty"` } +func (gp *GatewaysPrinter) Print(resourceModel *resourcediscovery.ResourceModel) { + tw := tabwriter.NewWriter(gp.Out, 0, 0, 2, ' ', 0) + row := []string{"NAME", "CLASS", "ADDRESSES", "PORTS", "PROGRAMMED", "AGE"} + tw.Write([]byte(strings.Join(row, "\t") + "\n")) + + for _, gatewayNode := range resourceModel.Gateways { + var addresses []string + for _, address := range gatewayNode.Gateway.Status.Addresses { + addresses = append(addresses, address.Value) + } + addressesOutput := strings.Join(addresses, ",") + if cnt := len(addresses); cnt > 2 { + addressesOutput = fmt.Sprintf("%v + %v more", strings.Join(addresses[:2], ","), cnt-2) + } + + var ports []string + for _, listener := range gatewayNode.Gateway.Spec.Listeners { + ports = append(ports, strconv.Itoa(int(listener.Port))) + } + portsOutput := strings.Join(ports, ",") + + programmedStatus := "Unknown" + for _, condition := range gatewayNode.Gateway.Status.Conditions { + if condition.Type == "Programmed" { + programmedStatus = string(condition.Status) + break + } + } + + age := duration.HumanDuration(time.Since(gatewayNode.Gateway.GetCreationTimestamp().Time)) + + row := []string{ + gatewayNode.Gateway.GetName(), + string(gatewayNode.Gateway.Spec.GatewayClassName), + addressesOutput, + portsOutput, + programmedStatus, + age, + } + tw.Write([]byte(strings.Join(row, "\t") + "\n")) + } + tw.Flush() +} + func (gp *GatewaysPrinter) PrintDescribeView(resourceModel *resourcediscovery.ResourceModel) { index := 0 for _, gatewayNode := range resourceModel.Gateways { diff --git a/gwctl/pkg/printer/gateways_test.go b/gwctl/pkg/printer/gateways_test.go index d7eb50f3f1..07b3906e19 100644 --- a/gwctl/pkg/printer/gateways_test.go +++ b/gwctl/pkg/printer/gateways_test.go @@ -19,20 +19,92 @@ package printer import ( "bytes" "testing" + "time" "github.com/google/go-cmp/cmp" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - "sigs.k8s.io/gateway-api/gwctl/pkg/cmd/utils" "sigs.k8s.io/gateway-api/gwctl/pkg/common" "sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery" ) +func TestGatewaysPrinter_Print(t *testing.T) { + objects := []runtime.Object{ + &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-gatewayclass", + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: "example.net/gateway-controller", + Description: common.PtrTo("random"), + }, + }, + + &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-gateway", + CreationTimestamp: metav1.Time{ + Time: time.Now().Add(-time.Second), + }, + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: "foo-gatewayclass", + Listeners: []gatewayv1.Listener{ + { + Name: gatewayv1.SectionName("http-1"), + Protocol: gatewayv1.HTTPProtocolType, + Port: gatewayv1.PortNumber(80), + }, + }, + }, + Status: gatewayv1.GatewayStatus{ + Addresses: []gatewayv1.GatewayStatusAddress{ + { + Value: "10.0.0.1", + }, + }, + Conditions: []metav1.Condition{ + { + Type: "Programmed", + Status: metav1.ConditionTrue, + }, + }, + }, + }, + } + + params := utils.MustParamsForTest(t, common.MustClientsForTest(t, objects...)) + discoverer := resourcediscovery.Discoverer{ + K8sClients: params.K8sClients, + PolicyManager: params.PolicyManager, + } + resourceModel, err := discoverer.DiscoverResourcesForGateway(resourcediscovery.Filter{}) + if err != nil { + t.Fatalf("Failed to construct resourceModel: %v", resourceModel) + } + + gp := &GatewaysPrinter{ + Out: params.Out, + } + gp.Print(resourceModel) + + got := params.Out.(*bytes.Buffer).String() + want := ` +NAME CLASS ADDRESSES PORTS PROGRAMMED AGE +foo-gateway foo-gatewayclass 10.0.0.1 80 True 1s +` + + if diff := cmp.Diff(common.YamlString(want), common.YamlString(got), common.YamlStringTransformer); diff != "" { + t.Errorf("Unexpected diff\ngot=\n%v\nwant=\n%v\ndiff (-want +got)=\n%v", got, want, diff) + } +} + func TestGatewaysPrinter_PrintDescribeView(t *testing.T) { objects := []runtime.Object{ &gatewayv1.GatewayClass{