mirror of
https://github.com/fluxcd/flux2.git
synced 2025-10-29 15:28:04 +08:00
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>
This commit is contained in:
@ -27,8 +27,10 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/cli-runtime/pkg/resource"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
@ -39,20 +41,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var traceCmd = &cobra.Command{
|
var traceCmd = &cobra.Command{
|
||||||
Use: "trace [name]",
|
Use: "trace <resource> <name> [<name> ...]",
|
||||||
Short: "Trace an in-cluster object throughout the GitOps delivery pipeline",
|
Short: "Trace in-cluster objects throughout the GitOps delivery pipeline",
|
||||||
Long: `The trace command shows how an object is managed by Flux,
|
Long: `The trace command shows how one or more objects are managed by Flux,
|
||||||
from which source and revision it comes, and what's the latest reconciliation status.'`,
|
from which source and revision they come, and what the latest reconciliation status is.
|
||||||
Example: ` # Trace a Kubernetes Deployment
|
|
||||||
flux trace my-app --kind=deployment --api-version=apps/v1 --namespace=apps
|
|
||||||
|
|
||||||
# Trace a Kubernetes Pod
|
You can also trace multiple objects with different resource kinds using <resource>/<name> multiple times.`,
|
||||||
flux trace redis-master-0 --kind=pod --api-version=v1 -n redis
|
Example: ` # Trace a Kubernetes Deployment
|
||||||
|
flux trace -n apps deployment my-app
|
||||||
|
|
||||||
|
# Trace a Kubernetes Pod and a config map
|
||||||
|
flux trace -n redis pod/redis-master-0 cm/redis
|
||||||
|
|
||||||
# Trace a Kubernetes global object
|
# Trace a Kubernetes global object
|
||||||
flux trace redis --kind=namespace --api-version=v1
|
flux trace namespace redis
|
||||||
|
|
||||||
# Trace a Kubernetes custom resource
|
# 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`,
|
flux trace redis --kind=helmrelease --api-version=helm.toolkit.fluxcd.io/v2beta1 -n redis`,
|
||||||
RunE: traceCmdRun,
|
RunE: traceCmdRun,
|
||||||
}
|
}
|
||||||
@ -73,19 +81,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func traceCmdRun(cmd *cobra.Command, args []string) error {
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -94,28 +89,35 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("invaild apiVersion: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := &unstructured.Unstructured{}
|
return traceObjects(ctx, kubeClient, objects)
|
||||||
obj.SetGroupVersionKind(schema.GroupVersionKind{
|
}
|
||||||
Group: gv.Group,
|
|
||||||
Version: gv.Version,
|
|
||||||
Kind: traceArgs.kind,
|
|
||||||
})
|
|
||||||
|
|
||||||
objName := types.NamespacedName{
|
func traceObjects(ctx context.Context, kubeClient client.Client, objects []*unstructured.Unstructured) error {
|
||||||
Namespace: *kubeconfigArgs.Namespace,
|
for i, obj := range objects {
|
||||||
Name: name,
|
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)
|
||||||
err = kubeClient.Get(ctx, objName, obj)
|
}
|
||||||
if err != nil {
|
if i < len(objects)-1 {
|
||||||
return fmt.Errorf("failed to find object: %w", err)
|
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 {
|
if ks, ok := isOwnerManagedByFlux(ctx, kubeClient, obj, kustomizev1.GroupVersion.Group); ok {
|
||||||
report, err := traceKustomization(ctx, kubeClient, ks, obj)
|
report, err := traceKustomization(ctx, kubeClient, ks, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -137,6 +139,78 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("object not managed by Flux")
|
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) {
|
func traceKustomization(ctx context.Context, kubeClient client.Client, ksName types.NamespacedName, obj *unstructured.Unstructured) (string, error) {
|
||||||
ks := &kustomizev1.Kustomization{}
|
ks := &kustomizev1.Kustomization{}
|
||||||
ksReady := &metav1.Condition{}
|
ksReady := &metav1.Condition{}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import (
|
|||||||
func TestTraceNoArgs(t *testing.T) {
|
func TestTraceNoArgs(t *testing.T) {
|
||||||
cmd := cmdTestCase{
|
cmd := cmdTestCase{
|
||||||
args: "trace",
|
args: "trace",
|
||||||
assert: assertError("object name is required"),
|
assert: assertError("either `<resource>/<name>` or `<resource> <name>` is required as an argument"),
|
||||||
}
|
}
|
||||||
cmd.runTestCmd(t)
|
cmd.runTestCmd(t)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user