@@ -2,14 +2,18 @@ package spinnakervalidating
22
33import (
44 "context"
5+ "crypto/md5"
6+ "encoding/hex"
57 "encoding/json"
68 "fmt"
79 "github.com/armory/spinnaker-operator/pkg/apis/spinnaker/interfaces"
810 "github.com/armory/spinnaker-operator/pkg/controller/webhook"
911 "github.com/armory/spinnaker-operator/pkg/halyard"
1012 "github.com/armory/spinnaker-operator/pkg/secrets"
1113 "github.com/armory/spinnaker-operator/pkg/validate"
14+ "gomodules.xyz/jsonpatch/v2"
1215 "k8s.io/api/admission/v1beta1"
16+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1317 "k8s.io/apimachinery/pkg/runtime"
1418 "k8s.io/apimachinery/pkg/types"
1519 "k8s.io/client-go/rest"
@@ -20,6 +24,12 @@ import (
2024 "sigs.k8s.io/controller-runtime/pkg/runtime/inject"
2125 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
2226 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
27+ "time"
28+ )
29+
30+ const (
31+ ValidationConfigHashKey = "validation"
32+ DefaultValidationFreqSeconds = 10
2333)
2434
2535// +kubebuilder:webhook:path=/validate-v1-spinnakerservice,mutating=false,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=vpod.kb.io
@@ -65,6 +75,13 @@ func (v *spinnakerValidatingController) Handle(ctx context.Context, req admissio
6575 return admission .ValidationResponse (true , "" )
6676 }
6777
78+ hc := svc .GetStatus ().GetHash (ValidationConfigHashKey )
79+ if hc != nil {
80+ if ! v .NeedsValidation (hc .LastUpdatedAt ) {
81+ return admission .Allowed ("" )
82+ }
83+ }
84+
6885 opts := validate.Options {
6986 Ctx : secrets .NewContext (ctx , v .restConfig , req .Namespace ),
7087 Client : v .client ,
@@ -86,6 +103,7 @@ func (v *spinnakerValidatingController) Handle(ctx context.Context, req admissio
86103 // Update the status with any admission status change, only if there's already an existing SpinnakerService
87104 if req .AdmissionRequest .Operation == v1beta1 .Update {
88105 if len (validationResult .StatusPatches ) > 0 {
106+ validationResult .StatusPatches = append (validationResult .StatusPatches , v .addLastValidation (svc ))
89107 log .Info (fmt .Sprintf ("patching SpinnakerService status with %v" , validationResult .StatusPatches ), "metadata.name" , svc .GetName ())
90108 if err := v .client .Status ().Patch (ctx , svc , & precomputedPatch {validationResult }); err != nil {
91109 return admission .Errored (http .StatusInternalServerError , err )
@@ -96,6 +114,31 @@ func (v *spinnakerValidatingController) Handle(ctx context.Context, req admissio
96114 return admission .ValidationResponse (true , "" )
97115}
98116
117+ func (v * spinnakerValidatingController ) NeedsValidation (lastValid metav1.Time ) bool {
118+ if lastValid .IsZero () {
119+ return true
120+ }
121+ n := lastValid .Time .Add (time .Duration (DefaultValidationFreqSeconds ) * time .Second )
122+ return time .Now ().After (n )
123+ }
124+
125+ func (v * spinnakerValidatingController ) getHash (config interface {}) (string , error ) {
126+ data , err := json .Marshal (config )
127+ if err != nil {
128+ return "" , err
129+ }
130+ m := md5 .Sum (data )
131+ return hex .EncodeToString (m [:]), nil
132+ }
133+
134+ func (v * spinnakerValidatingController ) addLastValidation (svc interfaces.SpinnakerService ) jsonpatch.JsonPatchOperation {
135+ hash , _ := v .getHash (svc .GetStatus ())
136+ return jsonpatch .NewPatch ("replace" , fmt .Sprintf ("/status/lastDeployed/%s" , ValidationConfigHashKey ), interfaces.HashStatus {
137+ Hash : hash ,
138+ LastUpdatedAt : metav1 .NewTime (time .Now ()),
139+ })
140+ }
141+
99142// InjectClient injects the client.
100143func (v * spinnakerValidatingController ) InjectClient (c client.Client ) error {
101144 v .client = c
0 commit comments