github.com/redhat-appstudio/release-service@v0.0.0-20240507143925-083712697924/api/v1alpha1/release_types.go (about) 1 /* 2 Copyright 2022. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1alpha1 18 19 import ( 20 "time" 21 22 "github.com/konflux-ci/operator-toolkit/conditions" 23 24 "github.com/redhat-appstudio/release-service/metrics" 25 "k8s.io/apimachinery/pkg/runtime" 26 27 "k8s.io/apimachinery/pkg/api/meta" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 ) 30 31 // ReleaseSpec defines the desired state of Release. 32 type ReleaseSpec struct { 33 // Snapshot to be released 34 // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ 35 // +required 36 Snapshot string `json:"snapshot"` 37 38 // ReleasePlan to use for this particular Release 39 // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ 40 // +required 41 ReleasePlan string `json:"releasePlan"` 42 43 // Data is an unstructured key used for providing data for the managed Release Pipeline 44 // +kubebuilder:pruning:PreserveUnknownFields 45 // +optional 46 Data *runtime.RawExtension `json:"data,omitempty"` 47 48 // GracePeriodDays is the number of days a Release should be kept 49 // This value is used to define the Release ExpirationTime 50 // +optional 51 GracePeriodDays int `json:"gracePeriodDays,omitempty"` 52 } 53 54 // ReleaseStatus defines the observed state of Release. 55 type ReleaseStatus struct { 56 // Attribution contains information about the entity authorizing the release 57 // +optional 58 Attribution AttributionInfo `json:"attribution,omitempty"` 59 60 // Conditions represent the latest available observations for the release 61 // +optional 62 Conditions []metav1.Condition `json:"conditions"` 63 64 // PostActionsExecution contains information about the post-actions execution 65 // +optional 66 PostActionsExecution PostActionsExecutionInfo `json:"postActionsExecution,omitempty"` 67 68 // Processing contains information about the release processing 69 // +optional 70 Processing ProcessingInfo `json:"processing,omitempty"` 71 72 // Validation contains information about the release validation 73 // +optional 74 Validation ValidationInfo `json:"validation,omitempty"` 75 76 // Target references where this release is intended to be released to 77 // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ 78 // +optional 79 Target string `json:"target,omitempty"` 80 81 // Automated indicates whether the Release was created as part of an automated process or manually by an end-user 82 // +optional 83 Automated bool `json:"automated,omitempty"` 84 85 // CompletionTime is the time when a Release was completed 86 // +optional 87 CompletionTime *metav1.Time `json:"completionTime,omitempty"` 88 89 // StartTime is the time when a Release started 90 // +optional 91 StartTime *metav1.Time `json:"startTime,omitempty"` 92 93 // ExpirationTime is the time when a Release can be purged 94 // +optional 95 ExpirationTime *metav1.Time `json:"expirationTime,omitempty"` 96 } 97 98 // AttributionInfo defines the observed state of the release attribution. 99 type AttributionInfo struct { 100 // Author is the username that the release is attributed to 101 // +optional 102 Author string `json:"author,omitempty"` 103 104 // StandingAuthorization indicates whether the release is attributed through a ReleasePlan 105 // +optional 106 StandingAuthorization bool `json:"standingAuthorization,omitempty"` 107 } 108 109 // PostActionsExecutionInfo defines the observed state of the post-actions execution. 110 type PostActionsExecutionInfo struct { 111 // CompletionTime is the time when the Release post-actions execution was completed 112 // +optional 113 CompletionTime *metav1.Time `json:"completionTime,omitempty"` 114 115 // StartTime is the time when the Release post-actions execution started 116 // +optional 117 StartTime *metav1.Time `json:"startTime,omitempty"` 118 } 119 120 // ProcessingInfo defines the observed state of the release processing. 121 type ProcessingInfo struct { 122 // CompletionTime is the time when the Release processing was completed 123 // +optional 124 CompletionTime *metav1.Time `json:"completionTime,omitempty"` 125 126 // PipelineRun contains the namespaced name of the managed Release PipelineRun executed as part of this release 127 // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?\/[a-z0-9]([-a-z0-9]*[a-z0-9])?$ 128 // +optional 129 PipelineRun string `json:"pipelineRun,omitempty"` 130 131 // RoleBinding contains the namespaced name of the roleBinding created for the managed Release PipelineRun 132 // executed as part of this release 133 // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?\/[a-z0-9]([-a-z0-9]*[a-z0-9])?$ 134 // +optional 135 RoleBinding string `json:"roleBinding,omitempty"` 136 137 // StartTime is the time when the Release processing started 138 // +optional 139 StartTime *metav1.Time `json:"startTime,omitempty"` 140 } 141 142 // ValidationInfo defines the observed state of the release validation. 143 type ValidationInfo struct { 144 // FailedPostValidation indicates whether the Release was marked as invalid after being initially marked as valid 145 FailedPostValidation bool `json:"failedPostValidation,omitempty"` 146 147 // Time is the time when the Release was validated or when the validation state changed 148 // +optional 149 Time *metav1.Time `json:"time,omitempty"` 150 } 151 152 // +kubebuilder:object:root=true 153 // +kubebuilder:resource:shortName=rel 154 // +kubebuilder:subresource:status 155 // +kubebuilder:printcolumn:name="Snapshot",type=string,JSONPath=`.spec.snapshot` 156 // +kubebuilder:printcolumn:name="ReleasePlan",type=string,JSONPath=`.spec.releasePlan` 157 // +kubebuilder:printcolumn:name="Release status",type=string,JSONPath=`.status.conditions[?(@.type=="Released")].reason` 158 // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` 159 160 // Release is the Schema for the releases API 161 type Release struct { 162 metav1.TypeMeta `json:",inline"` 163 metav1.ObjectMeta `json:"metadata,omitempty"` 164 165 Spec ReleaseSpec `json:"spec,omitempty"` 166 Status ReleaseStatus `json:"status,omitempty"` 167 } 168 169 // HasEveryPostActionExecutionFinished checks whether the Release post-actions execution has finished, 170 // regardless of the result. 171 func (r *Release) HasEveryPostActionExecutionFinished() bool { 172 return r.hasPhaseFinished(postActionsExecutedConditionType) 173 } 174 175 // HasProcessingFinished checks whether the Release processing has finished, regardless of the result. 176 func (r *Release) HasProcessingFinished() bool { 177 return r.hasPhaseFinished(processedConditionType) 178 } 179 180 // HasReleaseFinished checks whether the Release has finished, regardless of the result. 181 func (r *Release) HasReleaseFinished() bool { 182 return r.hasPhaseFinished(releasedConditionType) 183 } 184 185 // IsAttributed checks whether the Release was marked as attributed. 186 func (r *Release) IsAttributed() bool { 187 return r.Status.Attribution.Author != "" 188 } 189 190 // IsAutomated checks whether the Release was marked as automated. 191 func (r *Release) IsAutomated() bool { 192 return r.Status.Automated 193 } 194 195 // IsEveryPostActionExecuted checks whether the Release post-actions were successfully executed. 196 func (r *Release) IsEveryPostActionExecuted() bool { 197 return meta.IsStatusConditionTrue(r.Status.Conditions, postActionsExecutedConditionType.String()) 198 } 199 200 // IsEachPostActionExecuting checks whether the Release post-actions are in progress. 201 func (r *Release) IsEachPostActionExecuting() bool { 202 return r.isPhaseProgressing(postActionsExecutedConditionType) 203 } 204 205 // IsProcessed checks whether the Release was successfully processed. 206 func (r *Release) IsProcessed() bool { 207 return meta.IsStatusConditionTrue(r.Status.Conditions, processedConditionType.String()) 208 } 209 210 // IsProcessing checks whether the Release processing is in progress. 211 func (r *Release) IsProcessing() bool { 212 return r.isPhaseProgressing(processedConditionType) 213 } 214 215 // IsReleased checks whether the Release has finished successfully. 216 func (r *Release) IsReleased() bool { 217 return meta.IsStatusConditionTrue(r.Status.Conditions, releasedConditionType.String()) 218 } 219 220 // IsReleasing checks whether the Release is in progress. 221 func (r *Release) IsReleasing() bool { 222 return r.isPhaseProgressing(releasedConditionType) 223 } 224 225 // IsValid checks whether the Release validation has finished successfully. 226 func (r *Release) IsValid() bool { 227 return meta.IsStatusConditionTrue(r.Status.Conditions, validatedConditionType.String()) 228 } 229 230 // MarkProcessed marks the Release as processed. 231 func (r *Release) MarkProcessed() { 232 if !r.IsProcessing() || r.HasProcessingFinished() { 233 return 234 } 235 236 r.Status.Processing.CompletionTime = &metav1.Time{Time: time.Now()} 237 conditions.SetCondition(&r.Status.Conditions, processedConditionType, metav1.ConditionTrue, SucceededReason) 238 239 go metrics.RegisterCompletedReleaseProcessing( 240 r.Status.Processing.StartTime, 241 r.Status.Processing.CompletionTime, 242 SucceededReason.String(), 243 r.Status.Target, 244 ) 245 } 246 247 // MarkProcessing marks the Release as processing. 248 func (r *Release) MarkProcessing(message string) { 249 if r.HasProcessingFinished() { 250 return 251 } 252 253 if !r.IsProcessing() { 254 r.Status.Processing.StartTime = &metav1.Time{Time: time.Now()} 255 } 256 257 conditions.SetConditionWithMessage(&r.Status.Conditions, processedConditionType, metav1.ConditionFalse, ProgressingReason, message) 258 259 go metrics.RegisterNewReleaseProcessing( 260 r.Status.Processing.StartTime, 261 r.Status.StartTime, 262 ProgressingReason.String(), 263 r.Status.Target, 264 ) 265 } 266 267 // MarkProcessingFailed marks the Release processing as failed. 268 func (r *Release) MarkProcessingFailed(message string) { 269 if !r.IsProcessing() || r.HasProcessingFinished() { 270 return 271 } 272 273 r.Status.Processing.CompletionTime = &metav1.Time{Time: time.Now()} 274 conditions.SetConditionWithMessage(&r.Status.Conditions, processedConditionType, metav1.ConditionFalse, FailedReason, message) 275 276 go metrics.RegisterCompletedReleaseProcessing( 277 r.Status.Processing.StartTime, 278 r.Status.Processing.CompletionTime, 279 FailedReason.String(), 280 r.Status.Target, 281 ) 282 } 283 284 // MarkPostActionsExecuted marks the Release post-actions as executed. 285 func (r *Release) MarkPostActionsExecuted() { 286 if !r.IsEachPostActionExecuting() || r.HasEveryPostActionExecutionFinished() { 287 return 288 } 289 290 r.Status.PostActionsExecution.CompletionTime = &metav1.Time{Time: time.Now()} 291 conditions.SetCondition(&r.Status.Conditions, postActionsExecutedConditionType, metav1.ConditionTrue, SucceededReason) 292 293 go metrics.RegisterCompletedReleasePostActionsExecuted( 294 r.Status.PostActionsExecution.StartTime, 295 r.Status.PostActionsExecution.CompletionTime, 296 SucceededReason.String(), 297 ) 298 } 299 300 // MarkPostActionsExecuting marks the Release post-actions as executing. 301 func (r *Release) MarkPostActionsExecuting(message string) { 302 if r.HasEveryPostActionExecutionFinished() { 303 return 304 } 305 306 if !r.IsEachPostActionExecuting() { 307 r.Status.PostActionsExecution.StartTime = &metav1.Time{Time: time.Now()} 308 } 309 310 conditions.SetConditionWithMessage(&r.Status.Conditions, postActionsExecutedConditionType, metav1.ConditionFalse, ProgressingReason, message) 311 312 go metrics.RegisterNewReleasePostActionsExecution() 313 } 314 315 // MarkPostActionsExecutionFailed marks the Release post-actions execution as failed. 316 func (r *Release) MarkPostActionsExecutionFailed(message string) { 317 if !r.IsEachPostActionExecuting() || r.HasEveryPostActionExecutionFinished() { 318 return 319 } 320 321 r.Status.PostActionsExecution.CompletionTime = &metav1.Time{Time: time.Now()} 322 conditions.SetConditionWithMessage(&r.Status.Conditions, postActionsExecutedConditionType, metav1.ConditionFalse, FailedReason, message) 323 324 go metrics.RegisterCompletedReleasePostActionsExecuted( 325 r.Status.Processing.StartTime, 326 r.Status.Processing.CompletionTime, 327 FailedReason.String(), 328 ) 329 } 330 331 // MarkReleased marks the Release as released. 332 func (r *Release) MarkReleased() { 333 if !r.IsReleasing() || r.HasReleaseFinished() { 334 return 335 } 336 337 r.Status.CompletionTime = &metav1.Time{Time: time.Now()} 338 conditions.SetCondition(&r.Status.Conditions, releasedConditionType, metav1.ConditionTrue, SucceededReason) 339 340 go metrics.RegisterCompletedRelease( 341 r.Status.StartTime, 342 r.Status.CompletionTime, 343 r.getPhaseReason(postActionsExecutedConditionType), 344 r.getPhaseReason(processedConditionType), 345 SucceededReason.String(), 346 r.Status.Target, 347 r.getPhaseReason(validatedConditionType), 348 ) 349 } 350 351 // MarkReleasing marks the Release as releasing. 352 func (r *Release) MarkReleasing(message string) { 353 if r.HasReleaseFinished() { 354 return 355 } 356 357 if !r.IsReleasing() { 358 r.Status.StartTime = &metav1.Time{Time: time.Now()} 359 } 360 361 conditions.SetConditionWithMessage(&r.Status.Conditions, releasedConditionType, metav1.ConditionFalse, ProgressingReason, message) 362 363 go metrics.RegisterNewRelease() 364 } 365 366 // MarkReleaseFailed marks the Release as failed. 367 func (r *Release) MarkReleaseFailed(message string) { 368 if !r.IsReleasing() || r.HasReleaseFinished() { 369 return 370 } 371 372 r.Status.CompletionTime = &metav1.Time{Time: time.Now()} 373 conditions.SetConditionWithMessage(&r.Status.Conditions, releasedConditionType, metav1.ConditionFalse, FailedReason, message) 374 375 go metrics.RegisterCompletedRelease( 376 r.Status.StartTime, 377 r.Status.CompletionTime, 378 r.getPhaseReason(postActionsExecutedConditionType), 379 r.getPhaseReason(processedConditionType), 380 FailedReason.String(), 381 r.Status.Target, 382 r.getPhaseReason(validatedConditionType), 383 ) 384 } 385 386 // MarkValidated marks the Release as validated. 387 func (r *Release) MarkValidated() { 388 if r.IsValid() { 389 return 390 } 391 392 r.Status.Validation.Time = &metav1.Time{Time: time.Now()} 393 conditions.SetCondition(&r.Status.Conditions, validatedConditionType, metav1.ConditionTrue, SucceededReason) 394 395 go metrics.RegisterValidatedRelease( 396 r.Status.StartTime, 397 r.Status.Validation.Time, 398 SucceededReason.String(), 399 r.Status.Target, 400 ) 401 } 402 403 // MarkValidationFailed marks the Release validation as failed. 404 func (r *Release) MarkValidationFailed(message string) { 405 if r.IsValid() { 406 r.Status.Validation.FailedPostValidation = true 407 } 408 409 r.Status.Validation.Time = &metav1.Time{Time: time.Now()} 410 conditions.SetConditionWithMessage(&r.Status.Conditions, validatedConditionType, metav1.ConditionFalse, FailedReason, message) 411 412 go metrics.RegisterValidatedRelease( 413 r.Status.StartTime, 414 r.Status.Validation.Time, 415 FailedReason.String(), 416 r.Status.Target, 417 ) 418 } 419 420 // SetAutomated marks the Release as automated. 421 func (r *Release) SetAutomated() { 422 if r.IsAutomated() { 423 return 424 } 425 426 r.Status.Automated = true 427 } 428 429 // SetExpirationTime set the time when this release can be purged 430 func (r *Release) SetExpirationTime(expireDays time.Duration) { 431 creationTime := r.CreationTimestamp 432 r.Status.ExpirationTime = &metav1.Time{Time: creationTime.Add(time.Hour * 24 * expireDays)} 433 } 434 435 // getPhaseReason returns the current reason for the given ConditionType or empty string if no condition is found. 436 func (r *Release) getPhaseReason(conditionType conditions.ConditionType) string { 437 var reason string 438 439 condition := meta.FindStatusCondition(r.Status.Conditions, conditionType.String()) 440 if condition != nil { 441 reason = condition.Reason 442 } 443 444 return reason 445 } 446 447 // hasPhaseFinished checks whether a Release phase (e.g. deployment or processing) has finished. 448 func (r *Release) hasPhaseFinished(conditionType conditions.ConditionType) bool { 449 condition := meta.FindStatusCondition(r.Status.Conditions, conditionType.String()) 450 451 switch { 452 case condition == nil: 453 return false 454 case condition.Status == metav1.ConditionTrue: 455 return true 456 default: 457 return condition.Status == metav1.ConditionFalse && condition.Reason != ProgressingReason.String() 458 } 459 } 460 461 // isPhaseProgressing checks whether a Release phase (e.g. deployment or processing) is progressing. 462 func (r *Release) isPhaseProgressing(conditionType conditions.ConditionType) bool { 463 condition := meta.FindStatusCondition(r.Status.Conditions, conditionType.String()) 464 465 switch { 466 case condition == nil: 467 return false 468 case condition.Status == metav1.ConditionTrue: 469 return false 470 default: 471 return condition.Status == metav1.ConditionFalse && condition.Reason == ProgressingReason.String() 472 } 473 } 474 475 // +kubebuilder:object:root=true 476 477 // ReleaseList contains a list of Release 478 type ReleaseList struct { 479 metav1.TypeMeta `json:",inline"` 480 metav1.ListMeta `json:"metadata,omitempty"` 481 Items []Release `json:"items"` 482 } 483 484 func init() { 485 SchemeBuilder.Register(&Release{}, &ReleaseList{}) 486 }