/
stage_helpers.go
202 lines (186 loc) · 6.29 KB
/
stage_helpers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package v1alpha1
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/akuity/kargo/internal/api/user"
)
// VerificationRequest is a request payload with an optional actor field which
// can be used to annotate a Stage using the AnnotationKeyReverify or
// AnnotationKeyAbort annotations.
//
// The ID field is used to specify the VerificationInfo to be re-verified or
// aborted. If the ID is empty, the request is considered invalid. The Actor
// field is optional and can be used to track the user who initiated the
// re-verification or abort request. The ControlPlane field is optional and
// indicates if the request was initiated by a control plane.
//
// +protobuf=false
// +k8s:deepcopy-gen=false
// +k8s:openapi-gen=false
type VerificationRequest struct {
// ID is the identifier of the VerificationInfo for which the request is
// being made.
ID string `json:"id,omitempty"`
// Actor is the user who initiated the request.
Actor string `json:"actor,omitempty"`
// ControlPlane is a flag to indicate if the request has been initiated by
// a control plane.
ControlPlane bool `json:"controlPlane,omitempty"`
}
// Equals returns true if the VerificationRequest is equal to the other
// VerificationRequest, false otherwise. Two VerificationRequests are equal if
// their ID, Actor, and ControlPlane fields are equal.
func (r *VerificationRequest) Equals(other *VerificationRequest) bool {
if r == nil && other == nil {
return true
}
if r == nil || other == nil {
return false
}
return r.ID == other.ID && r.Actor == other.Actor && r.ControlPlane == other.ControlPlane
}
// HasID returns true if the VerificationRequest has a non-empty ID.
func (r *VerificationRequest) HasID() bool {
return r != nil && r.ID != ""
}
// ForID returns true if the VerificationRequest has the specified ID.
func (r *VerificationRequest) ForID(id string) bool {
return r != nil && r.ID != "" && r.ID == id
}
// String returns the JSON string representation of the VerificationRequest,
// or an empty string if the VerificationRequest is nil or has an empty ID.
func (r *VerificationRequest) String() string {
if r == nil || r.ID == "" {
return ""
}
b, _ := json.Marshal(r)
if b == nil {
return ""
}
return string(b)
}
// GetStage returns a pointer to the Stage resource specified by the
// namespacedName argument. If no such resource is found, nil is returned
// instead.
func GetStage(
ctx context.Context,
c client.Client,
namespacedName types.NamespacedName,
) (*Stage, error) {
stage := Stage{}
if err := c.Get(ctx, namespacedName, &stage); err != nil {
if err = client.IgnoreNotFound(err); err == nil {
return nil, nil
}
return nil, fmt.Errorf(
"error getting Stage %q in namespace %q: %w",
namespacedName.Name,
namespacedName.Namespace,
err,
)
}
return &stage, nil
}
// RefreshStage forces reconciliation of a Stage by setting an annotation
// on the Stage, causing the controller to reconcile it. Currently, the
// annotation value is the timestamp of the request, but might in the
// future include additional metadata/context necessary for the request.
func RefreshStage(
ctx context.Context,
c client.Client,
namespacedName types.NamespacedName,
) (*Stage, error) {
stage := &Stage{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespacedName.Namespace,
Name: namespacedName.Name,
},
}
if err := patchAnnotation(ctx, c, stage, AnnotationKeyRefresh, time.Now().Format(time.RFC3339)); err != nil {
return nil, fmt.Errorf("refresh: %w", err)
}
return stage, nil
}
// ReverifyStageFreight forces reconfirmation of the verification of the
// Freight associated with a Stage by setting an AnnotationKeyReverify
// annotation on the Stage, causing the controller to rerun the verification.
// The annotation value is the identifier of the existing VerificationInfo for
// the Stage.
func ReverifyStageFreight(
ctx context.Context,
c client.Client,
namespacedName types.NamespacedName,
) error {
stage, err := GetStage(ctx, c, namespacedName)
if err != nil || stage == nil {
if stage == nil {
err = fmt.Errorf("Stage %q in namespace %q not found", namespacedName.Name, namespacedName.Namespace)
}
return err
}
curFreight := stage.Status.CurrentFreight
if curFreight == nil {
return errors.New("stage has no current freight")
}
if curFreight.VerificationInfo == nil {
return errors.New("stage has no existing verification info")
}
if curFreight.VerificationInfo.ID == "" {
return fmt.Errorf("stage verification info has no ID")
}
rr := VerificationRequest{
ID: curFreight.VerificationInfo.ID,
}
// Put actor information to track on the controller side
if u, ok := user.InfoFromContext(ctx); ok {
rr.Actor = FormatEventUserActor(u)
}
return patchAnnotation(ctx, c, stage, AnnotationKeyReverify, rr.String())
}
// AbortStageFreightVerification forces aborting the verification of the
// Freight associated with a Stage by setting an AnnotationKeyAbort
// annotation on the Stage, causing the controller to abort the verification.
// The annotation value is the identifier of the existing VerificationInfo for
// the Stage.
func AbortStageFreightVerification(
ctx context.Context,
c client.Client,
namespacedName types.NamespacedName,
) error {
stage, err := GetStage(ctx, c, namespacedName)
if err != nil || stage == nil {
if stage == nil {
err = fmt.Errorf("Stage %q in namespace %q not found", namespacedName.Name, namespacedName.Namespace)
}
return err
}
curFreight := stage.Status.CurrentFreight
if curFreight == nil {
return errors.New("stage has no current freight")
}
if curFreight.VerificationInfo == nil {
return errors.New("stage has no existing verification info")
}
if stage.Status.CurrentFreight.VerificationInfo.Phase.IsTerminal() {
// The verification is already in a terminal phase, so we can skip the
// abort request.
return nil
}
if curFreight.VerificationInfo.ID == "" {
return fmt.Errorf("stage verification info has no ID")
}
ar := VerificationRequest{
ID: curFreight.VerificationInfo.ID,
}
// Put actor information to track on the controller side
if u, ok := user.InfoFromContext(ctx); ok {
ar.Actor = FormatEventUserActor(u)
}
return patchAnnotation(ctx, c, stage, AnnotationKeyAbort, ar.String())
}