Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add property GitLabCIBuildInfo.Source (#4431) #4432

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}
Loading