Skip to content

Commit b0a7148

Browse files
authored
support defaults. (#369)
1 parent 88875ca commit b0a7148

14 files changed

Lines changed: 187 additions & 14 deletions

src/Runner.Worker/ExecutionContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public interface IExecutionContext : IRunnerService
4848
PlanFeatures Features { get; }
4949
Variables Variables { get; }
5050
Dictionary<string, string> IntraActionState { get; }
51+
IDictionary<String, IDictionary<String, String>> JobDefaults { get; }
5152
Dictionary<string, VariableValue> JobOutputs { get; }
5253
IDictionary<String, String> EnvironmentVariables { get; }
5354
IDictionary<String, ContextScope> Scopes { get; }
@@ -140,6 +141,7 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext
140141
public List<ServiceEndpoint> Endpoints { get; private set; }
141142
public Variables Variables { get; private set; }
142143
public Dictionary<string, string> IntraActionState { get; private set; }
144+
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
143145
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
144146
public IDictionary<String, String> EnvironmentVariables { get; private set; }
145147
public IDictionary<String, ContextScope> Scopes { get; private set; }
@@ -270,6 +272,7 @@ public IExecutionContext CreateChild(Guid recordId, string displayName, string r
270272
child.IntraActionState = intraActionState;
271273
}
272274
child.EnvironmentVariables = EnvironmentVariables;
275+
child.JobDefaults = JobDefaults;
273276
child.Scopes = Scopes;
274277
child.FileTable = FileTable;
275278
child.StepsContext = StepsContext;
@@ -565,6 +568,9 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation
565568
// Environment variables shared across all actions
566569
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer);
567570

571+
// Job defaults shared across all actions
572+
JobDefaults = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
573+
568574
// Job Outputs
569575
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
570576

src/Runner.Worker/Handlers/ScriptHandler.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,21 @@ public override void PrintActionDetails(ActionRunStage stage)
5858
string shellCommandPath = null;
5959
bool validateShellOnHost = !(StepHost is ContainerStepHost);
6060
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
61-
Inputs.TryGetValue("shell", out var shell);
61+
string shell = null;
62+
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
63+
{
64+
// TODO: figure out how defaults interact with template later
65+
// for now, we won't check job.defaults if we are inside a template.
66+
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
67+
{
68+
runDefaults.TryGetValue("shell", out shell);
69+
}
70+
}
6271
if (string.IsNullOrEmpty(shell))
6372
{
6473
#if OS_WINDOWS
6574
shellCommand = "pwsh";
66-
if(validateShellOnHost)
75+
if (validateShellOnHost)
6776
{
6877
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
6978
if (string.IsNullOrEmpty(shellCommandPath))
@@ -139,11 +148,36 @@ public async Task RunAsync(ActionRunStage stage)
139148
Inputs.TryGetValue("script", out var contents);
140149
contents = contents ?? string.Empty;
141150

142-
Inputs.TryGetValue("workingDirectory", out var workingDirectory);
151+
string workingDirectory = null;
152+
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
153+
{
154+
// TODO: figure out how defaults interact with template later
155+
// for now, we won't check job.defaults if we are inside a template.
156+
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
157+
{
158+
if (runDefaults.TryGetValue("working-directory", out workingDirectory))
159+
{
160+
ExecutionContext.Debug("Overwrite 'working-directory' base on job defaults.");
161+
}
162+
}
163+
}
143164
var workspaceDir = githubContext["workspace"] as StringContextData;
144165
workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty);
145166

146-
Inputs.TryGetValue("shell", out var shell);
167+
string shell = null;
168+
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
169+
{
170+
// TODO: figure out how defaults interact with template later
171+
// for now, we won't check job.defaults if we are inside a template.
172+
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
173+
{
174+
if (runDefaults.TryGetValue("shell", out shell))
175+
{
176+
ExecutionContext.Debug("Overwrite 'shell' base on job defaults.");
177+
}
178+
}
179+
}
180+
147181
var isContainerStepHost = StepHost is ContainerStepHost;
148182

149183
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());

src/Runner.Worker/JobExtension.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,26 @@ public async Task<List<IStep>> InitializeJob(IExecutionContext jobContext, Pipel
161161
}
162162
}
163163

