-
Notifications
You must be signed in to change notification settings - Fork 114
/
stage_types.go
529 lines (480 loc) · 21 KB
/
stage_types.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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
package v1alpha1
import (
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum={Image,Tag}
type ImageUpdateValueType string
const (
ImageUpdateValueTypeImage ImageUpdateValueType = "Image"
ImageUpdateValueTypeTag ImageUpdateValueType = "Tag"
)
type HealthState string
const (
HealthStateHealthy HealthState = "Healthy"
HealthStateUnhealthy HealthState = "Unhealthy"
HealthStateProgressing HealthState = "Progressing"
HealthStateUnknown HealthState = "Unknown"
)
var stateOrder = map[HealthState]int{
HealthStateHealthy: 0,
HealthStateProgressing: 1,
HealthStateUnknown: 2,
HealthStateUnhealthy: 3,
}
// Merge returns the more severe of two HealthStates.
func (h HealthState) Merge(other HealthState) HealthState {
if stateOrder[h] > stateOrder[other] {
return h
}
return other
}
type ArgoCDAppHealthState string
const (
ArgoCDAppHealthStateUnknown ArgoCDAppHealthState = "Unknown"
ArgoCDAppHealthStateProgressing ArgoCDAppHealthState = "Progressing"
ArgoCDAppHealthStateHealthy ArgoCDAppHealthState = "Healthy"
ArgoCDAppHealthStateSuspended ArgoCDAppHealthState = "Suspended"
ArgoCDAppHealthStateDegraded ArgoCDAppHealthState = "Degraded"
ArgoCDAppHealthStateMissing ArgoCDAppHealthState = "Missing"
)
type ArgoCDAppSyncState string
const (
ArgoCDAppSyncStateUnknown ArgoCDAppSyncState = "Unknown"
ArgoCDAppSyncStateSynced ArgoCDAppSyncState = "Synced"
ArgoCDAppSyncStateOutOfSync ArgoCDAppSyncState = "OutOfSync"
)
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name=Current Freight,type=string,JSONPath=`.status.currentFreight.id`
//+kubebuilder:printcolumn:name=Health,type=string,JSONPath=`.status.health.status`
//+kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp`
// Stage is the Kargo API's main type.
type Stage struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec describes sources of Freight used by the Stage and how to incorporate
// Freight into the Stage.
//
//+kubebuilder:validation:Required
Spec *StageSpec `json:"spec"`
// Status describes the Stage's current and recent Freight, health, and more.
Status StageStatus `json:"status,omitempty"`
}
func (s *Stage) GetStatus() *StageStatus {
return &s.Status
}
// StageSpec describes the sources of Freight used by a Stage and how to
// incorporate Freight into the Stage.
type StageSpec struct {
// Subscriptions describes the Stage's sources of Freight. This is a required
// field.
//
//+kubebuilder:validation:Required
Subscriptions *Subscriptions `json:"subscriptions"`
// PromotionMechanisms describes how to incorporate Freight into the Stage.
// This is an optional field as it is sometimes useful to aggregates available
// Freight from multiple upstream Stages without performing any actions. The
// utility of this is to allow multiple downstream Stages to subscribe to a
// single upstream Stage where they may otherwise have subscribed to multiple
// upstream Stages.
PromotionMechanisms *PromotionMechanisms `json:"promotionMechanisms,omitempty"`
}
// Subscriptions describes a Stage's sources of Freight.
type Subscriptions struct {
// Warehouse is a subscription to a Warehouse. This field is mutually
// exclusive with the UpstreamStages field.
Warehouse string `json:"warehouse,omitempty"`
// UpstreamStages identifies other Stages as potential sources of Freight
// for this Stage. This field is mutually exclusive with the Repos field.
UpstreamStages []StageSubscription `json:"upstreamStages,omitempty"`
}
// StageSubscription defines a subscription to Freight from another Stage.
type StageSubscription struct {
// Name specifies the name of a Stage.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
Name string `json:"name"`
}
// PromotionMechanisms describes how to incorporate Freight into a Stage.
type PromotionMechanisms struct {
// GitRepoUpdates describes updates that should be applied to Git repositories
// to incorporate Freight into the Stage. This field is optional, as such
// actions are not required in all cases.
GitRepoUpdates []GitRepoUpdate `json:"gitRepoUpdates,omitempty"`
// ArgoCDAppUpdates describes updates that should be applied to Argo CD
// Application resources to incorporate Freight into the Stage. This field is
// optional, as such actions are not required in all cases. Note that all
// updates specified by the GitRepoUpdates field, if any, are applied BEFORE
// these.
ArgoCDAppUpdates []ArgoCDAppUpdate `json:"argoCDAppUpdates,omitempty"`
}
// GitRepoUpdate describes updates that should be applied to a Git repository
// (using various configuration management tools) to incorporate Freight into a
// Stage.
type GitRepoUpdate struct {
// RepoURL is the URL of the repository to update. This is a required field.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=`^https://(\w+([\.-]\w+)*@)?\w+([\.-]\w+)*(:[\d]+)?(/.*)?$`
RepoURL string `json:"repoURL"`
// ReadBranch specifies a particular branch of the repository from which to
// locate contents that will be written to the branch specified by the
// WriteBranch field. This field is optional. When not specified, the
// ReadBranch is implicitly the repository's default branch AND in cases where
// a Freight includes a GitCommit, that commit's ID will supersede the value
// of this field. Therefore, in practice, this field is only used to clarify
// what branch of a repository can be treated as a source of manifests or
// other configuration when a Stage has no subscription to that repository.
//
//+kubebuilder:validation:Optional
//+kubebuilder:validation:Pattern=`^(\w+([-/]\w+)*)?$`
ReadBranch string `json:"readBranch,omitempty"`
// WriteBranch specifies the particular branch of the repository to be
// updated. This is a required field.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=`^\w+([-/]\w+)*$`
WriteBranch string `json:"writeBranch"`
// Render describes how to use Kargo Render to incorporate Freight into the
// Stage. This is mutually exclusive with the Kustomize and Helm fields.
Render *KargoRenderPromotionMechanism `json:"render,omitempty"`
// Kustomize describes how to use Kustomize to incorporate Freight into the
// Stage. This is mutually exclusive with the Render and Helm fields.
Kustomize *KustomizePromotionMechanism `json:"kustomize,omitempty"`
// Helm describes how to use Helm to incorporate Freight into the Stage. This
// is mutually exclusive with the Render and Kustomize fields.
Helm *HelmPromotionMechanism `json:"helm,omitempty"`
}
// KargoRenderPromotionMechanism describes how to use Kargo Render to
// incorporate Freight into a Stage.
type KargoRenderPromotionMechanism struct{}
// KustomizePromotionMechanism describes how to use Kustomize to incorporate
// Freight into a Stage.
type KustomizePromotionMechanism struct {
// Images describes images for which `kustomize edit set image` should be
// executed and the paths in which those commands should be executed.
//
//+kubebuilder:validation:MinItems=1
Images []KustomizeImageUpdate `json:"images"`
}
// KustomizeImageUpdate describes how to run `kustomize edit set image`
// for a given image.
type KustomizeImageUpdate struct {
// Image specifies a container image (without tag). This is a required field.
//
//+kubebuilder:validation:MinLength=1
Image string `json:"image"`
// Path specifies a path in which the `kustomize edit set image` command
// should be executed. This is a required field.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=^[\w-\.]+(/[\w-\.]+)*$
Path string `json:"path"`
}
// HelmPromotionMechanism describes how to use Helm to incorporate Freight into
// a Stage.
type HelmPromotionMechanism struct {
// Images describes how specific image versions can be incorporated into Helm
// values files.
Images []HelmImageUpdate `json:"images,omitempty"`
// Charts describes how specific chart versions can be incorporated into an
// umbrella chart.
Charts []HelmChartDependencyUpdate `json:"charts,omitempty"`
}
// HelmImageUpdate describes how a specific image version can be incorporated
// into a specific Helm values file.
type HelmImageUpdate struct {
// Image specifies a container image (without tag). This is a required field.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=`^(\w+([\.-]\w+)*(:[\d]+)?/)?(\w+([\.-]\w+)*)(/\w+([\.-]\w+)*)*$`
Image string `json:"image"`
// ValuesFilePath specifies a path to the Helm values file that is to be
// updated. This is a required field.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=^[\w-\.]+(/[\w-\.]+)*$
ValuesFilePath string `json:"valuesFilePath"`
// Key specifies a key within the Helm values file that is to be updated. This
// is a required field.
//
//+kubebuilder:validation:MinLength=1
Key string `json:"key"`
// Value specifies the new value for the specified key in the specified Helm
// values file. Valid values are "Image", which replaces the value of the
// specified key with the entire <image name>:<tag>, or "Tag" which replaces
// the value of the specified with just the new tag. This is a required field.
Value ImageUpdateValueType `json:"value"`
}
// HelmChartDependencyUpdate describes how a specific Helm chart that is used
// as a subchart of an umbrella chart can be updated.
type HelmChartDependencyUpdate struct {
// RegistryURL along with Name identify a subchart of the umbrella chart at
// ChartPath whose version should be updated.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=`^(((https?)|(oci))://)([\w\d\.]+)(:[\d]+)?(/.*)*$`
RegistryURL string `json:"registryURL"`
// Name along with RegistryURL identify a subchart of the umbrella chart at
// ChartPath whose version should be updated.
//
//+kubebuilder:validation:MinLength=1
Name string `json:"name"`
// ChartPath is the path to an umbrella chart.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=^[\w-\.]+(/[\w-\.]+)*$
ChartPath string `json:"chartPath"`
}
// ArgoCDAppUpdate describes updates that should be applied to an Argo CD
// Application resources to incorporate Freight into a Stage.
type ArgoCDAppUpdate struct {
// AppName specifies the name of an Argo CD Application resource to be
// updated.
//
//+kubebuilder:validation:MinLength=1
//+kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
AppName string `json:"appName"`
// AppNamespace specifies the namespace of an Argo CD Application resource to
// be updated. If left unspecified, the namespace of this Application resource
// will use the value of ARGOCD_NAMESPACE or "argocd"
//
//+kubebuilder:validation:Optional
//+kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
AppNamespace string `json:"appNamespace,omitempty"`
// SourceUpdates describes updates to be applied to various sources of the
// specified Argo CD Application resource.
SourceUpdates []ArgoCDSourceUpdate `json:"sourceUpdates,omitempty"`
}
func (a *ArgoCDAppUpdate) AppNamespaceOrDefault() string {
if a.AppNamespace != "" {
return a.AppNamespace
}
if envArgocdNs := os.Getenv("ARGOCD_NAMESPACE"); envArgocdNs != "" {
return envArgocdNs
}
return "argocd"
}
// ArgoCDSourceUpdate describes updates that should be applied to one of an Argo
// CD Application resource's sources.
type ArgoCDSourceUpdate struct {
// RepoURL identifies which of the Argo CD Application's sources this update
// is intended for. Note: As of Argo CD 2.6, Application's can use multiple
// sources.
//
//+kubebuilder:validation:MinLength=1
RepoURL string `json:"repoURL"`
// Chart specifies a chart within a Helm chart registry if RepoURL points to a
// Helm chart registry. Application sources that point directly at a chart do
// so through a combination of their own RepoURL (registry) and Chart fields,
// so BOTH of those are used as criteria in selecting an Application source to
// update. This field MUST always be used when RepoURL points at a Helm chart
// registry. This field MUST never be used when RepoURL points at a Git
// repository.
//
//+kubebuilder:validation:Optional
Chart string `json:"chart,omitempty"`
// UpdateTargetRevision is a bool indicating whether the source should be
// updated such that its TargetRevision field points at the most recently git
// commit (if RepoURL references a git repository) or chart version (if
// RepoURL references a chart repository).
UpdateTargetRevision bool `json:"updateTargetRevision,omitempty"`
// Kustomize describes updates to the source's Kustomize-specific attributes.
Kustomize *ArgoCDKustomize `json:"kustomize,omitempty"`
// Helm describes updates to the source's Helm-specific attributes.
Helm *ArgoCDHelm `json:"helm,omitempty"`
}
// ArgoCDKustomize describes updates to an Argo CD Application source's
// Kustomize-specific attributes to incorporate newly observed Freight into a
// Stage.
type ArgoCDKustomize struct {
// Images describes how specific image versions can be incorporated into an
// Argo CD Application's Kustomize parameters.
//
//+kubebuilder:validation:MinItems=1
Images []string `json:"images"`
}
// ArgoCDHelm describes updates to an Argo CD Application source's Helm-specific
// attributes to incorporate newly observed Freight into a Stage.
type ArgoCDHelm struct {
// Images describes how specific image versions can be incorporated into an
// Argo CD Application's Helm parameters.
//
//+kubebuilder:validation:MinItems=1
Images []ArgoCDHelmImageUpdate `json:"images"`
}
// ArgoCDHelmImageUpdate describes how a specific image version can be
// incorporated into an Argo CD Application's Helm parameters.
type ArgoCDHelmImageUpdate struct {
// Image specifies a container image (without tag). This is a required field.
//
//+kubebuilder:validation:MinLength=1
Image string `json:"image"`
// Key specifies a key within an Argo CD Application's Helm parameters that is
// to be updated. This is a required field.
//
//+kubebuilder:validation:MinLength=1
Key string `json:"key"`
// Value specifies the new value for the specified key in the Argo CD
// Application's Helm parameters. Valid values are "Image", which replaces the
// value of the specified key with the entire <image name>:<tag>, or "Tag"
// which replaces the value of the specified with just the new tag. This is a
// required field.
Value ImageUpdateValueType `json:"value"`
}
// StageStatus describes a Stages's current and recent Freight, health, and
// more.
type StageStatus struct {
// CurrentFreight is a simplified representation of the Stage's current
// Freight describing what is currently deployed to the Stage.
CurrentFreight *SimpleFreight `json:"currentFreight,omitempty"`
// History is a stack of recent Freight. By default, the last ten Freight are
// stored.
History SimpleFreightStack `json:"history,omitempty"`
// Health is the Stage's last observed health.
Health *Health `json:"health,omitempty"`
// Error describes any errors that are preventing the Stage controller
// from assessing Stage health or from finding new Freight.
Error string `json:"error,omitempty"`
// ObservedGeneration represents the .metadata.generation that this Stage
// status was reconciled against.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// CurrentPromotion is a reference to the currently Running promotion.
CurrentPromotion *PromotionInfo `json:"currentPromotion,omitempty"`
}
// SimpleFreight is a simplified representation of a piece of Freight -- not a
// root resource type.
type SimpleFreight struct {
// ID is system-assigned value that is derived deterministically from the
// contents of the Freight. i.e. Two pieces of Freight can be compared for
// equality by comparing their IDs.
ID string `json:"id,omitempty"`
// Commits describes specific Git repository commits.
Commits []GitCommit `json:"commits,omitempty"`
// Images describes specific versions of specific container images.
Images []Image `json:"images,omitempty"`
// Charts describes specific versions of specific Helm charts.
Charts []Chart `json:"charts,omitempty"`
}
type SimpleFreightStack []SimpleFreight
// Empty returns a bool indicating whether or not the SimpleFreightStack is
// empty. nil counts as empty.
func (s SimpleFreightStack) Empty() bool {
return len(s) == 0
}
// Pop removes and returns the leading element from a SimpleFreightStack. If the
// SimpleFreightStack is empty, the SimpleFreightStack is not modified and a
// empty SimpleFreight is returned instead. A boolean is also returned
// indicating whether the returned SimpleFreight came from the top of the stack
// (true) or is a zero value for that type (false).
func (s *SimpleFreightStack) Pop() (SimpleFreight, bool) {
item, ok := s.Top()
if ok {
*s = (*s)[1:]
}
return item, ok
}
// Top returns the leading element from a SimpleFreightStack without modifying
// the SimpleFreightStack. If the SimpleFreightStack is empty, an empty
// SimpleFreight is returned instead. A boolean is also returned indicating
// whether the returned SimpleFreight came from the top of the stack (true) or
// is a zero value for that type (false).
func (s SimpleFreightStack) Top() (SimpleFreight, bool) {
if s.Empty() {
return SimpleFreight{}, false
}
item := *s[0].DeepCopy()
return item, true
}
// Push pushes one or more Freight onto the FreightStack. The order of
// the new elements at the top of the stack will be equal to the order in which
// they were passed to this function. i.e. The first new element passed will be
// the element at the top of the stack. If resulting modification grow the depth
// of the stack beyond 10 elements, the stack is truncated at the bottom. i.e.
// Modified to contain only the top 10 elements.
func (s *SimpleFreightStack) Push(freight ...SimpleFreight) {
*s = append(freight, *s...)
const max = 10
if len(*s) > max {
*s = (*s)[:max]
}
}
// Image describes a specific version of a container image.
type Image struct {
// RepoURL describes the repository in which the image can be found.
RepoURL string `json:"repoURL,omitempty"`
// GitRepoURL specifies the URL of a Git repository that contains the source
// code for the image repository referenced by the RepoURL field if Kargo was
// able to infer it.
GitRepoURL string `json:"gitRepoURL,omitempty"`
// Tag identifies a specific version of the image in the repository specified
// by RepoURL.
Tag string `json:"tag,omitempty"`
}
// Chart describes a specific version of a Helm chart.
type Chart struct {
// RepoURL specifies the remote registry in which this chart is located.
RegistryURL string `json:"registryURL,omitempty"`
// Name specifies the name of the chart.
Name string `json:"name,omitempty"`
// Version specifies a particular version of the chart.
Version string `json:"version,omitempty"`
}
// Equals returns a bool indicating whether two GitCommits are equivalent.
func (g *GitCommit) Equals(rhs *GitCommit) bool {
if g == nil && rhs == nil {
return true
}
if (g == nil && rhs != nil) || (g != nil && rhs == nil) {
return false
}
// If we get to here, both operands are non-nil
return g.RepoURL == rhs.RepoURL && g.ID == rhs.ID
}
// Health describes the health of a Stage.
type Health struct {
// Status describes the health of the Stage.
Status HealthState `json:"status,omitempty"`
// Issues clarifies why a Stage in any state other than Healthy is in that
// state. This field will always be the empty when a Stage is Healthy.
Issues []string `json:"issues,omitempty"`
// ArgoCDApps describes the current state of any related ArgoCD Applications.
ArgoCDApps []ArgoCDAppStatus `json:"argoCDApps,omitempty"`
}
// ArgoCDAppStatus describes the current state of a single ArgoCD Application.
type ArgoCDAppStatus struct {
// Namespace is the namespace of the ArgoCD Application.
Namespace string `json:"namespace"`
// Name is the name of the ArgoCD Application.
Name string `json:"name"`
// HealthStatus is the health of the ArgoCD Application.
HealthStatus ArgoCDAppHealthStatus `json:"healthStatus,omitempty"`
// SyncStatus is the sync status of the ArgoCD Application.
SyncStatus ArgoCDAppSyncStatus `json:"syncStatus,omitempty"`
}
// ArgoCDAppHealthStatus describes the health of an ArgoCD Application.
type ArgoCDAppHealthStatus struct {
Status ArgoCDAppHealthState `json:"status"`
Message string `json:"message,omitempty"`
}
// ArgoCDAppSyncStatus describes the sync status of an ArgoCD Application.
type ArgoCDAppSyncStatus struct {
Status ArgoCDAppSyncState `json:"status"`
Revision string `json:"revision,omitempty"`
Revisions []string `json:"revisions,omitempty"`
}
//+kubebuilder:object:root=true
// StageList is a list of Stage resources.
type StageList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Stage `json:"items"`
}
type PromotionInfo struct {
// Name is the name of the Promotion
Name string `json:"name"`
// Freight is the freight being promoted
Freight SimpleFreight `json:"freight"`
}