Skip to content

Commit 94e9130

Browse files
committed
Add property GitLabCIBuildInfo.Source
The Source property returns the trigger for the current pipeline as an enum value.
1 parent d51bb67 commit 94e9130

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed

src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using Cake.Common.Build.GitLabCI.Data;
56
using Cake.Common.Tests.Fixtures.Build;
7+
using NSubstitute;
68
using Xunit;
79

810
namespace Cake.Common.Tests.Unit.Build.GitLabCI.Data
@@ -362,5 +364,41 @@ public void Should_Return_Correct_Value()
362364
Assert.Equal(42, result);
363365
}
364366
}
367+
368+
public sealed class TheSourceProperty
369+
{
370+
// Values taken from https://docs.gitlab.com/ee/ci/jobs/job_rules.html#ci_pipeline_source-predefined-variable
371+
[Theory]
372+
[InlineData("", null)]
373+
[InlineData("unknown_source", null)]
374+
[InlineData("api", GitLabCIPipelineSource.Api)]
375+
[InlineData("chat", GitLabCIPipelineSource.Chat)]
376+
[InlineData("external", GitLabCIPipelineSource.External)]
377+
[InlineData("external_pull_request_event", GitLabCIPipelineSource.ExternalPullRequestEvent)]
378+
[InlineData("merge_request_event", GitLabCIPipelineSource.MergeRequestEvent)]
379+
[InlineData("ondemand_dast_scan", GitLabCIPipelineSource.OnDemandDastScan)]
380+
[InlineData("ondemand_dast_validation", GitLabCIPipelineSource.OnDemandDastValidation)]
381+
[InlineData("parent_pipeline", GitLabCIPipelineSource.ParentPipeline)]
382+
[InlineData("pipeline", GitLabCIPipelineSource.Pipeline)]
383+
[InlineData("push", GitLabCIPipelineSource.Push)]
384+
[InlineData("schedule", GitLabCIPipelineSource.Schedule)]
385+
[InlineData("security_orchestration_policy", GitLabCIPipelineSource.SecurityOrchestrationPolicy)]
386+
[InlineData("trigger", GitLabCIPipelineSource.Trigger)]
387+
[InlineData("web", GitLabCIPipelineSource.Web)]
388+
[InlineData("webide", GitLabCIPipelineSource.WebIde)]
389+
public void Should_Return_Correct_Value(string pipelineSourceEnvironmentVariable, GitLabCIPipelineSource? expectedSource)
390+
{
391+
// Given
392+
var fixture = new GitLabCIInfoFixture();
393+
fixture.Environment.GetEnvironmentVariable("CI_PIPELINE_SOURCE").Returns(pipelineSourceEnvironmentVariable);
394+
var info = fixture.CreateBuildInfo();
395+
396+
// When
397+
var result = info.Source;
398+
399+
// Then
400+
Assert.Equal(expectedSource, result);
401+
}
402+
}
365403
}
366404
}

