forked from konflux-ci/integration-service
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsnapshot.go
More file actions
1777 lines (1508 loc) · 79.7 KB
/
Copy pathsnapshot.go
File metadata and controls
1777 lines (1508 loc) · 79.7 KB
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
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright 2022 Red Hat Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gitops
import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/google/go-containerregistry/pkg/name"
applicationapiv1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1"
"github.com/konflux-ci/integration-service/api/v1beta2"
"github.com/konflux-ci/integration-service/helpers"
"github.com/konflux-ci/integration-service/pkg/metrics"
tektonconsts "github.com/konflux-ci/integration-service/tekton/consts"
"github.com/konflux-ci/operator-toolkit/metadata"
pacv1alpha1 "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
"github.com/santhosh-tekuri/jsonschema/v5"
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
const (
// PipelinesAsCodePrefix contains the prefix applied to labels and annotations copied from Pipelines as Code resources.
PipelinesAsCodePrefix = "pac.test.appstudio.openshift.io"
// TestLabelPrefix contains the prefix applied to labels and annotations related to testing.
TestLabelPrefix = "test.appstudio.openshift.io"
// ReleaseLabelPrefix contains the prefix applied to to labels and annotations related to release process.
ReleaseLabelPrefix = "release.appstudio.openshift.io"
// AutoReleaseLabel contains the label that allows users to overwrite the release behaviour of Snapshots
AutoReleaseLabel = ReleaseLabelPrefix + "/auto-release"
// CustomLabelPrefix contains the prefix applied to custom user-defined labels and annotations.
CustomLabelPrefix = "custom.appstudio.openshift.io"
// SnapshotTypeLabel contains the type of the Snapshot.
SnapshotTypeLabel = "test.appstudio.openshift.io/type"
// SnapshotIntegrationTestRun contains name of test we want to trigger run
SnapshotIntegrationTestRun = "test.appstudio.openshift.io/run"
// AppstudioLabelPrefix contains application, component, build-pipelinerun etc.
AppstudioLabelPrefix = "appstudio.openshift.io"
// SnapshotLabel contains the name of the Snapshot within appstudio
SnapshotLabel = "appstudio.openshift.io/snapshot"
// SnapshotTestScenarioLabel contains the name of the Snapshot test scenario.
SnapshotTestScenarioLabel = "test.appstudio.openshift.io/scenario"
// SnapshotTestsStatusAnnotation contains json data with test results of the particular snapshot
SnapshotTestsStatusAnnotation = "test.appstudio.openshift.io/status"
// (Deprecated) SnapshotPRLastUpdate contains timestamp of last time PR was updated
SnapshotPRLastUpdate = "test.appstudio.openshift.io/pr-last-update"
// SnapshotGitSourceRepoURLAnnotation contains URL of the git source repository (usually needed for forks)
SnapshotGitSourceRepoURLAnnotation = "test.appstudio.openshift.io/source-repo-url"
// PipelineAsCodeGitSourceURLAnnotation contains the source repository url information
PipelineAsCodeGitSourceURLAnnotation = PipelinesAsCodePrefix + "/source-repo-url"
// PipelineAsCodeSourceBranchAnnotation contains the source repository branch information
PipelineAsCodeSourceBranchAnnotation = PipelinesAsCodePrefix + "/source-branch"
// SnapshotStatusReportAnnotation contains metadata of tests related to status reporting to git provider
SnapshotStatusReportAnnotation = "test.appstudio.openshift.io/git-reporter-status"
// PRGroupAnnotation contains the pr group name
PRGroupAnnotation = "test.appstudio.openshift.io/pr-group"
// PRGroupHashLabel contains the pr group name in sha format
PRGroupHashLabel = "test.appstudio.openshift.io/pr-group-sha"
// PRGroupCreationAnnotation contains the info of groupsnapshot creation
PRGroupCreationAnnotation = "test.appstudio.openshift.io/create-groupsnapshot-status"
// GitReportingFailureAnnotation contains information about git reporting failures
GitReportingFailureAnnotation = "test.appstudio.openshift.io/git-reporting-failure"
// BuildPipelineRunResultAnnotationPrefix is the prefix for annotations derived from build PipelineRun results
BuildPipelineRunResultAnnotationPrefix = TestLabelPrefix + "/result-"
// BuildPipelineRunShouldReleaseAnnotation contains the SHOULD_RELEASE result from the build PipelineRun
BuildPipelineRunShouldReleaseAnnotation = BuildPipelineRunResultAnnotationPrefix + "should-release"
// BuildPipelineRunStartTime contains the start time of build pipelineRun
BuildPipelineRunStartTime = "test.appstudio.openshift.io/pipelinerunstarttime"
// BuildPipelineLastBuiltTime contains the time of the last built pipelineRun
BuildPipelineLastBuiltTime = "test.appstudio.openshift.io/lastbuilttime"
// IntegrationWorkflowAnnotation contains the workflow type that triggered the snapshot (push or pull-request)
IntegrationWorkflowAnnotation = "test.appstudio.openshift.io/integration-workflow"
// BuildPipelineRunPrefix contains the build pipeline run related labels and annotations
BuildPipelineRunPrefix = "build.appstudio"
// BuildPipelineRunFinishTimeLabel contains the build PipelineRun finish time of the Snapshot.
BuildPipelineRunFinishTimeLabel = "test.appstudio.openshift.io/pipelinerunfinishtime"
// GroupSnapshotInfoAnnotation contains the component snapshot info included in group snapshot
GroupSnapshotInfoAnnotation = "test.appstudio.openshift.io/group-test-info"
// PRStatusAnnotation contains the status of the PR
PRStatusAnnotation = "test.appstudio.openshift.io/pr-status"
// BuildPipelineRunNameLabel contains the build PipelineRun name
BuildPipelineRunNameLabel = AppstudioLabelPrefix + "/build-pipelinerun"
// ApplicationNameLabel contains the name of the application
ApplicationNameLabel = AppstudioLabelPrefix + "/application"
// ComponentGroupNameLabel contains the name of the ComponentGroup
ComponentGroupNameLabel = AppstudioLabelPrefix + "/component-group"
// SnapshotComponentType is the type of Snapshot which was created for a single component build.
SnapshotComponentType = "component"
// SnapshotOverrideType is the type of Snapshot which was created for override Global Candidate List.
SnapshotOverrideType = "override"
// SnapshotGroupType is the type of Snapshot which was created for pull request groups.
SnapshotGroupType = "group"
// PipelineAsCodeEventTypeLabel is the type of event which triggered the pipelinerun in build service
PipelineAsCodeEventTypeLabel = PipelinesAsCodePrefix + "/event-type"
// PipelineAsCodeGitProviderLabel is the git provider which triggered the pipelinerun in build service.
PipelineAsCodeGitProviderLabel = PipelinesAsCodePrefix + "/git-provider"
// PipelineAsCodeGitProviderAnnotation is the git provider which triggered the pipelinerun in build service.
PipelineAsCodeGitProviderAnnotation = PipelinesAsCodePrefix + "/git-provider"
// PipelineAsCodeSHALabel is the commit which triggered the pipelinerun in build service.
PipelineAsCodeSHALabel = PipelinesAsCodePrefix + "/sha"
// PipelineAsCodeURLOrgLabel is the organization for the git repo which triggered the pipelinerun in build service.
PipelineAsCodeURLOrgLabel = PipelinesAsCodePrefix + "/url-org"
// PipelineAsCodeURLRepositoryLabel is the git repository which triggered the pipelinerun in build service.
PipelineAsCodeURLRepositoryLabel = PipelinesAsCodePrefix + "/url-repository"
// PipelineAsCodeRepoURLAnnotation is the URL to the git repository which triggered the pipelinerun in build service.
PipelineAsCodeRepoURLAnnotation = PipelinesAsCodePrefix + "/repo-url"
// PipelineAsCodeTargetBranchAnnotation is the SHA of the git revision which triggered the pipelinerun in build service.
PipelineAsCodeTargetBranchAnnotation = PipelinesAsCodePrefix + "/branch"
// PipelineAsCodeInstallationIDAnnotation is the GitHub App installation ID for the git repo which triggered the pipelinerun in build service.
PipelineAsCodeInstallationIDAnnotation = PipelinesAsCodePrefix + "/installation-id"
// PipelineAsCodePullRequestAnnotation is the git repository's pull request identifier
PipelineAsCodePullRequestAnnotation = PipelinesAsCodePrefix + "/pull-request"
// PipelineAsCodeSourceProjectIDAnnotation is the source project ID for gitlab
PipelineAsCodeSourceProjectIDAnnotation = PipelinesAsCodePrefix + "/source-project-id"
// PipelineAsCodeTargetProjectIDAnnotation is the target project ID for gitlab
PipelineAsCodeTargetProjectIDAnnotation = PipelinesAsCodePrefix + "/target-project-id"
// PipelineAsCodeRepoUrlAnnotation is the target project Repo Url
PipelineAsCodeRepoUrlAnnotation = PipelinesAsCodePrefix + "/repo-url"
// PipelineAsCodeSHAAnnotation is the commit which triggered the pipelinerun in build service.
PipelineAsCodeSHAAnnotation = PipelinesAsCodePrefix + "/sha"
// PipelineAsCodePushType is the type of push event which triggered the pipelinerun in build service
PipelineAsCodePushType = "push"
// PipelineAsCodeGLPushType is the type of gitlab push event which triggered the pipelinerun in build service
PipelineAsCodeGLPushType = "Push"
// PipelineAsCodePullRequestType is the type of pull_request event which triggered the pipelinerun in build service
PipelineAsCodePullRequestType = "pull_request"
// PipelineAsCodeMergeRequestType is the type of merge request event which triggered the pipelinerun in build service
PipelineAsCodeMergeRequestType = "Merge Request"
// PipelineAsCodeGLMergeRequestType is the type of gitlab merge request event marked in label which triggered the pipelinerun in build service
PipelineAsCodeMergeUnderscoreRequestType = "Merge_Request"
// PipelineAsCodeRetestType is the type of retest event which triggered the pipelinerun in build service by commenting /retest
PipelineAsCodeRetestType = "retest-all-comment"
// IntegrationWorkflowPushValue is the value for push workflow snapshots
IntegrationWorkflowPushValue = "push"
// IntegrationWorkflowPullRequestValue is the value for pull request workflow snapshots
IntegrationWorkflowPullRequestValue = "pull-request"
// PipelineAsCodeGitHubProviderType is the git provider type for a GitHub event which triggered the pipelinerun in build service.
PipelineAsCodeGitHubProviderType = "github"
// PipelineAsCodeGitLabProviderType is the git provider type for a GitLab event which triggered the pipelinerun in build service.
PipelineAsCodeGitLabProviderType = "gitlab"
// PipelineAsCodeForgejoProviderType is the git provider type for a Forgejo event which triggered the pipelinerun in build service.
PipelineAsCodeForgejoProviderType = "forgejo"
// PipelineAsCodeGiteaProviderType is the git provider type for Gitea. PaC uses this when targeting Forgejo until PaC adds full Forgejo support.
// TODO: Remove this once PaC adds full Forgejo support expected March 2026
PipelineAsCodeGiteaProviderType = "gitea"
// PipelineAsCodeGitHubMergeQueueBranchPrefix is the prefix added to temporary branches which are created for merge queues
PipelineAsCodeGitHubMergeQueueBranchPrefix = "gh-readonly-queue/"
// GitRefBranchPrefix is the git prefix denoting a reference is a branch
GitRefBranchPrefix = "refs/heads/"
// AppStudioTestSucceededCondition is the condition for marking if the AppStudio Tests succeeded for the Snapshot.
AppStudioTestSucceededCondition = "AppStudioTestSucceeded"
// LegacyTestSucceededCondition is the condition for marking if the AppStudio Tests succeeded for the Snapshot.
LegacyTestSucceededCondition = "HACBSStudioTestSucceeded"
// AppStudioIntegrationStatusCondition is the condition for marking the AppStudio integration status of the Snapshot.
AppStudioIntegrationStatusCondition = "AppStudioIntegrationStatus"
// LegacyIntegrationStatusCondition is the condition for marking the AppStudio integration status of the Snapshot.
LegacyIntegrationStatusCondition = "HACBSIntegrationStatus"
// SnapshotAutoReleasedCondition is the condition for marking if Snapshot was auto-released released with AppStudio.
SnapshotAutoReleasedCondition = "AutoReleased"
// ParentSnapshotsCreatedCondition is the condition marking whether snapshots for all parent ComponentGroups for the
// ComponentGroup that the snapshot belongs to have been created
ParentSnapshotsCreatedCondition = "ParentSnapshotsCreated"
// SnapshotAddedToGlobalCandidateListCondition is the condition for marking if Snapshot's component was added to
// the global candidate list.
SnapshotAddedToGlobalCandidateListCondition = "AddedToGlobalCandidateList"
// AppStudioTestSucceededConditionSatisfied is the reason that's set when the AppStudio tests succeed.
AppStudioTestSucceededConditionSatisfied = "Passed"
// AppStudioTestSucceededConditionFailed is the reason that's set when the AppStudio tests fail.
AppStudioTestSucceededConditionFailed = "Failed"
// AppStudioIntegrationStatusCanceled is the reason that's set when the AppStudio tests cancel because of being superseded by newer Snapshot.
AppStudioIntegrationStatusCanceled = "Canceled"
// AppStudioIntegrationStatusInvalid is the reason that's set when the AppStudio integration gets into an invalid state.
AppStudioIntegrationStatusInvalid = "Invalid"
// AppStudioIntegrationStatusErrorOccured is the reason that's set when the AppStudio integration gets into an error state.
AppStudioIntegrationStatusErrorOccured = "ErrorOccured"
// AppStudioIntegrationStatusInProgress is the reason that's set when the AppStudio tests gets into an in progress state.
AppStudioIntegrationStatusInProgress = "InProgress"
// AppStudioIntegrationStatusFinished is the reason that's set when the AppStudio tests finish.
AppStudioIntegrationStatusFinished = "Finished"
// AppStudioIntegrationStatusCancelled is the reason that's set when the AppStudio tests pipelinerun gets cancelled.
AppStudioIntegrationStatusCancelled = "CancelledRunFinally"
// PRGroupCancelledAnnotation helps track PipelineRuns that were stopped before completion.
PRGroupCancelledAnnotation = PipelinesAsCodePrefix + "/cancelled"
// IntegrationTestStatusPendingGithub is the status reported to github when integration test is in a queue
IntegrationTestStatusPendingGithub = "pending"
// IntegrationTestStatusSuccessGithub is the status reported to github when integration test succeed
IntegrationTestStatusSuccessGithub = "success"
// IntegrationTestStatusFailureGithub is the status reported to github when integration test fail
IntegrationTestStatusFailureGithub = "failure"
// IntegrationTestStatusErrorGithub is the status reported to github when integration test experience error
IntegrationTestStatusErrorGithub = "error"
// IntegrationTestStatusInProgressGithub is the status reported to github when integration test is in progress
IntegrationTestStatusInProgressGithub = "in_progress"
// IntegrationTestStatusCancelledGithub is the status reported to github when integration test is cancelled
IntegrationTestStatusCancelledGithub = "cancelled"
// IntegrationTestStatusNeutralGithub is the status reported to github when integration test is neutral
IntegrationTestStatusNeutralGithub = "neutral"
// ComponentNameForGroupSnapshot is the component name used for group snapshots
ComponentNameForGroupSnapshot = "pr group"
// FailedToCreateGroupSnapshotMsg is the message when group snapshot creation fails
FailedToCreateGroupSnapshotMsg = "Failed to create group snapshot for pr group"
// GroupSnapshotCreationFailureReported is the message when group snapshot creation failure is reported to git provider
GroupSnapshotCreationFailureReported = "group snapshot creation failure is reported to git provider"
// Annotation that tells the integration service to skip checks for whether a build or snapshot is superseded
IgnoreSupersessionAnnotation = TestLabelPrefix + "/ignore-supersession"
// PRStatusMerged indicates that the PR has been merged
PRStatusMerged = "merged"
// Success is the success status value
Success = "Success"
// AddedToGlobalCandidateListAnnotation is the annotation for marking if Snapshot/build PLR's component was added to the global candidate list.
AddedToGlobalCandidateListAnnotation = "test.appstudio.openshift.io/added-to-global-candidate-list"
// GitCommentPolicyAnnotation is the annotation to control git comment policy for the component
GitCommentPolicyAnnotation = "test.appstudio.openshift.io/comment_strategy"
// GitCommentPolicyAllDisabled is the value to disable all test comments for the component got pac repository
GitCommentPolicyAllDisabled = "disable_all"
// ChildSnapshotAnnotation is used in nested snapshots. Denotes the name of the child snapshot whose creation triggered the creation of the snapshot
// on which the annotation is set
ChildSnapshotAnnotation = TestLabelPrefix + "/child-snapshot"
)
var (
// SnapshotComponentLabel contains the name of the updated Snapshot component - it should match the pipeline label.
SnapshotComponentLabel = tektonconsts.ComponentNameLabel
)
const (
// maxPrefixLength is the maximum length of the prefix for the snapshot name
// When a suffix is used for collision handling, reduce by 3 to accommodate the 2-char suffix and extra dash
// Format: {prefix}-{YYYYMMDD}-{HHMMSS}-{mmm}[-{suffix}]
// Without suffix: 43 + 1 + 8 + 1 + 6 + 1 + 3 = 63 chars
// With suffix: 40 + 1 + 8 + 1 + 6 + 1 + 3 + 1 + 2 = 63 chars
maxPrefixLength = 43
maxPrefixLengthWithSuffix = 40 // 43 - 3 (2 for suffix + 1 for extra dash)
)
// ComponentSnapshotInfo contains data about the component snapshots' info in group snapshot
type ComponentSnapshotInfo struct {
// Namespace
Namespace string `json:"namespace"`
// Component name
Component string `json:"component"`
// The build PLR name building the container image triggered by pull request
BuildPipelineRun string `json:"buildPipelineRun"`
// The built component snapshot from build PLR
Snapshot string `json:"snapshot"`
// The repo url for each component
RepoUrl string `json:"repoUrl"`
// Pull/Merge request number for updated component
PullRequestNumber string `json:"pullRequestNumber"`
// Version of the component
Version string `json:"version"`
}
// AddedToGlobalCandidateListStatus contains the information which will be added to build PLR or override snapshot about updating GCL
type AddedToGlobalCandidateListStatus struct {
// Result for AddedToGlobalCandidateList
Result bool `json:"result"`
// Reason for AddedToGlobalCandidateList result
Reason string `json:"reason"`
// LastUpdatedTime for AddedToGlobalCandidateList
LastUpdatedTime string `json:"lastupdatedtime"`
}
const componentSnapshotInfosSchema = `{
"$schema": "http://json-schema.org/draft/2020-12/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"namespace": {
"type": "string"
},
"component": {
"type": "string"
},
"buildPipelineRun": {
"type": "string"
},
"snapshot": {
"type": "string"
},
"repoUrl": {
"type": "string"
},
"pullRequestNumber": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": ["namespace", "component", "buildPipelineRun", "snapshot"]
}
}`
// IsSnapshotMarkedAsPassed returns true if snapshot is marked as passed
func IsSnapshotMarkedAsPassed(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, AppStudioTestSucceededCondition, metav1.ConditionTrue, "")
}
// MarkSnapshotAsPassed updates the AppStudio Test succeeded condition for the Snapshot to passed.
// If the patch command fails, an error will be returned.
func MarkSnapshotAsPassed(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
patch := client.MergeFrom(snapshot.DeepCopy())
condition := metav1.Condition{
Type: AppStudioTestSucceededCondition,
Status: metav1.ConditionTrue,
Reason: AppStudioTestSucceededConditionSatisfied,
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
err := adapterClient.Status().Patch(ctx, snapshot, patch)
if err != nil {
return err
}
snapshotCompletionTime := &metav1.Time{Time: time.Now()}
go metrics.RegisterCompletedSnapshot(condition.Type, condition.Reason, snapshot.GetCreationTimestamp(), snapshotCompletionTime)
return nil
}
// IsSnapshotMarkedAsFailed returns true if snapshot is marked as failed
func IsSnapshotMarkedAsFailed(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, AppStudioTestSucceededCondition, metav1.ConditionFalse, "")
}
// MarkSnapshotAsFailed updates the AppStudio Test succeeded condition for the Snapshot to failed.
// If the patch command fails, an error will be returned.
func MarkSnapshotAsFailed(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
patch := client.MergeFrom(snapshot.DeepCopy())
condition := metav1.Condition{
Type: AppStudioTestSucceededCondition,
Status: metav1.ConditionFalse,
Reason: AppStudioTestSucceededConditionFailed,
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
err := adapterClient.Status().Patch(ctx, snapshot, patch)
if err != nil {
return err
}
snapshotCompletionTime := &metav1.Time{Time: time.Now()}
go metrics.RegisterCompletedSnapshot(condition.Type, condition.Reason, snapshot.GetCreationTimestamp(), snapshotCompletionTime)
return nil
}
// MarkSnapshotAsCanceled updates the AppStudio Test canceled condition for the Snapshot to 'Canceled'.
// If the patch command fails, an error will be returned.
func MarkSnapshotAsCanceled(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
patch := client.MergeFrom(snapshot.DeepCopy())
condition := metav1.Condition{
Type: AppStudioIntegrationStatusCondition,
Status: metav1.ConditionTrue,
Reason: AppStudioIntegrationStatusCanceled,
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
err := adapterClient.Status().Patch(ctx, snapshot, patch)
if err != nil {
return err
}
snapshotCompletionTime := &metav1.Time{Time: time.Now()}
go metrics.RegisterCompletedSnapshot(condition.Type, condition.Reason, snapshot.GetCreationTimestamp(), snapshotCompletionTime)
return nil
}
// IsSnapshotMarkedAsCanceled returns true if snapshot is marked as AppStudioIntegrationStatusCanceled
func IsSnapshotMarkedAsCanceled(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, AppStudioIntegrationStatusCondition, metav1.ConditionTrue, AppStudioIntegrationStatusCanceled)
}
// MarkSnapshotAsInvalid updates the AppStudio integration status condition for the Snapshot to invalid.
// If the patch command fails, an error will be returned.
func MarkSnapshotAsInvalid(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
patch := client.MergeFrom(snapshot.DeepCopy())
SetSnapshotIntegrationStatusAsInvalid(snapshot, message)
err := adapterClient.Status().Patch(ctx, snapshot, patch)
if err != nil {
return err
}
return nil
}
// IsSnapshotMarkedAsInvalid returns true if snapshot is marked as failed
func IsSnapshotMarkedAsInvalid(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, AppStudioIntegrationStatusCondition, metav1.ConditionFalse, AppStudioIntegrationStatusInvalid)
}
// SetSnapshotIntegrationStatusAsInvalid sets the AppStudio integration status condition for the Snapshot to invalid.
func SetSnapshotIntegrationStatusAsInvalid(snapshot *applicationapiv1alpha1.Snapshot, message string) {
condition := metav1.Condition{
Type: AppStudioIntegrationStatusCondition,
Status: metav1.ConditionFalse,
Reason: AppStudioIntegrationStatusInvalid,
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
go metrics.RegisterInvalidSnapshot(AppStudioIntegrationStatusCondition, AppStudioIntegrationStatusInvalid)
}
// SetSnapshotIntegrationStatusAsError sets the AppStudio integration status condition for the Snapshot to error.
func SetSnapshotIntegrationStatusAsError(snapshot *applicationapiv1alpha1.Snapshot, message string) {
condition := metav1.Condition{
Type: AppStudioIntegrationStatusCondition,
Status: metav1.ConditionFalse,
Reason: AppStudioIntegrationStatusErrorOccured,
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
}
// MarkSnapshotIntegrationStatusAsInProgress sets the AppStudio integration status condition for the Snapshot to In Progress.
func MarkSnapshotIntegrationStatusAsInProgress(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
log := log.FromContext(ctx)
patch := client.MergeFrom(snapshot.DeepCopy())
meta.SetStatusCondition(&snapshot.Status.Conditions, metav1.Condition{
Type: AppStudioIntegrationStatusCondition,
Status: metav1.ConditionUnknown,
Reason: AppStudioIntegrationStatusInProgress,
Message: message,
})
err := adapterClient.Status().Patch(ctx, snapshot, patch)
if err != nil {
return err
}
snapshotInProgressTime := &metav1.Time{Time: time.Now()}
if metadata.HasLabel(snapshot, BuildPipelineRunFinishTimeLabel) {
buildPipelineRunFinishTimeStr := snapshot.Labels[BuildPipelineRunFinishTimeLabel]
buildPipelineRunFinishTimeInt, _ := strconv.ParseInt(buildPipelineRunFinishTimeStr, 10, 64)
buildPipelineRunFinishTime := time.Unix(buildPipelineRunFinishTimeInt, 0)
buildPipelineRunFinishTimeMeta := &metav1.Time{Time: buildPipelineRunFinishTime}
duration := snapshotInProgressTime.Sub(buildPipelineRunFinishTimeMeta.Time)
log.Info("Integration Service Response time (integration_svc_response_seconds)",
"snapshot.name", snapshot.Name,
"pipelinerun.name", snapshot.Labels[BuildPipelineRunNameLabel],
"duration", duration,
)
go metrics.RegisterIntegrationResponse(duration)
}
return nil
}
// PrepareToRegisterIntegrationPipelineRunStarted is to do preparation before calling RegisterPipelineRunStarted
// Don't use this function for PLR re-runs
func PrepareToRegisterIntegrationPipelineRunStarted(snapshot *applicationapiv1alpha1.Snapshot) {
pipelineRunStartTime := &metav1.Time{Time: time.Now()}
go metrics.RegisterPipelineRunStarted(snapshot.GetCreationTimestamp(), pipelineRunStartTime)
}
// MarkSnapshotIntegrationStatusAsFinished sets the AppStudio integration status condition for the Snapshot to Finished.
func MarkSnapshotIntegrationStatusAsFinished(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
patch := client.MergeFrom(snapshot.DeepCopy())
condition := metav1.Condition{
Type: AppStudioIntegrationStatusCondition,
Status: metav1.ConditionTrue,
Reason: AppStudioIntegrationStatusFinished,
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
err := adapterClient.Status().Patch(ctx, snapshot, patch)
if err != nil {
return err
}
return nil
}
// IsSnapshotNotStarted checks if the AppStudio Integration Status condition is not in progress status.
func IsSnapshotNotStarted(snapshot *applicationapiv1alpha1.Snapshot) bool {
condition := meta.FindStatusCondition(snapshot.Status.Conditions, AppStudioIntegrationStatusCondition)
if condition == nil {
condition = meta.FindStatusCondition(snapshot.Status.Conditions, LegacyIntegrationStatusCondition)
}
if condition == nil || condition.Reason != AppStudioIntegrationStatusInProgress {
return true
}
return false
}
// IsSnapshotError if the AppStudio Integration Status condition is in ErrorOcurred status.
func IsSnapshotError(snapshot *applicationapiv1alpha1.Snapshot) bool {
condition := meta.FindStatusCondition(snapshot.Status.Conditions, AppStudioIntegrationStatusCondition)
if condition == nil {
condition = meta.FindStatusCondition(snapshot.Status.Conditions, LegacyIntegrationStatusCondition)
}
if condition.Reason == AppStudioIntegrationStatusErrorOccured {
return true
}
return false
}
// IsSnapshotValid checks if the AppStudio Integration Status condition is not invalid.
func IsSnapshotValid(snapshot *applicationapiv1alpha1.Snapshot) bool {
condition := meta.FindStatusCondition(snapshot.Status.Conditions, AppStudioIntegrationStatusCondition)
if condition == nil {
condition = meta.FindStatusCondition(snapshot.Status.Conditions, LegacyIntegrationStatusCondition)
}
if condition == nil || condition.Reason != AppStudioIntegrationStatusInvalid {
return true
}
return false
}
// IsSnapshotIntegrationStatusMarkedAsFinished returns true if snapshot is marked as finished or canceled
func IsSnapshotIntegrationStatusMarkedAsFinished(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, AppStudioIntegrationStatusCondition, metav1.ConditionTrue, AppStudioIntegrationStatusFinished) || IsSnapshotStatusConditionSet(snapshot, AppStudioIntegrationStatusCondition, metav1.ConditionTrue, AppStudioIntegrationStatusCanceled)
}
// IsSnapshotStatusConditionSet checks if the condition with the conditionType in the status of Snapshot has been marked as the conditionStatus and reason.
func IsSnapshotStatusConditionSet(snapshot *applicationapiv1alpha1.Snapshot, conditionType string, conditionStatus metav1.ConditionStatus, reason string) bool {
condition := meta.FindStatusCondition(snapshot.Status.Conditions, conditionType)
if condition == nil && conditionType == AppStudioTestSucceededCondition {
condition = meta.FindStatusCondition(snapshot.Status.Conditions, LegacyTestSucceededCondition)
}
if condition == nil && conditionType == AppStudioIntegrationStatusCondition {
condition = meta.FindStatusCondition(snapshot.Status.Conditions, LegacyIntegrationStatusCondition)
}
if condition == nil || condition.Status != conditionStatus {
return false
}
if reason != "" && reason != condition.Reason {
return false
}
return true
}
// IsSnapshotMarkedAsAutoReleased returns true if snapshot is marked as deployed to root environments
func IsSnapshotMarkedAsAutoReleased(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, SnapshotAutoReleasedCondition, metav1.ConditionTrue, "")
}
// MarkSnapshotAsAutoReleased updates the SnapshotAutoReleasedCondition for the Snapshot to 'AutoReleased'.
// If the patch command fails, an error will be returned.
func MarkSnapshotAsAutoReleased(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
patch := client.MergeFrom(snapshot.DeepCopy())
condition := metav1.Condition{
Type: SnapshotAutoReleasedCondition,
Status: metav1.ConditionTrue,
Reason: "AutoReleased",
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
err := adapterClient.Status().Patch(ctx, snapshot, patch)
if err != nil {
return err
}
return nil
}
func ParentSnapshotsCreated(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, ParentSnapshotsCreatedCondition, metav1.ConditionTrue, "")
}
func SetParentSnapshotsCreatedCondition(snapshot *applicationapiv1alpha1.Snapshot, status metav1.ConditionStatus, reason, message string) {
condition := metav1.Condition{
Type: ParentSnapshotsCreatedCondition,
Status: status,
Reason: reason,
Message: message,
}
meta.SetStatusCondition(&snapshot.Status.Conditions, condition)
}
func AddParentSnapshotDataToSnapshotStatus(snapshot *applicationapiv1alpha1.Snapshot, created bool, parentCG, parentSnapshot, message string) {
if snapshot.Status.ParentSnapshots == nil {
snapshot.Status.ParentSnapshots = make(map[string]applicationapiv1alpha1.ParentSnapshotData)
}
snapshot.Status.ParentSnapshots[parentCG] = applicationapiv1alpha1.ParentSnapshotData{
Created: created,
Name: parentSnapshot,
Message: message,
}
}
// IsSnapshotMarkedAsAddedToGlobalCandidateList returns true if snapshot's AddedToGlobalCandidateListAnnotation result is marked as true to global candidate list
func IsSnapshotMarkedAsAddedToGlobalCandidateList(snapshot *applicationapiv1alpha1.Snapshot) bool {
annotationValue, ok := snapshot.GetAnnotations()[AddedToGlobalCandidateListAnnotation]
if !ok || annotationValue == "" {
return false
}
var addedToGlobalCandidateListStatus AddedToGlobalCandidateListStatus
if err := json.Unmarshal([]byte(annotationValue), &addedToGlobalCandidateListStatus); err != nil {
return false
}
return addedToGlobalCandidateListStatus.Result
}
// IsSnapshotMarkedAsAddedToGlobalCandidateList_Legacy is old way to track GCL update status by checking statusCondition and returns true if snapshot's component is marked as added to global candidate list
func IsSnapshotMarkedAsAddedToGlobalCandidateList_Legacy(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, SnapshotAddedToGlobalCandidateListCondition, metav1.ConditionTrue, "")
}
// MarkSnapshotAsAddedToGlobalCandidateList updates the AddedToGlobalCandidateListAnnotation for the Snapshot.
// If the patch command fails, an error will be returned.
func MarkSnapshotAsAddedToGlobalCandidateList(ctx context.Context, adapterClient client.Client, snapshot *applicationapiv1alpha1.Snapshot, message string) error {
return AnnotateSnapshot(ctx, snapshot, AddedToGlobalCandidateListAnnotation, message, adapterClient)
}
// ValidateImageDigest checks if image url contains valid digest, return error if check fails
func ValidateImageDigest(imageUrl string) error {
_, err := name.NewDigest(imageUrl)
return err
}
// HaveGitSource checks if snapshotComponent contains non-empty source.git field
// and have both url and revision fields defined
func HaveGitSource(snapshotComponent applicationapiv1alpha1.SnapshotComponent) bool {
return reflect.ValueOf(snapshotComponent.Source).IsValid() && snapshotComponent.Source.GitSource != nil &&
snapshotComponent.Source.GitSource.Revision != "" && snapshotComponent.Source.GitSource.URL != ""
}
// HaveGitSourceInComponent checks if component contains non-empty source.git field
// and have both url and revision fields defined
func HaveGitSourceInComponent(component applicationapiv1alpha1.Component) bool {
return reflect.ValueOf(component.Spec.Source).IsValid() && component.Spec.Source.GitSource != nil &&
component.Spec.Source.GitSource.Revision != "" && component.Spec.Source.GitSource.URL != ""
}
// HaveAppStudioTestsFinished checks if the AppStudio tests have finished by checking if the AppStudio Test Succeeded condition is set.
func HaveAppStudioTestsFinished(snapshot *applicationapiv1alpha1.Snapshot) bool {
statusCondition := meta.FindStatusCondition(snapshot.Status.Conditions, AppStudioTestSucceededCondition)
if statusCondition == nil {
statusCondition = meta.FindStatusCondition(snapshot.Status.Conditions, LegacyTestSucceededCondition)
return statusCondition != nil && statusCondition.Status != metav1.ConditionUnknown
}
return statusCondition != nil && statusCondition.Status != metav1.ConditionUnknown
}
// HaveAppStudioTestsSucceeded checks if the AppStudio tests have finished by checking if the AppStudio Test Succeeded condition is set.
func HaveAppStudioTestsSucceeded(snapshot *applicationapiv1alpha1.Snapshot) bool {
if meta.FindStatusCondition(snapshot.Status.Conditions, AppStudioTestSucceededCondition) == nil {
return meta.IsStatusConditionTrue(snapshot.Status.Conditions, LegacyTestSucceededCondition)
}
return meta.IsStatusConditionTrue(snapshot.Status.Conditions, AppStudioTestSucceededCondition)
}
// GetTestSucceededCondition checks status of tests on the snapshot
func GetTestSucceededCondition(snapshot *applicationapiv1alpha1.Snapshot) (condition *metav1.Condition, ok bool) {
condition = meta.FindStatusCondition(snapshot.Status.Conditions, AppStudioTestSucceededCondition)
if condition == nil {
condition = meta.FindStatusCondition(snapshot.Status.Conditions, LegacyTestSucceededCondition)
}
ok = (condition != nil && condition.Status != metav1.ConditionUnknown)
return
}
// GetAppStudioTestsFinishedTime finds the timestamp of tests succeeded condition
func GetAppStudioTestsFinishedTime(snapshot *applicationapiv1alpha1.Snapshot) (metav1.Time, bool) {
condition, ok := GetTestSucceededCondition(snapshot)
if ok {
return condition.LastTransitionTime, true
}
return metav1.Time{}, false
}
// CanSnapshotBePromoted checks if the Snapshot in question can be promoted for deployment and release.
func CanSnapshotBePromoted(snapshot *applicationapiv1alpha1.Snapshot) (bool, []string) {
canBePromoted := true
reasons := make([]string, 0)
if !HaveAppStudioTestsFinished(snapshot) {
canBePromoted = false
reasons = append(reasons, "the Snapshot has not yet finished testing")
} else {
if !HaveAppStudioTestsSucceeded(snapshot) {
canBePromoted = false
reasons = append(reasons, "the Snapshot hasn't passed all required integration tests")
}
if !IsSnapshotValid(snapshot) {
canBePromoted = false
reasons = append(reasons, "the Snapshot is invalid")
}
if !IsSnapshotCreatedByPACPushEvent(snapshot) {
canBePromoted = false
reasons = append(reasons, "the Snapshot was created for a PaC pull request event")
}
if IsSnapshotAutoReleaseDisabled(snapshot) {
canBePromoted = false
reasons = append(reasons, fmt.Sprintf("the Snapshot '%s' label is 'false'", AutoReleaseLabel))
}
if IsGroupSnapshot(snapshot) {
canBePromoted = false
reasons = append(reasons, "the Snapshot is group snapshot")
}
}
return canBePromoted, reasons
}
// NewSnapshot creates a new snapshot based on the supplied application and components
// TODO: Remove this function once application model is fully deprecated [APPLICATION] - a matching function which supports ComponentGroups is located in the snapshot package
func NewSnapshot(application *applicationapiv1alpha1.Application, snapshotComponents *[]applicationapiv1alpha1.SnapshotComponent) *applicationapiv1alpha1.Snapshot {
// Use fallback timestamp (current time) - will be overridden in prepareSnapshotForPipelineRun
// if BuildPipelineRunStartTime is available
fallbackTimestamp := time.Now().UnixMilli()
snapshot := &applicationapiv1alpha1.Snapshot{
ObjectMeta: metav1.ObjectMeta{
Name: GenerateSnapshotNameWithTimestamp(application.Name, fallbackTimestamp),
Namespace: application.Namespace,
},
Spec: applicationapiv1alpha1.SnapshotSpec{
Application: application.Name,
Components: *snapshotComponents,
},
}
return snapshot
}
// CompareSnapshots compares two Snapshots and returns boolean true if their images match exactly.
func CompareSnapshots(expectedSnapshot *applicationapiv1alpha1.Snapshot, foundSnapshot *applicationapiv1alpha1.Snapshot) bool {
// Check if the snapshots are created by the same event type
if !IsSnapshotCreatedBySamePACEvent(expectedSnapshot, foundSnapshot) {
return false
}
// If the number of components doesn't match, we immediately know that the snapshots are not equal.
if len(expectedSnapshot.Spec.Components) != len(foundSnapshot.Spec.Components) {
return false
}
// Check if all Component information matches, including the containerImage status field
for _, expectedSnapshotComponent := range expectedSnapshot.Spec.Components {
foundImage := false
for _, foundSnapshotComponent := range foundSnapshot.Spec.Components {
if reflect.DeepEqual(expectedSnapshotComponent, foundSnapshotComponent) {
foundImage = true
break
}
}
if !foundImage {
return false
}
}
return true
}
func IsSnapshotCreatedByPACMergeQueueEvent(snapshot *applicationapiv1alpha1.Snapshot) bool {
if branch, found := snapshot.Annotations[PipelineAsCodeSourceBranchAnnotation]; found {
if strings.HasPrefix(strings.TrimPrefix(branch, GitRefBranchPrefix), PipelineAsCodeGitHubMergeQueueBranchPrefix) {
return true
}
}
return false
}
// IsSnapshotCreatedByPACPushEvent checks if a snapshot has label PipelineAsCodeEventTypeLabel and with push value
// if the label doesn't exist for some manual snapshot
func IsSnapshotCreatedByPACPushEvent(snapshot *applicationapiv1alpha1.Snapshot) bool {
return !IsSnapshotCreatedByPACMergeQueueEvent(snapshot) && !IsGroupSnapshot(snapshot) &&
(metadata.HasLabelWithValue(snapshot, PipelineAsCodeEventTypeLabel, PipelineAsCodePushType) ||
metadata.HasLabelWithValue(snapshot, PipelineAsCodeEventTypeLabel, PipelineAsCodeGLPushType) ||
!metadata.HasLabel(snapshot, PipelineAsCodeEventTypeLabel) ||
!metadata.HasLabel(snapshot, PipelineAsCodePullRequestAnnotation))
}
// IsSnapshotAutoReleaseDisabled checks if a snapshot has a AutoReleaseLabel label and if its value is "false"
func IsSnapshotAutoReleaseDisabled(snapshot *applicationapiv1alpha1.Snapshot) bool {
return metadata.HasLabelWithValue(snapshot, AutoReleaseLabel, "false")
}
// IsSnapshotCreatedBySamePACEvent checks if the two snapshot are created by the same PAC event
// or they don't have event type
func IsSnapshotCreatedBySamePACEvent(snapshot1, snapshot2 *applicationapiv1alpha1.Snapshot) bool {
value1, ok1 := snapshot1.GetLabels()[PipelineAsCodeEventTypeLabel]
value2, ok2 := snapshot2.GetLabels()[PipelineAsCodeEventTypeLabel]
// if label exists and two snapshots have the same value
if ok1 && ok2 && value1 == value2 {
return true
}
// if label doesn't exist in two snapshot
if !ok1 && !ok2 {
return true
}
return false
}
// HasSnapshotTestingChangedToFinished returns a boolean indicating whether the Snapshot testing status has
// changed to finished. If the objects passed to this function are not Snapshots, the function will return false.
func HasSnapshotTestingChangedToFinished(objectOld, objectNew client.Object) bool {
if oldSnapshot, ok := objectOld.(*applicationapiv1alpha1.Snapshot); ok {
if newSnapshot, ok := objectNew.(*applicationapiv1alpha1.Snapshot); ok {
return !HaveAppStudioTestsFinished(oldSnapshot) && HaveAppStudioTestsFinished(newSnapshot)
}
}
return false
}
// HasSnapshotTestAnnotationChanged returns a boolean indicating whether the Snapshot annotation has
// changed. If the objects passed to this function are not Snapshots, the function will return false.
func HasSnapshotTestAnnotationChanged(objectOld, objectNew client.Object) bool {
if oldSnapshot, ok := objectOld.(*applicationapiv1alpha1.Snapshot); ok {
if newSnapshot, ok := objectNew.(*applicationapiv1alpha1.Snapshot); ok {
// update for integration test status change
if !metadata.HasAnnotation(oldSnapshot, SnapshotTestsStatusAnnotation) && metadata.HasAnnotation(newSnapshot, SnapshotTestsStatusAnnotation) {
return true
}
if old_value, ok := oldSnapshot.GetAnnotations()[SnapshotTestsStatusAnnotation]; ok {
if new_value, ok := newSnapshot.GetAnnotations()[SnapshotTestsStatusAnnotation]; ok {
if old_value != new_value {
return true
}
}
}
// update for component snapshot's group snapshot creation status
if !metadata.HasAnnotation(oldSnapshot, PRGroupCreationAnnotation) && metadata.HasAnnotation(newSnapshot, PRGroupCreationAnnotation) {
return true
}
if old_value, ok := oldSnapshot.GetAnnotations()[PRGroupCreationAnnotation]; ok {
if new_value, ok := newSnapshot.GetAnnotations()[PRGroupCreationAnnotation]; ok {
if old_value != new_value {
return true
}
}
}
}
}
return false
}
// HasSnapshotRerunLabelChanged returns a boolean indicating whether the Snapshot label for re-running
// integration test has changed. If the objects passed to this function are not Snapshots, the function will return false.
func HasSnapshotRerunLabelChanged(objectOld, objectNew client.Object) bool {
if oldSnapshot, ok := objectOld.(*applicationapiv1alpha1.Snapshot); ok {
if newSnapshot, ok := objectNew.(*applicationapiv1alpha1.Snapshot); ok {
if !metadata.HasLabel(oldSnapshot, SnapshotIntegrationTestRun) && metadata.HasLabel(newSnapshot, SnapshotIntegrationTestRun) {
return true
}
if old_value, ok := oldSnapshot.GetLabels()[SnapshotIntegrationTestRun]; ok {
if new_value, ok := newSnapshot.GetLabels()[SnapshotIntegrationTestRun]; ok {
if old_value != new_value {
return true
}
}
}
}
}
return false
}
// ExtractPullRequestNumberFromMergeQueueSnapshot attempts to extract the pull request number for the Snapshot
// If the pull request annotation is present, it returns it, otherwise it extracts it from the source branch name
func ExtractPullRequestNumberFromMergeQueueSnapshot(snapshot *applicationapiv1alpha1.Snapshot) string {
// Attempt to find the PaC pull request label or annotation first
if snapshot.Labels != nil && snapshot.Labels[PipelineAsCodePullRequestAnnotation] != "" {
return snapshot.Labels[PipelineAsCodePullRequestAnnotation]
}
if snapshot.Annotations != nil && snapshot.Annotations[PipelineAsCodePullRequestAnnotation] != "" {
return snapshot.Annotations[PipelineAsCodePullRequestAnnotation]
}
// If the PR number is not found above, attempt to extract it from the source branch name of the merge queue
// The branch should be in the format of 'gh-readonly-queue/{original_branch_name}/pr-{pull_request_number}-{sha}'
if branch, found := snapshot.Annotations[PipelineAsCodeSourceBranchAnnotation]; found {
// remove the refs/heads prefix to get the actual branch name
branchWithoutPrefix := strings.Split(strings.TrimPrefix(branch, GitRefBranchPrefix), "/")
if len(branchWithoutPrefix) > 1 {
branchSections := strings.Split(branchWithoutPrefix[len(branchWithoutPrefix)-1], "-")
if len(branchSections) > 1 && branchSections[0] == "pr" && branchSections[1] != "" {
return branchSections[1]
}
}
}
return ""
}