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

1.14 - Glooctl grpc fix augment status #9807

Merged
merged 3 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions changelog/v1.14.32/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 @@ -304,3 +314,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
Loading