fix: missing live resources without a health check should not affect application health (#6446)

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
This commit is contained in:
Alexander Matyushentsev
2021-06-10 13:29:22 -07:00
committed by GitHub
parent f533ed17f0
commit 2da0e81b05
12 changed files with 242 additions and 212 deletions

64
controller/health.go Normal file
View File

@@ -0,0 +1,64 @@
package controller
import (
"github.com/argoproj/gitops-engine/pkg/health"
hookutil "github.com/argoproj/gitops-engine/pkg/sync/hook"
"github.com/argoproj/gitops-engine/pkg/sync/ignore"
kubeutil "github.com/argoproj/gitops-engine/pkg/utils/kube"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/lua"
)
// setApplicationHealth updates the health statuses of all resources performed in the comparison
func setApplicationHealth(resources []managedResource, statuses []appv1.ResourceStatus, resourceOverrides map[string]appv1.ResourceOverride, app *appv1.Application) (*appv1.HealthStatus, error) {
var savedErr error
appHealth := appv1.HealthStatus{Status: health.HealthStatusHealthy}
for i, res := range resources {
if res.Target != nil && hookutil.Skip(res.Target) {
continue
}
if res.Live != nil && (hookutil.IsHook(res.Live) || ignore.Ignore(res.Live)) {
continue
}
var healthStatus *health.HealthStatus
var err error
healthOverrides := lua.ResourceHealthOverrides(resourceOverrides)
gvk := schema.GroupVersionKind{Group: res.Group, Version: res.Version, Kind: res.Kind}
if res.Live == nil {
healthStatus = &health.HealthStatus{Status: health.HealthStatusMissing}
} else {
// App the manages itself should not affect own health
if isSelfReferencedApp(app, kubeutil.GetObjectRef(res.Live)) {
continue
}
healthStatus, err = health.GetResourceHealth(res.Live, healthOverrides)
if err != nil && savedErr == nil {
savedErr = err
}
}
if healthStatus != nil {
resHealth := appv1.HealthStatus{Status: healthStatus.Status, Message: healthStatus.Message}
statuses[i].Health = &resHealth
// Is health status is missing but resource has not built-in/custom health check then it should not affect parent app health
if _, hasOverride := healthOverrides[lua.GetConfigMapKey(gvk)]; healthStatus.Status == health.HealthStatusMissing && !hasOverride && health.GetHealthCheckFunc(gvk) == nil {
continue
}
// Missing or Unknown health status of child Argo CD app should not affect parent
if res.Kind == application.ApplicationKind && res.Group == application.Group && (healthStatus.Status == health.HealthStatusMissing || healthStatus.Status == health.HealthStatusUnknown) {
continue
}
if health.IsWorse(appHealth.Status, healthStatus.Status) {
appHealth.Status = healthStatus.Status
}
}
}
return &appHealth, savedErr
}

161
controller/health_test.go Normal file
View File

@@ -0,0 +1,161 @@
package controller
import (
"io/ioutil"
"testing"
"github.com/argoproj/gitops-engine/pkg/health"
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/lua"
)
var app = &appv1.Application{}
func initStatuses(resources []managedResource) []appv1.ResourceStatus {
statuses := make([]appv1.ResourceStatus, len(resources))
for i := range resources {
statuses[i] = appv1.ResourceStatus{Group: resources[i].Group, Kind: resources[i].Kind, Version: resources[i].Version}
}
return statuses
}
func resourceFromFile(filePath string) unstructured.Unstructured {
yamlBytes, err := ioutil.ReadFile(filePath)
if err != nil {
panic(err)
}
var res unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &res)
if err != nil {
panic(err)
}
return res
}
func TestSetApplicationHealth(t *testing.T) {
failedJob := resourceFromFile("./testdata/job-failed.yaml")
runningPod := resourceFromFile("./testdata/pod-running-restart-always.yaml")
resources := []managedResource{{
Group: "", Version: "v1", Kind: "Pod", Live: &runningPod}, {
Group: "batch", Version: "v1", Kind: "Job", Live: &failedJob,
}}
resourceStatuses := initStatuses(resources)
healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{}, app)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusDegraded, healthStatus.Status)
// now mark the job as a hook and retry. it should ignore the hook and consider the app healthy
failedJob.SetAnnotations(map[string]string{synccommon.AnnotationKeyHook: "PreSync"})
healthStatus, err = setApplicationHealth(resources, resourceStatuses, nil, app)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusHealthy, healthStatus.Status)
}
func TestSetApplicationHealth_MissingResource(t *testing.T) {
pod := resourceFromFile("./testdata/pod-running-restart-always.yaml")
resources := []managedResource{{
Group: "", Version: "v1", Kind: "Pod", Target: &pod}, {}}
resourceStatuses := initStatuses(resources)
healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{}, app)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusMissing, healthStatus.Status)
}
func TestSetApplicationHealth_MissingResourceNoBuiltHealthCheck(t *testing.T) {
cm := resourceFromFile("./testdata/configmap.yaml")
resources := []managedResource{{
Group: "", Version: "v1", Kind: "ConfigMap", Target: &cm}}
resourceStatuses := initStatuses(resources)
t.Run("NoOverride", func(t *testing.T) {
healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{}, app)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusHealthy, healthStatus.Status)
assert.Equal(t, resourceStatuses[0].Health.Status, health.HealthStatusMissing)
})
t.Run("HasOverride", func(t *testing.T) {
healthStatus, err := setApplicationHealth(resources, resourceStatuses, lua.ResourceHealthOverrides{
lua.GetConfigMapKey(schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}): appv1.ResourceOverride{
HealthLua: "some health check",
},
}, app)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusMissing, healthStatus.Status)
})
}
func newAppLiveObj(status health.HealthStatusCode) *unstructured.Unstructured {
app := appv1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
TypeMeta: metav1.TypeMeta{
APIVersion: "argoproj.io/v1alpha1",
Kind: "Application",
},
Status: appv1.ApplicationStatus{
Health: appv1.HealthStatus{
Status: status,
},
},
}
return kube.MustToUnstructured(&app)
}
func TestChildAppHealth(t *testing.T) {
overrides := lua.ResourceHealthOverrides{
lua.GetConfigMapKey(appv1.ApplicationSchemaGroupVersionKind): appv1.ResourceOverride{
HealthLua: `
hs = {}
hs.status = "Progressing"
hs.message = ""
if obj.status ~= nil then
if obj.status.health ~= nil then
hs.status = obj.status.health.status
if obj.status.health.message ~= nil then
hs.message = obj.status.health.message
end
end
end
return hs`,
},
}
t.Run("ChildAppDegraded", func(t *testing.T) {
degradedApp := newAppLiveObj(health.HealthStatusDegraded)
resources := []managedResource{{
Group: application.Group, Version: "v1alpha1", Kind: application.ApplicationKind, Live: degradedApp}, {}}
resourceStatuses := initStatuses(resources)
healthStatus, err := setApplicationHealth(resources, resourceStatuses, overrides, app)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusDegraded, healthStatus.Status)
})
t.Run("ChildAppMissing", func(t *testing.T) {
degradedApp := newAppLiveObj(health.HealthStatusMissing)
resources := []managedResource{{
Group: application.Group, Version: "v1alpha1", Kind: application.ApplicationKind, Live: degradedApp}, {}}
resourceStatuses := initStatuses(resources)
healthStatus, err := setApplicationHealth(resources, resourceStatuses, overrides, app)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusHealthy, healthStatus.Status)
})
}

