@@ -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+
239296func (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+ }
0 commit comments