github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/k8s/environment/runner.go (about)

     1  package environment
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2"
    10  	"github.com/rs/zerolog/log"
    11  
    12  	"github.com/smartcontractkit/chainlink-testing-framework/libs/k8s/config"
    13  	"github.com/smartcontractkit/chainlink-testing-framework/libs/k8s/imports/k8s"
    14  	a "github.com/smartcontractkit/chainlink-testing-framework/libs/k8s/pkg/alias"
    15  	"github.com/smartcontractkit/chainlink-testing-framework/libs/utils/ptr"
    16  )
    17  
    18  const REMOTE_RUNNER_NAME = "remote-test-runner"
    19  
    20  type Chart struct {
    21  	Props *Props
    22  }
    23  
    24  func (m Chart) IsDeploymentNeeded() bool {
    25  	return true
    26  }
    27  
    28  func (m Chart) GetName() string {
    29  	return REMOTE_RUNNER_NAME
    30  }
    31  
    32  func (m Chart) GetProps() interface{} {
    33  	return m.Props
    34  }
    35  
    36  func (m Chart) GetPath() string {
    37  	return ""
    38  }
    39  
    40  func (m Chart) GetVersion() string {
    41  	return ""
    42  }
    43  
    44  func (m Chart) GetValues() *map[string]interface{} {
    45  	return nil
    46  }
    47  
    48  func (m Chart) ExportData(e *Environment) error {
    49  	return nil
    50  }
    51  
    52  func NewRunner(props *Props) func(root cdk8s.Chart) ConnectedChart {
    53  	return func(root cdk8s.Chart) ConnectedChart {
    54  		c := &Chart{
    55  			Props: props,
    56  		}
    57  		role(root, props)
    58  		job(root, props)
    59  		return c
    60  	}
    61  }
    62  
    63  type Props struct {
    64  	BaseName           string
    65  	TargetNamespace    string
    66  	Labels             *map[string]*string
    67  	Image              string
    68  	TestName           string
    69  	NoManifestUpdate   bool
    70  	PreventPodEviction bool
    71  }
    72  
    73  func role(chart cdk8s.Chart, props *Props) {
    74  	k8s.NewKubeRole(
    75  		chart,
    76  		ptr.Ptr(fmt.Sprintf("%s-role", props.BaseName)),
    77  		&k8s.KubeRoleProps{
    78  			Metadata: &k8s.ObjectMeta{
    79  				Name: ptr.Ptr(props.BaseName),
    80  			},
    81  			Rules: &[]*k8s.PolicyRule{
    82  				{
    83  					ApiGroups: &[]*string{
    84  						ptr.Ptr(""), // this empty line is needed or k8s get really angry
    85  						ptr.Ptr("apps"),
    86  						ptr.Ptr("batch"),
    87  						ptr.Ptr("core"),
    88  						ptr.Ptr("networking.k8s.io"),
    89  						ptr.Ptr("storage.k8s.io"),
    90  						ptr.Ptr("policy"),
    91  						ptr.Ptr("chaos-mesh.org"),
    92  						ptr.Ptr("monitoring.coreos.com"),
    93  						ptr.Ptr("rbac.authorization.k8s.io"),
    94  					},
    95  					Resources: &[]*string{
    96  						ptr.Ptr("*"),
    97  					},
    98  					Verbs: &[]*string{
    99  						ptr.Ptr("*"),
   100  					},
   101  				},
   102  			},
   103  		})
   104  	k8s.NewKubeRoleBinding(
   105  		chart,
   106  		ptr.Ptr(fmt.Sprintf("%s-role-binding", props.BaseName)),
   107  		&k8s.KubeRoleBindingProps{
   108  			RoleRef: &k8s.RoleRef{
   109  				ApiGroup: ptr.Ptr("rbac.authorization.k8s.io"),
   110  				Kind:     ptr.Ptr("Role"),
   111  				Name:     ptr.Ptr("remote-test-runner"),
   112  			},
   113  			Metadata: nil,
   114  			Subjects: &[]*k8s.Subject{
   115  				{
   116  					Kind:      ptr.Ptr("ServiceAccount"),
   117  					Name:      ptr.Ptr("default"),
   118  					Namespace: ptr.Ptr(props.TargetNamespace),
   119  				},
   120  			},
   121  		},
   122  	)
   123  }
   124  
   125  func job(chart cdk8s.Chart, props *Props) {
   126  	defaultRunnerPodAnnotations := markNotSafeToEvict(props.PreventPodEviction, nil)
   127  	restartPolicy := "Never"
   128  	backOffLimit := float64(0)
   129  	if os.Getenv(config.EnvVarDetachRunner) == "true" { // If we're running detached, we're likely running a long-form test
   130  		restartPolicy = "OnFailure"
   131  		backOffLimit = 100000 // effectively infinite (I hope)
   132  	}
   133  	k8s.NewKubeJob(
   134  		chart,
   135  		ptr.Ptr(fmt.Sprintf("%s-job", props.BaseName)),
   136  		&k8s.KubeJobProps{
   137  			Metadata: &k8s.ObjectMeta{
   138  				Name: ptr.Ptr(props.BaseName),
   139  			},
   140  			Spec: &k8s.JobSpec{
   141  				Template: &k8s.PodTemplateSpec{
   142  					Metadata: &k8s.ObjectMeta{
   143  						Labels:      props.Labels,
   144  						Annotations: a.ConvertAnnotations(defaultRunnerPodAnnotations),
   145  					},
   146  					Spec: &k8s.PodSpec{
   147  						ServiceAccountName: ptr.Ptr("default"),
   148  						Containers: &[]*k8s.Container{
   149  							container(props),
   150  						},
   151  						RestartPolicy: ptr.Ptr(restartPolicy),
   152  						Volumes: &[]*k8s.Volume{
   153  							{
   154  								Name:     ptr.Ptr("persistence"),
   155  								EmptyDir: &k8s.EmptyDirVolumeSource{},
   156  							},
   157  						},
   158  					},
   159  				},
   160  				ActiveDeadlineSeconds: nil,
   161  				BackoffLimit:          ptr.Ptr(backOffLimit),
   162  			},
   163  		})
   164  }
   165  
   166  func container(props *Props) *k8s.Container {
   167  	cpu := os.Getenv(config.EnvVarRemoteRunnerCpu)
   168  	if cpu == "" {
   169  		cpu = "1000m"
   170  	}
   171  	mem := os.Getenv(config.EnvVarRemoteRunnerMem)
   172  	if mem == "" {
   173  		mem = "1024Mi"
   174  	}
   175  	return &k8s.Container{
   176  		Name:            ptr.Ptr(fmt.Sprintf("%s-node", props.BaseName)),
   177  		Image:           ptr.Ptr(props.Image),
   178  		ImagePullPolicy: ptr.Ptr("Always"),
   179  		Env:             jobEnvVars(props),
   180  		Resources:       a.ContainerResources(cpu, mem, cpu, mem),
   181  		VolumeMounts: &[]*k8s.VolumeMount{
   182  			{
   183  				Name:      ptr.Ptr("persistence"),
   184  				MountPath: ptr.Ptr("/persistence"),
   185  			},
   186  		},
   187  	}
   188  }
   189  
   190  func jobEnvVars(props *Props) *[]*k8s.EnvVar {
   191  	// Use a map to set values so we can easily overwrite duplicate values
   192  	env := make(map[string]string)
   193  
   194  	// Propagate common environment variables to the runner
   195  	lookups := []string{
   196  		config.EnvVarCLImage,
   197  		config.EnvVarCLTag,
   198  		config.EnvVarCLCommitSha,
   199  		config.EnvVarLogLevel,
   200  		config.EnvVarTestTrigger,
   201  		config.EnvVarToleration,
   202  		config.EnvVarSlackChannel,
   203  		config.EnvVarSlackKey,
   204  		config.EnvVarSlackUser,
   205  		config.EnvVarUser,
   206  		config.EnvVarNodeSelector,
   207  		config.EnvVarDBURL,
   208  		config.EnvVarInternalDockerRepo,
   209  		config.EnvVarLocalCharts,
   210  		config.EnvBase64ConfigOverride,
   211  		config.EnvBase64NetworkConfig,
   212  	}
   213  	for _, k := range lookups {
   214  		v, success := os.LookupEnv(k)
   215  		if success && len(v) > 0 {
   216  			log.Debug().Str(k, v).Msg("Forwarding Env Var")
   217  			env[k] = v
   218  		}
   219  	}
   220  
   221  	// Propagate prefixed variables to the runner
   222  	// These should overwrite anything that was unprefixed if they match up
   223  	for _, e := range os.Environ() {
   224  		if i := strings.Index(e, "="); i >= 0 {
   225  			if strings.HasPrefix(e[:i], config.EnvVarPrefix) {
   226  				withoutPrefix := strings.Replace(e[:i], config.EnvVarPrefix, "", 1)
   227  				log.Debug().Str(e[:i], e[i+1:]).Msg("Forwarding generic Env Var")
   228  				env[withoutPrefix] = e[i+1:]
   229  			}
   230  		}
   231  	}
   232  
   233  	// Add variables that should need specific values for thre remote runner
   234  	env[config.EnvVarNamespace] = props.TargetNamespace
   235  	env["TEST_NAME"] = props.TestName
   236  	env[config.EnvVarInsideK8s] = "true"
   237  	env[config.EnvVarNoManifestUpdate] = strconv.FormatBool(props.NoManifestUpdate)
   238  
   239  	// convert from map to the expected array
   240  	cdk8sVars := make([]*k8s.EnvVar, 0)
   241  	for k, v := range env {
   242  		cdk8sVars = append(cdk8sVars, a.EnvVarStr(k, v))
   243  	}
   244  	return &cdk8sVars
   245  }