Skip to content

Commit

Permalink
Move merging of workflow section into webhook
Browse files Browse the repository at this point in the history
Currently, the merging of workflow section is done in controllers
of related CRs. We want to move the merging logic into webhooks
to make the process cleaner.
  • Loading branch information
kstrenkova authored and lpiwowar committed Feb 4, 2025
1 parent be35b07 commit 031df6c
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 155 deletions.
11 changes: 10 additions & 1 deletion api/v1beta1/ansibletest_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,16 @@ var _ webhook.Defaulter = &AnsibleTest{}
func (r *AnsibleTest) Default() {
ansibletestlog.Info("default", "name", r.Name)

// TODO(user): fill in your defaulting logic.
r.Spec.Default()
}

// Default - set defaults for this AnsibleTest spec.
func (spec *AnsibleTestSpec) Default() {
if len(spec.Workflow) > 0 {
for key, _ := range spec.Workflow {
mergeSectionIntoWorkflow(spec, key)
}
}
}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
Expand Down
93 changes: 93 additions & 0 deletions api/v1beta1/common_webhook.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package v1beta1

import (
"fmt"
"reflect"
)

const (
// ErrPrivilegedModeRequired
ErrPrivilegedModeRequired = "%s.Spec.Privileged is requied in order to successfully " +
Expand Down Expand Up @@ -28,3 +33,91 @@ const (
"ensures that the copying of the logs to the PV is completed without any " +
"complications."
)

// TODO remove repetitive code
// merge non-workflow section into workflow
func mergeSectionIntoWorkflow(instance interface{}, workflowStepNum int) {
switch spec := instance.(type) {
case *TempestSpec:
workflow := &spec.Workflow[workflowStepNum]
setWorkflowSpec(*spec, workflow)
case *TobikoSpec:
workflow := &spec.Workflow[workflowStepNum]
setWorkflowSpec(*spec, workflow)
case *AnsibleTestSpec:
workflow := &spec.Workflow[workflowStepNum]
setWorkflowSpec(*spec, workflow)
}
}

func isEmpty(value interface{}) bool {
if v, ok := value.(reflect.Value); ok {
switch v.Kind() {
case reflect.String, reflect.Map:
return v.Len() == 0
case reflect.Ptr, reflect.Interface, reflect.Slice:
return v.IsNil()
}
}
return false
}

func setWorkflowSpec(main interface{}, workflow interface{}) {
mReflect := reflect.ValueOf(main)
wReflect := reflect.ValueOf(workflow).Elem()

for i := 0; i < mReflect.NumField(); i++ {
name := mReflect.Type().Field(i).Name
mValue := mReflect.Field(i)
wValue := wReflect.FieldByName(name)

fmt.Println("Name: ", name)
//fmt.Println("M Value: ", mValue)
//fmt.Println("W Value: ", wValue)
//fmt.Println("M Kind: ", mValue.Kind())
//fmt.Println("W Kind: ", wValue.Kind())
//fmt.Println("M Empty: ", isEmpty(mValue))
//fmt.Println("W Empty: ", isEmpty(wValue))

if mValue.Kind() == reflect.Struct {
switch name {
case "CommonOptions":
wValue := wReflect.FieldByName("WorkflowCommonParameters")
setWorkflowSpec(mValue.Interface(), wValue.Addr().Interface())
case "TempestRun", "TempestconfRun":
setWorkflowSpec(mValue.Interface(), wValue.Addr().Interface())
case "Resources":
setWorkflowSpec(mValue.Interface(), wValue.Interface())

}
continue
}

if !wValue.IsValid() {
continue
}

if isEmpty(wValue) && !isEmpty(mValue) {
if mValue.Kind() == reflect.Map {
mapCopy := reflect.MakeMap(mValue.Type())
for _, key := range mValue.MapKeys() {
value := mValue.MapIndex(key)
mapCopy.SetMapIndex(key, value)
}
wValue = reflect.New(wValue.Type().Elem()).Elem()
wValue.Set(mapCopy)
} else if mValue.Kind() == reflect.Slice {
sliceCopy := reflect.MakeSlice(mValue.Type(), mValue.Len(), mValue.Cap())
reflect.Copy(sliceCopy, mValue)
wValue = reflect.New(wValue.Type().Elem()).Elem()
wValue.Set(sliceCopy)
} else if wValue.Kind() == reflect.Ptr {
mPtr := reflect.New(mValue.Type())
mPtr.Elem().Set(mValue)
wValue.Set(mPtr)
} else {
wValue.Set(mValue)
}
}
}
}
6 changes: 6 additions & 0 deletions api/v1beta1/tempest_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ func (spec *TempestSpec) Default() {
if spec.TempestconfRun == (TempestconfRunSpec{}) {
spec.TempestconfRun.Create = true
}

if len(spec.Workflow) > 0 {
for key, _ := range spec.Workflow {
mergeSectionIntoWorkflow(spec, key)
}
}
}

func (r *Tempest) PrivilegedRequired() bool {
Expand Down
11 changes: 10 additions & 1 deletion api/v1beta1/tobiko_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,16 @@ var _ webhook.Defaulter = &Tobiko{}
func (r *Tobiko) Default() {
tobikolog.Info("default", "name", r.Name)

// TODO(user): fill in your defaulting logic.
r.Spec.Default()
}

// Default - set defaults for this Tobiko spec.
func (spec *TobikoSpec) Default() {
if len(spec.Workflow) > 0 {
for key, _ := range spec.Workflow {
mergeSectionIntoWorkflow(spec, key)
}
}
}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
Expand Down
54 changes: 13 additions & 41 deletions controllers/ansibletest_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func (r *AnsibleTestReconciler) Reconcile(ctx context.Context, req ctrl.Request)

workflowLength := len(instance.Spec.Workflow)
nextAction, nextWorkflowStep, err := r.NextAction(ctx, instance, workflowLength)
instance.Spec = getMergedCR(instance, nextWorkflowStep).(testv1beta1.AnsibleTestSpec)

switch nextAction {
case Failure:
Expand Down Expand Up @@ -217,29 +218,11 @@ func (r *AnsibleTestReconciler) Reconcile(ctx context.Context, req ctrl.Request)
envVars, workflowOverrideParams := r.PrepareAnsibleEnv(instance, nextWorkflowStep)
logsPVCName := r.GetPVCLogsName(instance, 0)
containerImage, err := r.GetContainerImage(ctx, workflowOverrideParams["ContainerImage"], instance)
privileged := r.OverwriteAnsibleWithWorkflow(instance.Spec, "Privileged", "pbool", nextWorkflowStep).(bool)
privileged := instance.Spec.Privileged
if err != nil {
return ctrl.Result{}, err
}

if nextWorkflowStep < len(instance.Spec.Workflow) {
if instance.Spec.Workflow[nextWorkflowStep].NodeSelector != nil {
instance.Spec.NodeSelector = *instance.Spec.Workflow[nextWorkflowStep].NodeSelector
}

if instance.Spec.Workflow[nextWorkflowStep].Tolerations != nil {
instance.Spec.Tolerations = *instance.Spec.Workflow[nextWorkflowStep].Tolerations
}

if instance.Spec.Workflow[nextWorkflowStep].SELinuxLevel != nil {
instance.Spec.SELinuxLevel = *instance.Spec.Workflow[nextWorkflowStep].SELinuxLevel
}

if instance.Spec.Workflow[nextWorkflowStep].Resources != nil {
instance.Spec.Resources = *instance.Spec.Workflow[nextWorkflowStep].Resources
}
}

// Service account, role, binding
rbacRules := GetCommonRbacRules(privileged)
rbacResult, err := common_rbac.ReconcileRbac(ctx, helper, instance, rbacRules)
Expand Down Expand Up @@ -350,41 +333,30 @@ func (r *Reconciler) OverwriteAnsibleWithWorkflow(
// This function prepares env variables for a single workflow step.
func (r *AnsibleTestReconciler) PrepareAnsibleEnv(
instance *testv1beta1.AnsibleTest,
step int,
workflowStepNum int,
) (map[string]env.Setter, map[string]string) {
// Prepare env vars
envVars := make(map[string]env.Setter)
workflowOverrideParams := make(map[string]string)

// volumes workflow override
workflowOverrideParams["WorkloadSSHKeySecretName"] = r.OverwriteAnsibleWithWorkflow(instance.Spec, "WorkloadSSHKeySecretName", "string", step).(string)
workflowOverrideParams["ComputesSSHKeySecretName"] = r.OverwriteAnsibleWithWorkflow(instance.Spec, "ComputesSSHKeySecretName", "string", step).(string)
workflowOverrideParams["ContainerImage"] = r.OverwriteAnsibleWithWorkflow(instance.Spec, "ContainerImage", "string", step).(string)
workflowOverrideParams["WorkloadSSHKeySecretName"] = instance.Spec.WorkloadSSHKeySecretName
workflowOverrideParams["ComputesSSHKeySecretName"] = instance.Spec.ComputesSSHKeySecretName
workflowOverrideParams["ContainerImage"] = instance.Spec.ContainerImage

// bool
debug := r.OverwriteAnsibleWithWorkflow(instance.Spec, "Debug", "pbool", step).(bool)
debug := instance.Spec.Debug
if debug {
envVars["POD_DEBUG"] = env.SetValue("true")
}

// strings
extraVars := r.OverwriteAnsibleWithWorkflow(instance.Spec, "AnsibleExtraVars", "string", step).(string)
envVars["POD_ANSIBLE_EXTRA_VARS"] = env.SetValue(extraVars)

extraVarsFile := r.OverwriteAnsibleWithWorkflow(instance.Spec, "AnsibleVarFiles", "string", step).(string)
envVars["POD_ANSIBLE_FILE_EXTRA_VARS"] = env.SetValue(extraVarsFile)

inventory := r.OverwriteAnsibleWithWorkflow(instance.Spec, "AnsibleInventory", "string", step).(string)
envVars["POD_ANSIBLE_INVENTORY"] = env.SetValue(inventory)

gitRepo := r.OverwriteAnsibleWithWorkflow(instance.Spec, "AnsibleGitRepo", "string", step).(string)
envVars["POD_ANSIBLE_GIT_REPO"] = env.SetValue(gitRepo)

playbookPath := r.OverwriteAnsibleWithWorkflow(instance.Spec, "AnsiblePlaybookPath", "string", step).(string)
envVars["POD_ANSIBLE_PLAYBOOK"] = env.SetValue(playbookPath)

ansibleCollections := r.OverwriteAnsibleWithWorkflow(instance.Spec, "AnsibleCollections", "string", step).(string)
envVars["POD_INSTALL_COLLECTIONS"] = env.SetValue(ansibleCollections)
envVars["POD_ANSIBLE_EXTRA_VARS"] = env.SetValue(instance.Spec.AnsibleExtraVars)
envVars["POD_ANSIBLE_FILE_EXTRA_VARS"] = env.SetValue(instance.Spec.AnsibleVarFiles)
envVars["POD_ANSIBLE_INVENTORY"] = env.SetValue(instance.Spec.AnsibleInventory)
envVars["POD_ANSIBLE_GIT_REPO"] = env.SetValue(instance.Spec.AnsibleGitRepo)
envVars["POD_ANSIBLE_PLAYBOOK"] = env.SetValue(instance.Spec.AnsiblePlaybookPath)
envVars["POD_INSTALL_COLLECTIONS"] = env.SetValue(instance.Spec.AnsibleCollections)

return envVars, workflowOverrideParams
}
72 changes: 72 additions & 0 deletions controllers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,3 +693,75 @@ func EnsureCloudsConfigMapExists(

return ctrl.Result{}, nil
}

// TODO remove repetitive code
func getMergedCR(instance interface{}, workflowStepNum int) interface{} {
switch typedInstance := instance.(type) {
case *v1beta1.Tempest:
if workflowStepNum < len(typedInstance.Spec.Workflow) {
workflow := typedInstance.Spec.Workflow[workflowStepNum]
mergeSections(&typedInstance.Spec, workflow)
}
return typedInstance.Spec
case *v1beta1.Tobiko:
if workflowStepNum < len(typedInstance.Spec.Workflow) {
workflow := typedInstance.Spec.Workflow[workflowStepNum]
mergeSections(&typedInstance.Spec, workflow)
}
return typedInstance.Spec
case *v1beta1.AnsibleTest:
if workflowStepNum < len(typedInstance.Spec.Workflow) {
workflow := typedInstance.Spec.Workflow[workflowStepNum]
mergeSections(&typedInstance.Spec, workflow)
}
return typedInstance.Spec
default:
return nil
}
}

func mergeSections(main interface{}, workflow interface{}) {
mReflect := reflect.ValueOf(main).Elem()
wReflect := reflect.ValueOf(workflow)

for i := 0; i < mReflect.NumField(); i++ {
name := mReflect.Type().Field(i).Name
mValue := mReflect.Field(i)
wValue := wReflect.FieldByName(name)

//fmt.Println("MERGE SECTIONS")
//fmt.Println("Name", name)
//fmt.Println("M value", mValue)
//fmt.Println("M Kind", mValue.Kind())
//fmt.Println("W value", wValue)
//fmt.Println("W Kind", wValue.Kind())

if mValue.Kind() == reflect.Struct {
switch name {
case "CommonOptions":
wValue := wReflect.FieldByName("WorkflowCommonParameters")
mergeSections(mValue.Addr().Interface(), wValue.Interface())
case "TempestRun", "TempestconfRun":
mergeSections(mValue.Addr().Interface(), wValue.Interface())
case "Resources":
if !wValue.IsNil() {
mValue.Set(wValue.Elem())
}
}
continue
}

if !wValue.IsValid() {
continue
}

if wValue.Kind() == reflect.Ptr && mValue.Kind() != reflect.Ptr {
if wValue.IsNil() {
continue
}
wValue = wValue.Elem()
}
mValue.Set(wValue)

}
}
Loading

0 comments on commit 031df6c

Please sign in to comment.