Skip to content

Commit

Permalink
return to hiding hooks in the diff, render seperately
Browse files Browse the repository at this point in the history
  • Loading branch information
djeebus committed Apr 5, 2024
1 parent 14e5941 commit b3c7952
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 1 deletion.
9 changes: 9 additions & 0 deletions cmd/processors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/zapier/kubechecks/pkg/checks"
"github.com/zapier/kubechecks/pkg/checks/diff"
"github.com/zapier/kubechecks/pkg/checks/hooks"
"github.com/zapier/kubechecks/pkg/checks/kubeconform"
"github.com/zapier/kubechecks/pkg/checks/preupgrade"
"github.com/zapier/kubechecks/pkg/checks/rego"
Expand All @@ -19,6 +20,14 @@ func getProcessors(ctr container.Container) ([]checks.ProcessorEntry, error) {
Processor: diff.Check,
})

if ctr.Config.EnableHooksRenderer {
procs = append(procs, checks.ProcessorEntry{
Name: "render hooks",
Processor: hooks.Check,
WorstState: ctr.Config.WorstHooksState,
})
}

if ctr.Config.EnableKubeConform {
procs = append(procs, checks.ProcessorEntry{
Name: "validating app against schema",
Expand Down
4 changes: 4 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ func init() {
int64Flag(flags, "max-concurrenct-checks", "Number of concurrent checks to run.",
newInt64Opts().
withDefault(32))
boolFlag(flags, "enable-hook-renderer", "Render hooks.", newBoolOpts().withDefault(true))
stringFlag(flags, "worst-hooks-state", "The worst state that can be returned from the hooks renderer.",
newStringOpts().
withDefault("panic"))

panicIfError(viper.BindPFlags(flags))
setupLogOutput()
Expand Down
10 changes: 9 additions & 1 deletion pkg/checks/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/argoproj/argo-cd/v2/util/argo"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/gitops-engine/pkg/diff"
"github.com/argoproj/gitops-engine/pkg/sync/hook"
"github.com/argoproj/gitops-engine/pkg/sync/ignore"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/ghodss/yaml"
"github.com/go-logr/zerologr"
Expand Down Expand Up @@ -97,6 +99,10 @@ func Check(ctx context.Context, request checks.Request) (msg.Result, error) {
resourceId := fmt.Sprintf("%s/%s %s/%s", item.key.Group, item.key.Kind, item.key.Namespace, item.key.Name)
log.Trace().Str("resource", resourceId).Msg("diffing object")

if item.target != nil && hook.IsHook(item.target) || item.live != nil && hook.IsHook(item.live) {
continue
}

diffRes, err := generateDiff(ctx, request, argoSettings, item)
if err != nil {
return msg.Result{}, err
Expand Down Expand Up @@ -283,7 +289,9 @@ func groupObjsByKey(localObs []*unstructured.Unstructured, liveObjs []*unstructu
objByKey := make(map[kube.ResourceKey]*unstructured.Unstructured)
for i := range localObs {
obj := localObs[i]
objByKey[kube.GetResourceKey(obj)] = obj
if !(hook.IsHook(obj) || ignore.Ignore(obj)) {
objByKey[kube.GetResourceKey(obj)] = obj
}
}
return objByKey, nil
}
Expand Down
136 changes: 136 additions & 0 deletions pkg/checks/hooks/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package hooks

import (
"context"
"fmt"
"slices"
"strconv"
"strings"

"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/argoproj/gitops-engine/pkg/sync/hook"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/zapier/kubechecks/pkg"
"github.com/zapier/kubechecks/pkg/checks"
"github.com/zapier/kubechecks/pkg/msg"
)

var tracer = otel.Tracer("pkg/checks/hooks")

const triple = "```"

func Check(ctx context.Context, request checks.Request) (msg.Result, error) {
ctx, span := tracer.Start(ctx, "Check")
defer span.End()

grouped := make(groupedSyncWaves)

for _, manifest := range request.JsonManifests {
obj, err := v1alpha1.UnmarshalToUnstructured(manifest)
if err != nil {
return msg.Result{}, errors.Wrap(err, "failed to parse manifest")
}

waves, err := phasesAndWaves(obj)
if err != nil {
return msg.Result{}, errors.Wrap(err, "failed to get phases and waves")
}
for _, hookInfo := range waves {
grouped.addResource(hookInfo.hookType, hookInfo.hookWave, obj)
}
}

var phaseNames []common.HookType
var phaseDetails []string
for _, pw := range grouped.getSortedPhasesAndWaves() {
if !slices.Contains(phaseNames, pw.phase) {
phaseNames = append(phaseNames, pw.phase)
}

var renderedResources []string
for _, r := range pw.resources {
data, err := yaml.Marshal(r.Object)
if err != nil {
return msg.Result{}, errors.Wrap(err, "failed to unmarshal yaml")
}

renderedResource := fmt.Sprintf(
"===== %s/%s %s/%s =====\n\n%s\n",
r.GetAPIVersion(), r.GetKind(),
r.GetNamespace(), r.GetName(),
string(data),
)
renderedResources = append(renderedResources, renderedResource)
}

var countFmt string
resourceCount := len(renderedResources)
switch resourceCount {
case 0:
continue
case 1:
countFmt = "%d resource"
default:
countFmt = "%d resources"
}

sectionName := fmt.Sprintf("%s phase, wave %d (%s)", pw.phase, pw.wave, countFmt)
sectionName = fmt.Sprintf(sectionName, len(renderedResources))

phaseDetail := fmt.Sprintf(`<details>
<summary>%s</summary>
`+triple+`diff
%s
`+triple+`
</details>`, sectionName, strings.Join(renderedResources, "\n---\n"))

phaseDetails = append(phaseDetails, phaseDetail)
}

return msg.Result{
State: pkg.StateNone,
Summary: fmt.Sprintf("<b>Sync Phases: %s</b>", strings.Join(toStringSlice(phaseNames), ", ")),
Details: strings.Join(phaseDetails, "\n\n"),
NoChangesDetected: false,
}, nil
}

func toStringSlice(hookTypes []common.HookType) []string {
result := make([]string, len(hookTypes))
for idx := range hookTypes {
result[idx] = string(hookTypes[idx])
}
return result
}

type hookInfo struct {
hookType common.HookType
hookWave waveNum
}

func phasesAndWaves(obj *unstructured.Unstructured) ([]hookInfo, error) {
var (
syncWave int64
err error
hookInfos []hookInfo
)

if syncWaveStr, ok := obj.GetAnnotations()[common.AnnotationSyncWave]; ok {
if syncWave, err = strconv.ParseInt(syncWaveStr, 10, waveNumBits); err != nil {
return nil, errors.Wrapf(err, "failed to parse sync wave %s", syncWaveStr)
}
}

for _, hookType := range hook.Types(obj) {
hookInfos = append(hookInfos, hookInfo{hookType: hookType, hookWave: waveNum(syncWave)})
}

return hookInfos, nil
}
134 changes: 134 additions & 0 deletions pkg/checks/hooks/check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package hooks

import (
"context"
"encoding/json"
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

"github.com/zapier/kubechecks/pkg"
"github.com/zapier/kubechecks/pkg/checks"
)

type data map[string]any

func toJson(obj data) string {
data, err := json.Marshal(obj)
if err != nil {
panic(err)
}

return string(data)

}

func toYaml(obj data) string {
var buf strings.Builder
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)

err := enc.Encode(obj)
if err != nil {
panic(err)
}

text := buf.String()
return text
}

func TestCheck(t *testing.T) {
ctx := context.Background()

preSyncHookAndDefaultSyncWave := data{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": data{
"name": "preSyncHookAndDefaultSyncWave",
"namespace": "some-namespace",
"annotations": data{
"argocd.argoproj.io/hook": "PreSync",
},
},
}

preSyncHookAndNonDefaultSyncWave := data{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": data{
"name": "preSyncHookAndNonDefaultSyncWave",
"namespace": "some-namespace",
"annotations": data{
"argocd.argoproj.io/hook": "PreSync",
"argocd.argoproj.io/sync-wave": "5",
},
},
}

postSyncHookAndNonDefaultSyncWave := data{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": data{
"name": "postSyncHookAndNonDefaultSyncWave",
"namespace": "some-namespace",
"annotations": data{
"argocd.argoproj.io/hook": "PostSync",
"argocd.argoproj.io/sync-wave": "5",
},
},
}

req := checks.Request{
JsonManifests: []string{
toJson(preSyncHookAndDefaultSyncWave),
toJson(preSyncHookAndNonDefaultSyncWave),
toJson(postSyncHookAndNonDefaultSyncWave),
},
}

triple := "```"

res, err := Check(ctx, req)
require.NoError(t, err)
assert.Equal(t, pkg.StateNone, res.State)
assert.Equal(t, "<b>Sync Phases: PreSync, PostSync</b>", res.Summary)
assert.Equal(t, fmt.Sprintf(`<details>
<summary>PreSync phase, wave 0 (1 resource)</summary>
`+triple+`diff
===== v1/ConfigMap some-namespace/preSyncHookAndDefaultSyncWave =====
%s
`+triple+`
</details>
<details>
<summary>PreSync phase, wave 5 (1 resource)</summary>
`+triple+`diff
===== v1/ConfigMap some-namespace/preSyncHookAndNonDefaultSyncWave =====
%s
`+triple+`
</details>
<details>
<summary>PostSync phase, wave 5 (1 resource)</summary>
`+triple+`diff
===== v1/ConfigMap some-namespace/postSyncHookAndNonDefaultSyncWave =====
%s
`+triple+`
</details>`, toYaml(preSyncHookAndDefaultSyncWave), toYaml(preSyncHookAndNonDefaultSyncWave), toYaml(postSyncHookAndNonDefaultSyncWave)), res.Details)
}
Loading

0 comments on commit b3c7952

Please sign in to comment.