Skip to content

Commit bd45ccb

Browse files
committed
add support for new required k8s labels: chain.link/product, chain.link/team, chain.link/component, chain.link/cost-center
1 parent 8b02ed1 commit bd45ccb

File tree

18 files changed

+231
-0
lines changed

18 files changed

+231
-0
lines changed

lib/k8s/config/overrides.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ const (
4646
EnvVarUserDescription = "Owner of an environment"
4747
EnvVarUserExample = "Satoshi"
4848

49+
EnvVarTeam = "CHAINLINK_USER_TEAM"
50+
EnvVarTeamDescription = "Team to, which owner of the environment belongs to"
51+
EnvVarTeamExample = "BIX, CCIP, BCM"
52+
4953
EnvVarCLCommitSha = "CHAINLINK_COMMIT_SHA"
5054
EnvVarCLCommitShaDescription = "The sha of the commit that you're running tests on. Mostly used for CI"
5155
EnvVarCLCommitShaExample = "${{ github.sha }}"

lib/k8s/environment/environment.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ type ConnectedChart interface {
6666
GetValues() *map[string]any
6767
// ExportData export deployment part data in the env
6868
ExportData(e *Environment) error
69+
// GetLabels returns labels for the component
70+
GetLabels() map[string]string
6971
}
7072

7173
// Config is an environment common configuration, labels, annotations, connection types, readiness check, etc.
@@ -80,6 +82,8 @@ type Config struct {
8082
Labels []string
8183
// PodLabels is a set of labels applied to every pod in the namespace
8284
PodLabels map[string]string
85+
// WorkloadLabels is a set of labels applied to every workload in the namespace (job, deployment, replicaset), including service and ingress
86+
WorkloadLabels map[string]string
8387
// PreventPodEviction if true sets a k8s annotation safe-to-evict=false to prevent pods from being evicted
8488
// Note: This should only be used if your test is completely incapable of handling things like K8s rebalances without failing.
8589
// If that is the case, it's worth the effort to make your test fault-tolerant soon. The alternative is expensive and infuriating.
@@ -236,6 +240,59 @@ func New(cfg *Config) *Environment {
236240
return e
237241
}
238242

243+
var requiredChainLinkNsLabels = []string{"chain.link/team", "chain.link/cost-center", "chain.link/product"}
244+
var requiredChainLinkWorkloadLabels = append([]string{}, append(requiredChainLinkNsLabels, "chain.link/component")...)
245+
246+
func (m *Environment) validateRequiredChainLinkLabels() error {
247+
if m.root.Labels() == nil {
248+
return fmt.Errorf("namespace labels are nil, but it should contain at least '%s' labels. Please add them to your environment config under 'Labels' key", strings.Join(requiredChainLinkNsLabels, ", "))
249+
}
250+
251+
var missingNsLabels []string
252+
for _, l := range requiredChainLinkNsLabels {
253+
if _, ok := (*m.root.Labels())[l]; !ok {
254+
missingNsLabels = append(missingNsLabels, l)
255+
}
256+
}
257+
258+
children := m.root.Node().Children()
259+
missingWorkloadLabels := make(map[string][]string)
260+
261+
if children == nil {
262+
return nil
263+
}
264+
265+
for _, child := range *children {
266+
if h, ok := child.(cdk8s.Helm); ok {
267+
for _, ao := range *h.ApiObjects() {
268+
switch *ao.Kind() {
269+
case "Deployment", "ReplicaSet", "StatefulSet", "Service", "Job", "DaemonSet", "Ingress":
270+
for _, l := range requiredChainLinkWorkloadLabels {
271+
maybeLabel := ao.Metadata().GetLabel(&l)
272+
if maybeLabel == nil {
273+
missingWorkloadLabels[*ao.Name()] = append(missingWorkloadLabels[*ao.Name()], l)
274+
}
275+
}
276+
}
277+
}
278+
}
279+
}
280+
281+
if len(missingWorkloadLabels) > 0 {
282+
sb := strings.Builder{}
283+
sb.WriteString("missing required labels for workloads:\n")
284+
for chart, missingLabels := range missingWorkloadLabels {
285+
for _, label := range missingLabels {
286+
sb.WriteString(fmt.Sprintf("\t'%s': '%s'\n", chart, label))
287+
}
288+
}
289+
sb.WriteString("Please add them to your environment configuration under 'WorkloadLabels' key. And check whether every chart has 'chain.link/component' label defined.")
290+
return errors.New(sb.String())
291+
}
292+
293+
return nil
294+
}
295+
239296
func (m *Environment) initApp() error {
240297
var err error
241298
m.App = cdk8s.NewApp(&cdk8s.AppProps{
@@ -435,6 +492,17 @@ func addDefaultPodAnnotationsAndLabels(h cdk8s.Helm, annotations, labels map[str
435492
}
436493
}
437494

495+
func addRequiredChainLinkLabels(h cdk8s.Helm, labels map[string]string) {
496+
for _, ao := range *h.ApiObjects() {
497+
switch *ao.Kind() {
498+
case "Deployment", "ReplicaSet", "StatefulSet", "Service", "Job", "DaemonSet", "Ingress":
499+
for k, v := range labels {
500+
ao.Metadata().AddLabel(&k, &v)
501+
}
502+
}
503+
}
504+
}
505+
438506
// UpdateHelm update a helm chart with new values. The pod will launch with an `updated=true` label if it's a Chainlink node.
439507
// Note: If you're modifying ConfigMap values, you'll probably need to use RollOutStatefulSets to apply the changes to the pods.
440508
// https://stackoverflow.com/questions/57356521/rollingupdate-for-stateful-set-doesnt-restart-pods-and-changes-from-updated-con
@@ -508,11 +576,32 @@ func (m *Environment) AddHelm(chart ConnectedChart) *Environment {
508576
ReleaseName: ptr.Ptr(chart.GetName()),
509577
Values: values,
510578
})
579+
580+
componentLabels, err := getComponentLabels(m.Cfg.WorkloadLabels, chart.GetLabels())
581+
if err != nil {
582+
m.err = err
583+
}
584+
585+
addRequiredChainLinkLabels(h, componentLabels)
511586
addDefaultPodAnnotationsAndLabels(h, markNotSafeToEvict(m.Cfg.PreventPodEviction, nil), m.Cfg.PodLabels)
512587
m.Charts = append(m.Charts, chart)
513588
return m
514589
}
515590

591+
func getComponentLabels(podLabels, chartLabels map[string]string) (map[string]string, error) {
592+
componentLabels := make(map[string]string)
593+
err := mergo.Merge(&componentLabels, podLabels, mergo.WithOverride)
594+
if err != nil {
595+
return nil, err
596+
}
597+
err = mergo.Merge(&componentLabels, chartLabels, mergo.WithOverride)
598+
if err != nil {
599+
return nil, err
600+
}
601+
602+
return componentLabels, nil
603+
}
604+
516605
// PullOCIChart handles working with OCI format repositories
517606
// https://helm.sh/docs/topics/registries/
518607
// API is not compatible between helm repos and OCI repos, so we download and untar the chart
@@ -674,6 +763,12 @@ func (m *Environment) RunCustomReadyConditions(customCheck *client.ReadyCheckDat
674763
m.Cfg.SkipManifestUpdate = mu
675764
}
676765
log.Debug().Bool("ManifestUpdate", m.Cfg.SkipManifestUpdate).Msg("Update mode")
766+
767+
// make sure all required chain.link labels are present in the final manifest
768+
if err := m.validateRequiredChainLinkLabels(); err != nil {
769+
return err
770+
}
771+
677772
if !m.Cfg.SkipManifestUpdate || m.Cfg.JobImage != "" {
678773
if err := m.DeployCustomReadyConditions(customCheck, podCount); err != nil {
679774
log.Error().Err(err).Msg("Error deploying environment")
@@ -1084,3 +1179,39 @@ func markNotSafeToEvict(preventPodEviction bool, m map[string]string) map[string
10841179

10851180
return m
10861181
}
1182+
1183+
func GetRequiredChainLinkNamespaceLabels(product, testType string) ([]string, error) {
1184+
var nsLabels []string
1185+
createdLabels, err := createRequiredChainLinkLabels(product, testType)
1186+
if err != nil {
1187+
return nsLabels, err
1188+
}
1189+
1190+
for k, v := range createdLabels {
1191+
nsLabels = append(nsLabels, fmt.Sprintf("%s=%s", k, v))
1192+
}
1193+
1194+
return nsLabels, nil
1195+
}
1196+
1197+
func GetRequiredChainLinkWorkloadLabels(product, testType string) (map[string]string, error) {
1198+
createdLabels, err := createRequiredChainLinkLabels(product, testType)
1199+
if err != nil {
1200+
return nil, err
1201+
}
1202+
1203+
return createdLabels, nil
1204+
}
1205+
1206+
func createRequiredChainLinkLabels(product, testType string) (map[string]string, error) {
1207+
team := os.Getenv(config.EnvVarTeam)
1208+
if team == "" {
1209+
return nil, fmt.Errorf("missing team environment variable, please set %s to your team name or if you are seeing this in CI please either add a new input with team name or hardcode it if this jobs is only run by a single team", config.EnvVarUser)
1210+
}
1211+
1212+
return map[string]string{
1213+
"chain.link/product": product,
1214+
"chain.link/team": team,
1215+
"chain.link/cost-center": fmt.Sprintf("%s-%s-test", team, testType),
1216+
}, nil
1217+
}

lib/k8s/environment/runner.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func (m Chart) ExportData(e *Environment) error {
5050
return nil
5151
}
5252

53+
func (m Chart) GetLabels() map[string]string {
54+
return map[string]string{
55+
"chain.link/component": "k8s-legacy-test-runner",
56+
}
57+
}
58+
5359
func NewRunner(props *Props) func(root cdk8s.Chart) ConnectedChart {
5460
return func(root cdk8s.Chart) ConnectedChart {
5561
c := &Chart{

lib/k8s/pkg/helm/chainlink/chainlink.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ func (m Chart) GetValues() *map[string]any {
5757
return m.Values
5858
}
5959

60+
func (m Chart) GetLabels() map[string]string {
61+
return map[string]string{
62+
"chain.link/component": "chainlink",
63+
}
64+
}
65+
6066
func (m Chart) ExportData(e *environment.Environment) error {
6167
// fetching all apps with label app=chainlink-${deploymentIndex}:${instanceIndex}
6268
pods, err := e.Fwd.Client.ListPods(e.Cfg.Namespace, fmt.Sprintf("app=%s", m.Name))

lib/k8s/pkg/helm/ethereum/geth.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ func (m Chart) GetValues() *map[string]interface{} {
5757
return m.HelmProps.Values
5858
}
5959

60+
func (m Chart) GetLabels() map[string]string {
61+
return map[string]string{
62+
"chain.link/component": "geth",
63+
}
64+
}
65+
6066
func (m Chart) ExportData(e *environment.Environment) error {
6167
if m.Props.Simulated {
6268
gethLocalHttp, err := e.Fwd.FindPort("geth:0", "geth-network", "http-rpc").As(client.LocalConnection, client.HTTP)

lib/k8s/pkg/helm/foundry/foundry.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ func (m Chart) GetValues() *map[string]interface{} {
5858
return m.Values
5959
}
6060

61+
func (m Chart) GetLabels() map[string]string {
62+
return map[string]string{
63+
"chain.link/component": "anvil",
64+
}
65+
}
66+
6167
func (m *Chart) ExportData(e *environment.Environment) error {
6268
appInstance := fmt.Sprintf("%s:0", m.Name) // uniquely identifies an instance of an anvil service running in a pod
6369
var err error

lib/k8s/pkg/helm/grafana/grafana.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ func (m Chart) GetValues() *map[string]interface{} {
4040
return m.Values
4141
}
4242

43+
func (m Chart) GetLabels() map[string]string {
44+
return map[string]string{
45+
"chain.link/component": "grafana",
46+
}
47+
}
48+
4349
func (m Chart) ExportData(e *environment.Environment) error {
4450
return nil
4551
}

lib/k8s/pkg/helm/influxdb/influxdb.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ func (m Chart) GetValues() *map[string]interface{} {
4040
return m.Values
4141
}
4242

43+
func (m Chart) GetLabels() map[string]string {
44+
return map[string]string{
45+
"chain.link/component": "influxdb",
46+
}
47+
}
48+
4349
func (m Chart) ExportData(e *environment.Environment) error {
4450
return nil
4551
}

lib/k8s/pkg/helm/kafka-rest/kafka-rest.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ func (m Chart) GetValues() *map[string]interface{} {
4848
return m.Values
4949
}
5050

51+
func (m Chart) GetLabels() map[string]string {
52+
return map[string]string{
53+
"chain.link/component": "kafka-rest",
54+
}
55+
}
56+
5157
func (m Chart) ExportData(e *environment.Environment) error {
5258
urls := make([]string, 0)
5359
local, err := e.Fwd.FindPort("cp-kafka-rest:0", "kafka-rest", "http").As(client.LocalConnection, client.HTTP)

lib/k8s/pkg/helm/kafka/kafka.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ func (m Chart) GetValues() *map[string]interface{} {
4040
return m.Values
4141
}
4242

43+
func (m Chart) GetLabels() map[string]string {
44+
return map[string]string{
45+
"chain.link/component": "kafka",
46+
}
47+
}
48+
4349
func (m Chart) ExportData(e *environment.Environment) error {
4450
return nil
4551
}

0 commit comments

Comments
 (0)