Simplify arguments of flux trace command

It now accepts arguments in the forms <resource>/<name>
and <resource> <name> instead of requiring api version and
kind as flags.

Signed-off-by: Jakob Schrettenbrunner <jakob.schrettenbrunner@telekom.de>
pull/2141/head
Jakob Schrettenbrunner 3 years ago
parent 83de469967
commit b10eee87ee

@ -27,8 +27,10 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils"
@ -39,20 +41,26 @@ import (
)
var traceCmd = &cobra.Command{
Use: "trace [name]",
Short: "Trace an in-cluster object throughout the GitOps delivery pipeline",
Long: `The trace command shows how an object is managed by Flux,
from which source and revision it comes, and what's the latest reconciliation status.'`,
Use: "trace <resource> <name> [<name> ...]",
Short: "Trace in-cluster objects throughout the GitOps delivery pipeline",
Long: `The trace command shows how one or more objects are managed by Flux,
from which source and revision they come, and what the latest reconciliation status is.
You can also trace multiple objects with different resource kinds using <resource>/<name> multiple times.`,
Example: ` # Trace a Kubernetes Deployment
flux trace my-app --kind=deployment --api-version=apps/v1 --namespace=apps
flux trace -n apps deployment my-app
# Trace a Kubernetes Pod
flux trace redis-master-0 --kind=pod --api-version=v1 -n redis
# Trace a Kubernetes Pod and a config map
flux trace -n redis pod/redis-master-0 cm/redis
# Trace a Kubernetes global object
flux trace redis --kind=namespace --api-version=v1
flux trace namespace redis
# Trace a Kubernetes custom resource
flux trace -n redis helmrelease redis
# API Version and Kind can also be specified explicitly
# Note that either both, kind and api-version, or neither have to be specified.
flux trace redis --kind=helmrelease --api-version=helm.toolkit.fluxcd.io/v2beta1 -n redis`,
RunE: traceCmdRun,
}
@ -73,30 +81,80 @@ func init() {
}
func traceCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("object name is required")
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if err != nil {
return err
}
name := args[0]
if traceArgs.kind == "" {
return fmt.Errorf("object kind is required (--kind)")
var objects []*unstructured.Unstructured
if traceArgs.kind != "" || traceArgs.apiVersion != "" {
var obj *unstructured.Unstructured
obj, err = getObjectStatic(ctx, kubeClient, args)
objects = []*unstructured.Unstructured{obj}
} else {
objects, err = getObjectDynamic(args)
}
if err != nil {
return err
}
if traceArgs.apiVersion == "" {
return fmt.Errorf("object apiVersion is required (--api-version)")
return traceObjects(ctx, kubeClient, objects)
}
func traceObjects(ctx context.Context, kubeClient client.Client, objects []*unstructured.Unstructured) error {
for i, obj := range objects {
err := traceObject(ctx, kubeClient, obj)
if err != nil {
rootCmd.PrintErrf("failed to trace %v/%v in namespace %v: %v", obj.GetKind(), obj.GetName(), obj.GetNamespace(), err)
}
if i < len(objects)-1 {
rootCmd.Println("---")
}
}
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
func traceObject(ctx context.Context, kubeClient client.Client, obj *unstructured.Unstructured) error {
if ks, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, kustomizev1.GroupVersion.Group); ok {
report, err := traceKustomization(ctx, kubeClient, ks, obj)
if err != nil {
return err
}
rootCmd.Print(report)
return nil
}
kubeClient, err := utils.KubeClient(kubeconfigArgs)
if hr, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, helmv2.GroupVersion.Group); ok {
report, err := traceHelm(ctx, kubeClient, hr, obj)
if err != nil {
return err
}
rootCmd.Print(report)
return nil
}
return fmt.Errorf("object not managed by Flux")
}
func getObjectStatic(ctx context.Context, kubeClient client.Client, args []string) (*unstructured.Unstructured, error) {
if len(args) < 1 {
return nil, fmt.Errorf("object name is required")
}
if traceArgs.kind == "" {
return nil, fmt.Errorf("object kind is required (--kind)")
}
if traceArgs.apiVersion == "" {
return nil, fmt.Errorf("object apiVersion is required (--api-version)")
}
gv, err := schema.ParseGroupVersion(traceArgs.apiVersion)
if err != nil {
return fmt.Errorf("invaild apiVersion: %w", err)
return nil, fmt.Errorf("invaild apiVersion: %w", err)
}
obj := &unstructured.Unstructured{}
@ -108,33 +166,49 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
objName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Name: name,
Name: args[0],
}
err = kubeClient.Get(ctx, objName, obj)
if err != nil {
return fmt.Errorf("failed to find object: %w", err)
if err = kubeClient.Get(ctx, objName, obj); err != nil {
return nil, fmt.Errorf("failed to find object: %w", err)
}
return obj, nil
}
func getObjectDynamic(args []string) ([]*unstructured.Unstructured, error) {
r := resource.NewBuilder(kubeconfigArgs).
Unstructured().
NamespaceParam(*kubeconfigArgs.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, args...).
ContinueOnError().
Latest().
Do()
if err := r.Err(); err != nil {
if resource.IsUsageError(err) {
return nil, fmt.Errorf("either `<resource>/<name>` or `<resource> <name>` is required as an argument")
}
return nil, err
}
if ks, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, kustomizev1.GroupVersion.Group); ok {
report, err := traceKustomization(ctx, kubeClient, ks, obj)
infos, err := r.Infos()
if err != nil {
return err
return nil, fmt.Errorf("x: %v", err)
}
rootCmd.Print(report)
return nil
if len(infos) == 0 {
return nil, fmt.Errorf("failed to find object: %w", err)
}
if hr, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, helmv2.GroupVersion.Group); ok {
report, err := traceHelm(ctx, kubeClient, hr, obj)
objects := []*unstructured.Unstructured{}
for _, info := range infos {
obj := &unstructured.Unstructured{}
obj.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(info.Object)
if err != nil {
return err
return objects, err
}
rootCmd.Print(report)
return nil
objects = append(objects, obj)
}
return fmt.Errorf("object not managed by Flux")
return objects, nil
}
func traceKustomization(ctx context.Context, kubeClient client.Client, ksName types.NamespacedName, obj *unstructured.Unstructured) (string, error) {

@ -10,7 +10,7 @@ import (
func TestTraceNoArgs(t *testing.T) {
cmd := cmdTestCase{
args: "trace",
assert: assertError("object name is required"),
assert: assertError("either `<resource>/<name>` or `<resource> <name>` is required as an argument"),
}
cmd.runTestCmd(t)
}

Loading…
Cancel
Save