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 Jan 30, 2025
1 parent be35b07 commit 0ac4bba
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 104 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
106 changes: 106 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,104 @@ const (
"ensures that the copying of the logs to the PV is completed without any " +
"complications."
)

// merge non-workflow section into workflow
func mergeSectionIntoWorkflow(instance interface{}, workflowStepNum int) {
switch spec := instance.(type) {
case *TempestSpec:
fmt.Println("Instance is type *TempestSpec")

tRun := spec.TempestRun
wtRun := &spec.Workflow[workflowStepNum].TempestRun

tRunReflect := reflect.ValueOf(tRun)
wtRunReflect := reflect.ValueOf(wtRun).Elem()

setNonZeroValues(tRunReflect, wtRunReflect, false)

tcRun := spec.TempestconfRun
wtcRun := &spec.Workflow[workflowStepNum].TempestconfRun

tcRunReflect := reflect.ValueOf(tcRun)
wtcRunReflect := reflect.ValueOf(wtcRun).Elem()

setNonZeroValues(tcRunReflect, wtcRunReflect, false)
case *TobikoSpec:
fmt.Println("Instance is type *TobikoSpec")

workflow := &spec.Workflow[workflowStepNum]
setWorkflowSpec(*spec, workflow)
case *AnsibleTestSpec:
fmt.Println("Instance is type *AnsibleTestSpec")

workflow := &spec.Workflow[workflowStepNum]
setWorkflowSpec(*spec, workflow)
default:
fmt.Println("Error, instance of unknown type.")
}
}

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


// TODO combine with SetNonZeroValues
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)

if !wValue.IsValid() {
continue
}

if isEmpty(wValue) && !isEmpty(mValue){
if mValue.Kind() == reflect.Struct {
fmt.Println("TODO for Structures.")
} else if mValue.Kind() == reflect.String {
wValue.Set(mValue)
} else if mValue.Kind() == reflect.Bool || mValue.Kind() == reflect.Int {
mPtr := reflect.New(mValue.Type())
mPtr.Elem().Set(mValue)
wValue.Set(mPtr)
}
}
}
}

func setNonZeroValues(main reflect.Value, workflow reflect.Value, isStruct bool) {
for i := 0; i < main.NumField(); i++ {
name := main.Type().Field(i).Name
mValue := main.Field(i)
wValue := workflow.FieldByName(name)

if isEmpty(wValue) && !isEmpty(mValue) {
if mValue.Kind() == reflect.Struct {
setNonZeroValues(mValue, wValue, true)
} else {
if isStruct {
wValue.Set(mValue)
} else {
mPtr := reflect.New(mValue.Type())
mPtr.Elem().Set(mValue)
wValue.Set(mPtr)
}
}
}
}
}
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
36 changes: 14 additions & 22 deletions controllers/ansibletest_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ 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)
// TODO solution for commonOptions -> privileged := instance.Spec.Privileged
privileged := r.OverwriteAnsibleWithWorkflow(instance.Spec, "Privileged", "pbool", nextWorkflowStep).(bool)
if err != nil {
return ctrl.Result{}, err
Expand Down Expand Up @@ -350,41 +351,32 @@ 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) {
mCR := getMergedCR(instance, workflowStepNum).(testv1beta1.AnsibleTestSpec)

// 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"] = mCR.WorkloadSSHKeySecretName
workflowOverrideParams["ComputesSSHKeySecretName"] = mCR.ComputesSSHKeySecretName
workflowOverrideParams["ContainerImage"] = mCR.ContainerImage

// bool
debug := r.OverwriteAnsibleWithWorkflow(instance.Spec, "Debug", "pbool", step).(bool)
debug := mCR.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(mCR.AnsibleExtraVars)
envVars["POD_ANSIBLE_FILE_EXTRA_VARS"] = env.SetValue(mCR.AnsibleVarFiles)
envVars["POD_ANSIBLE_INVENTORY"] = env.SetValue(mCR.AnsibleInventory)
envVars["POD_ANSIBLE_GIT_REPO"] = env.SetValue(mCR.AnsibleGitRepo)
envVars["POD_ANSIBLE_PLAYBOOK"] = env.SetValue(mCR.AnsiblePlaybookPath)
envVars["POD_INSTALL_COLLECTIONS"] = env.SetValue(mCR.AnsibleCollections)

return envVars, workflowOverrideParams
}
59 changes: 59 additions & 0 deletions controllers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,3 +693,62 @@ 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) {
wtRun := typedInstance.Spec.Workflow[workflowStepNum].TempestRun
mergeSections(&typedInstance.Spec.TempestRun, wtRun)

wtcRun := typedInstance.Spec.Workflow[workflowStepNum].TempestconfRun
mergeSections(&typedInstance.Spec.TempestconfRun, wtcRun)
}
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{}) {
mReflected := reflect.ValueOf(main).Elem()
wReflected := reflect.ValueOf(workflow)

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

if !wValue.IsValid() {
continue
}

// TODO Temporary AnsibleTest fix
// Struct is TODO for CommonOpenstackConfig
// I would change option: Debug bool `json:"debug"` to pointer
if wValue.Kind() == reflect.Struct || wValue.Kind() == reflect.Bool {
continue
}

if wValue.Kind() == reflect.String {
if wValue.Len() != 0 {
mValue.Set(wValue)
}
} else if !wValue.IsNil() {
mValue.Set(wValue.Elem())
}
}
}
Loading

0 comments on commit 0ac4bba

Please sign in to comment.