Repository: Azure/operatortrace
Pull request #3: refactor into separate files
Author: login: fawadasaurus
Created: 2025-04-17T14:08:36Z | Merged: 2025-04-17T14:44:49Z

Added a few additional tests. All existing tests passing.

@microsoft-github-policy-service agree


Diff:
diff --git a/operatortrace-go/pkg/client/annotations.go b/operatortrace-go/pkg/client/annotations.go
new file mode 100644
index 0000000..d340908
--- /dev/null
+++ b/operatortrace-go/pkg/client/annotations.go
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+
+	constants "github.com/Azure/operatortrace/operatortrace-go/pkg/constants"
+	"go.opentelemetry.io/otel/trace"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// addTraceIDAnnotation adds the traceID as an annotation to the object
+func addTraceIDAnnotation(ctx context.Context, obj client.Object) {
+	span := trace.SpanFromContext(ctx)
+	traceID := span.SpanContext().TraceID().String()
+	if traceID != "" {
+		if obj.GetAnnotations() == nil {
+			obj.SetAnnotations(map[string]string{})
+		}
+		annotations := obj.GetAnnotations()
+		annotations[constants.TraceIDAnnotation] = traceID
+		obj.SetAnnotations(annotations)
+	}
+	spanID := span.SpanContext().SpanID().String()
+	if spanID != "" {
+		if obj.GetAnnotations() == nil {
+			obj.SetAnnotations(map[string]string{})
+		}
+		annotations := obj.GetAnnotations()
+		annotations[constants.SpanIDAnnotation] = spanID
+		obj.SetAnnotations(annotations)
+	}
+}
+
+// if the key.Name looks like this: f620f5cad0af940c294f980c5366a6a1;45f359cdc1c8ab06;Configmap;pod-configmap01;default-pod
+// then we can extract the traceID and spanID from the key.Name
+// and override the traceID and spanID in the object annotations
+func overrideTraceIDFromNamespacedName(key client.ObjectKey, obj client.Object) error {
+	embedTraceID := &EmbedTraceID{}
+	if err := embedTraceID.FromString(key.Name); err != nil {
+		return nil
+	}
+
+	if obj.GetAnnotations() == nil {
+		obj.SetAnnotations(map[string]string{})
+	}
+	annotations := obj.GetAnnotations()
+	annotations[constants.TraceIDAnnotation] = embedTraceID.TraceID
+	annotations[constants.SpanIDAnnotation] = embedTraceID.SpanID
+	obj.SetAnnotations(annotations)
+	return nil
+}
diff --git a/operatortrace-go/pkg/client/conditions.go b/operatortrace-go/pkg/client/conditions.go
new file mode 100644
index 0000000..f48fe8c
--- /dev/null
+++ b/operatortrace-go/pkg/client/conditions.go
@@ -0,0 +1,224 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"fmt"
+	"reflect"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+// getConditionMessage retrieves the message for a specific condition type from a Kubernetes object.
+func getConditionMessage(conditionType string, obj client.Object, scheme *runtime.Scheme) (string, error) {
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return "", err
+	}
+
+	for _, condition := range conditions {
+		// Check if "Type" key exists
+		conType, exists := condition["Type"]
+		if !exists {
+			return "", fmt.Errorf("condition does not contain a 'Type' field")
+		}
+
+		// Convert conType to string using reflection
+		conTypeStr, err := convertToString(conType)
+		if err != nil {
+			return "", fmt.Errorf("failed to convert 'Type' field to string: %v", err)
+		}
+
+		if conTypeStr == conditionType {
+			message := condition["Message"].(string)
+			return message, nil
+		}
+	}
+
+	return "", fmt.Errorf("condition of type %s not found", conditionType)
+}
+
+// setConditionMessage sets the message for a specific condition type in a Kubernetes object.
+func setConditionMessage(conditionType, message string, obj client.Object, scheme *runtime.Scheme) error {
+	deleteConditionAsMap(conditionType, obj, scheme)
+
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return err
+	}
+
+	newCondition := map[string]interface{}{
+		"Type":               conditionType,
+		"Status":             metav1.ConditionUnknown,
+		"LastTransitionTime": metav1.Now(),
+		"Message":            message,
+	}
+	conditions = append(conditions, newCondition)
+
+	return setConditionsFromMap(obj, conditions, scheme)
+}
+
+func deleteConditionAsMap(conditionType string, obj client.Object, scheme *runtime.Scheme) error {
+	// Retrieve the current conditions as a map
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return err
+	}
+
+	var outConditions []map[string]interface{}
+	for _, condition := range conditions {
+		// Check if "Type" key exists
+		conType, exists := condition["Type"]
+		if !exists {
+			return fmt.Errorf("condition does not contain a 'Type' field")
+		}
+
+		// Convert conType to string using reflection
+		conTypeStr, err := convertToString(conType)
+		if err != nil {
+			return fmt.Errorf("failed to convert 'Type' field to string: %v", err)
+		}
+
+		if conTypeStr != conditionType {
+			outConditions = append(outConditions, condition)
+		}
+	}
+
+	// Set the updated conditions back to the object
+	return setConditionsFromMap(obj, outConditions, scheme)
+}
+
+func getConditionsAsMap(obj client.Object, scheme *runtime.Scheme) ([]map[string]interface{}, error) {
+	gvk, err := apiutil.GVKForObject(obj, scheme)
+	if err != nil {
+		return nil, fmt.Errorf("problem getting the GVK: %w", err)
+	}
+
+	objTyped, err := scheme.New(gvk)
+	if err != nil {
+		return nil, fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err)
+	}
+
+	if err := scheme.Convert(obj, objTyped, nil); err != nil {
+		return nil, fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err)
+	}
+
+	val := reflect.ValueOf(objTyped)
+	statusField := val.Elem().FieldByName("Status")
+	if !statusField.IsValid() {
+		return nil, fmt.Errorf("status field not found in kind %s", gvk.Kind)
+	}
+
+	conditionsField := statusField.FieldByName("Conditions")
+	if !conditionsField.IsValid() {
+		return nil, fmt.Errorf("conditions field not found in kind %s", gvk.Kind)
+	}
+
+	conditionsValue := conditionsField.Interface()
+	val = reflect.ValueOf(conditionsValue)
+	if val.Kind() != reflect.Slice {
+		return nil, fmt.Errorf("conditions field is not a slice")
+	}
+
+	var conditionsAsMap []map[string]interface{}
+	for i := 0; i < val.Len(); i++ {
+		conditionVal := val.Index(i)
+		if conditionVal.Kind() == reflect.Ptr {
+			conditionVal = conditionVal.Elem()
+		}
+
+		conditionMap := make(map[string]interface{})
+		for _, field := range reflect.VisibleFields(conditionVal.Type()) {
+			fieldValue := conditionVal.FieldByIndex(field.Index)
+			conditionMap[field.Name] = fieldValue.Interface()
+		}
+
+		conditionsAsMap = append(conditionsAsMap, conditionMap)
+	}
+
+	return conditionsAsMap, nil
+}
+
+func setConditionsFromMap(obj client.Object, conditionsAsMap []map[string]interface{}, scheme *runtime.Scheme) error {
+	gvk, err := apiutil.GVKForObject(obj, scheme)
+	if err != nil {
+		return fmt.Errorf("problem getting the GVK: %w", err)
+	}
+
+	objTyped, err := scheme.New(gvk)
+	if err != nil {
+		return fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err)
+	}
+
+	if err := scheme.Convert(obj, objTyped, nil); err != nil {
+		return fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err)
+	}
+
+	val := reflect.ValueOf(objTyped)
+	statusField := val.Elem().FieldByName("Status")
+	if !statusField.IsValid() {
+		return fmt.Errorf("status field not found in kind %s", gvk.Kind)
+	}
+
+	conditionsField := statusField.FieldByName("Conditions")
+	if !conditionsField.IsValid() {
+		return fmt.Errorf("conditions field not found in kind %s", gvk.Kind)
+	}
+
+	elemType := conditionsField.Type().Elem()
+	result := reflect.MakeSlice(conditionsField.Type(), len(conditionsAsMap), len(conditionsAsMap))
+
+	for i, conditionMap := range conditionsAsMap {
+		targetCond := reflect.New(elemType).Elem()
+		for key, value := range conditionMap {
+			field := targetCond.FieldByName(key)
+			if field.IsValid() {
+				val := reflect.ValueOf(value)
+				if val.Type().ConvertibleTo(field.Type()) {
+					field.Set(val.Convert(field.Type()))
+				} else {
+					return fmt.Errorf("cannot convert value of field %s from %s to %s", key, val.Type(), field.Type())
+				}
+			}
+		}
+		if conditionsField.Type().Elem().Kind() == reflect.Ptr {
+			result.Index(i).Set(targetCond.Addr())
+		} else {
+			result.Index(i).Set(targetCond)
+		}
+	}
+
+	conditionsField.Set(result)
+	if err := scheme.Convert(objTyped, obj, nil); err != nil {
+		return fmt.Errorf("problem converting object back to unstructured: %w", err)
+	}
+
+	return nil
+}
+
+func mapToStruct(structVal reflect.Value, data map[string]interface{}) error {
+	for key, value := range data {
+		field := structVal.FieldByName(key)
+		if field.IsValid() {
+			switch field.Kind() {
+			case reflect.String:
+				field.SetString(value.(string))
+			case reflect.Bool:
+				field.SetBool(value.(bool))
+			case reflect.Int32:
+				field.SetInt(int64(value.(int32)))
+			case reflect.Int64:
+				field.SetInt(value.(int64))
+			case reflect.Float64:
+				field.SetFloat(value.(float64))
+			default:
+				field.Set(reflect.ValueOf(value))
+			}
+		}
+	}
+	return nil
+}
diff --git a/operatortrace-go/pkg/client/embed_traceid.go b/operatortrace-go/pkg/client/embed_traceid.go
new file mode 100644
index 0000000..2b37e9c
--- /dev/null
+++ b/operatortrace-go/pkg/client/embed_traceid.go
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"fmt"
+	"strings"
+)
+
+type EmbedTraceID struct {
+	TraceID    string
+	SpanID     string
+	ObjectKind string
+	ObjectName string
+	KeyName    string
+}
+
+func (e *EmbedTraceID) ToString() string {
+	return fmt.Sprintf("%s;%s;%s;%s;%s", e.TraceID, e.SpanID, e.ObjectKind, e.ObjectName, e.KeyName)
+}
+
+func (e *EmbedTraceID) FromString(s string) error {
+	parts := strings.Split(s, ";")
+	if len(parts) != 5 {
+		return fmt.Errorf("invalid string format")
+	}
+	e.TraceID = parts[0]
+	e.SpanID = parts[1]
+	e.ObjectKind = parts[2]
+	e.ObjectName = parts[3]
+	e.KeyName = parts[4]
+	return nil
+}
diff --git a/operatortrace-go/pkg/client/embed_traceid_test.go b/operatortrace-go/pkg/client/embed_traceid_test.go
new file mode 100644
index 0000000..70ce882
--- /dev/null
+++ b/operatortrace-go/pkg/client/embed_traceid_test.go
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEmbedTraceID_ToString(t *testing.T) {
+	embed := &EmbedTraceID{
+		TraceID:    "trace-123",
+		SpanID:     "span-456",
+		ObjectKind: "Deployment",
+		ObjectName: "my-deployment",
+		KeyName:    "original-key",
+	}
+
+	expected := "trace-123;span-456;Deployment;my-deployment;original-key"
+	assert.Equal(t, expected, embed.ToString())
+}
+
+func TestEmbedTraceID_FromString(t *testing.T) {
+	t.Run("valid string", func(t *testing.T) {
+		input := "trace-789;span-012;Pod;my-pod;pod-key"
+		expected := &EmbedTraceID{
+			TraceID:    "trace-789",
+			SpanID:     "span-012",
+			ObjectKind: "Pod",
+			ObjectName: "my-pod",
+			KeyName:    "pod-key",
+		}
+
+		actual := &EmbedTraceID{}
+		err := actual.FromString(input)
+
+		assert.NoError(t, err)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("invalid string format", func(t *testing.T) {
+		input := "trace-789;span-012;Pod;my-pod" // Missing one part
+
+		actual := &EmbedTraceID{}
+		err := actual.FromString(input)
+
+		assert.Error(t, err)
+		assert.EqualError(t, err, "invalid string format")
+	})
+}
+
+func TestEmbedTraceID_ToStringAndFromString_RoundTrip(t *testing.T) {
+	original := &EmbedTraceID{
+		TraceID:    "trace-abc",
+		SpanID:     "span-def",
+		ObjectKind: "Service",
+		ObjectName: "my-service",
+		KeyName:    "service-key",
+	}
+
+	str := original.ToString()
+
+	parsed := &EmbedTraceID{}
+	err := parsed.FromString(str)
+
+	assert.NoError(t, err)
+	assert.Equal(t, original, parsed)
+}
diff --git a/operatortrace-go/pkg/client/span_helpers.go b/operatortrace-go/pkg/client/span_helpers.go
new file mode 100644
index 0000000..6329bf7
--- /dev/null
+++ b/operatortrace-go/pkg/client/span_helpers.go
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+
+	constants "github.com/Azure/operatortrace/operatortrace-go/pkg/constants"
+	"github.com/go-logr/logr"
+	"go.opentelemetry.io/otel/trace"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// startSpanFromContext starts a new span from the context and attaches trace information to the object
+func startSpanFromContext(ctx context.Context, logger logr.Logger, tracer trace.Tracer, obj client.Object, scheme *runtime.Scheme, operationName string) (context.Context, trace.Span) {
+	span := trace.SpanFromContext(ctx)
+	if span.SpanContext().IsValid() {
+		spanContext := trace.NewSpanContext(trace.SpanContextConfig{
+			TraceID: span.SpanContext().TraceID(),
+			SpanID:  span.SpanContext().SpanID(),
+		})
+		ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
+		ctx, span = tracer.Start(ctx, operationName)
+		return ctx, span
+	}
+
+	if !span.SpanContext().IsValid() {
+		if obj != nil {
+			// no valid trace ID in context, check object conditions
+			if traceID, err := getConditionMessage("TraceID", obj, scheme); err == nil {
+				if traceIDValue, err := trace.TraceIDFromHex(traceID); err == nil {
+					spanContext := trace.NewSpanContext(trace.SpanContextConfig{})
+					if spanID, err := getConditionMessage("SpanID", obj, scheme); err == nil {
+						if spanIDValue, err := trace.SpanIDFromHex(spanID); err == nil {
+							spanContext = trace.NewSpanContext(trace.SpanContextConfig{
+								TraceID: traceIDValue,
+								SpanID:  spanIDValue,
+							})
+						} else {
+							spanContext = trace.NewSpanContext(trace.SpanContextConfig{
+								TraceID: traceIDValue,
+							})
+						}
+					}
+					ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
+				}
+			} else {
+				// No valid trace ID in context, check object annotations
+				if traceID, ok := obj.GetAnnotations()[constants.TraceIDAnnotation]; ok {
+					if traceIDValue, err := trace.TraceIDFromHex(traceID); err == nil {
+						spanContext := trace.NewSpanContext(trace.SpanContextConfig{})
+						if spanID, ok := obj.GetAnnotations()[constants.SpanIDAnnotation]; ok {
+							if spanIDValue, err := trace.SpanIDFromHex(spanID); err == nil {
+								spanContext = trace.NewSpanContext(trace.SpanContextConfig{
+									TraceID: traceIDValue,
+									SpanID:  spanIDValue,
+								})
+							} else {
+								spanContext = trace.NewSpanContext(trace.SpanContextConfig{
+									TraceID: traceIDValue,
+								})
+							}
+						}
+						ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
+					} else {
+						logger.Error(err, "Invalid trace ID", "traceID", traceID)
+					}
+				}
+			}
+		}
+	}
+
+	// Create a new span
+	ctx, span = tracer.Start(ctx, operationName)
+	return ctx, span
+}
+
+func startSpanFromContextList(ctx context.Context, logger logr.Logger, tracer trace.Tracer, obj client.ObjectList, operationName string) (context.Context, trace.Span) {
+	span := trace.SpanFromContext(ctx)
+	if span.SpanContext().IsValid() {
+		spanContext := trace.NewSpanContext(trace.SpanContextConfig{
+			TraceID: span.SpanContext().TraceID(),
+			SpanID:  span.SpanContext().SpanID(),
+		})
+		ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
+		ctx, span = tracer.Start(ctx, operationName)
+		return trace.ContextWithSpan(ctx, span), span
+	}
+
+	// Create a new span
+	ctx, span = tracer.Start(ctx, operationName)
+	return ctx, span
+}
diff --git a/operatortrace-go/pkg/client/tracing_client.go b/operatortrace-go/pkg/client/tracing_client.go
index 3b43961..8a3a716 100644
--- a/operatortrace-go/pkg/client/tracing_client.go
+++ b/operatortrace-go/pkg/client/tracing_client.go
@@ -6,13 +6,10 @@ package client
 import (
 	"context"
 	"fmt"
-	"reflect"
-	"strings"
 
 	constants "github.com/Azure/operatortrace/operatortrace-go/pkg/constants"
 	"github.com/go-logr/logr"
 	"go.opentelemetry.io/otel/trace"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -28,53 +25,7 @@ type tracingClient struct {
 	Logger logr.Logger
 }
 
-type tracingStatusClient struct {
-	scheme *runtime.Scheme
-	client.StatusWriter
-	trace.Tracer
-	Logger logr.Logger
-}
-
-// EmbedTraceID struct to handle embedding and extracting trace ID and span ID information.
-type EmbedTraceID struct {
-	TraceID    string
-	SpanID     string
-	ObjectKind string
-	ObjectName string
-	KeyName    string
-}
-
-// ToString method converts EmbedTraceID struct to a string representation.
-func (e *EmbedTraceID) ToString() string {
-	return fmt.Sprintf("%s;%s;%s;%s;%s", e.TraceID, e.SpanID, e.ObjectKind, e.ObjectName, e.KeyName)
-}
-
-// FromString method parses a string and fills the EmbedTraceID struct.
-func (e *EmbedTraceID) FromString(s string) error {
-	parts := strings.Split(s, ";")
-	if len(parts) != 5 {
-		return fmt.Errorf("invalid string format")
-	}
-	e.TraceID = parts[0]
-	e.SpanID = parts[1]
-	e.ObjectKind = parts[2]
-	e.ObjectName = parts[3]
-	e.KeyName = parts[4]
-	return nil
-}
-
-type TracingClient interface {
-	client.Client
-	trace.Tracer
-	// We use this to which calls client.Client Get
-	StartTrace(ctx context.Context, key *client.ObjectKey, obj client.Object, opts ...client.GetOption) (context.Context, trace.Span, error)
-	EndTrace(ctx context.Context, obj client.Object, opts ...client.PatchOption) (client.Object, error)
-	StartSpan(ctx context.Context, operationName string) (context.Context, trace.Span)
-	EmbedTraceIDInNamespacedName(key *client.ObjectKey, obj client.Object) error
-}
-
 var _ TracingClient = (*tracingClient)(nil)
-var _ client.StatusWriter = (*tracingStatusClient)(nil)
 
 // NewTracingClient initializes and returns a new TracingClient
 // optional scheme.  If not, it will use client-go scheme
@@ -366,457 +317,3 @@ func (tc *tracingClient) DeleteAllOf(ctx context.Context, obj client.Object, opt
 	return err
 
 }
-
-func (tc *tracingClient) Status() client.StatusWriter {
-	return &tracingStatusClient{
-		scheme:       tc.scheme,
-		Logger:       tc.Logger,
-		StatusWriter: tc.Client.Status(),
-		Tracer:       tc.Tracer,
-	}
-}
-
-func (ts *tracingStatusClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
-	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
-	if err != nil {
-		return fmt.Errorf("problem getting the scheme: %w", err)
-	}
-
-	kind := gvk.GroupKind().Kind
-
-	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusUpdate %s %s", kind, obj.GetName()))
-	defer span.End()
-
-	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
-	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
-
-	ts.Logger.Info("updating status object", "object", obj.GetName())
-	err = ts.StatusWriter.Update(ctx, obj, opts...)
-	if err != nil {
-		span.RecordError(err)
-	}
-	return err
-}
-
-func (ts *tracingStatusClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
-	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
-	if err != nil {
-		return fmt.Errorf("problem getting the scheme: %w", err)
-	}
-
-	kind := gvk.GroupKind().Kind
-
-	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusPatch %s %s", kind, obj.GetName()))
-	defer span.End()
-
-	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
-	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
-
-	ts.Logger.Info("patching status object", "object", obj.GetName())
-	err = ts.StatusWriter.Patch(ctx, obj, patch, opts...)
-	if err != nil {
-		span.RecordError(err)
-	}
-
-	return err
-}
-
-func (ts *tracingStatusClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
-	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
-	if err != nil {
-		return fmt.Errorf("problem getting the scheme: %w", err)
-	}
-
-	kind := gvk.GroupKind().Kind
-
-	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusCreate %s %s", kind, obj.GetName()))
-	defer span.End()
-
-	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
-	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
-
-	ts.Logger.Info("creating status object", "object", obj.GetName())
-	err = ts.StatusWriter.Create(ctx, obj, subResource, opts...)
-	if err != nil {
-		span.RecordError(err)
-	}
-	return err
-}
-
-// startSpanFromContext starts a new span from the context and attaches trace information to the object
-func startSpanFromContext(ctx context.Context, logger logr.Logger, tracer trace.Tracer, obj client.Object, scheme *runtime.Scheme, operationName string) (context.Context, trace.Span) {
-	span := trace.SpanFromContext(ctx)
-	if span.SpanContext().IsValid() {
-		spanContext := trace.NewSpanContext(trace.SpanContextConfig{
-			TraceID: span.SpanContext().TraceID(),
-			SpanID:  span.SpanContext().SpanID(),
-		})
-		ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
-		ctx, span = tracer.Start(ctx, operationName)
-		return ctx, span
-	}
-
-	if !span.SpanContext().IsValid() {
-		if obj != nil {
-			// no valid trace ID in context, check object conditions
-			if traceID, err := getConditionMessage("TraceID", obj, scheme); err == nil {
-				if traceIDValue, err := trace.TraceIDFromHex(traceID); err == nil {
-					spanContext := trace.NewSpanContext(trace.SpanContextConfig{})
-					if spanID, err := getConditionMessage("SpanID", obj, scheme); err == nil {
-						if spanIDValue, err := trace.SpanIDFromHex(spanID); err == nil {
-							spanContext = trace.NewSpanContext(trace.SpanContextConfig{
-								TraceID: traceIDValue,
-								SpanID:  spanIDValue,
-							})
-						} else {
-							spanContext = trace.NewSpanContext(trace.SpanContextConfig{
-								TraceID: traceIDValue,
-							})
-						}
-					}
-					ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
-				}
-			} else {
-				// No valid trace ID in context, check object annotations
-				if traceID, ok := obj.GetAnnotations()[constants.TraceIDAnnotation]; ok {
-					if traceIDValue, err := trace.TraceIDFromHex(traceID); err == nil {
-						spanContext := trace.NewSpanContext(trace.SpanContextConfig{})
-						if spanID, ok := obj.GetAnnotations()[constants.SpanIDAnnotation]; ok {
-							if spanIDValue, err := trace.SpanIDFromHex(spanID); err == nil {
-								spanContext = trace.NewSpanContext(trace.SpanContextConfig{
-									TraceID: traceIDValue,
-									SpanID:  spanIDValue,
-								})
-							} else {
-								spanContext = trace.NewSpanContext(trace.SpanContextConfig{
-									TraceID: traceIDValue,
-								})
-							}
-						}
-						ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
-					} else {
-						logger.Error(err, "Invalid trace ID", "traceID", traceID)
-					}
-				}
-			}
-		}
-	}
-
-	// Create a new span
-	ctx, span = tracer.Start(ctx, operationName)
-	return ctx, span
-}
-
-// getCallerKindFromNamespacedName extracts the caller kind from the key.Name
-func getCallerKindFromNamespacedName(key client.ObjectKey) string {
-	embedTraceID := &EmbedTraceID{}
-	if err := embedTraceID.FromString(key.Name); err != nil {
-		return ""
-	}
-	return embedTraceID.ObjectKind
-}
-
-// getCallerNameFromNamespacedName extracts the caller name from the key.Name
-func getCallerNameFromNamespacedName(key client.ObjectKey) string {
-	embedTraceID := &EmbedTraceID{}
-	if err := embedTraceID.FromString(key.Name); err != nil {
-		return ""
-	}
-	return embedTraceID.ObjectName
-}
-
-// getNameFromNamespacedName extracts the original name from the key.Name
-func getNameFromNamespacedName(key client.ObjectKey) string {
-	embedTraceID := &EmbedTraceID{}
-	if err := embedTraceID.FromString(key.Name); err != nil {
-		return key.Name
-	}
-	return embedTraceID.KeyName
-}
-
-// if the key.Name looks like this: f620f5cad0af940c294f980c5366a6a1;45f359cdc1c8ab06;Configmap;pod-configmap01;default-pod
-// then we can extract the traceID and spanID from the key.Name
-// and override the traceID and spanID in the object annotations
-func overrideTraceIDFromNamespacedName(key client.ObjectKey, obj client.Object) error {
-	embedTraceID := &EmbedTraceID{}
-	if err := embedTraceID.FromString(key.Name); err != nil {
-		return nil
-	}
-
-	if obj.GetAnnotations() == nil {
-		obj.SetAnnotations(map[string]string{})
-	}
-	annotations := obj.GetAnnotations()
-	annotations[constants.TraceIDAnnotation] = embedTraceID.TraceID
-	annotations[constants.SpanIDAnnotation] = embedTraceID.SpanID
-	obj.SetAnnotations(annotations)
-	return nil
-}
-
-func startSpanFromContextList(ctx context.Context, logger logr.Logger, tracer trace.Tracer, obj client.ObjectList, operationName string) (context.Context, trace.Span) {
-	span := trace.SpanFromContext(ctx)
-	if span.SpanContext().IsValid() {
-		spanContext := trace.NewSpanContext(trace.SpanContextConfig{
-			TraceID: span.SpanContext().TraceID(),
-			SpanID:  span.SpanContext().SpanID(),
-		})
-		ctx = trace.ContextWithRemoteSpanContext(ctx, spanContext)
-		ctx, span = tracer.Start(ctx, operationName)
-		return trace.ContextWithSpan(ctx, span), span
-	}
-
-	// Create a new span
-	ctx, span = tracer.Start(ctx, operationName)
-	return ctx, span
-}
-
-// addTraceIDAnnotation adds the traceID as an annotation to the object
-func addTraceIDAnnotation(ctx context.Context, obj client.Object) {
-	span := trace.SpanFromContext(ctx)
-	traceID := span.SpanContext().TraceID().String()
-	if traceID != "" {
-		if obj.GetAnnotations() == nil {
-			obj.SetAnnotations(map[string]string{})
-		}
-		annotations := obj.GetAnnotations()
-		annotations[constants.TraceIDAnnotation] = traceID
-		obj.SetAnnotations(annotations)
-	}
-	spanID := span.SpanContext().SpanID().String()
-	if spanID != "" {
-		if obj.GetAnnotations() == nil {
-			obj.SetAnnotations(map[string]string{})
-		}
-		annotations := obj.GetAnnotations()
-		annotations[constants.SpanIDAnnotation] = spanID
-		obj.SetAnnotations(annotations)
-	}
-}
-
-// getConditionMessage retrieves the message for a specific condition type from a Kubernetes object.
-func getConditionMessage(conditionType string, obj client.Object, scheme *runtime.Scheme) (string, error) {
-	conditions, err := getConditionsAsMap(obj, scheme)
-	if err != nil {
-		return "", err
-	}
-
-	for _, condition := range conditions {
-		// Check if "Type" key exists
-		conType, exists := condition["Type"]
-		if !exists {
-			return "", fmt.Errorf("condition does not contain a 'Type' field")
-		}
-
-		// Convert conType to string using reflection
-		conTypeStr, err := convertToString(conType)
-		if err != nil {
-			return "", fmt.Errorf("failed to convert 'Type' field to string: %v", err)
-		}
-
-		if conTypeStr == conditionType {
-			message := condition["Message"].(string)
-			return message, nil
-		}
-	}
-
-	return "", fmt.Errorf("condition of type %s not found", conditionType)
-}
-
-// setConditionMessage sets the message for a specific condition type in a Kubernetes object.
-func setConditionMessage(conditionType, message string, obj client.Object, scheme *runtime.Scheme) error {
-	deleteConditionAsMap(conditionType, obj, scheme)
-
-	conditions, err := getConditionsAsMap(obj, scheme)
-	if err != nil {
-		return err
-	}
-
-	newCondition := map[string]interface{}{
-		"Type":               conditionType,
-		"Status":             metav1.ConditionUnknown,
-		"LastTransitionTime": metav1.Now(),
-		"Message":            message,
-	}
-	conditions = append(conditions, newCondition)
-
-	return setConditionsFromMap(obj, conditions, scheme)
-}
-
-func deleteConditionAsMap(conditionType string, obj client.Object, scheme *runtime.Scheme) error {
-	// Retrieve the current conditions as a map
-	conditions, err := getConditionsAsMap(obj, scheme)
-	if err != nil {
-		return err
-	}
-
-	var outConditions []map[string]interface{}
-	for _, condition := range conditions {
-		// Check if "Type" key exists
-		conType, exists := condition["Type"]
-		if !exists {
-			return fmt.Errorf("condition does not contain a 'Type' field")
-		}
-
-		// Convert conType to string using reflection
-		conTypeStr, err := convertToString(conType)
-		if err != nil {
-			return fmt.Errorf("failed to convert 'Type' field to string: %v", err)
-		}
-
-		if conTypeStr != conditionType {
-			outConditions = append(outConditions, condition)
-		}
-	}
-
-	// Set the updated conditions back to the object
-	return setConditionsFromMap(obj, outConditions, scheme)
-}
-
-func getConditionsAsMap(obj client.Object, scheme *runtime.Scheme) ([]map[string]interface{}, error) {
-	gvk, err := apiutil.GVKForObject(obj, scheme)
-	if err != nil {
-		return nil, fmt.Errorf("problem getting the GVK: %w", err)
-	}
-
-	objTyped, err := scheme.New(gvk)
-	if err != nil {
-		return nil, fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err)
-	}
-
-	if err := scheme.Convert(obj, objTyped, nil); err != nil {
-		return nil, fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err)
-	}
-
-	val := reflect.ValueOf(objTyped)
-	statusField := val.Elem().FieldByName("Status")
-	if !statusField.IsValid() {
-		return nil, fmt.Errorf("status field not found in kind %s", gvk.Kind)
-	}
-
-	conditionsField := statusField.FieldByName("Conditions")
-	if !conditionsField.IsValid() {
-		return nil, fmt.Errorf("conditions field not found in kind %s", gvk.Kind)
-	}
-
-	conditionsValue := conditionsField.Interface()
-	val = reflect.ValueOf(conditionsValue)
-	if val.Kind() != reflect.Slice {
-		return nil, fmt.Errorf("conditions field is not a slice")
-	}
-
-	var conditionsAsMap []map[string]interface{}
-	for i := 0; i < val.Len(); i++ {
-		conditionVal := val.Index(i)
-		if conditionVal.Kind() == reflect.Ptr {
-			conditionVal = conditionVal.Elem()
-		}
-
-		conditionMap := make(map[string]interface{})
-		for _, field := range reflect.VisibleFields(conditionVal.Type()) {
-			fieldValue := conditionVal.FieldByIndex(field.Index)
-			conditionMap[field.Name] = fieldValue.Interface()
-		}
-
-		conditionsAsMap = append(conditionsAsMap, conditionMap)
-	}
-
-	return conditionsAsMap, nil
-}
-
-func setConditionsFromMap(obj client.Object, conditionsAsMap []map[string]interface{}, scheme *runtime.Scheme) error {
-	gvk, err := apiutil.GVKForObject(obj, scheme)
-	if err != nil {
-		return fmt.Errorf("problem getting the GVK: %w", err)
-	}
-
-	objTyped, err := scheme.New(gvk)
-	if err != nil {
-		return fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err)
-	}
-
-	if err := scheme.Convert(obj, objTyped, nil); err != nil {
-		return fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err)
-	}
-
-	val := reflect.ValueOf(objTyped)
-	statusField := val.Elem().FieldByName("Status")
-	if !statusField.IsValid() {
-		return fmt.Errorf("status field not found in kind %s", gvk.Kind)
-	}
-
-	conditionsField := statusField.FieldByName("Conditions")
-	if !conditionsField.IsValid() {
-		return fmt.Errorf("conditions field not found in kind %s", gvk.Kind)
-	}
-
-	elemType := conditionsField.Type().Elem()
-	result := reflect.MakeSlice(conditionsField.Type(), len(conditionsAsMap), len(conditionsAsMap))
-
-	for i, conditionMap := range conditionsAsMap {
-		targetCond := reflect.New(elemType).Elem()
-		for key, value := range conditionMap {
-			field := targetCond.FieldByName(key)
-			if field.IsValid() {
-				val := reflect.ValueOf(value)
-				if val.Type().ConvertibleTo(field.Type()) {
-					field.Set(val.Convert(field.Type()))
-				} else {
-					return fmt.Errorf("cannot convert value of field %s from %s to %s", key, val.Type(), field.Type())
-				}
-			}
-		}
-		if conditionsField.Type().Elem().Kind() == reflect.Ptr {
-			result.Index(i).Set(targetCond.Addr())
-		} else {
-			result.Index(i).Set(targetCond)
-		}
-	}
-
-	conditionsField.Set(result)
-	if err := scheme.Convert(objTyped, obj, nil); err != nil {
-		return fmt.Errorf("problem converting object back to unstructured: %w", err)
-	}
-
-	return nil
-}
-
-func mapToStruct(structVal reflect.Value, data map[string]interface{}) error {
-	for key, value := range data {
-		field := structVal.FieldByName(key)
-		if field.IsValid() {
-			switch field.Kind() {
-			case reflect.String:
-				field.SetString(value.(string))
-			case reflect.Bool:
-				field.SetBool(value.(bool))
-			case reflect.Int32:
-				field.SetInt(int64(value.(int32)))
-			case reflect.Int64:
-				field.SetInt(value.(int64))
-			case reflect.Float64:
-				field.SetFloat(value.(float64))
-			default:
-				field.Set(reflect.ValueOf(value))
-			}
-		}
-	}
-	return nil
-}
-
-func convertToString(value interface{}) (string, error) {
-	v := reflect.ValueOf(value)
-	switch v.Kind() {
-	case reflect.String:
-		return v.String(), nil
-	case reflect.Interface:
-		// Handle the case where the value is an interface
-		return convertToString(v.Elem().Interface())
-	default:
-		// Check if the value has a String() method
-		stringer, ok := value.(fmt.Stringer)
-		if ok {
-			return stringer.String(), nil
-		}
-		return "", fmt.Errorf("unsupported type: %T", value)
-	}
-}
diff --git a/operatortrace-go/pkg/client/tracing_interface.go b/operatortrace-go/pkg/client/tracing_interface.go
new file mode 100644
index 0000000..881b325
--- /dev/null
+++ b/operatortrace-go/pkg/client/tracing_interface.go
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+
+	"go.opentelemetry.io/otel/trace"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// TracingClient extends client.Client with tracing functionality
+type TracingClient interface {
+	client.Client
+	trace.Tracer
+
+	StartTrace(ctx context.Context, key *client.ObjectKey, obj client.Object, opts ...client.GetOption) (context.Context, trace.Span, error)
+	EndTrace(ctx context.Context, obj client.Object, opts ...client.PatchOption) (client.Object, error)
+	StartSpan(ctx context.Context, operationName string) (context.Context, trace.Span)
+	EmbedTraceIDInNamespacedName(key *client.ObjectKey, obj client.Object) error
+}
diff --git a/operatortrace-go/pkg/client/tracing_status_client.go b/operatortrace-go/pkg/client/tracing_status_client.go
new file mode 100644
index 0000000..be50456
--- /dev/null
+++ b/operatortrace-go/pkg/client/tracing_status_client.go
@@ -0,0 +1,100 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-logr/logr"
+	"go.opentelemetry.io/otel/trace"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+type tracingStatusClient struct {
+	scheme *runtime.Scheme
+	client.StatusWriter
+	trace.Tracer
+	Logger logr.Logger
+}
+
+var _ client.StatusWriter = (*tracingStatusClient)(nil)
+
+func (tc *tracingClient) Status() client.StatusWriter {
+	return &tracingStatusClient{
+		scheme:       tc.scheme,
+		Logger:       tc.Logger,
+		StatusWriter: tc.Client.Status(),
+		Tracer:       tc.Tracer,
+	}
+}
+
+func (ts *tracingStatusClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
+	if err != nil {
+		return fmt.Errorf("problem getting the scheme: %w", err)
+	}
+
+	kind := gvk.GroupKind().Kind
+
+	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusUpdate %s %s", kind, obj.GetName()))
+	defer span.End()
+
+	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
+	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
+
+	ts.Logger.Info("updating status object", "object", obj.GetName())
+	err = ts.StatusWriter.Update(ctx, obj, opts...)
+	if err != nil {
+		span.RecordError(err)
+	}
+	return err
+}
+
+func (ts *tracingStatusClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
+	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
+	if err != nil {
+		return fmt.Errorf("problem getting the scheme: %w", err)
+	}
+
+	kind := gvk.GroupKind().Kind
+
+	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusPatch %s %s", kind, obj.GetName()))
+	defer span.End()
+
+	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
+	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
+
+	ts.Logger.Info("patching status object", "object", obj.GetName())
+	err = ts.StatusWriter.Patch(ctx, obj, patch, opts...)
+	if err != nil {
+		span.RecordError(err)
+	}
+
+	return err
+}
+
+func (ts *tracingStatusClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
+	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
+	if err != nil {
+		return fmt.Errorf("problem getting the scheme: %w", err)
+	}
+
+	kind := gvk.GroupKind().Kind
+
+	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusCreate %s %s", kind, obj.GetName()))
+	defer span.End()
+
+	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
+	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
+
+	ts.Logger.Info("creating status object", "object", obj.GetName())
+	err = ts.StatusWriter.Create(ctx, obj, subResource, opts...)
+	if err != nil {
+		span.RecordError(err)
+	}
+	return err
+}
diff --git a/operatortrace-go/pkg/client/utils.go b/operatortrace-go/pkg/client/utils.go
new file mode 100644
index 0000000..e6fed70
--- /dev/null
+++ b/operatortrace-go/pkg/client/utils.go
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"fmt"
+	"reflect"
+
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// getCallerKindFromNamespacedName extracts the caller kind from the key.Name
+func getCallerKindFromNamespacedName(key client.ObjectKey) string {
+	embedTraceID := &EmbedTraceID{}
+	if err := embedTraceID.FromString(key.Name); err != nil {
+		return ""
+	}
+	return embedTraceID.ObjectKind
+}
+
+// getCallerNameFromNamespacedName extracts the caller name from the key.Name
+func getCallerNameFromNamespacedName(key client.ObjectKey) string {
+	embedTraceID := &EmbedTraceID{}
+	if err := embedTraceID.FromString(key.Name); err != nil {
+		return ""
+	}
+	return embedTraceID.ObjectName
+}
+
+// getNameFromNamespacedName extracts the original name from the key.Name
+func getNameFromNamespacedName(key client.ObjectKey) string {
+	embedTraceID := &EmbedTraceID{}
+	if err := embedTraceID.FromString(key.Name); err != nil {
+		return key.Name
+	}
+	return embedTraceID.KeyName
+}
+
+func convertToString(value interface{}) (string, error) {
+	v := reflect.ValueOf(value)
+	switch v.Kind() {
+	case reflect.String:
+		return v.String(), nil
+	case reflect.Interface:
+		// Handle the case where the value is an interface
+		return convertToString(v.Elem().Interface())
+	default:
+		// Check if the value has a String() method
+		stringer, ok := value.(fmt.Stringer)
+		if ok {
+			return stringer.String(), nil
+		}
+		return "", fmt.Errorf("unsupported type: %T", value)
+	}
+}
diff --git a/operatortrace-go/pkg/client/utils_test.go b/operatortrace-go/pkg/client/utils_test.go
new file mode 100644
index 0000000..d782ccc
--- /dev/null
+++ b/operatortrace-go/pkg/client/utils_test.go
@@ -0,0 +1,82 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func TestConvertToString(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    interface{}
+		expected string
+		wantErr  bool
+	}{
+		{"string input", "hello", "hello", false},
+		{"fmt.Stringer input", client.ObjectKey{Namespace: "default", Name: "key"}, "default/key", false},
+		{"unsupported type input", 12345, "", true},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result, err := convertToString(tt.input)
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.expected, result)
+			}
+		})
+	}
+}
+
+func TestGetCallerKindFromNamespacedName(t *testing.T) {
+	key := client.ObjectKey{Name: "traceid;spanid;Configmap;my-config;keyname"}
+	assert.Equal(t, "Configmap", getCallerKindFromNamespacedName(key))
+
+	keyInvalid := client.ObjectKey{Name: "invalid-format"}
+	assert.Equal(t, "", getCallerKindFromNamespacedName(keyInvalid))
+}
+
+func TestGetCallerNameFromNamespacedName(t *testing.T) {
+	key := client.ObjectKey{Name: "traceid;spanid;Configmap;my-config;keyname"}
+	assert.Equal(t, "my-config", getCallerNameFromNamespacedName(key))
+
+	keyInvalid := client.ObjectKey{Name: "invalid-format"}
+	assert.Equal(t, "", getCallerNameFromNamespacedName(keyInvalid))
+}
+
+func TestGetNameFromNamespacedName(t *testing.T) {
+	key := client.ObjectKey{Name: "traceid;spanid;Configmap;my-config;original-name"}
+	assert.Equal(t, "original-name", getNameFromNamespacedName(key))
+
+	keyInvalid := client.ObjectKey{Name: "not-formatted-name"}
+	assert.Equal(t, "not-formatted-name", getNameFromNamespacedName(keyInvalid))
+}
+
+func TestEmbedTraceID_ToStringAndFromString(t *testing.T) {
+	original := &EmbedTraceID{
+		TraceID:    "traceid",
+		SpanID:     "spanid",
+		ObjectKind: "Deployment",
+		ObjectName: "my-deployment",
+		KeyName:    "original-key",
+	}
+
+	str := original.ToString()
+	expectedStr := "traceid;spanid;Deployment;my-deployment;original-key"
+	assert.Equal(t, expectedStr, str)
+
+	parsed := &EmbedTraceID{}
+	err := parsed.FromString(str)
+	assert.NoError(t, err)
+	assert.Equal(t, original, parsed)
+
+	err = parsed.FromString("invalid-format-string")
+	assert.Error(t, err)
+}


