Skip to content

Commit 95ea28a

Browse files
committed
feat: add support for entity guid in health
1 parent e2c44c1 commit 95ea28a

29 files changed

+4695
-167
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ K8S_AGENTS_OPERATOR_VERSION = ""
1111
.DEFAULT_GOAL := help
1212

1313
# Go packages to test
14-
TEST_PACKAGES = ./api/v1beta2 \
14+
TEST_PACKAGES = ./api/v1beta3 \
15+
./api/v1beta2 \
1516
./api/v1beta1 \
1617
./api/v1alpha2 \
1718
./internal/apm \

api/current/current.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package current
22

3-
import currentapi "github.com/newrelic/k8s-agents-operator/api/v1beta2"
3+
import currentapi "github.com/newrelic/k8s-agents-operator/api/v1beta3"
44

55
type (
66
Agent = currentapi.Agent
@@ -22,6 +22,9 @@ type (
2222
ImageSelectorOperator = currentapi.ImageSelectorOperator
2323
NameSelectorOperator = currentapi.NameSelectorOperator
2424

25+
ImageSelectorKey = currentapi.ImageSelectorKey
26+
NameSelectorKey = currentapi.NameSelectorKey
27+
2528
EnvSelectorRequirement = currentapi.EnvSelectorRequirement
2629
ImageSelectorRequirement = currentapi.ImageSelectorRequirement
2730
NameSelectorRequirement = currentapi.NameSelectorRequirement
Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,152 @@
11
package v1beta2
22

3-
// Hub marks this type as a conversion hub.
4-
func (*Instrumentation) Hub() {}
3+
import (
4+
"github.com/newrelic/k8s-agents-operator/api/current"
5+
"sigs.k8s.io/controller-runtime/pkg/conversion"
6+
)
7+
8+
// ConvertTo converts this Instrumentation to the Hub version.
9+
func (src *Instrumentation) ConvertTo(dstRaw conversion.Hub) error {
10+
dst := dstRaw.(*current.Instrumentation)
11+
12+
// ObjectMeta
13+
dst.ObjectMeta = src.ObjectMeta
14+
15+
for _, srcExpr := range src.Spec.ContainerSelector.ImageSelector.MatchExpressions {
16+
dst.Spec.ContainerSelector.ImageSelector.MatchExpressions = append(dst.Spec.ContainerSelector.ImageSelector.MatchExpressions, current.ImageSelectorRequirement{
17+
Key: current.ImageSelectorKey(srcExpr.Key), Operator: current.ImageSelectorOperator(srcExpr.Operator), Values: srcExpr.Values,
18+
})
19+
}
20+
for _, srcExpr := range src.Spec.ContainerSelector.EnvSelector.MatchExpressions {
21+
dst.Spec.ContainerSelector.EnvSelector.MatchExpressions = append(dst.Spec.ContainerSelector.EnvSelector.MatchExpressions, current.EnvSelectorRequirement{
22+
Key: srcExpr.Key, Operator: current.EnvSelectorOperator(srcExpr.Operator), Values: srcExpr.Values,
23+
})
24+
}
25+
for _, srcExpr := range src.Spec.ContainerSelector.NameSelector.MatchExpressions {
26+
dst.Spec.ContainerSelector.NameSelector.MatchExpressions = append(dst.Spec.ContainerSelector.NameSelector.MatchExpressions, current.NameSelectorRequirement{
27+
Key: current.NameSelectorKey(srcExpr.Key), Operator: current.NameSelectorOperator(srcExpr.Operator), Values: srcExpr.Values,
28+
})
29+
}
30+
31+
dst.Spec.ContainerSelector.ImageSelector.MatchImages = src.Spec.ContainerSelector.ImageSelector.MatchImages
32+
dst.Spec.ContainerSelector.EnvSelector.MatchEnvs = src.Spec.ContainerSelector.EnvSelector.MatchEnvs
33+
dst.Spec.ContainerSelector.NameSelector.MatchNames = src.Spec.ContainerSelector.NameSelector.MatchNames
34+
dst.Spec.ContainerSelector.NamesFromPodAnnotation = src.Spec.ContainerSelector.NamesFromPodAnnotation
35+
36+
dst.Spec.PodLabelSelector = src.Spec.PodLabelSelector
37+
dst.Spec.NamespaceLabelSelector = src.Spec.NamespaceLabelSelector
38+
dst.Spec.LicenseKeySecret = src.Spec.LicenseKeySecret
39+
dst.Spec.AgentConfigMap = src.Spec.AgentConfigMap
40+
dst.Spec.Agent = current.Agent{
41+
Language: src.Spec.Agent.Language,
42+
Image: src.Spec.Agent.Image,
43+
VolumeSizeLimit: src.Spec.Agent.VolumeSizeLimit,
44+
Env: src.Spec.Agent.Env,
45+
Resources: src.Spec.Agent.Resources,
46+
ImagePullPolicy: src.Spec.Agent.ImagePullPolicy,
47+
SecurityContext: src.Spec.Agent.SecurityContext,
48+
}
49+
dst.Spec.HealthAgent = current.HealthAgent{
50+
Image: src.Spec.HealthAgent.Image,
51+
Env: src.Spec.HealthAgent.Env,
52+
ImagePullPolicy: src.Spec.HealthAgent.ImagePullPolicy,
53+
SecurityContext: src.Spec.HealthAgent.SecurityContext,
54+
Resources: src.Spec.HealthAgent.Resources,
55+
}
56+
var unhealthyPodErrors []current.UnhealthyPodError
57+
if l := len(src.Status.UnhealthyPodsErrors); l > 0 {
58+
unhealthyPodErrors = make([]current.UnhealthyPodError, l)
59+
for i, e := range src.Status.UnhealthyPodsErrors {
60+
unhealthyPodErrors[i] = current.UnhealthyPodError{
61+
Pod: e.Pod,
62+
LastError: e.LastError,
63+
}
64+
}
65+
}
66+
dst.Status = current.InstrumentationStatus{
67+
PodsMatching: src.Status.PodsMatching,
68+
PodsUnhealthy: src.Status.PodsUnhealthy,
69+
PodsHealthy: src.Status.PodsHealthy,
70+
PodsInjected: src.Status.PodsInjected,
71+
PodsOutdated: src.Status.PodsOutdated,
72+
PodsNotReady: src.Status.PodsNotReady,
73+
UnhealthyPodsErrors: unhealthyPodErrors,
74+
LastUpdated: src.Status.LastUpdated,
75+
ObservedVersion: src.Status.ObservedVersion,
76+
}
77+
return nil
78+
}
79+
80+
// ConvertFrom converts from the Hub version to this version.
81+
func (dst *Instrumentation) ConvertFrom(srcRaw conversion.Hub) error {
82+
src := srcRaw.(*current.Instrumentation)
83+
84+
// ObjectMeta
85+
dst.ObjectMeta = src.ObjectMeta
86+
87+
// Spec
88+
89+
for _, srcExpr := range src.Spec.ContainerSelector.ImageSelector.MatchExpressions {
90+
dst.Spec.ContainerSelector.ImageSelector.MatchExpressions = append(dst.Spec.ContainerSelector.ImageSelector.MatchExpressions, ImageSelectorRequirement{
91+
Key: ImageSelectorKey(srcExpr.Key), Operator: ImageSelectorOperator(srcExpr.Operator), Values: srcExpr.Values,
92+
})
93+
}
94+
for _, srcExpr := range src.Spec.ContainerSelector.EnvSelector.MatchExpressions {
95+
dst.Spec.ContainerSelector.EnvSelector.MatchExpressions = append(dst.Spec.ContainerSelector.EnvSelector.MatchExpressions, EnvSelectorRequirement{
96+
Key: srcExpr.Key, Operator: EnvSelectorOperator(srcExpr.Operator), Values: srcExpr.Values,
97+
})
98+
}
99+
for _, srcExpr := range src.Spec.ContainerSelector.NameSelector.MatchExpressions {
100+
dst.Spec.ContainerSelector.NameSelector.MatchExpressions = append(dst.Spec.ContainerSelector.NameSelector.MatchExpressions, NameSelectorRequirement{
101+
Key: NameSelectorKey(srcExpr.Key), Operator: NameSelectorOperator(srcExpr.Operator), Values: srcExpr.Values,
102+
})
103+
}
104+
105+
dst.Spec.ContainerSelector.ImageSelector.MatchImages = src.Spec.ContainerSelector.ImageSelector.MatchImages
106+
dst.Spec.ContainerSelector.EnvSelector.MatchEnvs = src.Spec.ContainerSelector.EnvSelector.MatchEnvs
107+
dst.Spec.ContainerSelector.NameSelector.MatchNames = src.Spec.ContainerSelector.NameSelector.MatchNames
108+
dst.Spec.ContainerSelector.NamesFromPodAnnotation = src.Spec.ContainerSelector.NamesFromPodAnnotation
109+
110+
dst.Spec.PodLabelSelector = src.Spec.PodLabelSelector
111+
dst.Spec.NamespaceLabelSelector = src.Spec.NamespaceLabelSelector
112+
dst.Spec.LicenseKeySecret = src.Spec.LicenseKeySecret
113+
dst.Spec.Agent = Agent{
114+
Env: src.Spec.Agent.Env,
115+
Image: src.Spec.Agent.Image,
116+
ImagePullPolicy: src.Spec.Agent.ImagePullPolicy,
117+
Language: src.Spec.Agent.Language,
118+
Resources: src.Spec.Agent.Resources,
119+
SecurityContext: src.Spec.Agent.SecurityContext,
120+
VolumeSizeLimit: src.Spec.Agent.VolumeSizeLimit,
121+
}
122+
dst.Spec.HealthAgent = HealthAgent{
123+
Env: src.Spec.HealthAgent.Env,
124+
Image: src.Spec.HealthAgent.Image,
125+
ImagePullPolicy: src.Spec.HealthAgent.ImagePullPolicy,
126+
Resources: src.Spec.HealthAgent.Resources,
127+
SecurityContext: src.Spec.HealthAgent.SecurityContext,
128+
}
129+
dst.Spec.AgentConfigMap = src.Spec.AgentConfigMap
130+
var unhealthyPodErrors []UnhealthyPodError
131+
if l := len(src.Status.UnhealthyPodsErrors); l > 0 {
132+
unhealthyPodErrors = make([]UnhealthyPodError, l)
133+
for i, e := range src.Status.UnhealthyPodsErrors {
134+
unhealthyPodErrors[i] = UnhealthyPodError{
135+
Pod: e.Pod,
136+
LastError: e.LastError,
137+
}
138+
}
139+
}
140+
dst.Status = InstrumentationStatus{
141+
PodsMatching: src.Status.PodsMatching,
142+
PodsUnhealthy: src.Status.PodsUnhealthy,
143+
PodsHealthy: src.Status.PodsHealthy,
144+
PodsInjected: src.Status.PodsInjected,
145+
PodsOutdated: src.Status.PodsOutdated,
146+
PodsNotReady: src.Status.PodsNotReady,
147+
UnhealthyPodsErrors: unhealthyPodErrors,
148+
LastUpdated: src.Status.LastUpdated,
149+
ObservedVersion: src.Status.ObservedVersion,
150+
}
151+
return nil
152+
}

api/v1beta2/instrumentation_types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ type InstrumentationStatus struct {
172172
ObservedVersion string `json:"observedVersion,omitempty"`
173173
}
174174

175-
// +kubebuilder:storageversion
176175
// +kubebuilder:object:root=true
177176
// +kubebuilder:resource:shortName=nragent;nragents
178177
// +kubebuilder:subresource:status

api/v1beta2/instrumentation_webhook.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type InstrumentationDefaulter struct {
5151
// Default to set the default values for Instrumentation
5252
func (r *InstrumentationDefaulter) Default(ctx context.Context, obj runtime.Object) error {
5353
inst := obj.(*Instrumentation)
54-
log.FromContext(ctx).V(1).Info("Setting defaults for v1beta1.Instrumentation", "name", inst.GetName())
54+
log.FromContext(ctx).V(1).Info("Setting defaults for v1beta2.Instrumentation", "name", inst.GetName())
5555
if inst.Labels == nil {
5656
inst.Labels = map[string]string{}
5757
}
@@ -64,18 +64,20 @@ func (r *InstrumentationDefaulter) Default(ctx context.Context, obj runtime.Obje
6464
return nil
6565
}
6666

67-
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
68-
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
69-
// +kubebuilder:webhook:verbs=create;update,path=/validate-newrelic-com-v1beta2-instrumentation,mutating=false,failurePolicy=fail,groups=newrelic.com,resources=instrumentations,versions=v1beta2,name=vinstrumentationcreateupdate-v1beta2.kb.io,sideEffects=none,admissionReviewVersions=v1
70-
// +kubebuilder:webhook:verbs=delete,path=/validate-newrelic-com-v1beta2-instrumentation,mutating=false,failurePolicy=ignore,groups=newrelic.com,resources=instrumentations,versions=v1beta2,name=vinstrumentationdelete-v1beta2.kb.io,sideEffects=none,admissionReviewVersions=v1
71-
7267
var validEnvPrefixes = []string{"NEW_RELIC_", "NEWRELIC_"}
7368
var validEnvPrefixesStr = strings.Join(validEnvPrefixes, ", ")
7469

7570
var _ webhook.CustomValidator = (*InstrumentationValidator)(nil)
7671

72+
// +k8s:deepcopy-gen=false
7773
type InstrumentationSpecValidator func(instrumentation *Instrumentation) error
7874

75+
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
76+
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
77+
// +kubebuilder:webhook:verbs=create;update,path=/validate-newrelic-com-v1beta2-instrumentation,mutating=false,failurePolicy=fail,groups=newrelic.com,resources=instrumentations,versions=v1beta2,name=vinstrumentationcreateupdate-v1beta2.kb.io,sideEffects=none,admissionReviewVersions=v1
78+
// +kubebuilder:webhook:verbs=delete,path=/validate-newrelic-com-v1beta2-instrumentation,mutating=false,failurePolicy=ignore,groups=newrelic.com,resources=instrumentations,versions=v1beta2,name=vinstrumentationdelete-v1beta2.kb.io,sideEffects=none,admissionReviewVersions=v1
79+
80+
// +k8s:deepcopy-gen=false
7981
// InstrumentationValidator is used to validate instrumentations
8082
type InstrumentationValidator struct {
8183
OperatorNamespace string

api/v1beta2/name_selector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type NameSelectorRequirement struct {
4242
// operator represents a key's relationship to a set of values.
4343
// Valid operators are In, NotIn, Exists, DoesNotExist.
4444
// The list of operators may grow in the future.
45-
Operator EnvSelectorOperator `json:"operator"`
45+
Operator NameSelectorOperator `json:"operator"`
4646
// values is an array of string values.
4747
// If the operator is In or NotIn, the values array must be non-empty.
4848
// If the operator is Exists or DoesNotExist, the values array must be empty.

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 0 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta3/env_selector.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package v1beta3
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/util/validation/field"
5+
6+
"github.com/newrelic/k8s-agents-operator/internal/selector"
7+
)
8+
9+
// EnvSelectorOperator A env selector operator is the set of operators that can be used in a selector requirement.
10+
type EnvSelectorOperator string
11+
12+
const (
13+
EnvSelectorOpIn EnvSelectorOperator = "In"
14+
EnvSelectorOpNotIn EnvSelectorOperator = "NotIn"
15+
EnvSelectorOpEquals EnvSelectorOperator = "=="
16+
EnvSelectorOpNotEquals EnvSelectorOperator = "!="
17+
EnvSelectorOpExists EnvSelectorOperator = "Exists"
18+
EnvSelectorOpDoesNotExist EnvSelectorOperator = "DoesNotExist"
19+
)
20+
21+
var acceptableEnvSelectorOps = map[EnvSelectorOperator]struct{}{
22+
EnvSelectorOpIn: {},
23+
EnvSelectorOpNotIn: {},
24+
EnvSelectorOpExists: {},
25+
EnvSelectorOpDoesNotExist: {},
26+
EnvSelectorOpEquals: {},
27+
EnvSelectorOpNotEquals: {},
28+
}
29+
30+
type EnvSelectorRequirement struct {
31+
// key is the field selector key that the requirement applies to.
32+
Key string `json:"key"`
33+
// operator represents a key's relationship to a set of values.
34+
// Valid operators are In, NotIn, Exists, DoesNotExist.
35+
// The list of operators may grow in the future.
36+
Operator EnvSelectorOperator `json:"operator"`
37+
// values is an array of string values.
38+
// If the operator is In or NotIn, the values array must be non-empty.
39+
// If the operator is Exists or DoesNotExist, the values array must be empty.
40+
// +optional
41+
// +listType=atomic
42+
Values []string `json:"values,omitempty"`
43+
}
44+
45+
type EnvSelector struct {
46+
// matchEnvs is a map of {key,value} pairs. A single {key,value} in the matchEnvs
47+
// map is equivalent to an element of matchExpressions, whose key field is "key", the
48+
// operator is "In", and the values array contains only "value". The requirements are ANDed.
49+
// +optional
50+
MatchEnvs map[string]string `json:"matchEnvs,omitempty"`
51+
// matchExpressions is a list of env selector requirements. The requirements are ANDed.
52+
// +optional
53+
// +listType=atomic
54+
MatchExpressions []EnvSelectorRequirement `json:"matchExpressions,omitempty"`
55+
}
56+
57+
// IsEmpty is used to check if the container selector is empty
58+
func (s *EnvSelector) IsEmpty() bool {
59+
return len(s.MatchEnvs) == 0 && len(s.MatchExpressions) == 0
60+
}
61+
62+
func (s *EnvSelector) AsSelector() (selector.Selector, error) {
63+
sel, err := selector.New(&selector.SimpleSelector{})
64+
if err != nil {
65+
return nil, err
66+
}
67+
if !s.IsEmpty() {
68+
lsr := make([]selector.SelectorRequirement, len(s.MatchExpressions))
69+
for i, entry := range s.MatchExpressions {
70+
lsr[i] = selector.SelectorRequirement{
71+
Key: entry.Key,
72+
Operator: selector.SelectionOperator(entry.Operator),
73+
Values: entry.Values,
74+
}
75+
}
76+
ls := selector.SimpleSelector{
77+
MatchExact: s.MatchEnvs,
78+
MatchExpressions: lsr,
79+
}
80+
sel, err = selector.New(&ls, selector.WithOperatorValidator(validateEnvOperator))
81+
if err != nil {
82+
return nil, err
83+
}
84+
}
85+
return sel, nil
86+
}
87+
88+
func validateEnvOperator(operator string, opts *field.Path) *field.Error {
89+
if _, ok := acceptableEnvSelectorOps[EnvSelectorOperator(operator)]; !ok {
90+
return field.Invalid(opts, operator, "invalid operator")
91+
}
92+
return nil
93+
}

api/v1beta3/env_selector_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package v1beta3

api/v1beta3/groupversion_info.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package v1beta3 contains API Schema definitions for the v1beta3 API group
18+
// +kubebuilder:object:generate=true
19+
// +groupName=newrelic.com
20+
package v1beta3
21+
22+
import (
23+
"k8s.io/apimachinery/pkg/runtime/schema"
24+
"sigs.k8s.io/controller-runtime/pkg/scheme"
25+
)
26+
27+
var (
28+
// GroupVersion is group version used to register these objects
29+
GroupVersion = schema.GroupVersion{Group: "newrelic.com", Version: "v1beta3"}
30+
31+
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
32+
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
33+
34+
// AddToScheme adds the types in this group-version to the given scheme.
35+
AddToScheme = SchemeBuilder.AddToScheme
36+
)

0 commit comments

Comments
 (0)