Skip to content

Commit

Permalink
1.17 - Glooctl grpc Upstream output in augmented status (#9804)
Browse files Browse the repository at this point in the history
* Glooctl grpc Upstream output in augmented status (#9799)

* first pass

* cleanup

* apply status to glooctl namespace

* unit tests

* comments, cleanup

* changelog

* codegen

---------

Co-authored-by: soloio-bulldozer[bot] <48420018+soloio-bulldozer[bot]@users.noreply.github.com>

* remove gcp, typos

* Revert "remove gcp, typos"

This reverts commit 5ecd74a.

---------

Co-authored-by: soloio-bulldozer[bot] <48420018+soloio-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
bewebi and soloio-bulldozer[bot] authored Jul 23, 2024
1 parent d18ea40 commit ac58c94
Show file tree
Hide file tree
Showing 6 changed files with 557 additions and 35 deletions.
7 changes: 7 additions & 0 deletions changelog/v1.17.1/glooctl-grpc-function-name-output.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
changelog:
- type: FIX
issueLink: https://github.com/solo-io/gloo/issues/9743
resolvesIssue: false
description: |
Fix a bug where the service and function names of a discovered gRPC service are not printed in JSON and YAML
output when running glooctl get upstreams
2 changes: 1 addition & 1 deletion projects/gloo/cli/pkg/cmd/create/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func createUpstream(opts *options.Options) error {
}
}

return printers.PrintUpstreams(v1.UpstreamList{us}, opts.Top.Output, nil)
return printers.PrintUpstreams(v1.UpstreamList{us}, opts.Top.Output, nil, opts.Metadata.GetNamespace())
}