View File

@@ -33,7 +33,6 @@ import (
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/gpg"
argohealth "github.com/argoproj/argo-cd/v2/util/health"
"github.com/argoproj/argo-cd/v2/util/io"
"github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v2/util/stats"
@@ -59,20 +58,6 @@ type managedResource struct {
ResourceVersion string
}
func GetLiveObjsForApplicationHealth(resources []managedResource, statuses []appv1.ResourceStatus) ([]*appv1.ResourceStatus, []*unstructured.Unstructured) {
liveObjs := make([]*unstructured.Unstructured, 0)
resStatuses := make([]*appv1.ResourceStatus, 0)
for i, resource := range resources {
if resource.Target != nil && hookutil.Skip(resource.Target) {
continue
}
liveObjs = append(liveObjs, resource.Live)
resStatuses = append(resStatuses, &statuses[i])
}
return resStatuses, liveObjs
}
// AppStateManager defines methods which allow to compare application spec and actual application state.
type AppStateManager interface {
CompareAppState(app *v1alpha1.Application, project *appv1.AppProject, revision string, source v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localObjects []string) *comparisonResult
@@ -621,11 +606,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
}
ts.AddCheckpoint("sync_ms")
resSumForAppHealth, liveObjsForAppHealth := GetLiveObjsForApplicationHealth(managedResources, resourceSummaries)
healthStatus, err := argohealth.SetApplicationHealth(resSumForAppHealth, liveObjsForAppHealth, resourceOverrides, func(obj *unstructured.Unstructured) bool {
return !isSelfReferencedApp(app, kubeutil.GetObjectRef(obj))
})
healthStatus, err := setApplicationHealth(managedResources, resourceSummaries, resourceOverrides, app)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
}

