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 94c4df1
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 235 deletions.
12 changes: 11 additions & 1 deletion api/v1beta1/ansibletest_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,17 @@ 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 i := range spec.Workflow {
mergeSectionIntoWorkflow(*spec, &spec.Workflow[i])
}

}
}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
Expand Down
78 changes: 78 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,76 @@ const (
"ensures that the copying of the logs to the PV is completed without any " +
"complications."
)

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
}

// merge non-workflow section into workflow
func mergeSectionIntoWorkflow(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")
mergeSectionIntoWorkflow(mValue.Interface(), wValue.Addr().Interface())
case "TempestRun", "TempestconfRun":
mergeSectionIntoWorkflow(mValue.Interface(), wValue.Addr().Interface())
case "Resources":
mergeSectionIntoWorkflow(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 i := range spec.Workflow {
mergeSectionIntoWorkflow(*spec, &spec.Workflow[i])
}
}
}

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 i := range spec.Workflow {
mergeSectionIntoWorkflow(*spec, &spec.Workflow[i])
}
}
}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
Expand Down
96 changes: 13 additions & 83 deletions controllers/ansibletest_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ import (
"strconv"
"time"

"reflect"

"github.com/go-logr/logr"
"github.com/openstack-k8s-operators/lib-common/modules/common"
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
"github.com/openstack-k8s-operators/lib-common/modules/common/job"
common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac"
"github.com/openstack-k8s-operators/test-operator/api/v1beta1"
testv1beta1 "github.com/openstack-k8s-operators/test-operator/api/v1beta1"
"github.com/openstack-k8s-operators/test-operator/pkg/ansibletest"
batchv1 "k8s.io/api/batch/v1"
Expand Down Expand Up @@ -137,6 +134,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 @@ -214,32 +212,14 @@ func (r *AnsibleTestReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// Create a new job
mountCerts := r.CheckSecretExists(ctx, instance, "combined-ca-bundle")
jobName := r.GetJobName(instance, nextWorkflowStep)
envVars, workflowOverrideParams := r.PrepareAnsibleEnv(instance, nextWorkflowStep)
envVars, workflowOverrideParams := r.PrepareAnsibleEnv(instance)
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 @@ -309,82 +289,32 @@ func (r *AnsibleTestReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r)
}

func (r *Reconciler) OverwriteAnsibleWithWorkflow(
instance v1beta1.AnsibleTestSpec,
sectionName string,
workflowValueType string,
workflowStepNum int,
) interface{} {
if len(instance.Workflow)-1 < workflowStepNum {
reflected := reflect.ValueOf(instance)
fieldValue := reflected.FieldByName(sectionName)
return fieldValue.Interface()
}

reflected := reflect.ValueOf(instance)
SpecValue := reflected.FieldByName(sectionName).Interface()

reflected = reflect.ValueOf(instance.Workflow[workflowStepNum])
WorkflowValue := reflected.FieldByName(sectionName).Interface()

if workflowValueType == "pbool" {
if val, ok := WorkflowValue.(*bool); ok && val != nil {
return *(WorkflowValue.(*bool))
}
return SpecValue.(bool)
} else if workflowValueType == "puint8" {
if val, ok := WorkflowValue.(*uint8); ok && val != nil {
return *(WorkflowValue.(*uint8))
}
return SpecValue
} else if workflowValueType == "string" {
if val, ok := WorkflowValue.(string); ok && val != "" {
return WorkflowValue
}
return SpecValue
}

return nil
}

// This function prepares env variables for a single workflow step.
func (r *AnsibleTestReconciler) PrepareAnsibleEnv(
instance *testv1beta1.AnsibleTest,
step 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
}
Loading

0 comments on commit 94c4df1

Please sign in to comment.