Reviews:
user: login: troy0820
body: This looks fine and trust your testing, just some nits and one question you can resolve.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T14:27:02Z
user: login: fawadasaurus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T14:35:53Z
user: login: fawadasaurus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T14:41:57Z
user: login: fawadasaurus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T14:44:27Z

Review comments:
diff_hunk: @@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+
+	"go.opentelemetry.io/otel/trace"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// TracingClient extends client.Client with tracing functionality
+type TracingClient interface {
+	client.Client
+	trace.Tracer
path: operatortrace-go/pkg/client/tracing_interface.go
user: login: troy0820
body: nit: Embedding this also brings the unexported method `tracer()` which you kind of don't need and may interfere with making mocks and extending it's functionality. You really only need  

`Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span)`
because whatever other tracer satisfies that method, etc. 
This is non-blocking.  
created_at: 2025-04-17T14:19:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+
+	"go.opentelemetry.io/otel/trace"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// TracingClient extends client.Client with tracing functionality
+type TracingClient interface {
+	client.Client
+	trace.Tracer
path: operatortrace-go/pkg/client/tracing_interface.go
user: login: troy0820
body: If you were to create an interface 
```go

type Tracer interface {
	Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span)
}
```

You could embed that rather than the library tracer because of the unexported method they embed in their interface.  You can resolve this.

created_at: 2025-04-17T14:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,100 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-logr/logr"
+	"go.opentelemetry.io/otel/trace"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+type tracingStatusClient struct {
+	scheme *runtime.Scheme
+	client.StatusWriter
+	trace.Tracer
+	Logger logr.Logger
+}
+
+var _ client.StatusWriter = (*tracingStatusClient)(nil)
+
+func (tc *tracingClient) Status() client.StatusWriter {
+	return &tracingStatusClient{
+		scheme:       tc.scheme,
+		Logger:       tc.Logger,
+		StatusWriter: tc.Client.Status(),
+		Tracer:       tc.Tracer,
+	}
+}
+
+func (ts *tracingStatusClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
+	if err != nil {
+		return fmt.Errorf("problem getting the scheme: %w", err)
+	}
+
+	kind := gvk.GroupKind().Kind
+
+	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusUpdate %s %s", kind, obj.GetName()))
+	defer span.End()
+
+	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
+	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
+
+	ts.Logger.Info("updating status object", "object", obj.GetName())
+	err = ts.StatusWriter.Update(ctx, obj, opts...)
path: operatortrace-go/pkg/client/tracing_status_client.go
user: login: troy0820
body: ```suggestion
	err = ts.Update(ctx, obj, opts...)
```
Not sure if you have to do that or not but it's non-blocking.
created_at: 2025-04-17T14:22:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,224 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"fmt"
+	"reflect"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+// getConditionMessage retrieves the message for a specific condition type from a Kubernetes object.
+func getConditionMessage(conditionType string, obj client.Object, scheme *runtime.Scheme) (string, error) {
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return "", err
+	}
+
+	for _, condition := range conditions {
+		// Check if "Type" key exists
+		conType, exists := condition["Type"]
+		if !exists {
+			return "", fmt.Errorf("condition does not contain a 'Type' field")
+		}
+
+		// Convert conType to string using reflection
+		conTypeStr, err := convertToString(conType)
+		if err != nil {
+			return "", fmt.Errorf("failed to convert 'Type' field to string: %v", err)
+		}
+
+		if conTypeStr == conditionType {
+			message := condition["Message"].(string)
+			return message, nil
+		}
+	}
+
+	return "", fmt.Errorf("condition of type %s not found", conditionType)
+}
+
+// setConditionMessage sets the message for a specific condition type in a Kubernetes object.
+func setConditionMessage(conditionType, message string, obj client.Object, scheme *runtime.Scheme) error {
+	deleteConditionAsMap(conditionType, obj, scheme)
+
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return err
+	}
+
+	newCondition := map[string]interface{}{
+		"Type":               conditionType,
+		"Status":             metav1.ConditionUnknown,
+		"LastTransitionTime": metav1.Now(),
+		"Message":            message,
+	}
+	conditions = append(conditions, newCondition)
+
+	return setConditionsFromMap(obj, conditions, scheme)
+}
+
+func deleteConditionAsMap(conditionType string, obj client.Object, scheme *runtime.Scheme) error {
+	// Retrieve the current conditions as a map
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return err
+	}
+
+	var outConditions []map[string]interface{}
+	for _, condition := range conditions {
+		// Check if "Type" key exists
+		conType, exists := condition["Type"]
+		if !exists {
+			return fmt.Errorf("condition does not contain a 'Type' field")
+		}
+
+		// Convert conType to string using reflection
+		conTypeStr, err := convertToString(conType)
+		if err != nil {
+			return fmt.Errorf("failed to convert 'Type' field to string: %v", err)
+		}
+
+		if conTypeStr != conditionType {
+			outConditions = append(outConditions, condition)
+		}
+	}
+
+	// Set the updated conditions back to the object
+	return setConditionsFromMap(obj, outConditions, scheme)
+}
+
+func getConditionsAsMap(obj client.Object, scheme *runtime.Scheme) ([]map[string]interface{}, error) {
+	gvk, err := apiutil.GVKForObject(obj, scheme)
+	if err != nil {
+		return nil, fmt.Errorf("problem getting the GVK: %w", err)
+	}
+
+	objTyped, err := scheme.New(gvk)
+	if err != nil {
+		return nil, fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err)
+	}
+
+	if err := scheme.Convert(obj, objTyped, nil); err != nil {
+		return nil, fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err)
+	}
+
+	val := reflect.ValueOf(objTyped)
+	statusField := val.Elem().FieldByName("Status")
+	if !statusField.IsValid() {
+		return nil, fmt.Errorf("status field not found in kind %s", gvk.Kind)
+	}
+
+	conditionsField := statusField.FieldByName("Conditions")
+	if !conditionsField.IsValid() {
+		return nil, fmt.Errorf("conditions field not found in kind %s", gvk.Kind)
+	}
+
+	conditionsValue := conditionsField.Interface()
+	val = reflect.ValueOf(conditionsValue)
+	if val.Kind() != reflect.Slice {
+		return nil, fmt.Errorf("conditions field is not a slice")
+	}
+
+	var conditionsAsMap []map[string]interface{}
+	for i := 0; i < val.Len(); i++ {
+		conditionVal := val.Index(i)
+		if conditionVal.Kind() == reflect.Ptr {
+			conditionVal = conditionVal.Elem()
+		}
+
+		conditionMap := make(map[string]interface{})
+		for _, field := range reflect.VisibleFields(conditionVal.Type()) {
+			fieldValue := conditionVal.FieldByIndex(field.Index)
+			conditionMap[field.Name] = fieldValue.Interface()
+		}
+
+		conditionsAsMap = append(conditionsAsMap, conditionMap)
+	}
+
+	return conditionsAsMap, nil
+}
+
+func setConditionsFromMap(obj client.Object, conditionsAsMap []map[string]interface{}, scheme *runtime.Scheme) error {
path: operatortrace-go/pkg/client/conditions.go
user: login: troy0820
body: conditions has it's own schema do we have to convert it to a map instead of marshaling it after the kind is found to the status field if they have conditions? 
created_at: 2025-04-17T14:26:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,224 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"fmt"
+	"reflect"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+// getConditionMessage retrieves the message for a specific condition type from a Kubernetes object.
+func getConditionMessage(conditionType string, obj client.Object, scheme *runtime.Scheme) (string, error) {
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return "", err
+	}
+
+	for _, condition := range conditions {
+		// Check if "Type" key exists
+		conType, exists := condition["Type"]
+		if !exists {
+			return "", fmt.Errorf("condition does not contain a 'Type' field")
+		}
+
+		// Convert conType to string using reflection
+		conTypeStr, err := convertToString(conType)
+		if err != nil {
+			return "", fmt.Errorf("failed to convert 'Type' field to string: %v", err)
+		}
+
+		if conTypeStr == conditionType {
+			message := condition["Message"].(string)
+			return message, nil
+		}
+	}
+
+	return "", fmt.Errorf("condition of type %s not found", conditionType)
+}
+
+// setConditionMessage sets the message for a specific condition type in a Kubernetes object.
+func setConditionMessage(conditionType, message string, obj client.Object, scheme *runtime.Scheme) error {
+	deleteConditionAsMap(conditionType, obj, scheme)
+
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return err
+	}
+
+	newCondition := map[string]interface{}{
+		"Type":               conditionType,
+		"Status":             metav1.ConditionUnknown,
+		"LastTransitionTime": metav1.Now(),
+		"Message":            message,
+	}
+	conditions = append(conditions, newCondition)
+
+	return setConditionsFromMap(obj, conditions, scheme)
+}
+
+func deleteConditionAsMap(conditionType string, obj client.Object, scheme *runtime.Scheme) error {
+	// Retrieve the current conditions as a map
+	conditions, err := getConditionsAsMap(obj, scheme)
+	if err != nil {
+		return err
+	}
+
+	var outConditions []map[string]interface{}
+	for _, condition := range conditions {
+		// Check if "Type" key exists
+		conType, exists := condition["Type"]
+		if !exists {
+			return fmt.Errorf("condition does not contain a 'Type' field")
+		}
+
+		// Convert conType to string using reflection
+		conTypeStr, err := convertToString(conType)
+		if err != nil {
+			return fmt.Errorf("failed to convert 'Type' field to string: %v", err)
+		}
+
+		if conTypeStr != conditionType {
+			outConditions = append(outConditions, condition)
+		}
+	}
+
+	// Set the updated conditions back to the object
+	return setConditionsFromMap(obj, outConditions, scheme)
+}
+
+func getConditionsAsMap(obj client.Object, scheme *runtime.Scheme) ([]map[string]interface{}, error) {
+	gvk, err := apiutil.GVKForObject(obj, scheme)
+	if err != nil {
+		return nil, fmt.Errorf("problem getting the GVK: %w", err)
+	}
+
+	objTyped, err := scheme.New(gvk)
+	if err != nil {
+		return nil, fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err)
+	}
+
+	if err := scheme.Convert(obj, objTyped, nil); err != nil {
+		return nil, fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err)
+	}
+
+	val := reflect.ValueOf(objTyped)
+	statusField := val.Elem().FieldByName("Status")
+	if !statusField.IsValid() {
+		return nil, fmt.Errorf("status field not found in kind %s", gvk.Kind)
+	}
+
+	conditionsField := statusField.FieldByName("Conditions")
+	if !conditionsField.IsValid() {
+		return nil, fmt.Errorf("conditions field not found in kind %s", gvk.Kind)
+	}
+
+	conditionsValue := conditionsField.Interface()
+	val = reflect.ValueOf(conditionsValue)
+	if val.Kind() != reflect.Slice {
+		return nil, fmt.Errorf("conditions field is not a slice")
+	}
+
+	var conditionsAsMap []map[string]interface{}
+	for i := 0; i < val.Len(); i++ {
+		conditionVal := val.Index(i)
+		if conditionVal.Kind() == reflect.Ptr {
+			conditionVal = conditionVal.Elem()
+		}
+
+		conditionMap := make(map[string]interface{})
+		for _, field := range reflect.VisibleFields(conditionVal.Type()) {
+			fieldValue := conditionVal.FieldByIndex(field.Index)
+			conditionMap[field.Name] = fieldValue.Interface()
+		}
+
+		conditionsAsMap = append(conditionsAsMap, conditionMap)
+	}
+
+	return conditionsAsMap, nil
+}
+
+func setConditionsFromMap(obj client.Object, conditionsAsMap []map[string]interface{}, scheme *runtime.Scheme) error {
path: operatortrace-go/pkg/client/conditions.go
user: login: fawadasaurus
body: Conditions can be different. I think an enhancement in the future could be something that we can do some reflection to get it into its actual struct but that is perhaps a backlog item.
created_at: 2025-04-17T14:35:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+
+	"go.opentelemetry.io/otel/trace"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// TracingClient extends client.Client with tracing functionality
+type TracingClient interface {
+	client.Client
+	trace.Tracer
path: operatortrace-go/pkg/client/tracing_interface.go
user: login: fawadasaurus
body: I am a bit hesitant to also wrap the tracing library in addition to wrapping the client library already.
created_at: 2025-04-17T14:41:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,100 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+package client
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-logr/logr"
+	"go.opentelemetry.io/otel/trace"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+type tracingStatusClient struct {
+	scheme *runtime.Scheme
+	client.StatusWriter
+	trace.Tracer
+	Logger logr.Logger
+}
+
+var _ client.StatusWriter = (*tracingStatusClient)(nil)
+
+func (tc *tracingClient) Status() client.StatusWriter {
+	return &tracingStatusClient{
+		scheme:       tc.scheme,
+		Logger:       tc.Logger,
+		StatusWriter: tc.Client.Status(),
+		Tracer:       tc.Tracer,
+	}
+}
+
+func (ts *tracingStatusClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+	gvk, err := apiutil.GVKForObject(obj, ts.scheme)
+	if err != nil {
+		return fmt.Errorf("problem getting the scheme: %w", err)
+	}
+
+	kind := gvk.GroupKind().Kind
+
+	ctx, span := startSpanFromContext(ctx, ts.Logger, ts.Tracer, obj, ts.scheme, fmt.Sprintf("StatusUpdate %s %s", kind, obj.GetName()))
+	defer span.End()
+
+	setConditionMessage("TraceID", span.SpanContext().TraceID().String(), obj, ts.scheme)
+	setConditionMessage("SpanID", span.SpanContext().SpanID().String(), obj, ts.scheme)
+
+	ts.Logger.Info("updating status object", "object", obj.GetName())
+	err = ts.StatusWriter.Update(ctx, obj, opts...)
path: operatortrace-go/pkg/client/tracing_status_client.go
user: login: fawadasaurus
body: I tried this and the test failed when its just `ts.Update`. I am leaving this as is for now.
created_at: 2025-04-17T14:44:27Z
author_association: CONTRIBUTOR

PR comments: