diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs
index 86ebe58d8e..14b9ace302 100644
--- a/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs
+++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Cake.Common.Build.GitLabCI.Data;
using Cake.Common.Tests.Fixtures.Build;
+using NSubstitute;
using Xunit;
namespace Cake.Common.Tests.Unit.Build.GitLabCI.Data
@@ -362,5 +364,41 @@ public void Should_Return_Correct_Value()
Assert.Equal(42, result);
}
}
+
+ public sealed class TheSourceProperty
+ {
+ // Values taken from https://docs.gitlab.com/ee/ci/jobs/job_rules.html#ci_pipeline_source-predefined-variable
+ [Theory]
+ [InlineData("", null)]
+ [InlineData("unknown_source", null)]
+ [InlineData("api", GitLabCIPipelineSource.Api)]
+ [InlineData("chat", GitLabCIPipelineSource.Chat)]
+ [InlineData("external", GitLabCIPipelineSource.External)]
+ [InlineData("external_pull_request_event", GitLabCIPipelineSource.ExternalPullRequestEvent)]
+ [InlineData("merge_request_event", GitLabCIPipelineSource.MergeRequestEvent)]
+ [InlineData("ondemand_dast_scan", GitLabCIPipelineSource.OnDemandDastScan)]
+ [InlineData("ondemand_dast_validation", GitLabCIPipelineSource.OnDemandDastValidation)]
+ [InlineData("parent_pipeline", GitLabCIPipelineSource.ParentPipeline)]
+ [InlineData("pipeline", GitLabCIPipelineSource.Pipeline)]
+ [InlineData("push", GitLabCIPipelineSource.Push)]
+ [InlineData("schedule", GitLabCIPipelineSource.Schedule)]
+ [InlineData("security_orchestration_policy", GitLabCIPipelineSource.SecurityOrchestrationPolicy)]
+ [InlineData("trigger", GitLabCIPipelineSource.Trigger)]
+ [InlineData("web", GitLabCIPipelineSource.Web)]
+ [InlineData("webide", GitLabCIPipelineSource.WebIde)]
+ public void Should_Return_Correct_Value(string pipelineSourceEnvironmentVariable, GitLabCIPipelineSource? expectedSource)
+ {
+ // Given
+ var fixture = new GitLabCIInfoFixture();
+ fixture.Environment.GetEnvironmentVariable("CI_PIPELINE_SOURCE").Returns(pipelineSourceEnvironmentVariable);
+ var info = fixture.CreateBuildInfo();
+
+ // When
+ var result = info.Source;
+
+ // Then
+ Assert.Equal(expectedSource, result);
+ }
+ }
}
}
diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs
index d97d18c5ef..c811bcf48d 100644
--- a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs
+++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs
@@ -131,5 +131,13 @@ public GitLabCIBuildInfo(ICakeEnvironment environment)
/// The email address of the user.
///
public string UserEmail => GetEnvironmentString("GITLAB_USER_EMAIL");
+
+ ///
+ /// Gets how the pipeline was triggered.
+ ///
+ ///
+ /// The trigger of the pipeline as or null if the trigger could not be determined.
+ ///
+ public GitLabCIPipelineSource? Source => GetEnvironmentEnum("CI_PIPELINE_SOURCE");
}
}
diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPipelineSource.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPipelineSource.cs
new file mode 100644
index 0000000000..0d2b3ea8d6
--- /dev/null
+++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPipelineSource.cs
@@ -0,0 +1,91 @@
+using System.Runtime.Serialization;
+
+namespace Cake.Common.Build.GitLabCI.Data;
+
+///
+/// Enumerates possible triggers of a GitLab CI pipeline.
+///
+/// CI_PIPELINE_SOURCE predefined variable (GitLab Documentation)
+public enum GitLabCIPipelineSource
+{
+ ///
+ /// Pipeline var triggered by the pipelines API.
+ ///
+ Api,
+
+ ///
+ /// Pipeline was created using a GitLab ChatOps command.
+ ///
+ Chat,
+
+ ///
+ /// Pipeline was created using a CI service other than GitLab
+ ///
+ External,
+
+ ///
+ /// Pipeline was created because an external pull request on GitHub was created or updated.
+ ///
+ [EnumMember(Value = "external_pull_request_event")]
+ ExternalPullRequestEvent,
+
+ ///
+ /// Pipeline was created because a merge request was created or updated.
+ ///
+ [EnumMember(Value = "merge_request_event")]
+ MergeRequestEvent,
+
+ ///
+ /// Pipeline is an on-demand DAST scan pipeline.
+ ///
+ [EnumMember(Value = "ondemand_dast_scan")]
+ OnDemandDastScan,
+
+ ///
+ /// Pipeline is an on-demand DAST validation pipeline.
+ ///
+ [EnumMember(Value = "ondemand_dast_validation")]
+ OnDemandDastValidation,
+
+ ///
+ /// Pipeline was created by a parent pipeline.
+ ///
+ [EnumMember(Value = "parent_pipeline")]
+ ParentPipeline,
+
+ ///
+ /// Pipeline was created by another pipeline
+ ///
+ Pipeline,
+
+ ///
+ /// Pipelune was triggered by a Git push event.
+ ///
+ Push,
+
+ ///
+ /// Pipeline was triggered by a schedule.
+ ///
+ Schedule,
+
+ ///
+ /// Pipeline is a security orchestration policy pipeline.
+ ///
+ [EnumMember(Value = "security_orchestration_policy")]
+ SecurityOrchestrationPolicy,
+
+ ///
+ /// Pipeline was created using a trigger token.
+ ///
+ Trigger,
+
+ ///
+ /// Pipeline was created by selecting New Pipeline in the GitLab UI.
+ ///
+ Web,
+
+ ///
+ /// Pipeline was created using the Web IDE.
+ ///
+ WebIde
+}
\ No newline at end of file
diff --git a/src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs b/src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs
index bcbb7a7251..03a3bd2dc7 100644
--- a/src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs
+++ b/src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs
@@ -3,6 +3,9 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization;
using Cake.Core;
namespace Cake.Common.Build.GitLabCI
@@ -99,5 +102,48 @@ protected bool GetEnvironmentBoolean(string primaryVariable, string secondaryVar
{
return GetEnvironmentBoolean(primaryVariable) ? GetEnvironmentBoolean(primaryVariable) : GetEnvironmentBoolean(secondaryVariable);
}
+
+ ///
+ /// Gets an environment variable as an enum.
+ ///
+ ///
+ /// By default, the environment variable value is presumed to be identical to the enum value name.
+ /// To define a mapping between environment variable value and enum name, apply the attribue in the enum definition.
+ ///
+ /// Parsing is case-insensitive.
+ ///
+ ///
+ /// The primary environment variable name.
+ /// The type of the enum to return.
+ ///
+ /// The environment variable value converted to the corresponding value of or
+ /// null if the variable is not set or the value could not be converted to the the specified enum type.
+ ///
+ protected TEnum? GetEnvironmentEnum(string variable) where TEnum : struct, Enum
+ {
+ var value = GetEnvironmentString(variable);
+ if (!string.IsNullOrWhiteSpace(value))
+ {
+ // Instead of using Enum.TryParse(), enumerate the enum fields using reflection.
+ // This defining enums where the environment variable value differs from the name of the corresponding enum member:
+ // - If the enum member has a [EnumMember] attribute, use the value defined by the attribute instead of the enum member name
+ // - Otherwise, use the enum member name (matching the behavior of Enum.TryParse())
+ foreach (var field in typeof(TEnum).GetFields().Where(fi => fi.FieldType == typeof(TEnum)))
+ {
+ var enumMemberName = field.Name;
+ if (field.GetCustomAttribute() is { Value: { } customName } && !string.IsNullOrEmpty(customName))
+ {
+ enumMemberName = customName;
+ }
+
+ if (StringComparer.OrdinalIgnoreCase.Equals(enumMemberName, value))
+ {
+ return (TEnum?)field.GetValue(null);
+ }
+ }
+ }
+
+ return null;
+ }
}
}
\ No newline at end of file