6
controller/testdata/configmap.yaml vendored Normal file
View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: my-map
data:
foo: bar

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/alicebob/miniredis/v2 v2.14.2
github.com/argoproj/gitops-engine v0.3.1-0.20210608200535-ddc92c9bdbe9
github.com/argoproj/gitops-engine v0.3.1-0.20210610000233-6884d330a049
github.com/argoproj/pkg v0.9.1-0.20210512035321-be5ba22dca5b
github.com/bombsimon/logrusr v1.0.0
github.com/bradleyfalzon/ghinstallation v1.1.1

4
go.sum
View File

@@ -86,8 +86,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/argoproj/gitops-engine v0.3.1-0.20210608200535-ddc92c9bdbe9 h1:C4Im0i0XYjn5bY6DUJZt8gF2kcmXNF28DPgSIRZi2bo=
github.com/argoproj/gitops-engine v0.3.1-0.20210608200535-ddc92c9bdbe9/go.mod h1:EdFe8qIOqsmbyxRhtIydU4BUeyZ4VTsY6R3XVQhU9LA=
github.com/argoproj/gitops-engine v0.3.1-0.20210610000233-6884d330a049 h1:Swa2K+be8ZB1YeyCe5s2kR/b+gyawo5jcPRMwT3L5zY=
github.com/argoproj/gitops-engine v0.3.1-0.20210610000233-6884d330a049/go.mod h1:EdFe8qIOqsmbyxRhtIydU4BUeyZ4VTsY6R3XVQhU9LA=
github.com/argoproj/pkg v0.9.1-0.20210512035321-be5ba22dca5b h1:qtlM7ioAFP40LPN7A5ZqquVmAtv08LLSZTcCNYUQx8s=
github.com/argoproj/pkg v0.9.1-0.20210512035321-be5ba22dca5b/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=

View File

