-
Notifications
You must be signed in to change notification settings - Fork 124
/
project_ref.go
3376 lines (3032 loc) · 121 KB
/
project_ref.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
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
package model
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"time"
"github.com/evergreen-ci/cocoa"
"github.com/evergreen-ci/evergreen"
"github.com/evergreen-ci/evergreen/apimodels"
"github.com/evergreen-ci/evergreen/db"
mgobson "github.com/evergreen-ci/evergreen/db/mgo/bson"
"github.com/evergreen-ci/evergreen/model/build"
"github.com/evergreen-ci/evergreen/model/commitqueue"
"github.com/evergreen-ci/evergreen/model/event"
"github.com/evergreen-ci/evergreen/model/parsley"
"github.com/evergreen-ci/evergreen/model/patch"
"github.com/evergreen-ci/evergreen/model/task"
"github.com/evergreen-ci/evergreen/model/user"
"github.com/evergreen-ci/evergreen/thirdparty"
"github.com/evergreen-ci/evergreen/util"
"github.com/evergreen-ci/gimlet"
"github.com/evergreen-ci/utility"
"github.com/mongodb/anser/bsonutil"
adb "github.com/mongodb/anser/db"
"github.com/mongodb/grip"
"github.com/mongodb/grip/message"
"github.com/mongodb/grip/recovery"
"github.com/mongodb/jasper"
"github.com/pkg/errors"
"github.com/robfig/cron"
"go.mongodb.org/mongo-driver/bson"
)
// ProjectRef contains Evergreen project-related settings which can be set
// independently of version control.
// Booleans that can be defined from both the repo and branch must be pointers, so that branch configurations can specify when to default to the repo.
type ProjectRef struct {
// Id is the unmodifiable unique ID for the configuration, used internally.
Id string `bson:"_id" json:"id" yaml:"id"`
// Identifier must be unique, but is modifiable. Used by users.
Identifier string `bson:"identifier" json:"identifier" yaml:"identifier"`
// RemotePath is the path to the Evergreen config file.
RemotePath string `bson:"remote_path" json:"remote_path" yaml:"remote_path"`
DisplayName string `bson:"display_name" json:"display_name,omitempty" yaml:"display_name"`
Enabled bool `bson:"enabled,omitempty" json:"enabled,omitempty" yaml:"enabled"`
Private *bool `bson:"private,omitempty" json:"private,omitempty" yaml:"private"`
Restricted *bool `bson:"restricted,omitempty" json:"restricted,omitempty" yaml:"restricted"`
Owner string `bson:"owner_name" json:"owner_name" yaml:"owner"`
Repo string `bson:"repo_name" json:"repo_name" yaml:"repo"`
Branch string `bson:"branch_name" json:"branch_name" yaml:"branch"`
PatchingDisabled *bool `bson:"patching_disabled,omitempty" json:"patching_disabled,omitempty"`
RepotrackerDisabled *bool `bson:"repotracker_disabled,omitempty" json:"repotracker_disabled,omitempty" yaml:"repotracker_disabled"`
DispatchingDisabled *bool `bson:"dispatching_disabled,omitempty" json:"dispatching_disabled,omitempty" yaml:"dispatching_disabled"`
StepbackDisabled *bool `bson:"stepback_disabled,omitempty" json:"stepback_disabled,omitempty" yaml:"stepback_disabled"`
StepbackBisect *bool `bson:"stepback_bisect,omitempty" json:"stepback_bisect,omitempty" yaml:"stepback_bisect"`
VersionControlEnabled *bool `bson:"version_control_enabled,omitempty" json:"version_control_enabled,omitempty" yaml:"version_control_enabled"`
PRTestingEnabled *bool `bson:"pr_testing_enabled,omitempty" json:"pr_testing_enabled,omitempty" yaml:"pr_testing_enabled"`
ManualPRTestingEnabled *bool `bson:"manual_pr_testing_enabled,omitempty" json:"manual_pr_testing_enabled,omitempty" yaml:"manual_pr_testing_enabled"`
GithubChecksEnabled *bool `bson:"github_checks_enabled,omitempty" json:"github_checks_enabled,omitempty" yaml:"github_checks_enabled"`
BatchTime int `bson:"batch_time" json:"batch_time" yaml:"batchtime"`
DeactivatePrevious *bool `bson:"deactivate_previous,omitempty" json:"deactivate_previous,omitempty" yaml:"deactivate_previous"`
NotifyOnBuildFailure *bool `bson:"notify_on_failure,omitempty" json:"notify_on_failure,omitempty"`
Triggers []TriggerDefinition `bson:"triggers" json:"triggers"`
// all aliases defined for the project
PatchTriggerAliases []patch.PatchTriggerDefinition `bson:"patch_trigger_aliases" json:"patch_trigger_aliases"`
// all PatchTriggerAliases applied to github patch intents
GithubTriggerAliases []string `bson:"github_trigger_aliases" json:"github_trigger_aliases"`
// OldestAllowedMergeBase is the commit hash of the oldest merge base on the target branch
// that PR patches can be created from.
OldestAllowedMergeBase string `bson:"oldest_allowed_merge_base" json:"oldest_allowed_merge_base"`
PeriodicBuilds []PeriodicBuildDefinition `bson:"periodic_builds" json:"periodic_builds"`
CommitQueue CommitQueueParams `bson:"commit_queue" json:"commit_queue" yaml:"commit_queue"`
// Admins contain a list of users who are able to access the projects page.
Admins []string `bson:"admins" json:"admins"`
// SpawnHostScriptPath is a path to a script to optionally be run by users on hosts triggered from tasks.
SpawnHostScriptPath string `bson:"spawn_host_script_path" json:"spawn_host_script_path" yaml:"spawn_host_script_path"`
// TracksPushEvents, if true indicates that Repotracker is triggered by Github PushEvents for this project.
// If a repo is enabled and this is what creates the hook, then TracksPushEvents will be set at the repo level.
TracksPushEvents *bool `bson:"tracks_push_events" json:"tracks_push_events" yaml:"tracks_push_events"`
// TaskSync holds settings for synchronizing task directories to S3.
TaskSync TaskSyncOptions `bson:"task_sync" json:"task_sync" yaml:"task_sync"`
// GitTagAuthorizedUsers contains a list of users who are able to create versions from git tags.
GitTagAuthorizedUsers []string `bson:"git_tag_authorized_users" json:"git_tag_authorized_users"`
GitTagAuthorizedTeams []string `bson:"git_tag_authorized_teams" json:"git_tag_authorized_teams"`
GitTagVersionsEnabled *bool `bson:"git_tag_versions_enabled,omitempty" json:"git_tag_versions_enabled,omitempty"`
// RepoDetails contain the details of the status of the consistency
// between what is in GitHub and what is in Evergreen
RepotrackerError *RepositoryErrorDetails `bson:"repotracker_error" json:"repotracker_error"`
// Disable task stats caching for this project.
DisabledStatsCache *bool `bson:"disabled_stats_cache,omitempty" json:"disabled_stats_cache,omitempty"`
// List of commands
// Lacks omitempty so that SetupCommands can be identified as either [] or nil in a ProjectSettingsEvent
WorkstationConfig WorkstationConfig `bson:"workstation_config" json:"workstation_config"`
// TaskAnnotationSettings holds settings for the file ticket button in the Task Annotations to call custom webhooks when clicked
TaskAnnotationSettings evergreen.AnnotationsSettings `bson:"task_annotation_settings,omitempty" json:"task_annotation_settings,omitempty"`
// Plugin settings
BuildBaronSettings evergreen.BuildBaronSettings `bson:"build_baron_settings,omitempty" json:"build_baron_settings,omitempty" yaml:"build_baron_settings,omitempty"`
PerfEnabled *bool `bson:"perf_enabled,omitempty" json:"perf_enabled,omitempty" yaml:"perf_enabled,omitempty"`
// Container settings
ContainerSizeDefinitions []ContainerResources `bson:"container_size_definitions,omitempty" json:"container_size_definitions,omitempty" yaml:"container_size_definitions,omitempty"`
ContainerSecrets []ContainerSecret `bson:"container_secrets,omitempty" json:"container_secrets,omitempty" yaml:"container_secrets,omitempty"`
// RepoRefId is the repo ref id that this project ref tracks, if any.
RepoRefId string `bson:"repo_ref_id" json:"repo_ref_id" yaml:"repo_ref_id"`
// The following fields are used by Evergreen and are not discoverable.
// Hidden determines whether or not the project is discoverable/tracked in the UI
Hidden *bool `bson:"hidden,omitempty" json:"hidden,omitempty"`
ExternalLinks []ExternalLink `bson:"external_links,omitempty" json:"external_links,omitempty" yaml:"external_links,omitempty"`
Banner ProjectBanner `bson:"banner,omitempty" json:"banner,omitempty" yaml:"banner,omitempty"`
// Filter/view settings
ProjectHealthView ProjectHealthView `bson:"project_health_view" json:"project_health_view" yaml:"project_health_view"`
ParsleyFilters []parsley.Filter `bson:"parsley_filters,omitempty" json:"parsley_filters,omitempty"`
}
type ProjectHealthView string
const (
ProjectHealthViewAll ProjectHealthView = "ALL"
ProjectHealthViewFailed ProjectHealthView = "FAILED"
)
type ProjectBanner struct {
Theme evergreen.BannerTheme `bson:"theme" json:"theme"`
Text string `bson:"text" json:"text"`
}
type ExternalLink struct {
DisplayName string `bson:"display_name,omitempty" json:"display_name,omitempty" yaml:"display_name,omitempty"`
Requesters []string `bson:"requesters,omitempty" json:"requesters,omitempty" yaml:"requesters,omitempty"`
URLTemplate string `bson:"url_template,omitempty" json:"url_template,omitempty" yaml:"url_template,omitempty"`
}
type MergeQueue string
const (
MergeQueueEvergreen MergeQueue = "EVERGREEN"
MergeQueueGitHub MergeQueue = "GITHUB"
)
type CommitQueueParams struct {
Enabled *bool `bson:"enabled" json:"enabled" yaml:"enabled"`
MergeMethod string `bson:"merge_method" json:"merge_method" yaml:"merge_method"`
MergeQueue MergeQueue `bson:"merge_queue" json:"merge_queue" yaml:"merge_queue"`
Message string `bson:"message,omitempty" json:"message,omitempty" yaml:"message"`
}
// TaskSyncOptions contains information about which features are allowed for
// syncing task directories to S3.
type TaskSyncOptions struct {
ConfigEnabled *bool `bson:"config_enabled" json:"config_enabled" yaml:"config_enabled"`
PatchEnabled *bool `bson:"patch_enabled" json:"patch_enabled" yaml:"patch_enabled"`
}
// RepositoryErrorDetails indicates whether or not there is an invalid revision and if there is one,
// what the guessed merge base revision is.
type RepositoryErrorDetails struct {
Exists bool `bson:"exists" json:"exists"`
InvalidRevision string `bson:"invalid_revision" json:"invalid_revision"`
MergeBaseRevision string `bson:"merge_base_revision" json:"merge_base_revision"`
}
type AlertConfig struct {
Provider string `bson:"provider" json:"provider"` //e.g. e-mail, flowdock, SMS
// Data contains provider-specific on how a notification should be delivered.
// Typed as bson.M so that the appropriate provider can parse out necessary details
Settings bson.M `bson:"settings" json:"settings"`
}
// ContainerResources specifies the computing resources given to the container.
// MemoryMB is the memory (in MB) that the container will be allocated, and
// CPU is the CPU units that will be allocated. 1024 CPU units is
// equivalent to 1vCPU.
type ContainerResources struct {
Name string `bson:"name,omitempty" json:"name" yaml:"name"`
MemoryMB int `bson:"memory_mb,omitempty" json:"memory_mb" yaml:"memory_mb"`
CPU int `bson:"cpu,omitempty" json:"cpu" yaml:"cpu"`
}
// ContainerSecret specifies the username and password required for authentication
// on a private image repository. The credential is saved in AWS Secrets Manager upon
// saving to the ProjectRef
type ContainerSecret struct {
// Name is the user-friendly display name of the secret.
Name string `bson:"name" json:"name" yaml:"name"`
// Type is the type of secret that is stored.
Type ContainerSecretType `bson:"type" json:"type" yaml:"type"`
// ExternalName is the name of the stored secret.
ExternalName string `bson:"external_name" json:"external_name" yaml:"external_name"`
// ExternalID is the unique resource identifier for the secret. This can be
// used to access and modify the secret.
ExternalID string `bson:"external_id" json:"external_id" yaml:"external_id"`
// Value is the plaintext value of the secret. This is not stored and must
// be retrieved using the external ID.
Value string `bson:"-" json:"-" yaml:"-"`
}
// ContainerSecretType represents a particular type of container secret, which
// designates its purpose.
type ContainerSecretType string
const (
// ContainerSecretPodSecret is a container secret representing the Evergreen
// agent's pod secret.
ContainerSecretPodSecret ContainerSecretType = "pod_secret"
// ContainerSecretRepoCreds is a container secret representing an image
// repository's credentials.
ContainerSecretRepoCreds ContainerSecretType = "repository_credentials"
)
// Validate checks that the container secret type is recognized.
func (t ContainerSecretType) Validate() error {
switch t {
case ContainerSecretPodSecret, ContainerSecretRepoCreds:
return nil
default:
return errors.Errorf("unrecognized container secret type '%s'", t)
}
}
type TriggerDefinition struct {
// completion of specified task(s) in the project listed here will cause a build in the current project
Project string `bson:"project" json:"project"`
Level string `bson:"level" json:"level"` //build or task
//used to enforce that only 1 version gets created from a given upstream commit + trigger combo
DefinitionID string `bson:"definition_id" json:"definition_id"`
// filters for this trigger
BuildVariantRegex string `bson:"variant_regex,omitempty" json:"variant_regex,omitempty"`
TaskRegex string `bson:"task_regex,omitempty" json:"task_regex,omitempty"`
Status string `bson:"status,omitempty" json:"status,omitempty"`
DateCutoff *int `bson:"date_cutoff,omitempty" json:"date_cutoff,omitempty"`
// definitions for tasks to run for this trigger
ConfigFile string `bson:"config_file,omitempty" json:"config_file,omitempty"`
Alias string `bson:"alias,omitempty" json:"alias,omitempty"`
UnscheduleDownstreamVersions bool `bson:"unschedule_downstream_versions,omitempty" json:"unschedule_downstream_versions,omitempty"`
}
type PeriodicBuildDefinition struct {
ID string `bson:"id" json:"id"`
ConfigFile string `bson:"config_file" json:"config_file"`
IntervalHours int `bson:"interval_hours" json:"interval_hours"`
Cron string `bson:"cron" json:"cron"`
Alias string `bson:"alias,omitempty" json:"alias,omitempty"`
Message string `bson:"message,omitempty" json:"message,omitempty"`
NextRunTime time.Time `bson:"next_run_time,omitempty" json:"next_run_time,omitempty"`
}
type WorkstationConfig struct {
SetupCommands []WorkstationSetupCommand `bson:"setup_commands" json:"setup_commands" yaml:"setup_commands"`
GitClone *bool `bson:"git_clone" json:"git_clone" yaml:"git_clone"`
}
type WorkstationSetupCommand struct {
Command string `bson:"command" json:"command" yaml:"command"`
Directory string `bson:"directory" json:"directory" yaml:"directory"`
}
type GithubProjectConflicts struct {
CommitQueueIdentifiers []string
PRTestingIdentifiers []string
CommitCheckIdentifiers []string
}
func (a AlertConfig) GetSettingsMap() map[string]string {
ret := make(map[string]string)
for k, v := range a.Settings {
ret[k] = fmt.Sprintf("%v", v)
}
return ret
}
type EmailAlertData struct {
Recipients []string `bson:"recipients"`
}
var (
// bson fields for the ProjectRef struct
ProjectRefIdKey = bsonutil.MustHaveTag(ProjectRef{}, "Id")
ProjectRefOwnerKey = bsonutil.MustHaveTag(ProjectRef{}, "Owner")
ProjectRefRepoKey = bsonutil.MustHaveTag(ProjectRef{}, "Repo")
ProjectRefBranchKey = bsonutil.MustHaveTag(ProjectRef{}, "Branch")
ProjectRefEnabledKey = bsonutil.MustHaveTag(ProjectRef{}, "Enabled")
ProjectRefPrivateKey = bsonutil.MustHaveTag(ProjectRef{}, "Private")
ProjectRefRestrictedKey = bsonutil.MustHaveTag(ProjectRef{}, "Restricted")
ProjectRefBatchTimeKey = bsonutil.MustHaveTag(ProjectRef{}, "BatchTime")
ProjectRefIdentifierKey = bsonutil.MustHaveTag(ProjectRef{}, "Identifier")
ProjectRefRepoRefIdKey = bsonutil.MustHaveTag(ProjectRef{}, "RepoRefId")
ProjectRefDisplayNameKey = bsonutil.MustHaveTag(ProjectRef{}, "DisplayName")
ProjectRefDeactivatePreviousKey = bsonutil.MustHaveTag(ProjectRef{}, "DeactivatePrevious")
ProjectRefRemotePathKey = bsonutil.MustHaveTag(ProjectRef{}, "RemotePath")
ProjectRefHiddenKey = bsonutil.MustHaveTag(ProjectRef{}, "Hidden")
ProjectRefRepotrackerErrorKey = bsonutil.MustHaveTag(ProjectRef{}, "RepotrackerError")
ProjectRefDisabledStatsCacheKey = bsonutil.MustHaveTag(ProjectRef{}, "DisabledStatsCache")
ProjectRefAdminsKey = bsonutil.MustHaveTag(ProjectRef{}, "Admins")
ProjectRefGitTagAuthorizedUsersKey = bsonutil.MustHaveTag(ProjectRef{}, "GitTagAuthorizedUsers")
ProjectRefGitTagAuthorizedTeamsKey = bsonutil.MustHaveTag(ProjectRef{}, "GitTagAuthorizedTeams")
ProjectRefTracksPushEventsKey = bsonutil.MustHaveTag(ProjectRef{}, "TracksPushEvents")
projectRefPRTestingEnabledKey = bsonutil.MustHaveTag(ProjectRef{}, "PRTestingEnabled")
projectRefManualPRTestingEnabledKey = bsonutil.MustHaveTag(ProjectRef{}, "ManualPRTestingEnabled")
projectRefGithubChecksEnabledKey = bsonutil.MustHaveTag(ProjectRef{}, "GithubChecksEnabled")
projectRefGitTagVersionsEnabledKey = bsonutil.MustHaveTag(ProjectRef{}, "GitTagVersionsEnabled")
projectRefRepotrackerDisabledKey = bsonutil.MustHaveTag(ProjectRef{}, "RepotrackerDisabled")
projectRefCommitQueueKey = bsonutil.MustHaveTag(ProjectRef{}, "CommitQueue")
projectRefTaskSyncKey = bsonutil.MustHaveTag(ProjectRef{}, "TaskSync")
projectRefPatchingDisabledKey = bsonutil.MustHaveTag(ProjectRef{}, "PatchingDisabled")
projectRefDispatchingDisabledKey = bsonutil.MustHaveTag(ProjectRef{}, "DispatchingDisabled")
projectRefStepbackDisabledKey = bsonutil.MustHaveTag(ProjectRef{}, "StepbackDisabled")
projectRefStepbackBisectKey = bsonutil.MustHaveTag(ProjectRef{}, "StepbackBisect")
projectRefVersionControlEnabledKey = bsonutil.MustHaveTag(ProjectRef{}, "VersionControlEnabled")
projectRefNotifyOnFailureKey = bsonutil.MustHaveTag(ProjectRef{}, "NotifyOnBuildFailure")
projectRefSpawnHostScriptPathKey = bsonutil.MustHaveTag(ProjectRef{}, "SpawnHostScriptPath")
projectRefTriggersKey = bsonutil.MustHaveTag(ProjectRef{}, "Triggers")
projectRefPatchTriggerAliasesKey = bsonutil.MustHaveTag(ProjectRef{}, "PatchTriggerAliases")
projectRefGithubTriggerAliasesKey = bsonutil.MustHaveTag(ProjectRef{}, "GithubTriggerAliases")
projectRefPeriodicBuildsKey = bsonutil.MustHaveTag(ProjectRef{}, "PeriodicBuilds")
projectRefOldestAllowedMergeBaseKey = bsonutil.MustHaveTag(ProjectRef{}, "OldestAllowedMergeBase")
projectRefWorkstationConfigKey = bsonutil.MustHaveTag(ProjectRef{}, "WorkstationConfig")
projectRefTaskAnnotationSettingsKey = bsonutil.MustHaveTag(ProjectRef{}, "TaskAnnotationSettings")
projectRefBuildBaronSettingsKey = bsonutil.MustHaveTag(ProjectRef{}, "BuildBaronSettings")
projectRefPerfEnabledKey = bsonutil.MustHaveTag(ProjectRef{}, "PerfEnabled")
projectRefContainerSecretsKey = bsonutil.MustHaveTag(ProjectRef{}, "ContainerSecrets")
projectRefContainerSizeDefinitionsKey = bsonutil.MustHaveTag(ProjectRef{}, "ContainerSizeDefinitions")
projectRefExternalLinksKey = bsonutil.MustHaveTag(ProjectRef{}, "ExternalLinks")
projectRefBannerKey = bsonutil.MustHaveTag(ProjectRef{}, "Banner")
projectRefParsleyFiltersKey = bsonutil.MustHaveTag(ProjectRef{}, "ParsleyFilters")
projectRefProjectHealthViewKey = bsonutil.MustHaveTag(ProjectRef{}, "ProjectHealthView")
commitQueueEnabledKey = bsonutil.MustHaveTag(CommitQueueParams{}, "Enabled")
commitQueueMergeQueueKey = bsonutil.MustHaveTag(CommitQueueParams{}, "MergeQueue")
triggerDefinitionProjectKey = bsonutil.MustHaveTag(TriggerDefinition{}, "Project")
containerSecretExternalNameKey = bsonutil.MustHaveTag(ContainerSecret{}, "ExternalName")
containerSecretExternalIDKey = bsonutil.MustHaveTag(ContainerSecret{}, "ExternalID")
)
// IsPrivate returns if this project requires the user to be authed to view it.
func (p *ProjectRef) IsPrivate() bool {
return utility.FromBoolTPtr(p.Private)
}
func (p *ProjectRef) IsRestricted() bool {
return utility.FromBoolPtr(p.Restricted)
}
func (p *ProjectRef) IsPatchingDisabled() bool {
return utility.FromBoolPtr(p.PatchingDisabled)
}
func (p *ProjectRef) IsRepotrackerDisabled() bool {
return utility.FromBoolPtr(p.RepotrackerDisabled)
}
func (p *ProjectRef) IsDispatchingDisabled() bool {
return utility.FromBoolPtr(p.DispatchingDisabled)
}
func (p *ProjectRef) IsPRTestingEnabled() bool {
return p.IsAutoPRTestingEnabled() || p.IsManualPRTestingEnabled()
}
func (p *ProjectRef) IsStepbackDisabled() bool {
return utility.FromBoolPtr(p.StepbackDisabled)
}
func (p *ProjectRef) IsStepbackBisect() bool {
return utility.FromBoolPtr(p.StepbackBisect)
}
func (p *ProjectRef) IsAutoPRTestingEnabled() bool {
return utility.FromBoolPtr(p.PRTestingEnabled)
}
func (p *ProjectRef) IsManualPRTestingEnabled() bool {
return utility.FromBoolPtr(p.ManualPRTestingEnabled)
}
func (p *ProjectRef) IsPRTestingEnabledByCaller(caller string) bool {
switch caller {
case patch.ManualCaller:
return p.IsManualPRTestingEnabled()
case patch.AutomatedCaller:
return p.IsAutoPRTestingEnabled()
default:
return p.IsPRTestingEnabled()
}
}
func (p *ProjectRef) IsGithubChecksEnabled() bool {
return utility.FromBoolPtr(p.GithubChecksEnabled)
}
func (p *ProjectRef) ShouldDeactivatePrevious() bool {
return utility.FromBoolPtr(p.DeactivatePrevious)
}
func (p *ProjectRef) ShouldNotifyOnBuildFailure() bool {
return utility.FromBoolPtr(p.NotifyOnBuildFailure)
}
func (p *ProjectRef) IsGitTagVersionsEnabled() bool {
return utility.FromBoolPtr(p.GitTagVersionsEnabled)
}
func (p *ProjectRef) IsStatsCacheDisabled() bool {
return utility.FromBoolPtr(p.DisabledStatsCache)
}
func (p *ProjectRef) IsHidden() bool {
return utility.FromBoolPtr(p.Hidden)
}
func (p *ProjectRef) UseRepoSettings() bool {
return p.RepoRefId != ""
}
func (p *ProjectRef) DoesTrackPushEvents() bool {
return utility.FromBoolPtr(p.TracksPushEvents)
}
func (p *ProjectRef) IsVersionControlEnabled() bool {
return utility.FromBoolPtr(p.VersionControlEnabled)
}
func (p *ProjectRef) IsPerfEnabled() bool {
return utility.FromBoolPtr(p.PerfEnabled)
}
func (p *CommitQueueParams) IsEnabled() bool {
return utility.FromBoolPtr(p.Enabled)
}
func (ts *TaskSyncOptions) IsPatchEnabled() bool {
return utility.FromBoolPtr(ts.PatchEnabled)
}
func (ts *TaskSyncOptions) IsConfigEnabled() bool {
return utility.FromBoolPtr(ts.ConfigEnabled)
}
func (c *WorkstationConfig) ShouldGitClone() bool {
return utility.FromBoolPtr(c.GitClone)
}
func (p *ProjectRef) AliasesNeeded() bool {
return p.IsGithubChecksEnabled() || p.IsGitTagVersionsEnabled() || p.IsGithubChecksEnabled() || p.IsPRTestingEnabled()
}
const (
ProjectRefCollection = "project_ref"
ProjectTriggerLevelTask = "task"
ProjectTriggerLevelBuild = "build"
ProjectTriggerLevelPush = "push"
intervalPrefix = "@every"
maxBatchTime = 153722867 // math.MaxInt64 / 60 / 1_000_000_000
)
type ProjectPageSection string
// These values must remain consistent with the GraphQL enum ProjectSettingsSection
const (
ProjectPageGeneralSection = "GENERAL"
ProjectPageAccessSection = "ACCESS"
ProjectPageVariablesSection = "VARIABLES"
ProjectPageGithubAndCQSection = "GITHUB_AND_COMMIT_QUEUE"
ProjectPageNotificationsSection = "NOTIFICATIONS"
ProjectPagePatchAliasSection = "PATCH_ALIASES"
ProjectPageWorkstationsSection = "WORKSTATION"
ProjectPageTriggersSection = "TRIGGERS"
ProjectPagePeriodicBuildsSection = "PERIODIC_BUILDS"
ProjectPagePluginSection = "PLUGINS"
ProjectPageContainerSection = "CONTAINERS"
ProjectPageViewsAndFiltersSection = "VIEWS_AND_FILTERS"
)
const (
tasksByProjectQueryMaxTime = 90 * time.Second
)
var adminPermissions = gimlet.Permissions{
evergreen.PermissionProjectSettings: evergreen.ProjectSettingsEdit.Value,
evergreen.PermissionTasks: evergreen.TasksAdmin.Value,
evergreen.PermissionPatches: evergreen.PatchSubmit.Value,
evergreen.PermissionLogs: evergreen.LogsView.Value,
}
func (projectRef *ProjectRef) Insert() error {
return db.Insert(ProjectRefCollection, projectRef)
}
func (p *ProjectRef) Add(creator *user.DBUser) error {
if p.Id == "" {
p.Id = mgobson.NewObjectId().Hex()
}
// Ensure that any new project is originally explicitly disabled and set to private.
p.Enabled = false
p.Private = utility.TruePtr()
// if a hidden project exists for this configuration, use that ID
if p.Owner != "" && p.Repo != "" && p.Branch != "" {
hidden, err := FindHiddenProjectRefByOwnerRepoAndBranch(p.Owner, p.Repo, p.Branch)
if err != nil {
return errors.Wrap(err, "finding hidden project")
}
if hidden != nil {
p.Id = hidden.Id
err := p.Upsert()
if err != nil {
return errors.Wrapf(err, "upserting project ref '%s'", hidden.Id)
}
if creator != nil {
_, err = p.UpdateAdminRoles([]string{creator.Id}, nil)
return err
}
return nil
}
}
err := db.Insert(ProjectRefCollection, p)
if err != nil {
return errors.Wrap(err, "inserting project ref")
}
if err = commitqueue.EnsureCommitQueueExistsForProject(p.Id); err != nil {
grip.Error(message.WrapError(err, message.Fields{
"message": "error ensuring commit queue exists",
"project_id": p.Id,
"project_identifier": p.Identifier,
}))
}
newProjectVars := ProjectVars{
Id: p.Id,
}
if err = newProjectVars.Insert(); err != nil {
return errors.Wrapf(err, "adding project variables for project '%s'", p.Id)
}
return p.addPermissions(creator)
}
func (p *ProjectRef) GetPatchTriggerAlias(aliasName string) (patch.PatchTriggerDefinition, bool) {
for _, alias := range p.PatchTriggerAliases {
if alias.Alias == aliasName {
return alias, true
}
}
return patch.PatchTriggerDefinition{}, false
}
// MergeWithProjectConfig looks up the project config with the given project ref id and modifies
// the project ref scanning for any properties that can be set on both project ref and project parser.
// Any values that are set at the project config level will be set on the project ref IF they are not set on
// the project ref. If the version isn't specified, we get the latest config.
func (p *ProjectRef) MergeWithProjectConfig(version string) (err error) {
projectConfig, err := FindProjectConfigForProjectOrVersion(p.Id, version)
if err != nil {
return err
}
if projectConfig != nil {
defer func() {
err = recovery.HandlePanicWithError(recover(), err, "project ref and project config structures do not match")
}()
pRefToMerge := ProjectRef{
GithubTriggerAliases: projectConfig.GithubTriggerAliases,
ContainerSizeDefinitions: projectConfig.ContainerSizeDefinitions,
}
if projectConfig.WorkstationConfig != nil {
pRefToMerge.WorkstationConfig = *projectConfig.WorkstationConfig
}
if projectConfig.BuildBaronSettings != nil {
pRefToMerge.BuildBaronSettings = *projectConfig.BuildBaronSettings
}
if projectConfig.TaskAnnotationSettings != nil {
pRefToMerge.TaskAnnotationSettings = *projectConfig.TaskAnnotationSettings
}
if projectConfig.TaskSync != nil {
pRefToMerge.TaskSync = *projectConfig.TaskSync
}
reflectedRef := reflect.ValueOf(p).Elem()
reflectedConfig := reflect.ValueOf(pRefToMerge)
util.RecursivelySetUndefinedFields(reflectedRef, reflectedConfig)
}
return err
}
// SetGitHubAppCredentials updates or creates an entry in
// GithubAppAuth for the project ref. If the provided values
// are empty, the entry is deleted.
func (p *ProjectRef) SetGithubAppCredentials(appID int64, privateKey []byte) error {
if appID == 0 && len(privateKey) == 0 {
return RemoveGithubAppAuth(p.Id)
}
if appID == 0 || len(privateKey) == 0 {
return errors.New("both app ID and private key must be provided")
}
auth := GithubAppAuth{
Id: p.Id,
AppId: appID,
PrivateKey: privateKey,
}
return auth.Upsert()
}
// AddToRepoScope validates that the branch can be attached to the matching repo,
// adds the branch to the unrestricted branches under repo scope, and
// adds repo view permission for branch admins, and adds branch edit access for repo admins.
func (p *ProjectRef) AddToRepoScope(u *user.DBUser) error {
rm := evergreen.GetEnvironment().RoleManager()
repoRef, err := FindRepoRefByOwnerAndRepo(p.Owner, p.Repo)
if err != nil {
return errors.Wrapf(err, "finding repo ref '%s'", p.RepoRefId)
}
if repoRef == nil {
repoRef, err = p.createNewRepoRef(u)
if err != nil {
return errors.Wrapf(err, "creating new repo ref")
}
}
if p.RepoRefId == "" {
p.RepoRefId = repoRef.Id
}
// Add the project to the repo admin scope.
if err := rm.AddResourceToScope(GetRepoAdminScope(p.RepoRefId), p.Id); err != nil {
return errors.Wrapf(err, "adding resource to repo '%s' admin scope", p.RepoRefId)
}
// Only give branch admins view access if the repo isn't restricted.
if !repoRef.IsRestricted() {
if err := addViewRepoPermissionsToBranchAdmins(p.RepoRefId, p.Admins); err != nil {
return errors.Wrapf(err, "giving branch '%s' admins view permission for repo '%s'", p.Id, p.RepoRefId)
}
}
// If the branch is unrestricted, add it to this scope so users who requested all-repo permissions have access.
if !p.IsRestricted() {
if err := rm.AddResourceToScope(GetUnrestrictedBranchProjectsScope(p.RepoRefId), p.Id); err != nil {
return errors.Wrap(err, "adding resource to unrestricted branches scope")
}
}
return nil
}
// DetachFromRepo removes the branch from the relevant repo scopes, and updates the project to not point to the repo.
// Any values that previously defaulted to repo will have the repo value explicitly set.
func (p *ProjectRef) DetachFromRepo(u *user.DBUser) error {
before, err := GetProjectSettingsById(p.Id, false)
if err != nil {
return errors.Wrap(err, "getting before project settings event")
}
// remove from relevant repo scopes
if err = p.RemoveFromRepoScope(); err != nil {
return err
}
mergedProject, err := FindMergedProjectRef(p.Id, "", false)
if err != nil {
return errors.Wrap(err, "finding merged project ref")
}
if mergedProject == nil {
return errors.Errorf("project ref '%s' doesn't exist", p.Id)
}
// Save repo variables that don't exist in the repo as the project variables.
// Wait to save merged project until we've gotten the variables.
mergedVars, err := FindMergedProjectVars(before.ProjectRef.Id)
if err != nil {
return errors.Wrap(err, "finding merged project vars")
}
mergedProject.RepoRefId = ""
if err := mergedProject.Upsert(); err != nil {
return errors.Wrap(err, "detaching project from repo")
}
// catch any resulting errors so that we log before returning
catcher := grip.NewBasicCatcher()
if mergedVars != nil {
_, err = mergedVars.Upsert()
catcher.Wrap(err, "saving merged vars")
}
if len(before.Subscriptions) == 0 {
// Save repo subscriptions as project subscriptions if none exist
subs, err := event.FindSubscriptionsByOwner(before.ProjectRef.RepoRefId, event.OwnerTypeProject)
catcher.Wrap(err, "finding repo subscriptions")
for _, s := range subs {
s.ID = ""
s.Owner = p.Id
catcher.Add(s.Upsert())
}
}
// Handle each category of aliases as its own case
repoAliases, err := FindAliasesForRepo(before.ProjectRef.RepoRefId)
catcher.Wrap(err, "finding repo aliases")
hasInternalAliases := map[string]bool{}
hasPatchAlias := false
for _, a := range before.Aliases {
if utility.StringSliceContains(evergreen.InternalAliases, a.Alias) {
hasInternalAliases[a.Alias] = true
} else { // if it's not an internal alias, it's a patch alias. Only add repo patch aliases if no patch aliases exist for the project.
hasPatchAlias = true
}
}
repoAliasesToCopy := []ProjectAlias{}
for _, internalAlias := range evergreen.InternalAliases {
// if the branch doesn't have the internal alias set, add any that exist for the repo
if !hasInternalAliases[internalAlias] {
for _, repoAlias := range repoAliases {
if repoAlias.Alias == internalAlias {
repoAliasesToCopy = append(repoAliasesToCopy, repoAlias)
}
}
}
}
if !hasPatchAlias {
// if the branch doesn't have patch aliases set, add any non-internal aliases that exist for the repo
for _, repoAlias := range repoAliases {
if !utility.StringSliceContains(evergreen.InternalAliases, repoAlias.Alias) {
repoAliasesToCopy = append(repoAliasesToCopy, repoAlias)
}
}
}
catcher.Add(UpsertAliasesForProject(repoAliasesToCopy, p.Id))
catcher.Add(GetAndLogProjectRepoAttachment(p.Id, u.Id, event.EventTypeProjectDetachedFromRepo, false, before))
return catcher.Resolve()
}
// AttachToRepo adds the branch to the relevant repo scopes, and updates the project to point to the repo.
// Any values that previously were unset will now use the repo value, unless this would introduce
// a GitHub project conflict. If no repo ref currently exists, the user attaching it will be added as the repo ref admin.
func (p *ProjectRef) AttachToRepo(ctx context.Context, u *user.DBUser) error {
// Before allowing a project to attach to a repo, verify that this is a valid GitHub organization.
config, err := evergreen.GetConfig(ctx)
if err != nil {
return errors.Wrap(err, "getting config")
}
if err := p.ValidateOwnerAndRepo(config.GithubOrgs); err != nil {
return errors.Wrap(err, "validating new owner/repo")
}
before, err := GetProjectSettingsById(p.Id, false)
if err != nil {
return errors.Wrap(err, "getting before project settings event")
}
if err := p.AddToRepoScope(u); err != nil {
return err
}
update := bson.M{
ProjectRefRepoRefIdKey: p.RepoRefId, // This is set locally in AddToRepoScope
}
update = p.addGithubConflictsToUpdate(update)
err = db.UpdateId(ProjectRefCollection, p.Id, bson.M{
"$set": update,
"$unset": bson.M{ProjectRefTracksPushEventsKey: 1},
})
if err != nil {
return errors.Wrap(err, "attaching repo to scope")
}
return GetAndLogProjectRepoAttachment(p.Id, u.Id, event.EventTypeProjectAttachedToRepo, false, before)
}
// AttachToNewRepo modifies the project's owner/repo, updates the old and new repo scopes (if relevant), and
// updates the project to point to the new repo. Any Github project conflicts are disabled.
// If no repo ref currently exists for the new repo, the user attaching it will be added as the repo ref admin.
func (p *ProjectRef) AttachToNewRepo(u *user.DBUser) error {
before, err := GetProjectSettingsById(p.Id, false)
if err != nil {
return errors.Wrap(err, "getting before project settings event")
}
allowedOrgs := evergreen.GetEnvironment().Settings().GithubOrgs
if err := p.ValidateOwnerAndRepo(allowedOrgs); err != nil {
return errors.Wrap(err, "validating new owner/repo")
}
if p.UseRepoSettings() {
if err := p.RemoveFromRepoScope(); err != nil {
return errors.Wrap(err, "removing project from old repo scope")
}
if err := p.AddToRepoScope(u); err != nil {
return errors.Wrap(err, "adding project to new repo scope")
}
}
update := bson.M{
ProjectRefOwnerKey: p.Owner,
ProjectRefRepoKey: p.Repo,
ProjectRefRepoRefIdKey: p.RepoRefId,
}
update = p.addGithubConflictsToUpdate(update)
err = db.UpdateId(ProjectRefCollection, p.Id, bson.M{
"$set": update,
"$unset": bson.M{ProjectRefTracksPushEventsKey: 1},
})
if err != nil {
return errors.Wrap(err, "updating owner/repo in the DB")
}
return GetAndLogProjectRepoAttachment(p.Id, u.Id, event.EventTypeProjectAttachedToRepo, false, before)
}
// addGithubConflictsToUpdate turns off any settings that may introduce conflicts by
// adding fields to the given update and returning them.
func (p *ProjectRef) addGithubConflictsToUpdate(update bson.M) bson.M {
// If the project ref doesn't default to repo, will just return the original project.
mergedProject, err := GetProjectRefMergedWithRepo(*p)
if err != nil {
grip.Debug(message.WrapError(err, message.Fields{
"message": "unable to merge project with attached repo",
"project_id": p.Id,
"project_identifier": p.Identifier,
"repo_id": p.RepoRefId,
}))
return update
}
if mergedProject.Enabled {
conflicts, err := mergedProject.GetGithubProjectConflicts()
if err != nil {
grip.Debug(message.WrapError(err, message.Fields{
"message": "unable to get github project conflicts",
"project_id": p.Id,
"project_identifier": p.Identifier,
"repo_id": mergedProject.RepoRefId,
}))
return update
}
if len(conflicts.CommitQueueIdentifiers) > 0 {
update[bsonutil.GetDottedKeyName(projectRefCommitQueueKey, commitQueueEnabledKey)] = false
}
if len(conflicts.CommitCheckIdentifiers) > 0 {
update[projectRefGithubChecksEnabledKey] = false
}
if len(conflicts.PRTestingIdentifiers) > 0 {
update[projectRefPRTestingEnabledKey] = false
}
}
return update
}
// RemoveFromRepoScope removes the branch from the unrestricted branches under repo scope, removes repo view permission
// for branch admins, and removes branch edit access for repo admins.
func (p *ProjectRef) RemoveFromRepoScope() error {
if p.RepoRefId == "" {
return nil
}
rm := evergreen.GetEnvironment().RoleManager()
if !p.IsRestricted() {
if err := rm.RemoveResourceFromScope(GetUnrestrictedBranchProjectsScope(p.RepoRefId), p.Id); err != nil {
return errors.Wrap(err, "removing resource from unrestricted branches scope")
}
}
if err := removeViewRepoPermissionsFromBranchAdmins(p.RepoRefId, p.Admins); err != nil {
return errors.Wrap(err, "removing view repo permissions from branch admins")
}
if err := rm.RemoveResourceFromScope(GetRepoAdminScope(p.RepoRefId), p.Id); err != nil {
return errors.Wrapf(err, "removing admin scope from repo '%s'", p.Repo)
}
p.RepoRefId = ""
return nil
}
// addPermissions adds the project ref to the general scope (and repo scope if applicable) and
// gives the inputted creator admin permissions.
func (p *ProjectRef) addPermissions(creator *user.DBUser) error {
rm := evergreen.GetEnvironment().RoleManager()
parentScope := evergreen.UnrestrictedProjectsScope
if p.IsRestricted() {
parentScope = evergreen.RestrictedProjectsScope
}
if err := rm.AddResourceToScope(parentScope, p.Id); err != nil {
return errors.Wrapf(err, "adding project '%s' to the scope '%s'", p.Id, parentScope)
}
// add scope for the branch-level project configurations
newScope := gimlet.Scope{
ID: fmt.Sprintf("project_%s", p.Id),
Resources: []string{p.Id},
Name: p.Id,
Type: evergreen.ProjectResourceType,
}
if err := rm.AddScope(newScope); err != nil {
return errors.Wrapf(err, "adding scope for project '%s'", p.Id)
}
newRole := gimlet.Role{
ID: fmt.Sprintf("admin_project_%s", p.Id),
Scope: newScope.ID,
Permissions: adminPermissions,
}
if creator != nil {
newRole.Owners = []string{creator.Id}
}
if err := rm.UpdateRole(newRole); err != nil {
return errors.Wrapf(err, "adding admin role for project '%s'", p.Id)
}
if creator != nil {
if err := creator.AddRole(newRole.ID); err != nil {
return errors.Wrapf(err, "adding role '%s' to user '%s'", newRole.ID, creator.Id)
}
}
if p.UseRepoSettings() {
if err := p.AddToRepoScope(creator); err != nil {
return errors.Wrapf(err, "adding project to repo '%s'", p.RepoRefId)
}
}
return nil
}
func findOneProjectRefQ(query db.Q) (*ProjectRef, error) {
projectRef := &ProjectRef{}
err := db.FindOneQ(ProjectRefCollection, query, projectRef)
if adb.ResultsNotFound(err) {
return nil, nil
}
return projectRef, err
}
// FindBranchProjectRef gets a project ref given the project identifier.
// This returns only branch-level settings; to include repo settings, use FindMergedProjectRef.
func FindBranchProjectRef(identifier string) (*ProjectRef, error) {
return findOneProjectRefQ(byId(identifier))
}
// FindMergedProjectRef also finds the repo ref settings and merges relevant fields.
// Relevant fields will also be merged from the parser project with a specified version.
// If no version is specified, the most recent valid parser project version will be used for merge.
func FindMergedProjectRef(identifier string, version string, includeProjectConfig bool) (*ProjectRef, error) {
pRef, err := FindBranchProjectRef(identifier)
if err != nil {
return nil, errors.Wrapf(err, "finding project ref '%s'", identifier)
}
if pRef == nil {
return nil, nil
}
if pRef.UseRepoSettings() {
repoRef, err := FindOneRepoRef(pRef.RepoRefId)
if err != nil {
return nil, errors.Wrapf(err, "finding repo ref '%s' for project '%s'", pRef.RepoRefId, pRef.Identifier)
}
if repoRef == nil {
return nil, errors.Errorf("repo ref '%s' does not exist for project '%s'", pRef.RepoRefId, pRef.Identifier)
}
pRef, err = mergeBranchAndRepoSettings(pRef, repoRef)
if err != nil {
return nil, errors.Wrapf(err, "merging repo ref '%s' for project '%s'", repoRef.RepoRefId, identifier)
}
}
if includeProjectConfig && pRef.IsVersionControlEnabled() {
err = pRef.MergeWithProjectConfig(version)
if err != nil {
return nil, errors.Wrapf(err, "merging project config with project ref '%s'", pRef.Identifier)
}
}
return pRef, nil
}
// GetNumberOfEnabledProjects returns the current number of enabled projects on evergreen.
func GetNumberOfEnabledProjects() (int, error) {
// Empty owner and repo will return all enabled project count.
return getNumberOfEnabledProjects("", "")
}
// GetNumberOfEnabledProjectsForOwnerRepo returns the number of enabled projects for a given owner/repo.
func GetNumberOfEnabledProjectsForOwnerRepo(owner, repo string) (int, error) {
if owner == "" || repo == "" {
return 0, errors.New("owner and repo must be specified")
}
return getNumberOfEnabledProjects(owner, repo)
}