From ae04147f960f2232d0a888c89d3e128f92f21677 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Fri, 2 Aug 2024 14:37:46 -0500 Subject: [PATCH] Warn for soon to be deprecated OS versions (#3413) --- src/Runner.Worker/JobExtension.cs | 4 + src/Runner.Worker/OSWarningChecker.cs | 87 ++++++ .../Pipelines/AgentJobRequestMessage.cs | 26 ++ src/Sdk/DTPipelines/Pipelines/OSWarning.cs | 44 +++ src/Test/L0/Listener/JobDispatcherL0.cs | 3 +- src/Test/L0/Listener/RunnerL0.cs | 2 +- src/Test/L0/Worker/ActionCommandManagerL0.cs | 2 +- .../L0/Worker/CreateStepSummaryCommandL0.cs | 2 +- src/Test/L0/Worker/ExecutionContextL0.cs | 32 +-- src/Test/L0/Worker/JobExtensionL0.cs | 3 +- src/Test/L0/Worker/JobRunnerL0.cs | 1 + src/Test/L0/Worker/OSWarningCheckerL0.cs | 271 ++++++++++++++++++ src/Test/L0/Worker/WorkerL0.cs | 2 +- 13 files changed, 457 insertions(+), 22 deletions(-) create mode 100644 src/Runner.Worker/OSWarningChecker.cs create mode 100644 src/Sdk/DTPipelines/Pipelines/OSWarning.cs create mode 100644 src/Test/L0/Worker/OSWarningCheckerL0.cs diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index c420ddfc88f..4ca06b90c94 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -127,6 +127,10 @@ public async Task> InitializeJob(IExecutionContext jobContext, Pipel } } + // Check OS warning + var osWarningChecker = HostContext.GetService(); + await osWarningChecker.CheckOSAsync(context, message.OSWarnings); + try { var tokenPermissions = jobContext.Global.Variables.Get("system.github.token.permissions") ?? ""; diff --git a/src/Runner.Worker/OSWarningChecker.cs b/src/Runner.Worker/OSWarningChecker.cs new file mode 100644 index 00000000000..900f8f1f0eb --- /dev/null +++ b/src/Runner.Worker/OSWarningChecker.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using GitHub.DistributedTask.WebApi; +using GitHub.DistributedTask.Pipelines; +using GitHub.Runner.Common; +using GitHub.Runner.Sdk; + +namespace GitHub.Runner.Worker +{ + [ServiceLocator(Default = typeof(OSWarningChecker))] + public interface IOSWarningChecker : IRunnerService + { + Task CheckOSAsync(IExecutionContext context, IList osWarnings); + } + +#if OS_WINDOWS || OS_OSX + public sealed class OSWarningChecker : RunnerService, IOSWarningChecker + { + public Task CheckOSAsync(IExecutionContext context, IList osWarnings) + { + ArgUtil.NotNull(context, nameof(context)); + ArgUtil.NotNull(osWarnings, nameof(osWarnings)); + return Task.CompletedTask; + } + } +#else + public sealed class OSWarningChecker : RunnerService, IOSWarningChecker + { + private static readonly TimeSpan s_matchTimeout = TimeSpan.FromMilliseconds(100); + private static readonly RegexOptions s_regexOptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase; + + public async Task CheckOSAsync(IExecutionContext context, IList osWarnings) + { + ArgUtil.NotNull(context, nameof(context)); + ArgUtil.NotNull(osWarnings, nameof(osWarnings)); + foreach (var osWarning in osWarnings) + { + if (string.IsNullOrEmpty(osWarning.FilePath)) + { + Trace.Error("The file path is not specified in the OS warning check."); + continue; + } + + if (string.IsNullOrEmpty(osWarning.RegularExpression)) + { + Trace.Error("The regular expression is not specified in the OS warning check."); + continue; + } + + if (string.IsNullOrEmpty(osWarning.Warning)) + { + Trace.Error("The warning message is not specified in the OS warning check."); + continue; + } + + try + { + if (File.Exists(osWarning.FilePath)) + { + var lines = await File.ReadAllLinesAsync(osWarning.FilePath, context.CancellationToken); + var regex = new Regex(osWarning.RegularExpression, s_regexOptions, s_matchTimeout); + foreach (var line in lines) + { + if (regex.IsMatch(line)) + { + context.Warning(osWarning.Warning); + context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = $"OS warning: {osWarning.Warning}" }); + return; + } + } + } + } + catch (Exception ex) + { + Trace.Error("An error occurred while checking OS warnings for file '{0}' and regex '{1}'.", osWarning.FilePath, osWarning.RegularExpression); + Trace.Error(ex); + context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = $"An error occurred while checking OS warnings for file '{osWarning.FilePath}' and regex '{osWarning.RegularExpression}': {ex.Message}" }); + } + } + } + } +#endif +} + diff --git a/src/Sdk/DTPipelines/Pipelines/AgentJobRequestMessage.cs b/src/Sdk/DTPipelines/Pipelines/AgentJobRequestMessage.cs index 070d86ee24f..030f1d82610 100644 --- a/src/Sdk/DTPipelines/Pipelines/AgentJobRequestMessage.cs +++ b/src/Sdk/DTPipelines/Pipelines/AgentJobRequestMessage.cs @@ -44,6 +44,7 @@ public AgentJobRequestMessage( IList defaults, ActionsEnvironmentReference actionsEnvironment, TemplateToken snapshot, + IList osWarnings, String messageType = JobRequestMessageTypes.PipelineAgentJobRequest) { this.MessageType = messageType; @@ -73,6 +74,11 @@ public AgentJobRequestMessage( m_defaults = new List(defaults); } + if (osWarnings?.Count > 0) + { + m_osWarnings = new List(osWarnings); + } + this.ContextData = new Dictionary(StringComparer.OrdinalIgnoreCase); if (contextData?.Count > 0) { @@ -288,6 +294,18 @@ public IList FileTable } } + public IList OSWarnings + { + get + { + if (m_osWarnings == null) + { + m_osWarnings = new List(); + } + return m_osWarnings; + } + } + // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere public void SetJobSidecarContainers(IDictionary value) { @@ -425,6 +443,11 @@ private void OnSerializing(StreamingContext context) { JobContainer = new StringToken(null, null, null, m_jobContainerResourceAlias); } + + if (m_osWarnings?.Count == 0) + { + m_osWarnings = null; + } } [DataMember(Name = "EnvironmentVariables", EmitDefaultValue = false)] @@ -449,6 +472,9 @@ private void OnSerializing(StreamingContext context) [DataMember(Name = "JobSidecarContainers", EmitDefaultValue = false)] private IDictionary m_jobSidecarContainers; + [DataMember(Name = "OSWarnings", EmitDefaultValue = false)] + private List m_osWarnings; + // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere [IgnoreDataMember] private string m_jobContainerResourceAlias; diff --git a/src/Sdk/DTPipelines/Pipelines/OSWarning.cs b/src/Sdk/DTPipelines/Pipelines/OSWarning.cs new file mode 100644 index 00000000000..7eb4168b26f --- /dev/null +++ b/src/Sdk/DTPipelines/Pipelines/OSWarning.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace GitHub.DistributedTask.Pipelines +{ + /// + /// Information to check whether the OS is going to be deprecated soon + /// + [DataContract] + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class OSWarning + { + /// + /// Gets or sets the file to check + /// + [DataMember(EmitDefaultValue = false)] + public String FilePath + { + get; + set; + } + + /// + /// Gets or sets the regular expression to match + /// + [DataMember(EmitDefaultValue = false)] + public String RegularExpression + { + get; + set; + } + + /// + /// Gets or sets the warning annotation message, if the regular expression matches the content of the file + /// + [DataMember(EmitDefaultValue = false)] + public String Warning + { + get; + set; + } + } +} diff --git a/src/Test/L0/Listener/JobDispatcherL0.cs b/src/Test/L0/Listener/JobDispatcherL0.cs index cc50c180456..c1d5346fe0a 100644 --- a/src/Test/L0/Listener/JobDispatcherL0.cs +++ b/src/Test/L0/Listener/JobDispatcherL0.cs @@ -41,7 +41,7 @@ private Pipelines.AgentJobRequestMessage CreateJobRequestMessage() TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = null; Guid jobId = Guid.NewGuid(); - var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); return result; } @@ -810,6 +810,7 @@ private static AgentJobRequestMessage GetAgentJobRequestMessage() null, new List(), new ActionsEnvironmentReference("env"), + null, null ); return message; diff --git a/src/Test/L0/Listener/RunnerL0.cs b/src/Test/L0/Listener/RunnerL0.cs index b29f8835c2b..3010dcb402a 100644 --- a/src/Test/L0/Listener/RunnerL0.cs +++ b/src/Test/L0/Listener/RunnerL0.cs @@ -44,7 +44,7 @@ private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName) TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = null; Guid jobId = Guid.NewGuid(); - return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); } private JobCancelMessage CreateJobCancelMessage() diff --git a/src/Test/L0/Worker/ActionCommandManagerL0.cs b/src/Test/L0/Worker/ActionCommandManagerL0.cs index 3a1f8f70fe9..8be95ae4da5 100644 --- a/src/Test/L0/Worker/ActionCommandManagerL0.cs +++ b/src/Test/L0/Worker/ActionCommandManagerL0.cs @@ -232,7 +232,7 @@ public void EchoProcessCommandDebugOn() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, diff --git a/src/Test/L0/Worker/CreateStepSummaryCommandL0.cs b/src/Test/L0/Worker/CreateStepSummaryCommandL0.cs index 185f44b3865..972d6843bd7 100644 --- a/src/Test/L0/Worker/CreateStepSummaryCommandL0.cs +++ b/src/Test/L0/Worker/CreateStepSummaryCommandL0.cs @@ -193,7 +193,7 @@ private TestHostContext Setup([CallerMemberName] string name = "") TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "Summary Job"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, diff --git a/src/Test/L0/Worker/ExecutionContextL0.cs b/src/Test/L0/Worker/ExecutionContextL0.cs index 08abcd09585..0054c969938 100644 --- a/src/Test/L0/Worker/ExecutionContextL0.cs +++ b/src/Test/L0/Worker/ExecutionContextL0.cs @@ -29,7 +29,7 @@ public void AddIssue_CountWarningsErrors() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -106,7 +106,7 @@ public void ApplyContinueOnError_CheckResultAndOutcome() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -162,7 +162,7 @@ public void AddIssue_TrimMessageSize() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -216,7 +216,7 @@ public void AddIssue_OverrideLogMessage() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -271,7 +271,7 @@ public void AddIssue_AddStepAndLineNumberInformation() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -322,7 +322,7 @@ public void Debug_Multilines() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -373,7 +373,7 @@ public void RegisterPostJobAction_ShareState() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -471,7 +471,7 @@ public void RegisterPostJobAction_NotRegisterPostTwice() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -555,7 +555,7 @@ public void ActionResult_Lowercase() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -610,7 +610,7 @@ public void PublishStepTelemetry_RegularStep_NoOpt() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -653,7 +653,7 @@ public void PublishStepTelemetry_RegularStep() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -717,7 +717,7 @@ public void PublishStepTelemetry_EmbeddedStep() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -781,7 +781,7 @@ public void PublishStepResult_EmbeddedStep() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -969,7 +969,7 @@ public void ActionVariables_AddedToVarsContext() TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -1014,7 +1014,7 @@ public void ActionVariables_DebugUsingVars() TimelineReference timeline = new TimelineReference(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, @@ -1057,7 +1057,7 @@ public void ActionVariables_SecretsPrecedenceForDebugUsingVars() TimelineReference timeline = new TimelineReference(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, diff --git a/src/Test/L0/Worker/JobExtensionL0.cs b/src/Test/L0/Worker/JobExtensionL0.cs index d66ded66372..6ada9143dc3 100644 --- a/src/Test/L0/Worker/JobExtensionL0.cs +++ b/src/Test/L0/Worker/JobExtensionL0.cs @@ -114,7 +114,7 @@ private TestHostContext CreateTestContext([CallerMemberName] String testName = " }; Guid jobId = Guid.NewGuid(); - _message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), steps, null, null, null, null, null); + _message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), steps, null, null, null, null, null, null); GitHubContext github = new(); github["repository"] = new Pipelines.ContextData.StringContextData("actions/runner"); github["secret_source"] = new Pipelines.ContextData.StringContextData("Actions"); @@ -140,6 +140,7 @@ private TestHostContext CreateTestContext([CallerMemberName] String testName = " hc.SetSingleton(_diagnosticLogManager.Object); hc.SetSingleton(_jobHookProvider.Object); hc.SetSingleton(_snapshotOperationProvider.Object); + hc.SetSingleton(new Mock().Object); hc.EnqueueInstance(_logger.Object); // JobExecutionContext hc.EnqueueInstance(_logger.Object); // job start hook hc.EnqueueInstance(_logger.Object); // Initial Job diff --git a/src/Test/L0/Worker/JobRunnerL0.cs b/src/Test/L0/Worker/JobRunnerL0.cs index e8011b9b051..c1021150c6f 100644 --- a/src/Test/L0/Worker/JobRunnerL0.cs +++ b/src/Test/L0/Worker/JobRunnerL0.cs @@ -102,6 +102,7 @@ private Pipelines.AgentJobRequestMessage GetMessage(String messageType = JobRequ testName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, new ActionsEnvironmentReference("staging"), null, + null, messageType: messageType); message.Variables[Constants.Variables.System.Culture] = "en-US"; message.Resources.Endpoints.Add(new ServiceEndpoint() diff --git a/src/Test/L0/Worker/OSWarningCheckerL0.cs b/src/Test/L0/Worker/OSWarningCheckerL0.cs new file mode 100644 index 00000000000..7f4d0f32ee2 --- /dev/null +++ b/src/Test/L0/Worker/OSWarningCheckerL0.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using GitHub.DistributedTask.Pipelines; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Worker; +using Moq; +using Xunit; + +namespace GitHub.Runner.Common.Tests.Worker +{ + public sealed class OSWarningCheckerL0 + { + private CancellationTokenSource _ecTokenSource; + private Mock _ec; + private TestHostContext _hc; + private OSWarningChecker _osWarningChecker; + private List _issues; + private string _workFolder; + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async void CheckOS_FileNotExists() + { + try + { + // Arrange + Setup(); + var osWarnings = new List + { + new OSWarning + { + FilePath = Path.Combine(_workFolder, "os-release"), + RegularExpression = "some OS version", + Warning = "Some OS version will be deprecated soon" + }, + }; + + // Act + await _osWarningChecker.CheckOSAsync(_ec.Object, osWarnings); + + // Assert + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _ec.Object.Global.JobTelemetry.Count); + } + finally + { + Teardown(); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async void CheckOS_CaseInsensitive() + { + try + { + // Arrange + Setup(); + var osWarnings = new List + { + new OSWarning + { + FilePath = Path.Combine(_workFolder, "os-release"), + RegularExpression = "some OS verSION", + Warning = "Some OS version will be deprecated soon" + }, + }; + File.WriteAllText(Path.Combine(_workFolder, "os-release"), "some OS version\n"); + + // Act + await _osWarningChecker.CheckOSAsync(_ec.Object, osWarnings); + +#if OS_WINDOWS || OS_OSX + // Assert + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _ec.Object.Global.JobTelemetry.Count); +#else + // Assert + Assert.Equal(1, _issues.Count); + Assert.Equal(IssueType.Warning, _issues[0].Type); + Assert.Equal("Some OS version will be deprecated soon", _issues[0].Message); + Assert.Equal(1, _ec.Object.Global.JobTelemetry.Count); + Assert.Equal(JobTelemetryType.General, _ec.Object.Global.JobTelemetry[0].Type); + Assert.Equal("OS warning: Some OS version will be deprecated soon", _ec.Object.Global.JobTelemetry[0].Message); +#endif + } + finally + { + Teardown(); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async void CheckOS_MatchesOnceWithinAFile() + { + try + { + // Arrange + Setup(); + var osWarnings = new List + { + new OSWarning + { + FilePath = Path.Combine(_workFolder, "os-release"), + RegularExpression = "some OS version", + Warning = "Some OS version will be deprecated soon" + }, + }; + File.WriteAllText(Path.Combine(_workFolder, "os-release"), "some OS version\nsome OS version\n"); + + // Act + await _osWarningChecker.CheckOSAsync(_ec.Object, osWarnings); + +#if OS_WINDOWS || OS_OSX + // Assert + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _ec.Object.Global.JobTelemetry.Count); +#else + // Assert + Assert.Equal(1, _issues.Count); + Assert.Equal(IssueType.Warning, _issues[0].Type); + Assert.Equal("Some OS version will be deprecated soon", _issues[0].Message); + Assert.Equal(1, _ec.Object.Global.JobTelemetry.Count); + Assert.Equal(JobTelemetryType.General, _ec.Object.Global.JobTelemetry[0].Type); + Assert.Equal("OS warning: Some OS version will be deprecated soon", _ec.Object.Global.JobTelemetry[0].Message); +#endif + } + finally + { + Teardown(); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async void CheckOS_MatchesOnceAcrossFiles() + { + try + { + // Arrange + Setup(); + var osWarnings = new List + { + new OSWarning + { + FilePath = Path.Combine(_workFolder, "os-release"), + RegularExpression = "some OS version", + Warning = "Some OS version will be deprecated soon" + }, + new OSWarning + { + FilePath = Path.Combine(_workFolder, "os-release-2"), + RegularExpression = "some OS version", + Warning = "Some OS version will be deprecated soon" + }, + }; + File.WriteAllText(Path.Combine(_workFolder, "os-release"), "some OS version\n"); + File.WriteAllText(Path.Combine(_workFolder, "os-release-2"), "some OS version\n"); + + // Act + await _osWarningChecker.CheckOSAsync(_ec.Object, osWarnings); + +#if OS_WINDOWS || OS_OSX + // Assert + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _ec.Object.Global.JobTelemetry.Count); +#else + // Assert + Assert.Equal(1, _issues.Count); + Assert.Equal(IssueType.Warning, _issues[0].Type); + Assert.Equal("Some OS version will be deprecated soon", _issues[0].Message); + Assert.Equal(1, _ec.Object.Global.JobTelemetry.Count); + Assert.Equal(JobTelemetryType.General, _ec.Object.Global.JobTelemetry[0].Type); + Assert.Equal("OS warning: Some OS version will be deprecated soon", _ec.Object.Global.JobTelemetry[0].Message); +#endif + } + finally + { + Teardown(); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async void CheckOS_LogsTelemetryUponException() + { + try + { + // Arrange + Setup(); + var osWarnings = new List + { + new OSWarning + { + FilePath = Path.Combine(_workFolder, "os-release"), + RegularExpression = "abc[", // Invalid pattern + Warning = "Some OS version will be deprecated soon" + }, + }; + File.WriteAllText(Path.Combine(_workFolder, "os-release"), "some OS version\n"); + + // Act + await _osWarningChecker.CheckOSAsync(_ec.Object, osWarnings); + +#if OS_WINDOWS || OS_OSX + // Assert + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _ec.Object.Global.JobTelemetry.Count); +#else + // Assert + Assert.Equal(0, _issues.Count); + Assert.Equal(1, _ec.Object.Global.JobTelemetry.Count); + Assert.Equal(JobTelemetryType.General, _ec.Object.Global.JobTelemetry[0].Type); + Assert.Equal( + $"An error occurred while checking OS warnings for file '{osWarnings[0].FilePath}' and regex '{osWarnings[0].RegularExpression}': Invalid pattern 'abc[' at offset 4. Unterminated [] set.", + _ec.Object.Global.JobTelemetry[0].Message); +#endif + } + finally + { + Teardown(); + } + } + + private void Setup([CallerMemberName] string name = "") + { + _issues = new List(); + + // Test host context + _hc = new TestHostContext(this, name); + + // Random work folder + _workFolder = _hc.GetDirectory(WellKnownDirectory.Work); + Directory.CreateDirectory(_workFolder); + + // Execution context token source + _ecTokenSource?.Dispose(); + _ecTokenSource = new CancellationTokenSource(); + + // Execution context + _ec = new Mock(); + _ec.Setup(x => x.Global).Returns(new GlobalContext()); + _ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token); + _ec.Object.Global.JobTelemetry = new List(); + _ec.Setup(x => x.AddIssue(It.IsAny(), It.IsAny())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _issues.Add(issue); }); + + // OS warning checker + _osWarningChecker = new OSWarningChecker(); + _osWarningChecker.Initialize(_hc); + } + + private void Teardown() + { + _hc?.Dispose(); + if (!string.IsNullOrEmpty(_workFolder) && Directory.Exists(_workFolder)) + { + Directory.Delete(_workFolder, recursive: true); + } + } + } +} diff --git a/src/Test/L0/Worker/WorkerL0.cs b/src/Test/L0/Worker/WorkerL0.cs index defcc981404..ba6a933afdd 100644 --- a/src/Test/L0/Worker/WorkerL0.cs +++ b/src/Test/L0/Worker/WorkerL0.cs @@ -67,7 +67,7 @@ private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName) new Pipelines.ContextData.DictionaryContextData() }, }; - var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, new StringToken(null, null, null, "ubuntu"), sidecarContainers, null, variables, new List(), resources, context, null, actions, null, null, null, null, null); + var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, new StringToken(null, null, null, "ubuntu"), sidecarContainers, null, variables, new List(), resources, context, null, actions, null, null, null, null, null, null); return jobRequest; }