@@ -28,7 +28,7 @@ func TestFixingDegradedApp(t *testing.T) {
Then().
Expect(OperationPhaseIs(OperationFailed)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(HealthIs(health.HealthStatusMissing)).
Expect(HealthIs(health.HealthStatusDegraded)).
Expect(ResourceResultNumbering(1)).
Expect(ResourceSyncStatusIs("ConfigMap", "cm-1", SyncStatusCodeSynced)).
Expect(ResourceHealthIs("ConfigMap", "cm-1", health.HealthStatusDegraded)).

View File

@@ -1,59 +0,0 @@
package health
import (
"github.com/argoproj/gitops-engine/pkg/health"
hookutil "github.com/argoproj/gitops-engine/pkg/sync/hook"
"github.com/argoproj/gitops-engine/pkg/sync/ignore"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/lua"
)
// SetApplicationHealth updates the health statuses of all resources performed in the comparison
func SetApplicationHealth(resStatuses []*appv1.ResourceStatus, liveObjs []*unstructured.Unstructured, resourceOverrides map[string]appv1.ResourceOverride, filter func(obj *unstructured.Unstructured) bool) (*appv1.HealthStatus, error) {
var savedErr error
appHealth := appv1.HealthStatus{Status: health.HealthStatusHealthy}
for i, liveObj := range liveObjs {
var healthStatus *health.HealthStatus
var err error
if liveObj == nil {
healthStatus = &health.HealthStatus{Status: health.HealthStatusMissing}
} else {
if filter(liveObj) {
healthStatus, err = health.GetResourceHealth(liveObj, lua.ResourceHealthOverrides(resourceOverrides))
if err != nil && savedErr == nil {
savedErr = err
}
}
}
if healthStatus != nil {
resHealth := appv1.HealthStatus{Status: healthStatus.Status, Message: healthStatus.Message}
resStatuses[i].Health = &resHealth
ignore := ignoreLiveObjectHealth(liveObj, resHealth)
if !ignore && health.IsWorse(appHealth.Status, healthStatus.Status) {
appHealth.Status = healthStatus.Status
}
}
}
return &appHealth, savedErr
}
// ignoreLiveObjectHealth determines if we should not allow the live object to affect the overall
// health of the application (e.g. hooks, missing child applications)
func ignoreLiveObjectHealth(liveObj *unstructured.Unstructured, resHealth appv1.HealthStatus) bool {
if liveObj != nil {
// Don't allow resource hooks to affect health status
if hookutil.IsHook(liveObj) || ignore.Ignore(liveObj) {
return true
}
gvk := liveObj.GroupVersionKind()
if gvk.Group == "argoproj.io" && gvk.Kind == "Application" && (resHealth.Status == health.HealthStatusMissing || resHealth.Status == health.HealthStatusUnknown) {
// Covers the app-of-apps corner case where child app is deployed but that app itself
// has a status of 'Missing', which we don't want to cause the parent's health status
// to also be Missing
return true
}
}
return false
}

View File

@@ -1,121 +0,0 @@
package health
import (
"io/ioutil"
"testing"
"github.com/argoproj/gitops-engine/pkg/health"
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestSetApplicationHealth(t *testing.T) {
yamlBytes, err := ioutil.ReadFile("./testdata/job-failed.yaml")
assert.Nil(t, err)
var failedJob unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &failedJob)
assert.Nil(t, err)
yamlBytes, err = ioutil.ReadFile("./testdata/pod-running-restart-always.yaml")
assert.Nil(t, err)
var runningPod unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &runningPod)
assert.Nil(t, err)
resources := []*appv1.ResourceStatus{
&appv1.ResourceStatus{
Group: "",
Version: "v1",
Kind: "Pod",
Name: runningPod.GetName(),
},
&appv1.ResourceStatus{
Group: "batch",
Version: "v1",
Kind: "Job",
Name: failedJob.GetName(),
},
}
liveObjs := []*unstructured.Unstructured{
&runningPod,
&failedJob,
}
healthStatus, err := SetApplicationHealth(resources, liveObjs, nil, noFilter)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusDegraded, healthStatus.Status)
// now mark the job as a hook and retry. it should ignore the hook and consider the app healthy
failedJob.SetAnnotations(map[string]string{synccommon.AnnotationKeyHook: "PreSync"})
healthStatus, err = SetApplicationHealth(resources, liveObjs, nil, noFilter)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusHealthy, healthStatus.Status)
}
func TestAppOfAppsHealth(t *testing.T) {
newAppLiveObj := func(name string, status health.HealthStatusCode) (*unstructured.Unstructured, *appv1.ResourceStatus) {
app := appv1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
TypeMeta: metav1.TypeMeta{
APIVersion: "argoproj.io/v1alpha1",
Kind: "Application",
},
Status: appv1.ApplicationStatus{
Health: appv1.HealthStatus{
Status: status,
},
},
}
resStatus := &appv1.ResourceStatus{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
Name: name,
}
return kube.MustToUnstructured(&app), resStatus
}
missingApp, missingStatus := newAppLiveObj("foo", health.HealthStatusMissing)
unknownApp, unknownStatus := newAppLiveObj("fooz", health.HealthStatusUnknown)
healthyApp, healthyStatus := newAppLiveObj("bar", health.HealthStatusHealthy)
degradedApp, degradedStatus := newAppLiveObj("baz", health.HealthStatusDegraded)
// verify missing child app does not affect app health
{
missingAndHealthyStatuses := []*appv1.ResourceStatus{missingStatus, healthyStatus}
missingAndHealthyLiveObjects := []*unstructured.Unstructured{missingApp, healthyApp}
healthStatus, err := SetApplicationHealth(missingAndHealthyStatuses, missingAndHealthyLiveObjects, nil, noFilter)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusHealthy, healthStatus.Status)
}
// verify unknown child app does not affect app health
{
unknownAndHealthyStatuses := []*appv1.ResourceStatus{unknownStatus, healthyStatus}
unknownAndHealthyLiveObjects := []*unstructured.Unstructured{unknownApp, healthyApp}
healthStatus, err := SetApplicationHealth(unknownAndHealthyStatuses, unknownAndHealthyLiveObjects, nil, noFilter)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusHealthy, healthStatus.Status)
}
// verify degraded does affect
{
degradedAndHealthyStatuses := []*appv1.ResourceStatus{degradedStatus, healthyStatus}
degradedAndHealthyLiveObjects := []*unstructured.Unstructured{degradedApp, healthyApp}
healthStatus, err := SetApplicationHealth(degradedAndHealthyStatuses, degradedAndHealthyLiveObjects, nil, noFilter)
assert.NoError(t, err)
assert.Equal(t, health.HealthStatusHealthy, healthStatus.Status)
}
}
func noFilter(obj *unstructured.Unstructured) bool {
return true
}

