@@ -23,108 +23,143 @@ const (
2323 managedByLabel = "app.kubernetes.io/managed-by"
2424 managedByValue = "opamp-bridge"
2525 restartAnnotation = "kubectl.kubernetes.io/restartedAt"
26+
27+ // ManagedLabelKey is the label that opts a Deployment or DaemonSet into standalone OpAMP ConfigMap management.
28+ // Workloads must carry this label with value "true" to be discovered by the bridge.
29+ ManagedLabelKey = "opentelemetry.io/opamp-standalone-configmap"
30+
31+ // AnnotationConfigMapName is a required annotation specifying the ConfigMap the bridge writes collector config into.
32+ AnnotationConfigMapName = "opentelemetry.io/opamp-standalone-configmap-name"
33+
34+ // AnnotationConfigMapKey is an optional annotation specifying the key within the ConfigMap.
35+ // Defaults to the workload name if not set.
36+ AnnotationConfigMapKey = "opentelemetry.io/opamp-standalone-configmap-key"
2637)
2738
2839var _ operator.ConfigApplier = & Client {}
2940
30- // TargetWorkload describes a Deployment or DaemonSet whose collector config
31- // is managed via a ConfigMap.
32- type TargetWorkload struct {
33- Name string `yaml:"name"`
34- Namespace string `yaml:"namespace"`
35- Kind string `yaml:"kind"`
36- WorkloadName string `yaml:"workloadName"`
37- ConfigMapName string `yaml:"configMapName"`
38- ConfigMapKey string `yaml:"configMapKey"`
39- }
40-
4141// Client implements operator.ConfigApplier for standalone Deployment/DaemonSet
42- // workloads. Configuration is managed through ConfigMaps rather than CRDs.
42+ // workloads. It discovers targets dynamically via the ManagedLabelKey label and
43+ // reads ConfigMap details from annotations on the workload.
4344type Client struct {
4445 log logr.Logger
4546 k8sClient client.Client
46- targets map [string ]TargetWorkload
4747 name string
4848}
4949
50- func NewClient (name string , log logr.Logger , c client.Client , targets []TargetWorkload ) * Client {
51- targetMap := make (map [string ]TargetWorkload , len (targets ))
52- for _ , t := range targets {
53- key := fmt .Sprintf ("%s/%s" , t .Namespace , t .Name )
54- targetMap [key ] = t
55- }
50+ func NewClient (name string , log logr.Logger , c client.Client ) * Client {
5651 return & Client {
5752 log : log ,
5853 k8sClient : c ,
59- targets : targetMap ,
6054 name : name ,
6155 }
6256}
6357
64- func (c * Client ) Apply (name , namespace string , configFile * protobufs.AgentConfigFile ) error {
65- key := fmt .Sprintf ("%s/%s" , namespace , name )
66- target , ok := c .targets [key ]
67- if ! ok {
68- return fmt .Errorf ("no target workload configured for %s" , key )
58+ // resolveTarget looks up a Deployment or DaemonSet by name/namespace and returns
59+ // its ConfigMap name and key from annotations.
60+ func (c * Client ) resolveTarget (ctx context.Context , name , namespace string ) (configMapName , configMapKey string , workloadKind string , workloadName string , err error ) {
61+ // Try Deployment first
62+ deploy := & appsv1.Deployment {}
63+ if getErr := c .k8sClient .Get (ctx , client.ObjectKey {Name : name , Namespace : namespace }, deploy ); getErr == nil {
64+ if deploy .Labels [ManagedLabelKey ] != "true" {
65+ return "" , "" , "" , "" , fmt .Errorf ("workload %s/%s does not have label %s=true" , namespace , name , ManagedLabelKey )
66+ }
67+ cmName , cmKey := configMapDetailsFromAnnotations (deploy .Annotations , name )
68+ return cmName , cmKey , "Deployment" , name , nil
69+ }
70+
71+ // Try DaemonSet
72+ ds := & appsv1.DaemonSet {}
73+ if getErr := c .k8sClient .Get (ctx , client.ObjectKey {Name : name , Namespace : namespace }, ds ); getErr == nil {
74+ if ds .Labels [ManagedLabelKey ] != "true" {
75+ return "" , "" , "" , "" , fmt .Errorf ("workload %s/%s does not have label %s=true" , namespace , name , ManagedLabelKey )
76+ }
77+ cmName , cmKey := configMapDetailsFromAnnotations (ds .Annotations , name )
78+ return cmName , cmKey , "DaemonSet" , name , nil
6979 }
7080
71- c .log .Info ("Applying config" , "name" , name , "namespace" , namespace , "configMap" , target .ConfigMapName )
81+ return "" , "" , "" , "" , fmt .Errorf ("no managed Deployment or DaemonSet named %s found in namespace %s" , name , namespace )
82+ }
83+
84+ func configMapDetailsFromAnnotations (annotations map [string ]string , defaultKey string ) (cmName , cmKey string ) {
85+ cmName = annotations [AnnotationConfigMapName ]
86+ cmKey = annotations [AnnotationConfigMapKey ]
87+ if cmKey == "" {
88+ cmKey = defaultKey
89+ }
90+ return
91+ }
7292
93+ func (c * Client ) Apply (name , namespace string , configFile * protobufs.AgentConfigFile ) error {
7394 if len (configFile .Body ) == 0 {
7495 return fmt .Errorf ("invalid config to apply: config is empty" )
7596 }
7697
7798 ctx := context .Background ()
7899
100+ configMapName , configMapKey , workloadKind , workloadName , err := c .resolveTarget (ctx , name , namespace )
101+ if err != nil {
102+ return err
103+ }
104+ if configMapName == "" {
105+ return fmt .Errorf ("workload %s/%s is missing annotation %s" , namespace , name , AnnotationConfigMapName )
106+ }
107+
108+ c .log .Info ("Applying config" , "name" , name , "namespace" , namespace , "configMap" , configMapName )
109+
79110 cm := & v1.ConfigMap {}
80- err : = c .k8sClient .Get (ctx , client.ObjectKey {
81- Name : target . ConfigMapName ,
82- Namespace : target . Namespace ,
111+ err = c .k8sClient .Get (ctx , client.ObjectKey {
112+ Name : configMapName ,
113+ Namespace : namespace ,
83114 }, cm )
84115
85116 if errors .IsNotFound (err ) {
86117 cm = & v1.ConfigMap {
87118 ObjectMeta : metav1.ObjectMeta {
88- Name : target . ConfigMapName ,
89- Namespace : target . Namespace ,
119+ Name : configMapName ,
120+ Namespace : namespace ,
90121 Labels : map [string ]string {
91122 managedByLabel : managedByValue ,
92123 },
93124 },
94125 Data : map [string ]string {
95- target . ConfigMapKey : string (configFile .Body ),
126+ configMapKey : string (configFile .Body ),
96127 },
97128 }
98129 if createErr := c .k8sClient .Create (ctx , cm ); createErr != nil {
99- return fmt .Errorf ("failed to create ConfigMap %s/%s: %w" , target . Namespace , target . ConfigMapName , createErr )
130+ return fmt .Errorf ("failed to create ConfigMap %s/%s: %w" , namespace , configMapName , createErr )
100131 }
101132 } else if err != nil {
102- return fmt .Errorf ("failed to get ConfigMap %s/%s: %w" , target . Namespace , target . ConfigMapName , err )
133+ return fmt .Errorf ("failed to get ConfigMap %s/%s: %w" , namespace , configMapName , err )
103134 } else {
104135 if cm .Data == nil {
105136 cm .Data = map [string ]string {}
106137 }
107- cm .Data [target . ConfigMapKey ] = string (configFile .Body )
138+ cm .Data [configMapKey ] = string (configFile .Body )
108139 if updateErr := c .k8sClient .Update (ctx , cm ); updateErr != nil {
109- return fmt .Errorf ("failed to update ConfigMap %s/%s: %w" , target . Namespace , target . ConfigMapName , updateErr )
140+ return fmt .Errorf ("failed to update ConfigMap %s/%s: %w" , namespace , configMapName , updateErr )
110141 }
111142 }
112143
113- return c .triggerRollout (ctx , target )
144+ return c .triggerRollout (ctx , workloadKind , workloadName , namespace )
114145}
115146
116147func (c * Client ) Delete (name , namespace string ) error {
117- key := fmt .Sprintf ("%s/%s" , namespace , name )
118- target , ok := c .targets [key ]
119- if ! ok {
148+ ctx := context .Background ()
149+
150+ configMapName , _ , _ , _ , err := c .resolveTarget (ctx , name , namespace )
151+ if err != nil {
152+ // Workload no longer exists or isn't managed; nothing to delete.
153+ return nil
154+ }
155+ if configMapName == "" {
120156 return nil
121157 }
122158
123- ctx := context .Background ()
124159 cm := & v1.ConfigMap {}
125- err : = c .k8sClient .Get (ctx , client.ObjectKey {
126- Name : target . ConfigMapName ,
127- Namespace : target . Namespace ,
160+ err = c .k8sClient .Get (ctx , client.ObjectKey {
161+ Name : configMapName ,
162+ Namespace : namespace ,
128163 }, cm )
129164 if err != nil {
130165 if errors .IsNotFound (err ) {
@@ -139,14 +174,34 @@ func (c *Client) ListInstances() ([]operator.CollectorInstance, error) {
139174 ctx := context .Background ()
140175 var result []operator.CollectorInstance
141176
142- for _ , target := range c .targets {
143- instance , err := c .getInstance (ctx , target )
177+ managedLabel := client.MatchingLabels {ManagedLabelKey : "true" }
178+
179+ deployList := & appsv1.DeploymentList {}
180+ if err := c .k8sClient .List (ctx , deployList , managedLabel ); err != nil {
181+ return nil , fmt .Errorf ("failed to list managed Deployments: %w" , err )
182+ }
183+ for i := range deployList .Items {
184+ instance , err := c .getDeploymentInstance (ctx , & deployList .Items [i ])
185+ if err != nil {
186+ c .log .Error (err , "Skipping Deployment" , "name" , deployList .Items [i ].Name , "namespace" , deployList .Items [i ].Namespace )
187+ continue
188+ }
189+ result = append (result , instance )
190+ }
191+
192+ dsList := & appsv1.DaemonSetList {}
193+ if err := c .k8sClient .List (ctx , dsList , managedLabel ); err != nil {
194+ return nil , fmt .Errorf ("failed to list managed DaemonSets: %w" , err )
195+ }
196+ for i := range dsList .Items {
197+ instance , err := c .getDaemonSetInstance (ctx , & dsList .Items [i ])
144198 if err != nil {
145- c .log .Error (err , "Error getting target " , "name" , target . Name , "namespace" , target .Namespace )
199+ c .log .Error (err , "Skipping DaemonSet " , "name" , dsList . Items [ i ]. Name , "namespace" , dsList . Items [ i ] .Namespace )
146200 continue
147201 }
148202 result = append (result , instance )
149203 }
204+
150205 return result , nil
151206}
152207
@@ -157,73 +212,62 @@ func (c *Client) GetCollectorPods(selectorLabels map[string]string, namespace st
157212 return podList , err
158213}
159214
160- func (c * Client ) getInstance (ctx context.Context , target TargetWorkload ) (* standaloneCollectorInstance , error ) {
161- var selectorLabels map [string ]string
162- var creationTimestamp time.Time
163- var statusReplicas string
215+ func (c * Client ) getDeploymentInstance (ctx context.Context , deploy * appsv1.Deployment ) (* standaloneCollectorInstance , error ) {
216+ cmName , cmKey := configMapDetailsFromAnnotations (deploy .Annotations , deploy .Name )
217+ if cmName == "" {
218+ return nil , fmt .Errorf ("missing annotation %s on Deployment %s/%s" , AnnotationConfigMapName , deploy .Namespace , deploy .Name )
219+ }
164220
165- switch target .Kind {
166- case "Deployment" :
167- deploy := & appsv1.Deployment {}
168- if err := c .k8sClient .Get (ctx , client.ObjectKey {
169- Name : target .WorkloadName ,
170- Namespace : target .Namespace ,
171- }, deploy ); err != nil {
172- return nil , fmt .Errorf ("failed to get Deployment %s/%s: %w" , target .Namespace , target .WorkloadName , err )
173- }
174- selectorLabels = deploy .Spec .Selector .MatchLabels
175- creationTimestamp = deploy .GetCreationTimestamp ().Time
176- var desired int32
177- if deploy .Spec .Replicas != nil {
178- desired = * deploy .Spec .Replicas
179- }
180- statusReplicas = fmt .Sprintf ("%d/%d" , deploy .Status .ReadyReplicas , desired )
221+ var desired int32
222+ if deploy .Spec .Replicas != nil {
223+ desired = * deploy .Spec .Replicas
224+ }
181225
182- case "DaemonSet" :
183- ds := & appsv1.DaemonSet {}
184- if err := c .k8sClient .Get (ctx , client.ObjectKey {
185- Name : target .WorkloadName ,
186- Namespace : target .Namespace ,
187- }, ds ); err != nil {
188- return nil , fmt .Errorf ("failed to get DaemonSet %s/%s: %w" , target .Namespace , target .WorkloadName , err )
189- }
190- selectorLabels = ds .Spec .Selector .MatchLabels
191- creationTimestamp = ds .GetCreationTimestamp ().Time
192- statusReplicas = fmt .Sprintf ("%d/%d" , ds .Status .NumberReady , ds .Status .DesiredNumberScheduled )
226+ var configBody []byte
227+ cm := & v1.ConfigMap {}
228+ if err := c .k8sClient .Get (ctx , client.ObjectKey {Name : cmName , Namespace : deploy .Namespace }, cm ); err == nil && cm .Data != nil {
229+ configBody = []byte (cm .Data [cmKey ])
230+ }
231+
232+ return & standaloneCollectorInstance {
233+ name : deploy .Name ,
234+ namespace : deploy .Namespace ,
235+ createdAt : deploy .GetCreationTimestamp ().Time ,
236+ selectorLabels : deploy .Spec .Selector .MatchLabels ,
237+ statusReplicas : fmt .Sprintf ("%d/%d" , deploy .Status .ReadyReplicas , desired ),
238+ configBody : configBody ,
239+ }, nil
240+ }
193241
194- default :
195- return nil , fmt .Errorf ("unsupported workload kind: %s" , target .Kind )
242+ func (c * Client ) getDaemonSetInstance (ctx context.Context , ds * appsv1.DaemonSet ) (* standaloneCollectorInstance , error ) {
243+ cmName , cmKey := configMapDetailsFromAnnotations (ds .Annotations , ds .Name )
244+ if cmName == "" {
245+ return nil , fmt .Errorf ("missing annotation %s on DaemonSet %s/%s" , AnnotationConfigMapName , ds .Namespace , ds .Name )
196246 }
197247
198248 var configBody []byte
199249 cm := & v1.ConfigMap {}
200- if err := c .k8sClient .Get (ctx , client.ObjectKey {
201- Name : target .ConfigMapName ,
202- Namespace : target .Namespace ,
203- }, cm ); err == nil && cm .Data != nil {
204- configBody = []byte (cm .Data [target .ConfigMapKey ])
250+ if err := c .k8sClient .Get (ctx , client.ObjectKey {Name : cmName , Namespace : ds .Namespace }, cm ); err == nil && cm .Data != nil {
251+ configBody = []byte (cm .Data [cmKey ])
205252 }
206253
207254 return & standaloneCollectorInstance {
208- name : target .Name ,
209- namespace : target .Namespace ,
210- createdAt : creationTimestamp ,
211- selectorLabels : selectorLabels ,
212- statusReplicas : statusReplicas ,
255+ name : ds .Name ,
256+ namespace : ds .Namespace ,
257+ createdAt : ds . GetCreationTimestamp (). Time ,
258+ selectorLabels : ds . Spec . Selector . MatchLabels ,
259+ statusReplicas : fmt . Sprintf ( "%d/%d" , ds . Status . NumberReady , ds . Status . DesiredNumberScheduled ) ,
213260 configBody : configBody ,
214261 }, nil
215262}
216263
217- func (c * Client ) triggerRollout (ctx context.Context , target TargetWorkload ) error {
264+ func (c * Client ) triggerRollout (ctx context.Context , kind , name , namespace string ) error {
218265 restartVal := time .Now ().Format (time .RFC3339 )
219266
220- switch target . Kind {
267+ switch kind {
221268 case "Deployment" :
222269 deploy := & appsv1.Deployment {}
223- if err := c .k8sClient .Get (ctx , client.ObjectKey {
224- Name : target .WorkloadName ,
225- Namespace : target .Namespace ,
226- }, deploy ); err != nil {
270+ if err := c .k8sClient .Get (ctx , client.ObjectKey {Name : name , Namespace : namespace }, deploy ); err != nil {
227271 return fmt .Errorf ("failed to get Deployment for rollout: %w" , err )
228272 }
229273 if deploy .Spec .Template .ObjectMeta .Annotations == nil {
@@ -234,10 +278,7 @@ func (c *Client) triggerRollout(ctx context.Context, target TargetWorkload) erro
234278
235279 case "DaemonSet" :
236280 ds := & appsv1.DaemonSet {}
237- if err := c .k8sClient .Get (ctx , client.ObjectKey {
238- Name : target .WorkloadName ,
239- Namespace : target .Namespace ,
240- }, ds ); err != nil {
281+ if err := c .k8sClient .Get (ctx , client.ObjectKey {Name : name , Namespace : namespace }, ds ); err != nil {
241282 return fmt .Errorf ("failed to get DaemonSet for rollout: %w" , err )
242283 }
243284 if ds .Spec .Template .ObjectMeta .Annotations == nil {
0 commit comments