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,19 +81,6 @@ func init() {
}
func traceCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("object name is required")
}
name := args[0]
if traceArgs.kind == "" {
return fmt.Errorf("object kind is required (--kind)")
}
if traceArgs.apiVersion == "" {
return fmt.Errorf("object apiVersion is required (--api-version)")
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
@ -94,28 +89,35 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
return err
}
gv, err := schema.ParseGroupVersion(traceArgs.apiVersion)
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 fmt.Errorf("invaild apiVersion: %w", err)
return err
}
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: traceArgs.kind,
})
objName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Name: name,
}
return traceObjects(ctx, kubeClient, objects)
}
err = kubeClient.Get(ctx, objName, obj)
if err != nil {
return fmt.Errorf("failed to find object: %w", err)
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
}
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 {
@ -137,6 +139,78 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
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 nil, fmt.Errorf("invaild apiVersion: %w", err)
}
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: traceArgs.kind,
})
objName := types.NamespacedName{
Namespace: *kubeconfigArgs.Namespace,
Name: args[0],
}
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
}
infos, err := r.Infos()
if err != nil {
return nil, fmt.Errorf("x: %v", err)
}
if len(infos) == 0 {
return nil, fmt.Errorf("failed to find object: %w", err)
}
objects := []*unstructured.Unstructured{}
for _, info := range infos {
obj := &unstructured.Unstructured{}
obj.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(info.Object)
if err != nil {
return objects, err
}
objects = append(objects, obj)
}
return objects, nil
}
func traceKustomization(ctx context.Context, kubeClient client.Client, ksName types.NamespacedName, obj *unstructured.Unstructured) (string, error) {
ks := &kustomizev1.Kustomization{}
ksReady := &metav1.Condition{}

@ -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