164+
// Evaluate the job defaults
165+
context.Debug("Evaluating job defaults");
166+
foreach (var token in message.Defaults)
167+
{
168+
var defaults = token.AssertMapping("defaults");
169+
if (defaults.Any(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase)))
170+
{
171+
context.JobDefaults["run"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
172+
var defaultsRun = defaults.First(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase));
173+
var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues);
174+
foreach (var pair in jobDefaults)
175+
{
176+
if (!string.IsNullOrEmpty(pair.Value))
177+
{
178+
context.JobDefaults["run"][pair.Key] = pair.Value;
179+
}
180+
}
181+
}
182+
}
183+
164184
// Build up 2 lists of steps, pre-job, job
165185
// Download actions not already in the cache
166186
Trace.Info("Downloading actions");

src/Sdk/DTPipelines/Pipelines/AgentJobRequestMessage.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public AgentJobRequestMessage(
4141
IEnumerable<JobStep> steps,
4242
IEnumerable<ContextScope> scopes,
4343
IList<String> fileTable,
44-
TemplateToken jobOutputs)
44+
TemplateToken jobOutputs,
45+
IList<TemplateToken> defaults)
4546
{
4647
this.MessageType = JobRequestMessageTypes.PipelineAgentJobRequest;
4748
this.Plan = plan;
@@ -69,6 +70,11 @@ public AgentJobRequestMessage(
6970
m_environmentVariables = new List<TemplateToken>(environmentVariables);
7071
}
7172

73+
if (defaults?.Count > 0)
74+
{
75+
m_defaults = new List<TemplateToken>(defaults);
76+
}
77+
7278
this.ContextData = new Dictionary<String, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
7379
if (contextData?.Count > 0)
7480
{
@@ -213,6 +219,21 @@ public IList<TemplateToken> EnvironmentVariables
213219
}
214220
}
215221

222+
/// <summary>
223+
/// Gets the hierarchy of defaults to overlay, last wins.
224+
/// </summary>
225+
public IList<TemplateToken> Defaults
226+
{
227+
get
228+
{
229+
if (m_defaults == null)
230+
{
231+
m_defaults = new List<TemplateToken>();
232+
}
233+
return m_defaults;
234+
}
235+
}
236+
216237
/// <summary>
217238
/// Gets the collection of variables associated with the current context.
218239
/// </summary>
@@ -252,6 +273,9 @@ public IList<ContextScope> Scopes
252273
}
253274
}
254275

276+
/// <summary>
277+
/// Gets the table of files used when parsing the pipeline (e.g. yaml files)
278+
/// </summary>
255279
public IList<String> FileTable
256280
{
257281
get
@@ -372,6 +396,11 @@ private void OnSerializing(StreamingContext context)
372396
m_environmentVariables = null;
373397
}
374398

