mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
* Added ability to run Dockerfile.SUFFIX ContainerAction * Extracted IsDockerFile method * reformatted, moved from index to Last() * extracted IsDockerfile to DockerUtil with L0 * added check for IsDockerfile to account for docker:// * updated test to clearly show path/dockerfile:tag * fail if Data.Image is not Dockerfile or docker://[image] * Setup noops for JobPrepare and JobCleanup hooks * Add container jobstarted and jobcomplete hooks * Run 'index.js' instead of specific command hooks * Call jobprepare with command arg * Use right command name (hardcoded) Co-authored-by: Nikola Jokic <nikola-jokic@users.noreply.github.com> * Invoke hooks with arguments * Add PrepareJob hook to work with jobcontainers Co-authored-by: Nikola Jokic <nikola-jokic@users.noreply.github.com> * Rename methods * Use new hookcontainer to run prep and clean hooks * Get path from ENV * Use enums * Use IOUtils.cs * Move container files to folder * Move namespaces * Store "state" between hooks * Remove stdin stream in containerstephosts * Update Constants.cs * Throw if stdin fails * Cleanup obvious nullrefs and unused vars * Cleanup containerhook directory * Call step exec hook * Fix windows build * Remove hook from hookContainer * Rename file * More renamings * Add TODOs * Fix env name * Fix missing imports * Fix imports * Run script step on jobcontainer * Enable feature if env is set * Update ContainerHookManager.cs * Update ContainerHookManager.cs * Hooks allowed to work even when context isn't returned * Custom hooks enabled flag and additional null checks * New line at the end of the FeatureFlagManager.cs * Code refactoring * Supported just in time container building or pulling * Try mock-build for osx * Build all platforms * Run mock on self-hosted * Remove GITHUB prefix * Use ContainerHooksPath instead of CustomHooksPath * Null checks simplified * Code refactoring * Changing condition for image builing/pulling * Code refactoring * TODO comment removed Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com> * Call container step if FF is on * Rename run script function * Use JToken instead of dynamic * Add TODO * Small refactoring + renames + TODOs * Throw on DetermineNodeRuntimeVersion * Fix formatting * Add run-container-step * Supported nodeJS in Alpine containers * Renamed Alpine to IsAlpine in HookResponse * Method for checking platform for alpine container * Added container hooks feature flag check * Update IsHookFeatureEnabled with new params * Rename featureflag method * Finish rename * Set collection null values to empty arrays when JSON serialising them * Disable FF until we merge * Update src/Runner.Worker/Container/ContainerHooks/HookContainer.cs * Fix method name * Change hookargs to superclass from interface * Using only Path.Combine in GenerateResponsePath * fix merge error * EntryPointArgs changed to list of args instead of one args string * Changed List to IEnumerable for EntryPointArgs and MountVolumes * Get ContainerRuntimePath for JobContainers from hooks * Read ContainerEnv from response file * Port mappings saved after creating services * Support case when responseFile doesn't exist * Check if response file exists * Logging in ExecuteHookScript * Save hook state after all 4 hooks * Code refactoring * Remove TODO Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com> * Remove second TODO Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com> * Removing container env changes * Removing containerEnv and dockerManager * Delete mock-build.yml * Update IOUtil.cs * Add comment about containerhooks * Fix merge mistake * Remove solved todo * Determine which shell to use for hooks scenario * Overload for method ExecuteHookScript with prependPath as arg * Adding HostContext to the GetDefaultShellForScript call * prependPath as a mandatory parameter * Improve logging for hooks * Small changes in logging * Allow null for ContainerEntryPointArgs * Changed log messages * Skip setting EntryPoint and EntryPointArgs if hooks are enabled * Throw if IsAlpine is null in PrepareJob * Code refactoring - added GetAndValidateResponse method * Code refactoring * Changes in exception message * Only save hookState if returned * Use FF from server * Empty line * Code refactoring Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com> * Send null instead of string empty * Remove TODO * Code refactoring and some small changes * Allow Globals to be null to pass L0 * Fix setup in StepHostL0 * Throw exception earlier if response file doesn't exist and prepare_job hook is running * Refactoring GetResponse method * Changing exception message if response file is not found Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com> * Chaning exception message if isAlpine is null for prepare_job hook * Rename hook folder * Fail if compatible hookfile not found * Use .Value instead of casting bool? to bool * Format spacing * Formatting * User user and system mvs * Use variables instead of entire context in featuremanager * Update stepTelemetry if step uses containerhooks * Restore import * Remove unneccessary field from HookContainer * Refactor response context and portmappings * Force allow hooks if FF is on * Code refactoring * Revert deleting usings * Better hookContainer defaults and use correct portmapping list * Make GetDefaultShellForScript a HostContext extension method * Generic hookresponse * Code refactoring, unnecessary properties removed - HookContainer moved to the HookInput.cs * Remove empty line * Code refactoring and better exception handling * code refactor, removing unnecessary props * Move hookstate to global ContainerHookState * Trace exception before we throw it for not losing information * Fix for null ref exception in GetResponse * Adding additional check for null response in prepareJob hook * Refactoring GetResponse with additional check * Update error messages * Ports in ResponseContainer changed from IList to IDictionary * Fix port format * Include dockerfile * Send null Registry obj if there's nothing in it * Minor formatting * Check if hookIndexPath exists relocated to the ContainerHookManager * Code refactoring - ValidateHookExecutable added to the ContainerHookManager * check if ContainerHooksPath when AllowRunnerContainerHooks is on * Submit JSON telemetry instead of boolean * Prefix step hooks with "run" * Rename FeatureManager * Fix flipped condition * Unify js shell path getter with ps1 and sh getter * Validate on run, not on instantiation of manager * Cleanup ExecuteAsync methods * Handle exception in executeHookScript * Better exception types * Remove comment * Simplify boolean * Allow jobs without jobContainer to run * Use JObject instead of JToken * Use correct Response type * Format class to move cleanupJobAsync to the end of public methods * Rename HookIndexPath to HookScriptPath * Refactor methods into expression bodies * Fix args class hierarchy * Fix argument order * Formatting * Fix nullref and don't swallow stacktrace * Whilelist HookArgs * Use FF in FeatureManager * Update src/Runner.Worker/ContainerOperationProvider.cs Co-authored-by: Tingluo Huang <tingluohuang@github.com> * Update src/Runner.Worker/ActionRunner.cs Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com> * Update src/Runner.Worker/ActionRunner.cs Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com> * Only mount well known dirs to job containers * Get trace from hostcontext * Use hook execution for setting telemetry Co-authored-by: Nikola Jokic <nikola.jokic@akvelon.com> Co-authored-by: Nikola Jokic <nikola-jokic@users.noreply.github.com> Co-authored-by: Nikola Jokic <97525037+nikola-jokic@users.noreply.github.com> Co-authored-by: Stefan Ruvceski <stefan.ruvceski@akvelon.com> Co-authored-by: ruvceskistefan <96768603+ruvceskistefan@users.noreply.github.com> Co-authored-by: Thomas Boop <thboop@github.com> Co-authored-by: stefanruvceski <ruvceskistefan@github.com> Co-authored-by: Tingluo Huang <tingluohuang@github.com> Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
376 lines
13 KiB
C#
376 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using GitHub.Runner.Common;
|
|
using GitHub.Runner.Sdk;
|
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
|
|
namespace GitHub.Runner.Worker.Container
|
|
{
|
|
public class ContainerInfo
|
|
{
|
|
private List<MountVolume> _mountVolumes;
|
|
private IDictionary<string, string> _userPortMappings;
|
|
private List<PortMapping> _portMappings;
|
|
private IDictionary<string, string> _environmentVariables;
|
|
private List<PathMapping> _pathMappings = new List<PathMapping>();
|
|
|
|
public ContainerInfo()
|
|
{
|
|
}
|
|
|
|
public ContainerInfo(IHostContext hostContext)
|
|
{
|
|
UpdateWebProxyEnv(hostContext.WebProxy);
|
|
}
|
|
|
|
public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer container, bool isJobContainer = true, string networkAlias = null)
|
|
{
|
|
this.ContainerName = container.Alias;
|
|
|
|
string containerImage = container.Image;
|
|
ArgUtil.NotNullOrEmpty(containerImage, nameof(containerImage));
|
|
|
|
this.ContainerImage = containerImage;
|
|
this.ContainerDisplayName = $"{container.Alias}_{Pipelines.Validation.NameValidation.Sanitize(containerImage)}_{Guid.NewGuid().ToString("N").Substring(0, 6)}";
|
|
this.ContainerCreateOptions = container.Options;
|
|
_environmentVariables = container.Environment;
|
|
this.IsJobContainer = isJobContainer;
|
|
this.ContainerNetworkAlias = networkAlias;
|
|
this.RegistryAuthUsername = container.Credentials?.Username;
|
|
this.RegistryAuthPassword = container.Credentials?.Password;
|
|
this.RegistryServer = DockerUtil.ParseRegistryHostnameFromImageName(this.ContainerImage);
|
|
|
|
#if OS_WINDOWS
|
|
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Work), "C:\\__w"));
|
|
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Tools), "C:\\__t")); // Tool cache folder may come from ENV, so we need a unique folder to avoid collision
|
|
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Externals), "C:\\__e"));
|
|
// add -v '\\.\pipe\docker_engine:\\.\pipe\docker_engine' when they are available (17.09)
|
|
#else
|
|
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Work), "/__w"));
|
|
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Tools), "/__t")); // Tool cache folder may come from ENV, so we need a unique folder to avoid collision
|
|
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Externals), "/__e"));
|
|
if (this.IsJobContainer)
|
|
{
|
|
this.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock"));
|
|
}
|
|
#endif
|
|
if (container.Ports?.Count > 0)
|
|
{
|
|
foreach (var port in container.Ports)
|
|
{
|
|
UserPortMappings[port] = port;
|
|
}
|
|
}
|
|
if (container.Volumes?.Count > 0)
|
|
{
|
|
foreach (var volume in container.Volumes)
|
|
{
|
|
MountVolumes.Add(new MountVolume(volume, isUserProvided: true));
|
|
}
|
|
}
|
|
|
|
UpdateWebProxyEnv(hostContext.WebProxy);
|
|
}
|
|
|
|
public string ContainerId { get; set; }
|
|
public string ContainerDisplayName { get; set; }
|
|
public string ContainerNetwork { get; set; }
|
|
public string ContainerNetworkAlias { get; set; }
|
|
public string ContainerImage { get; set; }
|
|
public string ContainerName { get; set; }
|
|
public string ContainerEntryPointArgs { get; set; }
|
|
public string ContainerEntryPoint { get; set; }
|
|
public string ContainerWorkDirectory { get; set; }
|
|
public string ContainerCreateOptions { get; private set; }
|
|
public string ContainerRuntimePath { get; set; }
|
|
public string RegistryServer { get; set; }
|
|
public string RegistryAuthUsername { get; set; }
|
|
public string RegistryAuthPassword { get; set; }
|
|
public bool IsJobContainer { get; set; }
|
|
public bool IsAlpine { get; set; }
|
|
|
|
public IDictionary<string, string> ContainerEnvironmentVariables
|
|
{
|
|
get
|
|
{
|
|
if (_environmentVariables == null)
|
|
{
|
|
_environmentVariables = new Dictionary<string, string>();
|
|
}
|
|
|
|
return _environmentVariables;
|
|
}
|
|
}
|
|
public ReadOnlyCollection<MountVolume> UserMountVolumes
|
|
{
|
|
get
|
|
{
|
|
return MountVolumes.Where(v => !string.IsNullOrEmpty(v.UserProvidedValue)).ToList().AsReadOnly();
|
|
}
|
|
}
|
|
public ReadOnlyCollection<MountVolume> SystemMountVolumes
|
|
{
|
|
get
|
|
{
|
|
return MountVolumes.Where(v => string.IsNullOrEmpty(v.UserProvidedValue)).ToList().AsReadOnly();
|
|
}
|
|
}
|
|
public List<MountVolume> MountVolumes
|
|
{
|
|
get
|
|
{
|
|
if (_mountVolumes == null)
|
|
{
|
|
_mountVolumes = new List<MountVolume>();
|
|
}
|
|
|
|
return _mountVolumes;
|
|
}
|
|
}
|
|
|
|
public IDictionary<string, string> UserPortMappings
|
|
{
|
|
get
|
|
{
|
|
if (_userPortMappings == null)
|
|
{
|
|
_userPortMappings = new Dictionary<string, string>();
|
|
}
|
|
|
|
return _userPortMappings;
|
|
}
|
|
}
|
|
|
|
public List<PortMapping> PortMappings
|
|
{
|
|
get
|
|
{
|
|
if (_portMappings == null)
|
|
{
|
|
_portMappings = new List<PortMapping>();
|
|
}
|
|
|
|
return _portMappings;
|
|
}
|
|
}
|
|
|
|
public string TranslateToContainerPath(string path)
|
|
{
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
foreach (var mapping in _pathMappings)
|
|
{
|
|
#if OS_WINDOWS
|
|
if (string.Equals(path, mapping.HostPath, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return mapping.ContainerPath;
|
|
}
|
|
|
|
if (path.StartsWith(mapping.HostPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ||
|
|
path.StartsWith(mapping.HostPath + Path.AltDirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return mapping.ContainerPath + path.Remove(0, mapping.HostPath.Length);
|
|
}
|
|
#else
|
|
if (string.Equals(path, mapping.HostPath))
|
|
{
|
|
return mapping.ContainerPath;
|
|
}
|
|
|
|
if (path.StartsWith(mapping.HostPath + Path.DirectorySeparatorChar))
|
|
{
|
|
return mapping.ContainerPath + path.Remove(0, mapping.HostPath.Length);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
public string TranslateToHostPath(string path)
|
|
{
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
foreach (var mapping in _pathMappings)
|
|
{
|
|
#if OS_WINDOWS
|
|
if (string.Equals(path, mapping.ContainerPath, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return mapping.HostPath;
|
|
}
|
|
|
|
if (path.StartsWith(mapping.ContainerPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ||
|
|
path.StartsWith(mapping.ContainerPath + Path.AltDirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return mapping.HostPath + path.Remove(0, mapping.ContainerPath.Length);
|
|
}
|
|
#else
|
|
if (string.Equals(path, mapping.ContainerPath))
|
|
{
|
|
return mapping.HostPath;
|
|
}
|
|
|
|
if (path.StartsWith(mapping.ContainerPath + Path.DirectorySeparatorChar))
|
|
{
|
|
return mapping.HostPath + path.Remove(0, mapping.ContainerPath.Length);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
public void AddPortMappings(List<PortMapping> portMappings)
|
|
{
|
|
foreach (var port in portMappings)
|
|
{
|
|
PortMappings.Add(port);
|
|
}
|
|
}
|
|
|
|
public void AddPortMappings(IDictionary<string, string> portMappings)
|
|
{
|
|
foreach (var pair in portMappings)
|
|
{
|
|
PortMappings.Add(new PortMapping(pair.Key, pair.Value));
|
|
}
|
|
}
|
|
|
|
public void AddPathTranslateMapping(string hostCommonPath, string containerCommonPath)
|
|
{
|
|
_pathMappings.Insert(0, new PathMapping(hostCommonPath, containerCommonPath));
|
|
}
|
|
|
|
private void UpdateWebProxyEnv(RunnerWebProxy webProxy)
|
|
{
|
|
// Set common forms of proxy variables if configured in Runner and not set directly by container.env
|
|
if (!String.IsNullOrEmpty(webProxy.HttpProxyAddress))
|
|
{
|
|
ContainerEnvironmentVariables.TryAdd("HTTP_PROXY", webProxy.HttpProxyAddress);
|
|
ContainerEnvironmentVariables.TryAdd("http_proxy", webProxy.HttpProxyAddress);
|
|
}
|
|
if (!String.IsNullOrEmpty(webProxy.HttpsProxyAddress))
|
|
{
|
|
ContainerEnvironmentVariables.TryAdd("HTTPS_PROXY", webProxy.HttpsProxyAddress);
|
|
ContainerEnvironmentVariables.TryAdd("https_proxy", webProxy.HttpsProxyAddress);
|
|
}
|
|
if (!String.IsNullOrEmpty(webProxy.NoProxyString))
|
|
{
|
|
ContainerEnvironmentVariables.TryAdd("NO_PROXY", webProxy.NoProxyString);
|
|
ContainerEnvironmentVariables.TryAdd("no_proxy", webProxy.NoProxyString);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class MountVolume
|
|
{
|
|
public string UserProvidedValue { get; set; }
|
|
public MountVolume(string sourceVolumePath, string targetVolumePath, bool readOnly = false)
|
|
{
|
|
this.SourceVolumePath = sourceVolumePath;
|
|
this.TargetVolumePath = targetVolumePath;
|
|
this.ReadOnly = readOnly;
|
|
}
|
|
public MountVolume(string fromString)
|
|
{
|
|
ParseVolumeString(fromString);
|
|
}
|
|
|
|
public MountVolume(string fromString, bool isUserProvided)
|
|
{
|
|
ParseVolumeString(fromString);
|
|
if (isUserProvided)
|
|
{
|
|
UserProvidedValue = fromString;
|
|
}
|
|
}
|
|
|
|
private void ParseVolumeString(string volume)
|
|
{
|
|
var volumeSplit = volume.Split(":");
|
|
if (volumeSplit.Length == 3)
|
|
{
|
|
// source:target:ro
|
|
SourceVolumePath = volumeSplit[0];
|
|
TargetVolumePath = volumeSplit[1];
|
|
ReadOnly = String.Equals(volumeSplit[2], "ro", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
else if (volumeSplit.Length == 2)
|
|
{
|
|
if (String.Equals(volumeSplit[1], "ro", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// target:ro
|
|
TargetVolumePath = volumeSplit[0];
|
|
ReadOnly = true;
|
|
}
|
|
else
|
|
{
|
|
// source:target
|
|
SourceVolumePath = volumeSplit[0];
|
|
TargetVolumePath = volumeSplit[1];
|
|
ReadOnly = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// target - or, default to passing straight through
|
|
TargetVolumePath = volume;
|
|
ReadOnly = false;
|
|
}
|
|
}
|
|
|
|
public string SourceVolumePath { get; set; }
|
|
public string TargetVolumePath { get; set; }
|
|
public bool ReadOnly { get; set; }
|
|
}
|
|
|
|
public class PortMapping
|
|
{
|
|
public PortMapping(string hostPort, string containerPort)
|
|
{
|
|
this.HostPort = hostPort;
|
|
this.ContainerPort = containerPort;
|
|
}
|
|
|
|
public PortMapping(string hostPort, string containerPort, string protocol)
|
|
{
|
|
this.HostPort = hostPort;
|
|
this.ContainerPort = containerPort;
|
|
this.Protocol = protocol;
|
|
}
|
|
|
|
public string HostPort { get; set; }
|
|
public string ContainerPort { get; set; }
|
|
public string Protocol { get; set; }
|
|
}
|
|
|
|
public class DockerVersion
|
|
{
|
|
public DockerVersion(Version serverVersion, Version clientVersion)
|
|
{
|
|
this.ServerVersion = serverVersion;
|
|
this.ClientVersion = clientVersion;
|
|
}
|
|
|
|
public Version ServerVersion { get; set; }
|
|
public Version ClientVersion { get; set; }
|
|
}
|
|
|
|
public class PathMapping
|
|
{
|
|
public PathMapping(string hostPath, string containerPath)
|
|
{
|
|
this.HostPath = hostPath;
|
|
this.ContainerPath = containerPath;
|
|
}
|
|
|
|
public string HostPath { get; set; }
|
|
public string ContainerPath { get; set; }
|
|
}
|
|
}
|