@@ -2,21 +2,21 @@ package main
22
33import (
44 "context"
5- "crypto/md5"
65 "crypto/sha256"
76 "crypto/sha512"
87 "encoding/hex"
98 "encoding/json"
109 "hash"
1110 "strings"
1211
12+ "github.com/upbound/function-approve/input/v1beta1"
13+
1314 "github.com/crossplane/crossplane-runtime/pkg/errors"
1415 "github.com/crossplane/crossplane-runtime/pkg/logging"
1516 fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
1617 "github.com/crossplane/function-sdk-go/request"
1718 "github.com/crossplane/function-sdk-go/resource"
1819 "github.com/crossplane/function-sdk-go/response"
19- "github.com/upbound/function-approve/input/v1beta1"
2020)
2121
2222// Function implements the manual approval workflow function.
@@ -27,95 +27,141 @@ type Function struct {
2727}
2828
2929// RunFunction runs the Function.
30- func (f * Function ) RunFunction (ctx context.Context , req * fnv1.RunFunctionRequest ) (* fnv1.RunFunctionResponse , error ) {
30+ func (f * Function ) RunFunction (_ context.Context , req * fnv1.RunFunctionRequest ) (* fnv1.RunFunctionResponse , error ) {
3131 f .log .Info ("Running function" , "tag" , req .GetMeta ().GetTag ())
3232
3333 rsp := response .To (req , response .DefaultTTL )
3434
35+ // Parse input and set up initial state
36+ in , err := f .initializeFunction (req , rsp )
37+ if err != nil {
38+ return rsp , nil //nolint:nilerr // errors are handled in rsp
39+ }
40+
41+ // Process hashing logic and get approval status
42+ newHash , oldHash , approved , err := f .processHashingAndApproval (req , in , rsp )
43+ if err != nil {
44+ return rsp , nil //nolint:nilerr // errors are handled in rsp
45+ }
46+
47+ // Check if changes need approval
48+ if f .needsApproval (approved , oldHash , newHash ) {
49+ err := f .handleUnapprovedChanges (req , in , rsp , oldHash , newHash )
50+ if err != nil {
51+ return rsp , nil //nolint:nilerr // errors are handled in rsp
52+ }
53+ return rsp , nil
54+ }
55+
56+ // Handle approved changes
57+ err = f .handleApprovedChanges (req , in , rsp , newHash )
58+ if err != nil {
59+ return rsp , nil //nolint:nilerr // errors are handled in rsp
60+ }
61+
62+ return rsp , nil
63+ }
64+
65+ // initializeFunction parses input and initializes the function
66+ func (f * Function ) initializeFunction (req * fnv1.RunFunctionRequest , rsp * fnv1.RunFunctionResponse ) (* v1beta1.Input , error ) {
3567 // Parse input and get defaults
3668 in , err := f .parseInput (req , rsp )
3769 if err != nil {
38- return rsp , nil //nolint:nilerr // errors are handled in rsp
70+ return nil , err
3971 }
4072
4173 // Initialize response with desired XR and preserve context
4274 if err := f .initializeResponse (req , rsp ); err != nil {
43- return rsp , nil //nolint:nilerr // errors are handled in rsp
75+ return nil , err
4476 }
4577
78+ return in , nil
79+ }
80+
81+ // processHashingAndApproval handles hash computation and approval checks
82+ func (f * Function ) processHashingAndApproval (req * fnv1.RunFunctionRequest , in * v1beta1.Input , rsp * fnv1.RunFunctionResponse ) (newHash , oldHash string , approved bool , err error ) {
4683 // Extract data to hash
4784 dataToHash , err := f .extractDataToHash (req , in , rsp )
4885 if err != nil {
49- return rsp , nil //nolint:nilerr // errors are handled in rsp
86+ return "" , "" , false , err
5087 }
5188
5289 // Calculate hash
53- newHash : = f .calculateHash (dataToHash , in )
90+ newHash = f .calculateHash (dataToHash , in )
5491
5592 // Get old hash from status
56- oldHash , err : = f .getOldHash (req , in , rsp )
93+ oldHash , err = f .getOldHash (req , in , rsp )
5794 if err != nil {
58- return rsp , nil //nolint:nilerr // errors are handled in rsp
95+ return "" , "" , false , err
5996 }
6097
6198 // Save new hash to status
6299 if err := f .saveNewHash (req , in , newHash , rsp ); err != nil {
63- return rsp , nil //nolint:nilerr // errors are handled in rsp
100+ return "" , "" , false , err
64101 }
65102
66103 // Check approval status
67- approved , err : = f .checkApprovalStatus (req , in , rsp )
104+ approved , err = f .checkApprovalStatus (req , in , rsp )
68105 if err != nil {
69- return rsp , nil //nolint:nilerr // errors are handled in rsp
106+ return "" , "" , false , err
70107 }
71108
72- // Reconciliation pause logic
73- if ! approved || (oldHash != "" && oldHash != newHash ) {
74- // Changes detected and not approved, pause reconciliation
75- if err := f .pauseReconciliation (req , in , rsp ); err != nil {
76- return rsp , nil //nolint:nilerr // errors are handled in rsp
77- }
109+ return newHash , oldHash , approved , nil
110+ }
78111
79- // Set condition to show approval is needed
80- msg := "Changes detected. Approval required."
81- if in .ApprovalMessage != nil {
82- msg = * in .ApprovalMessage
83- }
112+ // needsApproval determines if the changes require approval
113+ func (f * Function ) needsApproval (approved bool , oldHash , newHash string ) bool {
114+ return ! approved || (oldHash != "" && oldHash != newHash )
115+ }
84116
85- condition := response .ConditionFalse (rsp , "ApprovalRequired" , "WaitingForApproval" ).
86- WithMessage (msg ).
87- TargetCompositeAndClaim ()
117+ // handleUnapprovedChanges processes the case where changes need approval
118+ func (f * Function ) handleUnapprovedChanges (req * fnv1.RunFunctionRequest , in * v1beta1.Input , rsp * fnv1.RunFunctionResponse , oldHash , newHash string ) error {
119+ // Changes detected and not approved, pause reconciliation
120+ if err := f .pauseReconciliation (req , in , rsp ); err != nil {
121+ return err
122+ }
88123
89- if in .DetailedCondition != nil && * in .DetailedCondition {
90- // Add detailed information about what changed and what needs approval
91- detailedMsg := msg + "\n Current hash: " + newHash + "\n " +
92- "Approved hash: " + oldHash + "\n " +
93- "Approve this change by setting " + * in .ApprovalField + " to true"
94- condition = condition .WithMessage (detailedMsg )
95- }
124+ // Set condition to show approval is needed
125+ msg := "Changes detected. Approval required."
126+ if in .ApprovalMessage != nil {
127+ msg = * in .ApprovalMessage
128+ }
96129
97- f .log .Debug ("Pausing reconciliation asking for approval" , "message" , msg )
98- // Return early, pausing reconciliation
99- return rsp , nil
130+ condition := response .ConditionFalse (rsp , "ApprovalRequired" , "WaitingForApproval" ).
131+ WithMessage (msg ).
132+ TargetCompositeAndClaim ()
133+
134+ if in .DetailedCondition != nil && * in .DetailedCondition {
135+ // Add detailed information about what changed and what needs approval
136+ detailedMsg := msg + "\n Current hash: " + newHash + "\n " +
137+ "Approved hash: " + oldHash + "\n " +
138+ "Approve this change by setting " + * in .ApprovalField + " to true"
139+ _ = condition .WithMessage (detailedMsg )
100140 }
101141
142+ f .log .Debug ("Pausing reconciliation asking for approval" , "message" , msg )
143+ return nil
144+ }
145+
146+ // handleApprovedChanges processes the case where changes are approved
147+ func (f * Function ) handleApprovedChanges (req * fnv1.RunFunctionRequest , in * v1beta1.Input , rsp * fnv1.RunFunctionResponse , newHash string ) error {
102148 // If we got here, the changes are approved or there are no changes
103149 // Update the old hash with the new hash
104150 if err := f .saveOldHash (req , in , newHash , rsp ); err != nil {
105- return rsp , nil //nolint:nilerr // errors are handled in rsp
151+ return err
106152 }
107153
108154 // Remove pause annotation if it exists
109155 if err := f .resumeReconciliation (req , in , rsp ); err != nil {
110- return rsp , nil //nolint:nilerr // errors are handled in rsp
156+ return err
111157 }
112158
113159 // Set success condition
114160 response .ConditionTrue (rsp , "FunctionSuccess" , "Success" ).
115161 WithMessage ("Approved successfully" ).
116162 TargetCompositeAndClaim ()
117163
118- return rsp , nil
164+ return nil
119165}
120166
121167// parseInput parses the function input and sets defaults.
@@ -411,8 +457,6 @@ func (f *Function) calculateHash(data interface{}, in *v1beta1.Input) string {
411457 // Choose hash algorithm
412458 var h hash.Hash
413459 switch * in .HashAlgorithm {
414- case "md5" :
415- h = md5 .New ()
416460 case "sha512" :
417461 h = sha512 .New ()
418462 default :
@@ -434,10 +478,7 @@ func (f *Function) getOldHash(req *fnv1.RunFunctionRequest, in *v1beta1.Input, r
434478 }
435479
436480 // Remove status. prefix if present
437- hashField := * in .OldHashField
438- if strings .HasPrefix (hashField , "status." ) {
439- hashField = strings .TrimPrefix (hashField , "status." )
440- }
481+ hashField := strings .TrimPrefix (* in .OldHashField , "status." )
441482
442483 // Get the old hash from status
443484 value , exists , err := GetNestedValue (xrStatus , hashField )
@@ -469,10 +510,7 @@ func (f *Function) saveNewHash(req *fnv1.RunFunctionRequest, in *v1beta1.Input,
469510 }
470511
471512 // Remove status. prefix if present
472- hashField := * in .NewHashField
473- if strings .HasPrefix (hashField , "status." ) {
474- hashField = strings .TrimPrefix (hashField , "status." )
475- }
513+ hashField := strings .TrimPrefix (* in .NewHashField , "status." )
476514
477515 // Set the new hash in status
478516 if err := SetNestedValue (xrStatus , hashField , hash ); err != nil {
@@ -504,10 +542,7 @@ func (f *Function) saveOldHash(req *fnv1.RunFunctionRequest, in *v1beta1.Input,
504542 }
505543
506544 // Remove status. prefix if present
507- hashField := * in .OldHashField
508- if strings .HasPrefix (hashField , "status." ) {
509- hashField = strings .TrimPrefix (hashField , "status." )
510- }
545+ hashField := strings .TrimPrefix (* in .OldHashField , "status." )
511546
512547 // Set the old hash in status
513548 if err := SetNestedValue (xrStatus , hashField , hash ); err != nil {
@@ -539,10 +574,7 @@ func (f *Function) checkApprovalStatus(req *fnv1.RunFunctionRequest, in *v1beta1
539574 }
540575
541576 // Remove status. prefix if present
542- approvalField := * in .ApprovalField
543- if strings .HasPrefix (approvalField , "status." ) {
544- approvalField = strings .TrimPrefix (approvalField , "status." )
545- }
577+ approvalField := strings .TrimPrefix (* in .ApprovalField , "status." )
546578
547579 // Get the approval status
548580 value , exists , err := GetNestedValue (xrStatus , approvalField )
@@ -573,10 +605,10 @@ func (f *Function) pauseReconciliation(req *fnv1.RunFunctionRequest, in *v1beta1
573605 response .ConditionFalse (rsp , "Synced" , "ReconciliationPaused" ).
574606 WithMessage ("Resource synchronization paused due to pending approval" ).
575607 TargetCompositeAndClaim ()
576-
608+
577609 return nil
578610 }
579-
611+
580612 // Otherwise use the pause annotation as before
581613 _ , dxr , err := f .getXRAndStatus (req )
582614 if err != nil {
@@ -611,10 +643,10 @@ func (f *Function) resumeReconciliation(req *fnv1.RunFunctionRequest, in *v1beta
611643 response .ConditionTrue (rsp , "Synced" , "ReconciliationResumed" ).
612644 WithMessage ("Resource synchronization resumed after approval" ).
613645 TargetCompositeAndClaim ()
614-
646+
615647 return nil
616648 }
617-
649+
618650 // Otherwise remove the pause annotation as before
619651 _ , dxr , err := f .getXRAndStatus (req )
620652 if err != nil {
0 commit comments