mirror of https://github.com/linkerd/linkerd2.git
191 lines
5.0 KiB
Go
191 lines
5.0 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/runconduit/conduit/controller/api/util"
|
|
pb "github.com/runconduit/conduit/controller/gen/public"
|
|
"github.com/runconduit/conduit/pkg/k8s"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
toResource string
|
|
validArgs = []string{
|
|
k8s.KubernetesDeployments,
|
|
k8s.KubernetesNamespaces,
|
|
k8s.KubernetesPods,
|
|
k8s.KubernetesReplicationControllers,
|
|
}
|
|
)
|
|
|
|
var tapByResourceCmd = &cobra.Command{
|
|
Use: "tapByResource [flags] (RESOURCE)",
|
|
Short: "Listen to a traffic stream",
|
|
Long: `Listen to a traffic stream.
|
|
|
|
The RESOURCE argument specifies the resource to tap, for example:
|
|
|
|
* deploy
|
|
* deploy/my-deploy
|
|
* deploy my-deploy
|
|
* ns/my-ns
|
|
|
|
Valid resource types include:
|
|
|
|
* deployments
|
|
* namespaces
|
|
* pods
|
|
* replicationcontrollers`,
|
|
Example: ` # tap the web deployment in the default namespace
|
|
conduit tapByResource deploy/web
|
|
|
|
# tap the web-dlbvj pod in the default namespace
|
|
conduit tapByResource pod/web-dlbvj
|
|
|
|
# tap the test namespace, filter by request to prod namespace
|
|
conduit tapByResource ns/test --to ns/prod`,
|
|
Args: cobra.RangeArgs(1, 2),
|
|
ValidArgs: validArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
req, err := buildTapByResourceRequest(
|
|
args, namespace,
|
|
toResource, toNamespace,
|
|
maxRps,
|
|
scheme, method, authority, path,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client, err := newPublicAPIClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return requestTapByResourceFromAPI(os.Stdout, client, req)
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
RootCmd.AddCommand(tapByResourceCmd)
|
|
tapByResourceCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default",
|
|
"Namespace of the specified resource")
|
|
tapByResourceCmd.PersistentFlags().StringVar(&toResource, "to", "",
|
|
"Display requests to this resource")
|
|
tapByResourceCmd.PersistentFlags().StringVar(&toNamespace, "to-namespace", "",
|
|
"Sets the namespace used to lookup the \"--to\" resource; by default the current \"--namespace\" is used")
|
|
tapByResourceCmd.PersistentFlags().Float32Var(&maxRps, "max-rps", 1.0,
|
|
"Maximum requests per second to tap.")
|
|
tapByResourceCmd.PersistentFlags().StringVar(&scheme, "scheme", "",
|
|
"Display requests with this scheme")
|
|
tapByResourceCmd.PersistentFlags().StringVar(&method, "method", "",
|
|
"Display requests with this HTTP method")
|
|
tapByResourceCmd.PersistentFlags().StringVar(&authority, "authority", "",
|
|
"Display requests with this :authority")
|
|
tapByResourceCmd.PersistentFlags().StringVar(&path, "path", "",
|
|
"Display requests with paths that start with this prefix")
|
|
}
|
|
|
|
func buildTapByResourceRequest(
|
|
resource []string, namespace string,
|
|
toResource, toNamespace string,
|
|
maxRps float32,
|
|
scheme, method, authority, path string,
|
|
) (*pb.TapByResourceRequest, error) {
|
|
|
|
target, err := util.BuildResource(namespace, resource...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("target resource invalid: %s", err)
|
|
}
|
|
if !contains(validArgs, target.Type) {
|
|
return nil, fmt.Errorf("unsupported resource type [%s]", target.Type)
|
|
}
|
|
|
|
matches := []*pb.TapByResourceRequest_Match{}
|
|
|
|
if toResource != "" {
|
|
destination, err := util.BuildResource(toNamespace, toResource)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("destination resource invalid: %s", err)
|
|
}
|
|
|
|
match := pb.TapByResourceRequest_Match{
|
|
Match: &pb.TapByResourceRequest_Match_Destinations{
|
|
Destinations: &pb.ResourceSelection{
|
|
Resource: &destination,
|
|
},
|
|
},
|
|
}
|
|
matches = append(matches, &match)
|
|
}
|
|
|
|
if scheme != "" {
|
|
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
|
|
Match: &pb.TapByResourceRequest_Match_Http_Scheme{Scheme: scheme},
|
|
})
|
|
matches = append(matches, &match)
|
|
}
|
|
if method != "" {
|
|
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
|
|
Match: &pb.TapByResourceRequest_Match_Http_Method{Method: method},
|
|
})
|
|
matches = append(matches, &match)
|
|
}
|
|
if authority != "" {
|
|
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
|
|
Match: &pb.TapByResourceRequest_Match_Http_Authority{Authority: authority},
|
|
})
|
|
matches = append(matches, &match)
|
|
}
|
|
if path != "" {
|
|
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
|
|
Match: &pb.TapByResourceRequest_Match_Http_Path{Path: path},
|
|
})
|
|
matches = append(matches, &match)
|
|
}
|
|
|
|
return &pb.TapByResourceRequest{
|
|
Target: &pb.ResourceSelection{
|
|
Resource: &target,
|
|
},
|
|
MaxRps: maxRps,
|
|
Match: &pb.TapByResourceRequest_Match{
|
|
Match: &pb.TapByResourceRequest_Match_All{
|
|
All: &pb.TapByResourceRequest_Match_Seq{
|
|
Matches: matches,
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func contains(list []string, s string) bool {
|
|
for _, elem := range list {
|
|
if s == elem {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func buildMatchHTTP(match *pb.TapByResourceRequest_Match_Http) pb.TapByResourceRequest_Match {
|
|
return pb.TapByResourceRequest_Match{
|
|
Match: &pb.TapByResourceRequest_Match_Http_{
|
|
Http: match,
|
|
},
|
|
}
|
|
}
|
|
|
|
func requestTapByResourceFromAPI(w io.Writer, client pb.ApiClient, req *pb.TapByResourceRequest) error {
|
|
rsp, err := client.TapByResource(context.Background(), req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return renderTap(w, rsp)
|
|
}
|