mirror of
https://github.com/actions/runner.git
synced 2025-12-11 12:57:05 +00:00
Support action.yaml file (#288)
* Support action.yaml file * L0 tests. * l0 Co-authored-by: Tingluo Huang <tingluohuang@github.com>
This commit is contained in:
committed by
Tingluo Huang
parent
7a382facb3
commit
c59c0e2ded
@@ -162,7 +162,8 @@ namespace GitHub.Runner.Common
|
|||||||
public static class Path
|
public static class Path
|
||||||
{
|
{
|
||||||
public static readonly string ActionsDirectory = "_actions";
|
public static readonly string ActionsDirectory = "_actions";
|
||||||
public static readonly string ActionManifestFile = "action.yml";
|
public static readonly string ActionManifestYmlFile = "action.yml";
|
||||||
|
public static readonly string ActionManifestYamlFile = "action.yaml";
|
||||||
public static readonly string BinDirectory = "bin";
|
public static readonly string BinDirectory = "bin";
|
||||||
public static readonly string DiagDirectory = "_diag";
|
public static readonly string DiagDirectory = "_diag";
|
||||||
public static readonly string ExternalsDirectory = "externals";
|
public static readonly string ExternalsDirectory = "externals";
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
private const int _defaultFileStreamBufferSize = 4096;
|
private const int _defaultFileStreamBufferSize = 4096;
|
||||||
|
|
||||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||||
private const int _defaultCopyBufferSize = 81920;
|
private const int _defaultCopyBufferSize = 81920;
|
||||||
|
|
||||||
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
||||||
@@ -198,14 +198,21 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Load action that reference repository from '{actionDirectory}'");
|
Trace.Info($"Load action that reference repository from '{actionDirectory}'");
|
||||||
definition.Directory = actionDirectory;
|
definition.Directory = actionDirectory;
|
||||||
|
|
||||||
string manifestFile = Path.Combine(actionDirectory, "action.yml");
|
string manifestFile = Path.Combine(actionDirectory, Constants.Path.ActionManifestYmlFile);
|
||||||
|
string manifestFileYaml = Path.Combine(actionDirectory, Constants.Path.ActionManifestYamlFile);
|
||||||
string dockerFile = Path.Combine(actionDirectory, "Dockerfile");
|
string dockerFile = Path.Combine(actionDirectory, "Dockerfile");
|
||||||
string dockerFileLowerCase = Path.Combine(actionDirectory, "dockerfile");
|
string dockerFileLowerCase = Path.Combine(actionDirectory, "dockerfile");
|
||||||
if (File.Exists(manifestFile))
|
if (File.Exists(manifestFile) || File.Exists(manifestFileYaml))
|
||||||
{
|
{
|
||||||
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
||||||
definition.Data = manifestManager.Load(executionContext, manifestFile);
|
if (File.Exists(manifestFile))
|
||||||
|
{
|
||||||
|
definition.Data = manifestManager.Load(executionContext, manifestFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
definition.Data = manifestManager.Load(executionContext, manifestFileYaml);
|
||||||
|
}
|
||||||
Trace.Verbose($"Action friendly name: '{definition.Data.Name}'");
|
Trace.Verbose($"Action friendly name: '{definition.Data.Name}'");
|
||||||
Trace.Verbose($"Action description: '{definition.Data.Description}'");
|
Trace.Verbose($"Action description: '{definition.Data.Description}'");
|
||||||
|
|
||||||
@@ -314,7 +321,7 @@ namespace GitHub.Runner.Worker
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var fullPath = IOUtil.ResolvePath(actionDirectory, "."); // resolve full path without access filesystem.
|
var fullPath = IOUtil.ResolvePath(actionDirectory, "."); // resolve full path without access filesystem.
|
||||||
throw new NotSupportedException($"Can't find 'action.yml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
|
throw new NotSupportedException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (action.Reference.Type == Pipelines.ActionSourceType.Script)
|
else if (action.Reference.Type == Pipelines.ActionSourceType.Script)
|
||||||
@@ -477,7 +484,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
|
|
||||||
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
||||||
int timeoutSeconds = 20 * 60;
|
int timeoutSeconds = 20 * 60;
|
||||||
while (retryCount < 3)
|
while (retryCount < 3)
|
||||||
{
|
{
|
||||||
@@ -655,12 +662,21 @@ namespace GitHub.Runner.Worker
|
|||||||
// find the docker file or action.yml file
|
// find the docker file or action.yml file
|
||||||
var dockerFile = Path.Combine(actionEntryDirectory, "Dockerfile");
|
var dockerFile = Path.Combine(actionEntryDirectory, "Dockerfile");
|
||||||
var dockerFileLowerCase = Path.Combine(actionEntryDirectory, "dockerfile");
|
var dockerFileLowerCase = Path.Combine(actionEntryDirectory, "dockerfile");
|
||||||
var actionManifest = Path.Combine(actionEntryDirectory, "action.yml");
|
var actionManifest = Path.Combine(actionEntryDirectory, Constants.Path.ActionManifestYmlFile);
|
||||||
if (File.Exists(actionManifest))
|
var actionManifestYaml = Path.Combine(actionEntryDirectory, Constants.Path.ActionManifestYamlFile);
|
||||||
|
if (File.Exists(actionManifest) || File.Exists(actionManifestYaml))
|
||||||
{
|
{
|
||||||
executionContext.Debug($"action.yml for action: '{actionManifest}'.");
|
executionContext.Debug($"action.yml for action: '{actionManifest}'.");
|
||||||
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
||||||
var actionDefinitionData = manifestManager.Load(executionContext, actionManifest);
|
ActionDefinitionData actionDefinitionData = null;
|
||||||
|
if (File.Exists(actionManifest))
|
||||||
|
{
|
||||||
|
actionDefinitionData = manifestManager.Load(executionContext, actionManifest);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actionDefinitionData = manifestManager.Load(executionContext, actionManifestYaml);
|
||||||
|
}
|
||||||
|
|
||||||
if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Container)
|
if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Container)
|
||||||
{
|
{
|
||||||
@@ -720,7 +736,7 @@ namespace GitHub.Runner.Worker
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var fullPath = IOUtil.ResolvePath(actionEntryDirectory, "."); // resolve full path without access filesystem.
|
var fullPath = IOUtil.ResolvePath(actionEntryDirectory, "."); // resolve full path without access filesystem.
|
||||||
throw new InvalidOperationException($"Can't find 'action.yml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
|
throw new InvalidOperationException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,6 +373,45 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async void PrepareActions_RepositoryActionWithActionYamlFile_DockerHubImage()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var actions = new List<Pipelines.ActionStep>
|
||||||
|
{
|
||||||
|
new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "TingluoHuang/runner_L0",
|
||||||
|
Ref = "RepositoryActionWithActionYamlFile_DockerHubImage",
|
||||||
|
RepositoryType = "GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionYamlFile_DockerHubImage");
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
|
Assert.Equal((steps[0].Data as ContainerSetupInfo).StepIds[0], actionId);
|
||||||
|
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -672,7 +711,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -772,7 +811,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -871,7 +910,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -925,6 +964,87 @@ runs:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void LoadsNodeActionDefinitionYaml()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
const string Content = @"
|
||||||
|
# Container action
|
||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet the world and record the time'
|
||||||
|
author: 'GitHub'
|
||||||
|
inputs:
|
||||||
|
greeting: # id of input
|
||||||
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
|
required: true
|
||||||
|
default: 'Hello'
|
||||||
|
entryPoint: # id of input
|
||||||
|
description: 'optional docker entrypoint overwrite.'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we did the greeting'
|
||||||
|
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||||
|
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||||
|
runs:
|
||||||
|
using: 'node12'
|
||||||
|
main: 'task.js'
|
||||||
|
";
|
||||||
|
Pipelines.ActionStep instance;
|
||||||
|
string directory;
|
||||||
|
directory = Path.Combine(_workFolder, Constants.Path.ActionsDirectory, "GitHub/actions".Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), "master");
|
||||||
|
string file = Path.Combine(directory, Constants.Path.ActionManifestYamlFile);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
||||||
|
File.WriteAllText(file, Content);
|
||||||
|
instance = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "GitHub/actions",
|
||||||
|
Ref = "master",
|
||||||
|
RepositoryType = Pipelines.RepositoryTypes.GitHub
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
Definition definition = _actionManager.LoadAction(_ec.Object, instance);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.NotNull(definition);
|
||||||
|
Assert.Equal(directory, definition.Directory);
|
||||||
|
Assert.NotNull(definition.Data);
|
||||||
|
Assert.NotNull(definition.Data.Inputs); // inputs
|
||||||
|
Dictionary<string, string> inputDefaults = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var input in definition.Data.Inputs)
|
||||||
|
{
|
||||||
|
var name = input.Key.AssertString("key").Value;
|
||||||
|
var value = input.Value.AssertScalar("value").ToString();
|
||||||
|
|
||||||
|
_hc.GetTrace().Info($"Default: {name} = {value}");
|
||||||
|
inputDefaults[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(2, inputDefaults.Count);
|
||||||
|
Assert.True(inputDefaults.ContainsKey("greeting"));
|
||||||
|
Assert.Equal("Hello", inputDefaults["greeting"]);
|
||||||
|
Assert.True(string.IsNullOrEmpty(inputDefaults["entryPoint"]));
|
||||||
|
Assert.NotNull(definition.Data.Execution); // execution
|
||||||
|
|
||||||
|
Assert.NotNull((definition.Data.Execution as NodeJSActionExecutionData));
|
||||||
|
Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -940,7 +1060,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -1039,7 +1159,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -1137,7 +1257,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -1205,7 +1325,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -1276,7 +1396,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -1376,7 +1496,7 @@ runs:
|
|||||||
name: 'Hello World'
|
name: 'Hello World'
|
||||||
description: 'Greet the world and record the time'
|
description: 'Greet the world and record the time'
|
||||||
author: 'Test Corporation'
|
author: 'Test Corporation'
|
||||||
inputs:
|
inputs:
|
||||||
greeting: # id of input
|
greeting: # id of input
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
required: true
|
required: true
|
||||||
@@ -1433,7 +1553,7 @@ runs:
|
|||||||
private void CreateAction(string yamlContent, out Pipelines.ActionStep instance, out string directory)
|
private void CreateAction(string yamlContent, out Pipelines.ActionStep instance, out string directory)
|
||||||
{
|
{
|
||||||
directory = Path.Combine(_workFolder, Constants.Path.ActionsDirectory, "GitHub/actions".Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), "master");
|
directory = Path.Combine(_workFolder, Constants.Path.ActionsDirectory, "GitHub/actions".Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), "master");
|
||||||
string file = Path.Combine(directory, Constants.Path.ActionManifestFile);
|
string file = Path.Combine(directory, Constants.Path.ActionManifestYmlFile);
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
||||||
File.WriteAllText(file, yamlContent);
|
File.WriteAllText(file, yamlContent);
|
||||||
instance = new Pipelines.ActionStep()
|
instance = new Pipelines.ActionStep()
|
||||||
@@ -1451,7 +1571,7 @@ runs:
|
|||||||
private void CreateSelfRepoAction(string yamlContent, out Pipelines.ActionStep instance, out string directory)
|
private void CreateSelfRepoAction(string yamlContent, out Pipelines.ActionStep instance, out string directory)
|
||||||
{
|
{
|
||||||
directory = Path.Combine(_workFolder, "actions", "actions");
|
directory = Path.Combine(_workFolder, "actions", "actions");
|
||||||
string file = Path.Combine(directory, Constants.Path.ActionManifestFile);
|
string file = Path.Combine(directory, Constants.Path.ActionManifestYmlFile);
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
||||||
File.WriteAllText(file, yamlContent);
|
File.WriteAllText(file, yamlContent);
|
||||||
instance = new Pipelines.ActionStep()
|
instance = new Pipelines.ActionStep()
|
||||||
|
|||||||
Reference in New Issue
Block a user