66 "fmt"
77 "reflect"
88 "strconv"
9+ "strings"
910 "time"
1011
1112 "crypto/sha256"
@@ -15,7 +16,6 @@ import (
1516 "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
1617 "github.com/openstack-k8s-operators/lib-common/modules/common/pvc"
1718 "github.com/openstack-k8s-operators/lib-common/modules/common/util"
18- v1beta1 "github.com/openstack-k8s-operators/test-operator/api/v1beta1"
1919 "gopkg.in/yaml.v3"
2020 corev1 "k8s.io/api/core/v1"
2121 rbacv1 "k8s.io/api/rbac/v1"
@@ -30,15 +30,13 @@ import (
3030)
3131
3232const (
33- podNameStepInfix = "-s"
34- envVarsConfigMapInfix = "-env-vars-s"
35- customDataConfigMapInfix = "-custom-data-s"
36- workflowStepNumInvalid = - 1
37- workflowStepNameInvalid = "no-name"
38- workflowStepLabel = "workflowStep"
39- instanceNameLabel = "instanceName"
40- operatorNameLabel = "operator"
41-
33+ podNameStepInfix = "-s"
34+ envVarsConfigMapInfix = "-env-vars-s"
35+ customDataConfigMapInfix = "-custom-data-s"
36+ workflowStepNameInvalid = "no-name"
37+ workflowStepLabel = "workflowStep"
38+ instanceNameLabel = "instanceName"
39+ operatorNameLabel = "operator"
4240 testOperatorLockName = "test-operator-lock"
4341 testOperatorLockOwnerField = "owner"
4442)
8280
8381 // ErrLockFieldMissing indicates that a required field is missing in the lock config map.
8482 ErrLockFieldMissing = errors .New ("field is missing in the config map" )
83+
84+ // ErrFieldExpectedStruct indicates attempting to access a field on a non-struct type.
85+ ErrFieldExpectedStruct = errors .New ("field cannot be accessed: expected struct" )
86+
87+ // ErrFieldNilPointer indicates attempting to dereference a nil pointer.
88+ ErrFieldNilPointer = errors .New ("field cannot be accessed: nil pointer" )
89+
90+ // ErrFieldNotFound indicates a field name does not exist on the struct.
91+ ErrFieldNotFound = errors .New ("field not found" )
8592)
8693
8794// Reconciler provides common functionality for all test framework reconcilers
@@ -253,164 +260,68 @@ func (r *Reconciler) GetLastPod(
253260 return maxPod , nil
254261}
255262
256- // GetEnvVarsConfigMapName returns the name of the environment variables ConfigMap for the given instance and workflow step
257- func GetEnvVarsConfigMapName (instance interface {}, workflowStepNum int ) string {
258- if _ , ok := instance .(* v1beta1.Tobiko ); ok {
259- return "not-implemented"
260- } else if typedInstance , ok := instance .(* v1beta1.Tempest ); ok {
261- return typedInstance .Name + envVarsConfigMapInfix + strconv .Itoa (workflowStepNum )
262- }
263-
264- return "not-implemented"
265- }
266-
267- // GetCustomDataConfigMapName returns the name of the custom data ConfigMap for the given instance and workflow step
268- func GetCustomDataConfigMapName (instance interface {}, workflowStepNum int ) string {
269- if _ , ok := instance .(* v1beta1.Tobiko ); ok {
270- return "not-implemented"
271- } else if typedInstance , ok := instance .(* v1beta1.Tempest ); ok {
272- return typedInstance .Name + customDataConfigMapInfix + strconv .Itoa (workflowStepNum )
273- }
274-
275- return "not-implemented"
276- }
277-
278263// GetContainerImage returns the container image to use for the given instance, either from the provided parameter or from configuration
279264func (r * Reconciler ) GetContainerImage (
280265 ctx context.Context ,
281- containerImage string ,
282266 instance interface {},
283267) (string , error ) {
284- cm := & corev1.ConfigMap {}
285- testOperatorConfigMapName := "test-operator-config"
286- if typedInstance , ok := instance .(* v1beta1.Tempest ); ok {
287- if len (containerImage ) > 0 {
288- return containerImage , nil
289- }
290-
291- objectKey := client.ObjectKey {Namespace : typedInstance .Namespace , Name : testOperatorConfigMapName }
292- err := r .Client .Get (ctx , objectKey , cm )
293- if err != nil {
294- return "" , err
295- }
296-
297- if cm .Data == nil {
298- return util .GetEnvVar ("RELATED_IMAGE_TEST_TEMPEST_IMAGE_URL_DEFAULT" , "" ), nil
299-
300- }
301-
302- if cmImage , exists := cm .Data ["tempest-image" ]; exists {
303- return cmImage , nil
304- }
305-
306- return util .GetEnvVar ("RELATED_IMAGE_TEST_TEMPEST_IMAGE_URL_DEFAULT" , "" ), nil
307- } else if typedInstance , ok := instance .(* v1beta1.Tobiko ); ok {
308- if len (containerImage ) > 0 {
309- return containerImage , nil
310- }
311-
312- objectKey := client.ObjectKey {Namespace : typedInstance .Namespace , Name : testOperatorConfigMapName }
313- err := r .Client .Get (ctx , objectKey , cm )
314- if err != nil {
315- return "" , err
316- }
317-
318- if cm .Data == nil {
319- return util .GetEnvVar ("RELATED_IMAGE_TEST_TOBIKO_IMAGE_URL_DEFAULT" , "" ), nil
320-
321- }
322-
323- if cmImage , exists := cm .Data ["tobiko-image" ]; exists {
324- return cmImage , nil
325- }
326-
327- return util .GetEnvVar ("RELATED_IMAGE_TEST_TOBIKO_IMAGE_URL_DEFAULT" , "" ), nil
328- } else if typedInstance , ok := instance .(* v1beta1.HorizonTest ); ok {
329- if len (containerImage ) > 0 {
330- return containerImage , nil
331- }
332-
333- objectKey := client.ObjectKey {Namespace : typedInstance .Namespace , Name : testOperatorConfigMapName }
334- err := r .Client .Get (ctx , objectKey , cm )
335- if err != nil {
336- return "" , err
337- }
338-
339- if cm .Data == nil {
340- return util .GetEnvVar ("RELATED_IMAGE_TEST_HORIZONTEST_IMAGE_URL_DEFAULT" , "" ), nil
341-
342- }
268+ v := reflect .ValueOf (instance )
343269
344- if cmImage , exists := cm .Data ["horizontest-image" ]; exists {
345- return cmImage , nil
346- }
270+ spec , err := SafetyCheck (v , "Spec" )
271+ if err != nil {
272+ return "" , err
273+ }
347274
348- return util .GetEnvVar ("RELATED_IMAGE_TEST_HORIZONTEST_IMAGE_URL_DEFAULT" , "" ), nil
349- } else if typedInstance , ok := instance .(* v1beta1.AnsibleTest ); ok {
350- if len (containerImage ) > 0 {
351- return containerImage , nil
352- }
275+ containerImage := GetStringField (spec , "ContainerImage" )
276+ if containerImage != "" {
277+ return containerImage , nil
278+ }
353279
354- objectKey := client.ObjectKey {Namespace : typedInstance .Namespace , Name : testOperatorConfigMapName }
355- err := r .Client .Get (ctx , objectKey , cm )
356- if err != nil {
357- return "" , err
358- }
280+ namespace := GetStringField (v , "Namespace" )
281+ kind := GetStringField (v , "Kind" )
359282
360- if cm .Data == nil {
361- return util .GetEnvVar ("RELATED_IMAGE_TEST_ANSIBLETEST_IMAGE_URL_DEFAULT" , "" ), nil
362- }
283+ cm := & corev1.ConfigMap {}
284+ objectKey := client.ObjectKey {Namespace : namespace , Name : "test-operator-config" }
285+ if err := r .Client .Get (ctx , objectKey , cm ); err != nil {
286+ return "" , err
287+ }
363288
364- if cmImage , exists := cm .Data ["ansibletest-image" ]; exists {
365- return cmImage , nil
289+ imageKey := strings .ToLower (kind ) + "-image"
290+ if cm .Data != nil {
291+ if image , exists := cm .Data [imageKey ]; exists && image != "" {
292+ return image , nil
366293 }
367-
368- return util .GetEnvVar ("RELATED_IMAGE_TEST_ANSIBLETEST_IMAGE_URL_DEFAULT" , "" ), nil
369294 }
370295
371- return "" , nil
296+ relatedImage := "RELATED_IMAGE_TEST_" + strings .ToUpper (kind ) + "_IMAGE_URL_DEFAULT"
297+ return util .GetEnvVar (relatedImage , "" ), nil
372298}
373299
374300// GetPodName returns the name of the pod for the given instance and workflow step
375- func (r * Reconciler ) GetPodName (instance interface {}, workflowStepNum int ) string {
376- if typedInstance , ok := instance .(* v1beta1.Tobiko ); ok {
377- if len (typedInstance .Spec .Workflow ) == 0 || workflowStepNum == workflowStepNumInvalid {
378- return typedInstance .Name
379- }
380-
381- workflowStepName := workflowStepNameInvalid
382- if workflowStepNum < len (typedInstance .Spec .Workflow ) {
383- workflowStepName = typedInstance .Spec .Workflow [workflowStepNum ].StepName
384- }
385-
386- return typedInstance .Name + podNameStepInfix + fmt .Sprintf ("%02d" , workflowStepNum ) + "-" + workflowStepName
387- } else if typedInstance , ok := instance .(* v1beta1.Tempest ); ok {
388- if len (typedInstance .Spec .Workflow ) == 0 || workflowStepNum == workflowStepNumInvalid {
389- return typedInstance .Name
390- }
301+ func (r * Reconciler ) GetPodName (instance interface {}, stepNum int ) string {
302+ v := reflect .ValueOf (instance )
391303
392- workflowStepName := workflowStepNameInvalid
393- if workflowStepNum < len (typedInstance .Spec .Workflow ) {
394- workflowStepName = typedInstance .Spec .Workflow [workflowStepNum ].StepName
395- }
304+ name := GetStringField (v , "Name" )
305+ spec , err := SafetyCheck (v , "Spec" )
306+ if err != nil {
307+ return name
308+ }
396309
397- return typedInstance .Name + podNameStepInfix + fmt .Sprintf ("%02d" , workflowStepNum ) + "-" + workflowStepName
398- } else if typedInstance , ok := instance .(* v1beta1.HorizonTest ); ok {
399- return typedInstance .Name
400- } else if typedInstance , ok := instance .(* v1beta1.AnsibleTest ); ok {
401- if len (typedInstance .Spec .Workflow ) == 0 || workflowStepNum == workflowStepNumInvalid {
402- return typedInstance .Name
403- }
310+ workflow , err := SafetyCheck (spec , "Workflow" )
311+ if err != nil || workflow .Len () == 0 {
312+ return name
313+ }
404314
405- workflowStepName := workflowStepNameInvalid
406- if workflowStepNum < len (typedInstance .Spec .Workflow ) {
407- workflowStepName = typedInstance .Spec .Workflow [workflowStepNum ].StepName
315+ // Get workflow step name
316+ stepName := workflowStepNameInvalid
317+ if stepNum >= 0 && stepNum < workflow .Len () {
318+ stepName = GetStringField (workflow .Index (stepNum ), "StepName" )
319+ if stepName == "" {
320+ stepName = workflowStepNameInvalid
408321 }
409-
410- return typedInstance .Name + podNameStepInfix + fmt .Sprintf ("%02d" , workflowStepNum ) + "-" + workflowStepName
411322 }
412323
413- return workflowStepNameInvalid
324+ return name + podNameStepInfix + fmt . Sprintf ( "%02d" , stepNum ) + "-" + stepName
414325}
415326
416327// GetPVCLogsName returns the name of the PVC for logs for the given instance and workflow step
@@ -493,11 +404,6 @@ func (r *Reconciler) EnsureLogsPVCExists(
493404 return ctrlResult , nil
494405}
495406
496- // GetClient returns the Kubernetes client
497- func (r * Reconciler ) GetClient () client.Client {
498- return r .Client
499- }
500-
501407// GetLogger returns the logger instance
502408func (r * Reconciler ) GetLogger () logr.Logger {
503409 return r .Log
@@ -624,12 +530,6 @@ func (r *Reconciler) PodExists(ctx context.Context, instance client.Object, work
624530 return true
625531}
626532
627- func (r * Reconciler ) setConfigOverwrite (customData map [string ]string , configOverwrite map [string ]string ) {
628- for key , data := range configOverwrite {
629- customData [key ] = data
630- }
631- }
632-
633533// GetCommonRbacRules returns the common RBAC rules for test operations, with optional privileged permissions
634534func GetCommonRbacRules (privileged bool ) []rbacv1.PolicyRule {
635535 rbacPolicyRule := rbacv1.PolicyRule {
@@ -717,6 +617,37 @@ func EnsureCloudsConfigMapExists(
717617 return ctrl.Result {}, nil
718618}
719619
620+ // GetStringField returns reflect string field safely
621+ func GetStringField (v reflect.Value , fieldName string ) string {
622+ field , err := SafetyCheck (v , fieldName )
623+ if err != nil || field .Kind () != reflect .String {
624+ return ""
625+ }
626+
627+ return field .String ()
628+ }
629+
630+ // SafetyCheck returns reflect value after checking its validity
631+ func SafetyCheck (v reflect.Value , fieldName string ) (reflect.Value , error ) {
632+ if v .Kind () == reflect .Ptr {
633+ if v .IsNil () {
634+ return reflect.Value {}, fmt .Errorf ("%s: %w" , fieldName , ErrFieldNilPointer )
635+ }
636+ v = v .Elem ()
637+ }
638+
639+ if v .Kind () != reflect .Struct {
640+ return reflect.Value {}, fmt .Errorf ("%s: %w, got %s" , fieldName , ErrFieldExpectedStruct , v .Kind ())
641+ }
642+
643+ field := v .FieldByName (fieldName )
644+ if ! field .IsValid () {
645+ return reflect.Value {}, fmt .Errorf ("%s: %w" , fieldName , ErrFieldNotFound )
646+ }
647+
648+ return field , nil
649+ }
650+
720651// IsEmpty checks if the provided value is empty based on its type
721652func IsEmpty (value interface {}) bool {
722653 if v , ok := value .(reflect.Value ); ok {
0 commit comments