func moduleCheckStatus(c *v1beta1.CRDRelease, lastModuleMap map[string]internal.Module, crdUpdate bool) plugin.StatusGet {
	return func(name string, version string) (act plugin.Action, s string, e error) {
		var lastState *internal.ModuleState
		external := false
		lastApplied := lastModuleMap[name]
		for _, i := range c.Status.Modules {
			if i.Name == name {
				lastState = i.State
				if i.GetConditionStatus(internal.ModuleExternal) == apiextensions.ConditionTrue {
					external = true
					releaseLog.V(utils.Debug).Info("check external module", "crd release name", c.Name,
						"module", name)
				}
			}
		}
		releaseLog.V(utils.Debug).Info(fmt.Sprintf("last module %v", lastApplied),
			"crd release name", c.Name, "module", name)
		for _, i := range c.Spec.Modules {
			if i.Name == name {
				return i.CheckStatus(lastApplied, lastState, external, crdUpdate, moduleUpdateCondition(c))
			}
		}
		releaseLog.V(utils.Warn).Info("can not find module to check status, delete it", "name", c.Name,
			"version", c.Spec.Version, "module", name)
		return plugin.NeedUninstall, internal.ModuleDontCare, nil
	}
}
moduleCheckStatus Check the status of module, return the act and phase.
package controllers
import (
	"cloudnativeapp/clm/api/v1beta1"
	"cloudnativeapp/clm/internal"
	"cloudnativeapp/clm/pkg/check/condition"
	"cloudnativeapp/clm/pkg/dag"
	"cloudnativeapp/clm/pkg/plugin"
	"cloudnativeapp/clm/pkg/utils"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	"reflect"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
)
var releaseLog = ctrl.Log.WithName("crd release")
var MGRClient client.Client
var EventRecorder record.EventRecorder
const lastStatus = "clm.cloudnativeapp.io/last-configuration-status"
const MaxRecordLen = 32 * 1024
func CheckCRDRelease(c *v1beta1.CRDRelease) (bool, error) {
	if ok, err := checkDependencies(c); err != nil || !ok {
		return false, err
	}
	updateCRDReleaseCondition(c, internal.CRDReleasesDependenciesSatisfied, apiextensions.ConditionTrue)
	if ok, err := checkModules(c); err != nil || !ok {
		return false, err
	}
	c.Status.CurrentVersion = c.Spec.Version
	updateCRDReleaseCondition(c, internal.CRDReleaseModulesReady, apiextensions.ConditionTrue)
	updateCRDReleaseCondition(c, internal.CRDReleaseReady, apiextensions.ConditionTrue)
	return true, nil
}
func getActivedDependencies(c *v1beta1.CRDRelease) ([]plugin.Iplugin, error) {
	releaseLog.V(utils.Debug).Info("get dependencies union", "name", c.Name, "version", c.Spec.Version)
	dmap := make(map[string]internal.Dependency)
	for _, i := range c.Spec.Dependencies {
		if _, ok := dmap[i.Name]; ok {
			return nil, errors.New("repeated crd release dependency spec")
		} else {
			dmap[i.Name] = i
		}
	}
	var activeDeps []internal.DependencyStatus
	for _, i := range c.Status.Dependencies {
		if _, ok := dmap[i.Name]; ok {
			activeDeps = append(activeDeps, i)
		} else {
			releaseLog.V(utils.Info).Info("orphan dependency omitted", "name", c.Name,
				"version", c.Spec.Version, "dependency name", i.Name, "dependency version", i.Version)
		}
	}
	c.Status.Dependencies = activeDeps
	dependencies := make([]plugin.Iplugin, len(c.Spec.Dependencies))
	k := 0
	for _, j := range dmap {
		dependencies[k] = j
		k++
	}
	return dependencies, nil
}
func checkDependencies(c *v1beta1.CRDRelease) (bool, error) {
	releaseLog.V(utils.Debug).Info("check dependencies", "name", c.Name, "version", c.Spec.Version)
	updateCRDReleaseCondition(c, internal.CRDReleaseInitialized, apiextensions.ConditionTrue)
	dependencies, err := getActivedDependencies(c)
	if err != nil {
		return false, err
	}
	ready, err := plugin.CheckPlugins(dependencies, dependencySetStatus(c), dependencyCheckStatus())
	if !ready {
		releaseLog.V(utils.Warn).Info("not all dependencies ready", "name", c.Name, "version", c.Spec.Version)
		updateCRDReleaseCondition(c, internal.CRDReleasesDependenciesSatisfied, apiextensions.ConditionFalse)
	} else {
		releaseLog.V(utils.Info).Info("all dependencies ready", "name", c.Name, "version", c.Spec.Version)
		updateCRDReleaseCondition(c, internal.CRDReleasesDependenciesSatisfied, apiextensions.ConditionTrue)
	}
	return ready, err
}
func dependencySetStatus(c *v1beta1.CRDRelease) plugin.StatusSet {
	return func(name string, version string, phase interface{}) (ok bool, e error) {
		p, o := phase.(internal.DependencyStatus)
		if !o {
			return
		}
		if p.Phase != internal.DependencyRunning {
			EventRecorder.Eventf(c, corev1.EventTypeWarning, "Dependency:"+string(p.Phase), p.Reason)
		}
		releaseLog.V(utils.Debug).Info("set phase from check", "phase", p)
		if p.Phase == internal.DependencyAbnormal || p.Phase == internal.DependencyAbsentErr || p.Phase == internal.DependencyPullingErr {
			e = errors.New(utils.DependencyStateAbnormal)
		}
		for j, i := range c.Status.Dependencies {
			if i.Name == name {
				if i.Version != version {
					releaseLog.V(utils.Info).Info("upgrade module status", "previous", i.Version,
						"current", version)
				} else {
					releaseLog.V(utils.Debug).Info("update dependency phase", "target phase", p, "name", name)
				}
				c.Status.Dependencies[j] = p
				return p.Phase == internal.DependencyRunning, e
			}
		}
		c.Status.Dependencies = append(c.Status.Dependencies, p)
		releaseLog.V(utils.Debug).Info("add dependency status", "phase", p, "name", name)
		return p.Phase == internal.DependencyRunning, e
	}
}
func dependencyCheckStatus() plugin.StatusGet {
	return func(name string, version string) (act plugin.Action, phase string, e error) {
		release := &v1beta1.CRDRelease{}
		if err := MGRClient.Get(context.Background(), types.NamespacedName{Name: name, Namespace: ""}, release); err != nil {
			err = client.IgnoreNotFound(err)
			if err == nil {
				releaseLog.Info("crd release absent", "name", name, "version", version)
				return plugin.NeedInstall, string(internal.DependencyDontCare), nil
			} else {
				return plugin.NeedRecover, string(internal.DependencyDontCare), err
			}
		}
		if release.Status.Phase == internal.CRDReleaseRunning && !utils.VersionMatch(release.Status.CurrentVersion, version, "") {
			releaseLog.Info("crd release version mismatch", "name", name,
				"current", release.Spec.Version, "expected", version)
			return plugin.NeedUpgrade, string(internal.DependencyDontCare), nil
		}
		return plugin.NeedConvert, string(release.Status.Phase), nil
	}
}
func getModulesUnion(current []internal.Module, lastModuleMap map[string]internal.Module,
	imported []internal.Module) ([]plugin.Iplugin, error) {
	releaseLog.V(utils.Debug).Info("get modules union")
	dmap := make(map[string]internal.Module)
	for _, i := range current {
		if _, ok := dmap[i.Name]; ok {
			return nil, errors.New("repeated crd release module spec")
		} else {
			dmap[i.Name] = i
		}
	}
	for _, k := range imported {
		delete(dmap, k.Name)
	}
	if len(lastModuleMap) > 0 {
		for k, v := range lastModuleMap {
			if _, ok := dmap[v.Name]; !ok {
				dmap[k] = v
			}
		}
	}
	modules := make([]plugin.Iplugin, len(dmap))
	k := 0
	for _, j := range dmap {
		modules[k] = j
		k++
	}
	return modules, nil
}
func checkModules(c *v1beta1.CRDRelease) (bool, error) {
	releaseLog.Info("check modules", "name", c.Name, "version", c.Spec.Version)
	var modulesExclude []internal.Module
	if len(c.Status.Modules) == 0 {
		releaseLog.V(utils.Debug).Info("check new release modules", "name", c.Name, "version", c.Spec.Version)
		for _, m := range c.Spec.Modules {
			if ok, err := m.ConditionCheck(); err != nil {
				return false, err
			} else if !ok {
				if !reflect.DeepEqual(m.Conditions, condition.Condition{}) && m.Conditions.Strategy == condition.Import {
					releaseLog.V(utils.Debug).Info("condition check failed, take it as an imported module",
						"name", m.Name)
					moduleSetStatus(c)(m.Name, "", internal.GenerateImportedModuleStatus(m))
					EventRecorder.Eventf(c, corev1.EventTypeNormal, "Imported", "module %v imported", m.Name)
				} else {
					releaseLog.V(utils.Debug).Info("condition check failed, take it as an external module",
						"name", m.Name)
					moduleSetStatus(c)(m.Name, "", internal.GenerateExternalModuleStatus(m.Name))
					EventRecorder.Eventf(c, corev1.EventTypeNormal, "External", "module %v external", m.Name)
				}
				modulesExclude = append(modulesExclude, m)
			}
			releaseLog.V(utils.Debug).Info("condition check passed", "name", m.Name)
		}
	}
	mmap, update, err := getLastConfigModuleMap(c)
	if err != nil {
		return false, err
	}
	if update {
		releaseLog.V(utils.Info).Info("crd release updated", "crd release", c.Name, "version", c.Spec.Version)
	}
	modules, err := getModulesUnion(c.Spec.Modules, mmap, modulesExclude)
	if err != nil {
		return false, err
	}
	ready, err := plugin.CheckPlugins(modules, moduleSetStatus(c), moduleCheckStatus(c, mmap, update))
	if !ready {
		releaseLog.Info("not all modules ready", "name", c.Name, "version", c.Spec.Version)
		updateCRDReleaseCondition(c, internal.CRDReleaseModulesReady, apiextensions.ConditionFalse)
	} else {
		releaseLog.V(utils.Debug).Info("all modules ready", "name", c.Name, "version", c.Spec.Version)
		updateCRDReleaseCondition(c, internal.CRDReleaseModulesReady, apiextensions.ConditionTrue)
	}
	return ready, err
}
func getLastConfigModuleMap(c *v1beta1.CRDRelease) (map[string]internal.Module, bool, error) {
	mmap := make(map[string]internal.Module)
	var lastCRDRelease v1beta1.CRDRelease
	update := false
	str := c.Annotations[lastStatus]
	releaseLog.V(utils.Debug).Info(fmt.Sprintf("last release crd: %s", str), "crd release name",
		c.Name)
	if len(str) > 0 {
		if err := json.Unmarshal([]byte(str), &lastCRDRelease); err != nil {
			releaseLog.Error(err, "unmarshal last crd release failed")
			return nil, false, err
		}
		releaseLog.V(utils.Debug).Info(fmt.Sprintf("unmarshal last crd release %s", str))
		if c.Generation > lastCRDRelease.Generation+1 {
			update = true
		}
		for _, i := range lastCRDRelease.Spec.Modules {
			mmap[i.Name] = i
		}
	}
	return mmap, update, nil
}
func recordModuleState(c *v1beta1.CRDRelease, state *internal.ModuleState, name string) {
	if state.Abnormal != nil {
		EventRecorder.Eventf(c, corev1.EventTypeWarning, name+":Abnormal", "message:%v reason:%v",
			state.Abnormal.Message, state.Abnormal.Reason)
	}
	if state.Recovering != nil {
		EventRecorder.Eventf(c, corev1.EventTypeWarning, name+":Recovering", "message:%v reason:%v",
			state.Recovering.Message, state.Recovering.Reason)
	}
	if state.Terminated != nil {
		EventRecorder.Eventf(c, corev1.EventTypeNormal, name+":Terminated", "message:%v reason:%v",
			state.Terminated.Message, state.Terminated.Reason)
	}
	if state.Installing != nil {
		EventRecorder.Eventf(c, corev1.EventTypeNormal, name+":Installing", "message:%v reason:%v",
			state.Installing.Message, state.Installing.Reason)
	}
	if state.Running != nil {
		EventRecorder.Eventf(c, corev1.EventTypeNormal, name+":Running", "")
	}
}
func moduleSetStatus(c *v1beta1.CRDRelease) plugin.StatusSet {
	return func(name string, version string, status interface{}) (b bool, e error) {
		s, ok := status.(internal.ModuleStatus)
		if !ok {
			return
		}
		releaseLog.V(utils.Debug).Info(fmt.Sprintf("try to set module status %v", s),
			"crd release name", c.Name, "module", name)
		if s.State != nil && s.State.Abnormal != nil {
			e = errors.New(utils.ModuleStateAbnormal)
		}
		for i, j := range c.Status.Modules {
			if j.Name == name {
				releaseLog.V(utils.Debug).Info("update module status", "crd release name", name,
					"module", name)
				if s.State != nil && !internal.ModuleStateEqual(s.State, j.State) {
					recordModuleState(c, s.State, s.Name)
				}
				c.Status.Modules[i] = j.UpdateStatus(s)
				return s.Ready || (s.State != nil && s.State.Terminated != nil), e
			}
		}
		releaseLog.V(utils.Debug).Info("add module status", "crd release name", name, "module", name)
		recordModuleState(c, s.State, s.Name)
		c.Status.Modules = append(c.Status.Modules, status.(internal.ModuleStatus))
		return s.Ready || (s.State != nil && s.State.Terminated != nil), e
	}
}