func upstreamFromOpts(opts *options.Options) (*v1.Upstream, error) {
Expand Down
2 changes: 1 addition & 1 deletion projects/gloo/cli/pkg/cmd/get/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func Upstream(opts *options.Options) *cobra.Command {
return err
}
}
return printers.PrintUpstreams(upstreams, opts.Top.Output, xdsDump)
return printers.PrintUpstreams(upstreams, opts.Top.Output, xdsDump, opts.Metadata.GetNamespace())
},
}
return cmd
Expand Down
2 changes: 1 addition & 1 deletion projects/gloo/cli/pkg/common/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func CreateAndPrintObject(ctx context.Context, yml []byte, outputType printers.O
if err != nil {
return eris.Wrapf(err, "saving Upstream to storage")
}
_ = printers.PrintUpstreams(gloov1.UpstreamList{us}, outputType, nil)
_ = printers.PrintUpstreams(gloov1.UpstreamList{us}, outputType, nil, namespace)
case *v1.VirtualService:
vs, err := helpers.MustNamespacedVirtualServiceClient(ctx, namespace).Write(res, clients.WriteOpts{})
if err != nil {
Expand Down
126 changes: 123 additions & 3 deletions projects/gloo/cli/pkg/printers/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"os"
"sort"

"github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/grpc_json"
"google.golang.org/protobuf/types/known/structpb"

"github.com/golang/protobuf/protoc-gen-go/descriptor"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
Expand All @@ -21,19 +24,26 @@ import (
v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
)

// PrintUpstreams
func PrintUpstreams(upstreams v1.UpstreamList, outputType OutputType, xdsDump *xdsinspection.XdsDump) error {
// key for the functionNames within the Details field of statuses, to be populated with the names of functions
// retrieved from gRPC descriptors if applicable
const functionNamesKey = "functionNames"

// PrintUpstreams prints an UpstreamList, leveraging cliutils.PrintList()
func PrintUpstreams(upstreams v1.UpstreamList, outputType OutputType, xdsDump *xdsinspection.XdsDump, namespace string) error {
if outputType == KUBE_YAML {
return PrintKubeCrdList(upstreams.AsInputResources(), v1.UpstreamCrd)
}

upstreams.Each(addFunctionsFromGrpcTranscoder(namespace))

return cliutils.PrintList(outputType.String(), "", upstreams,
func(data interface{}, w io.Writer) error {
UpstreamTable(xdsDump, data.(v1.UpstreamList), w)
return nil
}, os.Stdout)
}

// PrintTable prints upstreams using tables to io.Writer
// UpstreamTable prints upstreams in table format to io.Writer
func UpstreamTable(xdsDump *xdsinspection.XdsDump, upstreams []*v1.Upstream, w io.Writer) {
table := tablewriter.NewWriter(w)
table.SetHeader([]string{"Upstream", "type", "status", "details"})
Expand Down Expand Up @@ -312,3 +322,113 @@ func getEc2TagFiltersString(filters []*ec2.TagFilter) []string {
}
return out
}

// returns a function that, if applicable, augments a given Upstream's status to contain the names of gRPC functions
// extracted from the GrpcJsonTranscoder
// this is a no-op if the Upstream doesn't have a GrpcJsonTranscoder
// TODO: retrieve function names from GrpcJsonTranscoder descriptor sources other than in-line bin (see https://github.com/solo-io/gloo/issues/9798)
func addFunctionsFromGrpcTranscoder(namespace string) func(*v1.Upstream) {
return func(up *v1.Upstream) {
var functionNames map[string]any

// retrieve function names if applicable
switch usType := up.GetUpstreamType().(type) {
case *v1.Upstream_Kube:
if gjt := usType.GetServiceSpec().GetGrpcJsonTranscoder(); gjt != nil {
if gjt.GetProtoDescriptorBin() != nil {
functionNames = getFunctionsFromDescriptorBin(gjt)
}
}
case *v1.Upstream_Consul:
if gjt := usType.GetServiceSpec().GetGrpcJsonTranscoder(); gjt != nil {
if gjt.GetProtoDescriptorBin() != nil {
functionNames = getFunctionsFromDescriptorBin(gjt)
}
}
case *v1.Upstream_Static:
if gjt := usType.GetServiceSpec().GetGrpcJsonTranscoder(); gjt != nil {
if gjt.GetProtoDescriptorBin() != nil {
functionNames = getFunctionsFromDescriptorBin(gjt)
}
}
}

// if we got any function names, add them to the status for the given namespace
// the function names are the same regardless of namespace, so adding to multiple statuses would be redundant
// we therefore only add to the given namespace's status
if functionNames != nil {
for ns, status := range up.GetNamespacedStatuses().GetStatuses() {
if ns == namespace {
addFunctionNamesToStatus(status, functionNames)
}
}
}
}
}

// returns a map of service names to lists of function names
// the values in the map are of `any` type, which is supported by structpb.NewStruct(), while []string is not
func getFunctionsFromDescriptorBin(gjt *grpc_json.GrpcJsonTranscoder) map[string]any {
grpcFunctions := make(map[string]any)

descriptorBin := gjt.GetProtoDescriptorBin()

for _, grpcService := range gjt.GetServices() {
methodDescriptors := getMethodDescriptors(grpcService, descriptorBin)

funcList := make([]any, methodDescriptors.Len())
for i := 0; i < methodDescriptors.Len(); i++ {
funcList[i] = fmt.Sprintf("%s", methodDescriptors.Get(i).Name())
}
grpcFunctions[grpcService] = funcList
}

return grpcFunctions
}

// add a struct created from the functionNames map to the given status under the Details field for function names
// note that this will result in an object in the following format:
//
// {
// "fields": {
// "functionNames": {
// "structValue": {
// "fields": {
// "solo.examples.v1.StoreService": {
// "listValue": {
// "values": [
// {
// "stringValue": "CreateItem"
// },
// {
// "stringValue": "ListItems"
// },
// {
// "stringValue": "DeleteItem"
// },
// {
// "stringValue": "GetItem"
// }
// ]
// }
// }
// }
// }
// }
// }
// }
func addFunctionNamesToStatus(status *core.Status, functionNames map[string]any) {
if status.GetDetails() == nil {
status.Details = &structpb.Struct{
Fields: make(map[string]*structpb.Value),
}
}

functionNamesStruct, _ := structpb.NewStruct(functionNames)

status.GetDetails().GetFields()[functionNamesKey] = &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: functionNamesStruct,
},
}
}
Loading

0 comments on commit ac58c94

Please sign in to comment.