Skip to content

Commit 6b5203f

Browse files
committed
use labels and annotations instead of explcit config
1 parent 877b20a commit 6b5203f

6 files changed

Lines changed: 248 additions & 210 deletions

File tree

cmd/operator-opamp-bridge/internal/config/config.go

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,6 @@ const (
7474
ReportsRemoteConfig Capability = "ReportsRemoteConfig"
7575
)
7676

77-
// TargetWorkload describes a Deployment or DaemonSet whose collector config
78-
// is managed via a ConfigMap. Used only in "standalone" mode.
79-
type TargetWorkload struct {
80-
Name string `yaml:"name"`
81-
Namespace string `yaml:"namespace"`
82-
Kind string `yaml:"kind"`
83-
WorkloadName string `yaml:"workloadName"`
84-
ConfigMapName string `yaml:"configMapName"`
85-
ConfigMapKey string `yaml:"configMapKey"`
86-
}
87-
8877
type Config struct {
8978
// KubeConfigFilePath is empty if InClusterConfig() should be used, otherwise it's a path to where a valid
9079
// kubernetes configuration file.
@@ -104,11 +93,8 @@ type Config struct {
10493
AgentDescription AgentDescription `yaml:"description,omitempty"`
10594

10695
// Mode selects the operating mode: "operator" (default) uses OpenTelemetryCollector CRDs,
107-
// "standalone" manages ConfigMaps for standard Deployments/DaemonSets.
96+
// "standalone" discovers Deployments/DaemonSets by label and manages their ConfigMaps.
10897
Mode string `yaml:"mode,omitempty"`
109-
110-
// Targets defines the workloads to manage in "standalone" mode.
111-
Targets []TargetWorkload `yaml:"targets,omitempty"`
11298
}
11399

114100
// AgentDescription is copied from the OpAMP Extension in the collector.
@@ -258,10 +244,6 @@ func (c *Config) IsStandaloneMode() bool {
258244
return c.Mode == "standalone"
259245
}
260246

261-
func (c *Config) GetTargets() []TargetWorkload {
262-
return c.Targets
263-
}
264-
265247
func Load(logger logr.Logger, args []string) (*Config, error) {
266248
flagSet := GetFlagSet(pflag.ExitOnError)
267249
err := flagSet.Parse(args)

cmd/operator-opamp-bridge/internal/workload/client.go

Lines changed: 141 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2839
var _ 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.
4344
type 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

116147
func (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

Comments
 (0)