src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,13 @@ public GitLabCIBuildInfo(ICakeEnvironment environment)
131131
/// The email address of the user.
132132
/// </value>
133133
public string UserEmail => GetEnvironmentString("GITLAB_USER_EMAIL");
134+
135+
/// <summary>
136+
/// Gets how the pipeline was triggered.
137+
/// </summary>
138+
/// <value>
139+
/// The trigger of the pipeline as <see cref="GitLabCIPipelineSource"/> or <c>null</c> if the trigger could not be determined.
140+
/// </value>
141+
public GitLabCIPipelineSource? Source => GetEnvironmentEnum<GitLabCIPipelineSource>("CI_PIPELINE_SOURCE");
134142
}
135143
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace Cake.Common.Build.GitLabCI.Data;
4+
5+
/// <summary>
6+
/// Enumerates possible triggers of a GitLab CI pipeline.
7+
/// </summary>
8+
/// <seealso href="https://docs.gitlab.com/ee/ci/jobs/job_rules.html#ci_pipeline_source-predefined-variable"> CI_PIPELINE_SOURCE predefined variable (GitLab Documentation)</seealso>
9+
public enum GitLabCIPipelineSource
10+
{
11+
/// <summary>
12+
/// Pipeline var triggered by the pipelines API.
13+
/// </summary>
14+
Api,
15+
16+
/// <summary>
17+
/// Pipeline was created using a GitLab ChatOps command.
18+
/// </summary>
19+
Chat,
20+
21+
/// <summary>
22+
/// Pipeline was created using a CI service other than GitLab
23+
/// </summary>
24+
External,
25+
26+
/// <summary>
27+
/// Pipeline was created because an external pull request on GitHub was created or updated.
28+
/// </summary>
29+
[EnumMember(Value = "external_pull_request_event")]
30+
ExternalPullRequestEvent,
31+
32+
/// <summary>
33+
/// Pipeline was created because a merge request was created or updated.
34+
/// </summary>
35+
[EnumMember(Value = "merge_request_event")]
36+
MergeRequestEvent,
37+
38+
/// <summary>
39+
/// Pipeline is an on-demand DAST scan pipeline.
40+
/// </summary>
41+
[EnumMember(Value = "ondemand_dast_scan")]
42+
OnDemandDastScan,
43+
44+
/// <summary>
45+
/// Pipeline is an on-demand DAST validation pipeline.
46+
/// </summary>
47+
[EnumMember(Value = "ondemand_dast_validation")]
48+
OnDemandDastValidation,
49+
50+
/// <summary>
51+
/// Pipeline was created by a parent pipeline.
52+
/// </summary>
53+
[EnumMember(Value = "parent_pipeline")]
54+
ParentPipeline,
55+
56+
/// <summary>
57+
/// Pipeline was created by another pipeline
58+
/// </summary>
59+
Pipeline,
60+
61+
/// <summary>
62+
/// Pipelune was triggered by a Git push event.
63+
/// </summary>
64+
Push,
65+
66+
/// <summary>
67+
/// Pipeline was triggered by a schedule.
68+
/// </summary>
69+
Schedule,
70+
71+
/// <summary>
72+
/// Pipeline is a security orchestration policy pipeline.
73+
/// </summary>
74+
[EnumMember(Value = "security_orchestration_policy")]
75+
SecurityOrchestrationPolicy,
76+
77+
/// <summary>
78+
/// Pipeline was created using a trigger token.
79+
/// </summary>
80+
Trigger,
81+
82+
/// <summary>
83+
/// Pipeline was created by selecting <i>New Pipeline</i> in the GitLab UI.
84+
/// </summary>
85+
Web,
86+
87+
/// <summary>
88+
/// Pipeline was created using the Web IDE.
89+
/// </summary>
90+
WebIde
91+
}

src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Linq;
7+
using System.Reflection;
8+
using System.Runtime.Serialization;
69
using Cake.Core;
710

811
namespace Cake.Common.Build.GitLabCI
@@ -99,5 +102,48 @@ protected bool GetEnvironmentBoolean(string primaryVariable, string secondaryVar
99102
{
100103
return GetEnvironmentBoolean(primaryVariable) ? GetEnvironmentBoolean(primaryVariable) : GetEnvironmentBoolean(secondaryVariable);
101104
}
105+
106+
/// <summary>
107+
/// Gets an environment variable as an enum.
108+
/// </summary>
109+
/// <remarks>
110+
/// By default, the environment variable value is presumed to be identical to the enum value name.
111+
/// To define a mapping between environment variable value and enum name, apply the <see cref="EnumMemberAttribute"/> attribue in the enum definition.
112+
/// <para>
113+
/// Parsing is case-insensitive.
114+
/// </para>
115+
/// </remarks>
116+
/// <param name="variable">The primary environment variable name.</param>
117+
/// <typeparam name="TEnum">The type of the enum to return.</typeparam>
118+
/// <returns>
119+
/// The environment variable value converted to the corresponding value of <typeparamref name="TEnum"/> or
120+
/// <c>null</c> if the variable is not set or the value could not be converted to the the specified enum type.
121+
/// </returns>
122+
protected TEnum? GetEnvironmentEnum<TEnum>(string variable) where TEnum : struct, Enum
123+
{
124+
var value = GetEnvironmentString(variable);
125+
if (!string.IsNullOrWhiteSpace(value))
126+
{
127+
// Instead of using Enum.TryParse(), enumerate the enum fields using reflection.
128+
// This defining enums where the environment variable value differs from the name of the corresponding enum member:
129+
// - If the enum member has a [EnumMember] attribute, use the value defined by the attribute instead of the enum member name
130+
// - Otherwise, use the enum member name (matching the behavior of Enum.TryParse())
131+
foreach (var field in typeof(TEnum).GetFields().Where(fi => fi.FieldType == typeof(TEnum)))
132+
{
133+
var enumMemberName = field.Name;
134+
if (field.GetCustomAttribute<EnumMemberAttribute>() is { Value: { } customName } && !string.IsNullOrEmpty(customName))
135+
{
136+
enumMemberName = customName;
137+
}
138+
139+
if (StringComparer.OrdinalIgnoreCase.Equals(enumMemberName, value))
140+
{
141+
return (TEnum?)field.GetValue(null);
142+
}
143+
}
144+
}
145+
146+
return null;
147+
}
102148
}
103149
}

0 commit comments

Comments
 (0)