View File

@@ -8,14 +8,14 @@ import (
"path/filepath"
"time"
"github.com/argoproj/argo-cd/v2/resource_customizations"
"github.com/argoproj/gitops-engine/pkg/health"
lua "github.com/yuin/gopher-lua"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
luajson "layeh.com/gopher-json"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/resource_customizations"
)
const (
@@ -122,7 +122,7 @@ func (vm VM) ExecuteHealthLua(obj *unstructured.Unstructured, script string) (*h
// GetHealthScript attempts to read lua script from config and then filesystem for that resource
func (vm VM) GetHealthScript(obj *unstructured.Unstructured) (string, bool, error) {
key := getConfigMapKey(obj)
key := GetConfigMapKey(obj.GroupVersionKind())
if script, ok := vm.ResourceOverrides[key]; ok && script.HealthLua != "" {
return script.HealthLua, script.UseOpenLibs, nil
}
@@ -281,7 +281,7 @@ func noAvailableActions(jsonBytes []byte) bool {
}
func (vm VM) GetResourceActionDiscovery(obj *unstructured.Unstructured) (string, error) {
key := getConfigMapKey(obj)
key := GetConfigMapKey(obj.GroupVersionKind())
override, ok := vm.ResourceOverrides[key]
if ok && override.Actions != "" {
actions, err := override.GetActions()
@@ -300,7 +300,7 @@ func (vm VM) GetResourceActionDiscovery(obj *unstructured.Unstructured) (string,
// GetResourceAction attempts to read lua script from config and then filesystem for that resource
func (vm VM) GetResourceAction(obj *unstructured.Unstructured, actionName string) (appv1.ResourceActionDefinition, error) {
key := getConfigMapKey(obj)
key := GetConfigMapKey(obj.GroupVersionKind())
override, ok := vm.ResourceOverrides[key]
if ok && override.Actions != "" {
actions, err := override.GetActions()
@@ -326,13 +326,11 @@ func (vm VM) GetResourceAction(obj *unstructured.Unstructured, actionName string
}, nil
}
func getConfigMapKey(obj *unstructured.Unstructured) string {
gvk := obj.GroupVersionKind()
func GetConfigMapKey(gvk schema.GroupVersionKind) string {
if gvk.Group == "" {
return gvk.Kind
}
return fmt.Sprintf("%s/%s", gvk.Group, gvk.Kind)
}
func (vm VM) getPredefinedLuaScripts(objKey string, scriptFile string) (string, error) {