Skip to content

Commit

Permalink
Add property GitLabCIBuildInfo.Source
Browse files Browse the repository at this point in the history
The Source property returns the trigger for the current pipeline as an enum value.
  • Loading branch information
ap0llo committed Jan 5, 2025
1 parent d51bb67 commit 94e9130
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
}
}
8 changes: 8 additions & 0 deletions src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,13 @@ public GitLabCIBuildInfo(ICakeEnvironment environment)
/// The email address of the user.
/// </value>
public string UserEmail => GetEnvironmentString("GITLAB_USER_EMAIL");

/// <summary>
/// Gets how the pipeline was triggered.
/// </summary>
/// <value>
/// The trigger of the pipeline as <see cref="GitLabCIPipelineSource"/> or <c>null</c> if the trigger could not be determined.
/// </value>
public GitLabCIPipelineSource? Source => GetEnvironmentEnum<GitLabCIPipelineSource>("CI_PIPELINE_SOURCE");
}
}
91 changes: 91 additions & 0 deletions src/Cake.Common/Build/GitLabCI/Data/GitLabCIPipelineSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System.Runtime.Serialization;

namespace Cake.Common.Build.GitLabCI.Data;

/// <summary>
/// Enumerates possible triggers of a GitLab CI pipeline.
/// </summary>
/// <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>
public enum GitLabCIPipelineSource
{
/// <summary>
/// Pipeline var triggered by the pipelines API.
/// </summary>
Api,

/// <summary>
/// Pipeline was created using a GitLab ChatOps command.
/// </summary>
Chat,

/// <summary>
/// Pipeline was created using a CI service other than GitLab
/// </summary>
External,

/// <summary>
/// Pipeline was created because an external pull request on GitHub was created or updated.
/// </summary>
[EnumMember(Value = "external_pull_request_event")]
ExternalPullRequestEvent,

/// <summary>
/// Pipeline was created because a merge request was created or updated.
/// </summary>
[EnumMember(Value = "merge_request_event")]
MergeRequestEvent,

/// <summary>
/// Pipeline is an on-demand DAST scan pipeline.
/// </summary>
[EnumMember(Value = "ondemand_dast_scan")]
OnDemandDastScan,

/// <summary>
/// Pipeline is an on-demand DAST validation pipeline.
/// </summary>
[EnumMember(Value = "ondemand_dast_validation")]
OnDemandDastValidation,

/// <summary>
/// Pipeline was created by a parent pipeline.
/// </summary>
[EnumMember(Value = "parent_pipeline")]
ParentPipeline,

/// <summary>
/// Pipeline was created by another pipeline
/// </summary>
Pipeline,

/// <summary>
/// Pipelune was triggered by a Git push event.
/// </summary>
Push,

/// <summary>
/// Pipeline was triggered by a schedule.
/// </summary>
Schedule,

/// <summary>
/// Pipeline is a security orchestration policy pipeline.
/// </summary>
[EnumMember(Value = "security_orchestration_policy")]
SecurityOrchestrationPolicy,

/// <summary>
/// Pipeline was created using a trigger token.
/// </summary>
Trigger,

/// <summary>
/// Pipeline was created by selecting <i>New Pipeline</i> in the GitLab UI.
/// </summary>
Web,

/// <summary>
/// Pipeline was created using the Web IDE.
/// </summary>
WebIde
}
46 changes: 46 additions & 0 deletions src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -99,5 +102,48 @@ protected bool GetEnvironmentBoolean(string primaryVariable, string secondaryVar
{
return GetEnvironmentBoolean(primaryVariable) ? GetEnvironmentBoolean(primaryVariable) : GetEnvironmentBoolean(secondaryVariable);
}

/// <summary>
/// Gets an environment variable as an enum.
/// </summary>
/// <remarks>
/// 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 <see cref="EnumMemberAttribute"/> attribue in the enum definition.
/// <para>
/// Parsing is case-insensitive.
/// </para>
/// </remarks>
/// <param name="variable">The primary environment variable name.</param>
/// <typeparam name="TEnum">The type of the enum to return.</typeparam>
/// <returns>
/// The environment variable value converted to the corresponding value of <typeparamref name="TEnum"/> or
/// <c>null</c> if the variable is not set or the value could not be converted to the the specified enum type.
/// </returns>
protected TEnum? GetEnvironmentEnum<TEnum>(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<EnumMemberAttribute>() is { Value: { } customName } && !string.IsNullOrEmpty(customName))
{
enumMemberName = customName;
}

if (StringComparer.OrdinalIgnoreCase.Equals(enumMemberName, value))
{
return (TEnum?)field.GetValue(null);
}
}
}

return null;
}
}
}

0 comments on commit 94e9130

Please sign in to comment.