399+
if (m_defaults?.Count == 0)
400+
{
401+
m_defaults = null;
402+
}
403+
375404
if (m_fileTable?.Count == 0)
376405
{
377406
m_fileTable = null;
@@ -406,6 +435,9 @@ private void OnSerializing(StreamingContext context)
406435
[DataMember(Name = "EnvironmentVariables", EmitDefaultValue = false)]
407436
private List<TemplateToken> m_environmentVariables;
408437

438+
[DataMember(Name = "Defaults", EmitDefaultValue = false)]
439+
private List<TemplateToken> m_defaults;
440+
409441
[DataMember(Name = "FileTable", EmitDefaultValue = false)]
410442
private List<String> m_fileTable;
411443

src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public sealed class PipelineTemplateConstants
1414
public const String Clean = "clean";
1515
public const String Container = "container";
1616
public const String ContinueOnError = "continue-on-error";
17+
public const String Defaults = "defaults";
1718
public const String Env = "env";
1819
public const String Event = "event";
1920
public const String EventPattern = "github.event";
@@ -29,6 +30,7 @@ public sealed class PipelineTemplateConstants
2930
public const String Include = "include";
3031
public const String Inputs = "inputs";
3132
public const String Job = "job";
33+
public const String JobDefaultsRun = "job-defaults-run";
3234
public const String JobOutputs = "job-outputs";
3335
public const String Jobs = "jobs";
3436
public const String Labels = "labels";

src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,42 @@ public Dictionary<String, String> EvaluateJobOutput(
267267
return result;
268268
}
269269

270+
public Dictionary<String, String> EvaluateJobDefaultsRun(
271+
TemplateToken token,
272+
DictionaryContextData contextData)
273+
{
274+
var result = default(Dictionary<String, String>);
275+
276+
if (token != null && token.Type != TokenType.Null)
277+
{
278+
var context = CreateContext(contextData);
279+
try
280+
{
281+
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobDefaultsRun, token, 0, null, omitHeader: true);
282+
context.Errors.Check();
283+
result = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
284+
var mapping = token.AssertMapping("defaults run");
285+
foreach (var pair in mapping)
286+
{
287+
// Literal key
288+
var key = pair.Key.AssertString("defaults run key");
289+
290+
// Literal value
291+
var value = pair.Value.AssertString("defaults run value");
292+
result[key.Value] = value.Value;
293+
}
294+
}
295+
catch (Exception ex) when (!(ex is TemplateValidationException))
296+
{
297+
context.Errors.Add(ex);
298+
}
299+
300+
context.Errors.Check();
301+
}
302+
303+
return result;
304+
}
305+
270306
public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
271307
TemplateToken token,
272308
DictionaryContextData contextData)

src/Sdk/DTPipelines/workflow-v1.0.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"properties": {
1010
"on": "any",
1111
"name": "string",
12+
"defaults": "workflow-defaults",
1213
"env": "workflow-env",
1314
"jobs": "jobs"
1415
}
@@ -125,6 +126,23 @@
125126
"string": {}
126127
},
127128

129+
"workflow-defaults": {
130+
"mapping": {
131+
"properties": {
132+
"run": "workflow-defaults-run"
133+
}
134+
}
135+
},
136+
137+
"workflow-defaults-run": {
138+
"mapping": {
139+
"properties": {
140+
"shell": "non-empty-string",
141+
"working-directory": "non-empty-string"
142+
}
143+
}
144+
},
145+
128146
"workflow-env": {
129147
"context": [
130148
"github",
@@ -161,6 +179,7 @@
161179
"services": "services",
162180
"env": "job-env",
163181
"outputs": "job-outputs",
182+
"defaults": "job-defaults",
164183
"steps": "steps"
165184
}
166185
}
@@ -289,6 +308,30 @@
289308
}
290309
},
291310

311+
"job-defaults": {
312+
"mapping": {
313+
"properties": {
314+
"run": "job-defaults-run"
315+
}
316+
}
317+
},
318+
319+
"job-defaults-run": {
320+
"context": [
321+
"github",
322+
"strategy",
323+
"matrix",
324+
"needs",
325+
"env"
326+
],
327+
"mapping": {
328+
"properties": {
329+
"shell": "non-empty-string",
330+
"working-directory": "non-empty-string"
331+
}
332+
}
333+
},
334+
292335
"job-outputs": {
293336
"mapping": {
294337
"loose-key-type": "non-empty-string",

src/Test/L0/Listener/JobDispatcherL0.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ private Pipelines.AgentJobRequestMessage CreateJobRequestMessage()
3333
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
3434
TimelineReference timeline = null;
3535
Guid jobId = Guid.NewGuid();
36-
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null);
36+
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
3737
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
3838
return result;
3939
}

src/Test/L0/Listener/RunnerL0.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
4343
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
4444
TimelineReference timeline = null;
4545
Guid jobId = Guid.NewGuid();
46-
return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null);
46+
return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
4747
}
4848

4949
private JobCancelMessage CreateJobCancelMessage()

src/Test/L0/Worker/ActionCommandManagerL0.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public void EchoProcessCommandDebugOn()
150150
TimelineReference timeline = new TimelineReference();
151151
Guid jobId = Guid.NewGuid();
152152
string jobName = "some job name";
153-
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null);
153+
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
154154
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
155155
{
156156
Alias = Pipelines.PipelineConstants.SelfAlias,

0 commit comments

Comments
 (0)