2019-10-10 00:52:42 -04:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Threading.Tasks ;
using System.Linq ;
using System.Threading ;
using GitHub.Runner.Worker.Container ;
using GitHub.Services.Common ;
using GitHub.Runner.Common ;
using GitHub.Runner.Sdk ;
using GitHub.DistributedTask.Pipelines.ContextData ;
2019-11-04 13:19:21 -05:00
using GitHub.DistributedTask.Pipelines.ObjectTemplating ;
2022-06-10 15:51:20 +02:00
using GitHub.Runner.Worker.Container.ContainerHooks ;
#if OS_WINDOWS // keep win specific imports around even through we don't support containers on win at the moment
using System.ServiceProcess ;
using Microsoft.Win32 ;
#endif
2019-10-10 00:52:42 -04:00
namespace GitHub.Runner.Worker
{
[ServiceLocator(Default = typeof(ContainerOperationProvider))]
public interface IContainerOperationProvider : IRunnerService
{
Task StartContainersAsync ( IExecutionContext executionContext , object data );
Task StopContainersAsync ( IExecutionContext executionContext , object data );
}
public class ContainerOperationProvider : RunnerService , IContainerOperationProvider
{
2021-06-04 16:51:30 +02:00
private IDockerCommandManager _dockerManager ;
2022-06-10 15:51:20 +02:00
private IContainerHookManager _containerHookManager ;
2019-10-10 00:52:42 -04:00
public override void Initialize ( IHostContext hostContext )
{
base . Initialize ( hostContext );
2021-06-04 16:51:30 +02:00
_dockerManager = HostContext . GetService < IDockerCommandManager >();
2022-06-10 15:51:20 +02:00
_containerHookManager = HostContext . GetService < IContainerHookManager >();
2019-10-10 00:52:42 -04:00
}
public async Task StartContainersAsync ( IExecutionContext executionContext , object data )
{
Trace . Entering ();
2019-12-16 14:53:19 -05:00
if (! Constants . Runner . Platform . Equals ( Constants . OSPlatform . Linux ))
{
throw new NotSupportedException ( "Container operations are only supported on Linux runners" );
}
2019-10-10 00:52:42 -04:00
ArgUtil . NotNull ( executionContext , nameof ( executionContext ));
List < ContainerInfo > containers = data as List < ContainerInfo >;
ArgUtil . NotNull ( containers , nameof ( containers ));
2019-11-04 13:19:21 -05:00
var postJobStep = new JobExtensionRunner ( runAsync : this . StopContainersAsync ,
condition : $"{PipelineTemplateConstants.Always}()" ,
displayName : "Stop containers" ,
data : data );
2020-04-08 11:17:54 -04:00
2019-12-16 15:45:00 -05:00
executionContext . Debug ( $"Register post job cleanup for stopping/deleting containers." );
2020-04-13 21:46:30 -04:00
executionContext . RegisterPostJobStep ( postJobStep );
2022-06-10 15:51:20 +02:00
if ( FeatureManager . IsContainerHooksEnabled ( executionContext . Global . Variables ))
2019-10-10 00:52:42 -04:00
{
2022-06-10 15:51:20 +02:00
// Initialize the containers
containers . ForEach ( container => UpdateRegistryAuthForGitHubToken ( executionContext , container ));
containers . Where ( container => container . IsJobContainer ). ForEach ( container => MountWellKnownDirectories ( executionContext , container ));
await _containerHookManager . PrepareJobAsync ( executionContext , containers );
return ;
2019-10-10 00:52:42 -04:00
}
2022-06-10 15:51:20 +02:00
await AssertCompatibleOS ( executionContext );
2019-10-10 00:52:42 -04:00
// Clean up containers left by previous runs
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[group]Clean up resources from previous jobs" );
2021-06-04 16:51:30 +02:00
var staleContainers = await _dockerManager . DockerPS ( executionContext , $"--all --quiet --no-trunc --filter \" label ={ _dockerManager . DockerInstanceLabel } \ "" );
2019-10-10 00:52:42 -04:00
foreach ( var staleContainer in staleContainers )
{
2021-06-04 16:51:30 +02:00
int containerRemoveExitCode = await _dockerManager . DockerRemove ( executionContext , staleContainer );
2019-10-10 00:52:42 -04:00
if ( containerRemoveExitCode != 0 )
{
executionContext . Warning ( $"Delete stale containers failed, docker rm fail with exit code {containerRemoveExitCode} for container {staleContainer}" );
}
}
2021-06-04 16:51:30 +02:00
int networkPruneExitCode = await _dockerManager . DockerNetworkPrune ( executionContext );
2019-10-10 00:52:42 -04:00
if ( networkPruneExitCode != 0 )
{
executionContext . Warning ( $"Delete stale container networks failed, docker network prune fail with exit code {networkPruneExitCode}" );
}
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[endgroup]" );
2019-10-10 00:52:42 -04:00
2019-12-16 15:45:00 -05:00
// Create local docker network for this job to avoid port conflict when multiple runners run on same machine.
2019-10-10 00:52:42 -04:00
// All containers within a job join the same network
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[group]Create local container network" );
2019-10-10 00:52:42 -04:00
var containerNetwork = $"github_network_{Guid.NewGuid().ToString(" N ")}" ;
await CreateContainerNetworkAsync ( executionContext , containerNetwork );
executionContext . JobContext . Container [ "network" ] = new StringContextData ( containerNetwork );
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[endgroup]" );
2019-10-10 00:52:42 -04:00
foreach ( var container in containers )
{
container . ContainerNetwork = containerNetwork ;
await StartContainerAsync ( executionContext , container );
}
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[group]Waiting for all services to be ready" );
2019-10-10 00:52:42 -04:00
foreach ( var container in containers . Where ( c => ! c . IsJobContainer ))
{
2022-08-30 12:17:35 +02:00
var healthcheck = await Healthcheck ( executionContext , container );
await ContainerHealthcheckLogs ( executionContext , container , healthcheck );
2019-10-10 00:52:42 -04:00
}
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[endgroup]" );
2019-10-10 00:52:42 -04:00
}
public async Task StopContainersAsync ( IExecutionContext executionContext , object data )
{
Trace . Entering ();
ArgUtil . NotNull ( executionContext , nameof ( executionContext ));
List < ContainerInfo > containers = data as List < ContainerInfo >;
ArgUtil . NotNull ( containers , nameof ( containers ));
2022-06-10 15:51:20 +02:00
if ( FeatureManager . IsContainerHooksEnabled ( executionContext . Global . Variables ))
{
await _containerHookManager . CleanupJobAsync ( executionContext , containers );
return ;
}
2019-10-10 00:52:42 -04:00
foreach ( var container in containers )
{
await StopContainerAsync ( executionContext , container );
}
// Remove the container network
await RemoveContainerNetworkAsync ( executionContext , containers . First (). ContainerNetwork );
}
private async Task StartContainerAsync ( IExecutionContext executionContext , ContainerInfo container )
{
Trace . Entering ();
ArgUtil . NotNull ( executionContext , nameof ( executionContext ));
ArgUtil . NotNull ( container , nameof ( container ));
ArgUtil . NotNullOrEmpty ( container . ContainerImage , nameof ( container . ContainerImage ));
Trace . Info ( $"Container name: {container.ContainerName}" );
Trace . Info ( $"Container image: {container.ContainerImage}" );
Trace . Info ( $"Container options: {container.ContainerCreateOptions}" );
2020-07-22 14:55:49 -04:00
var groupName = container . IsJobContainer ? "Starting job container" : $"Starting {container.ContainerNetworkAlias} service container" ;
executionContext . Output ( $"##[group]{groupName}" );
2019-10-10 00:52:42 -04:00
foreach ( var port in container . UserPortMappings )
{
Trace . Info ( $"User provided port: {port.Value}" );
}
2022-05-23 12:07:38 +02:00
foreach ( var mount in container . UserMountVolumes )
2019-10-10 00:52:42 -04:00
{
2022-05-23 12:07:38 +02:00
Trace . Info ( $"User provided volume: {mount.UserProvidedValue}" );
2020-04-08 11:17:54 -04:00
if ( string . Equals ( mount . SourceVolumePath , "/" , StringComparison . OrdinalIgnoreCase ))
{
2022-05-23 12:07:38 +02:00
executionContext . Warning ( $"Volume mount {mount.UserProvidedValue} is going to mount '/' into the container which may cause file ownership change in the entire file system and cause Actions Runner to lose permission to access the disk." );
2020-04-08 11:17:54 -04:00
}
2019-10-10 00:52:42 -04:00
}
2021-02-19 03:55:58 +01:00
UpdateRegistryAuthForGitHubToken ( executionContext , container );
2020-09-11 12:28:58 -04:00
// Before pulling, generate client authentication if required
var configLocation = await ContainerRegistryLogin ( executionContext , container );
2019-10-10 00:52:42 -04:00
// Pull down docker image with retry up to 3 times
int retryCount = 0 ;
int pullExitCode = 0 ;
while ( retryCount < 3 )
{
2021-06-04 16:51:30 +02:00
pullExitCode = await _dockerManager . DockerPull ( executionContext , container . ContainerImage , configLocation );
2019-10-10 00:52:42 -04:00
if ( pullExitCode == 0 )
{
break ;
}
else
{
retryCount ++;
if ( retryCount < 3 )
{
var backOff = BackoffTimerHelper . GetRandomBackoff ( TimeSpan . FromSeconds ( 1 ), TimeSpan . FromSeconds ( 10 ));
executionContext . Warning ( $"Docker pull failed with exit code {pullExitCode}, back off {backOff.TotalSeconds} seconds before retry." );
await Task . Delay ( backOff );
}
}
}
2020-09-11 12:28:58 -04:00
// Remove credentials after pulling
ContainerRegistryLogout ( configLocation );
2019-10-10 00:52:42 -04:00
if ( retryCount == 3 && pullExitCode != 0 )
{
throw new InvalidOperationException ( $"Docker pull failed with exit code {pullExitCode}" );
}
if ( container . IsJobContainer )
{
2022-06-10 15:51:20 +02:00
MountWellKnownDirectories ( executionContext , container );
2019-10-10 00:52:42 -04:00
}
2021-06-04 16:51:30 +02:00
container . ContainerId = await _dockerManager . DockerCreate ( executionContext , container );
2019-10-10 00:52:42 -04:00
ArgUtil . NotNullOrEmpty ( container . ContainerId , nameof ( container . ContainerId ));
// Start container
2021-06-04 16:51:30 +02:00
int startExitCode = await _dockerManager . DockerStart ( executionContext , container . ContainerId );
2019-10-10 00:52:42 -04:00
if ( startExitCode != 0 )
{
throw new InvalidOperationException ( $"Docker start fail with exit code {startExitCode}" );
}
try
{
// Make sure container is up and running
2021-06-04 16:51:30 +02:00
var psOutputs = await _dockerManager . DockerPS ( executionContext , $"--all --filter id={container.ContainerId} --filter status=running --no-trunc --format \" {{{{. ID }}}} {{{{. Status }}}} \ "" );
2019-10-10 00:52:42 -04:00
if ( psOutputs . FirstOrDefault ( x => ! string . IsNullOrEmpty ( x ))?. StartsWith ( container . ContainerId ) != true )
{
// container is not up and running, pull docker log for this container.
2021-06-04 16:51:30 +02:00
await _dockerManager . DockerPS ( executionContext , $"--all --filter id={container.ContainerId} --no-trunc --format \" {{{{. ID }}}} {{{{. Status }}}} \ "" );
int logsExitCode = await _dockerManager . DockerLogs ( executionContext , container . ContainerId );
2019-10-10 00:52:42 -04:00
if ( logsExitCode != 0 )
{
executionContext . Warning ( $"Docker logs fail with exit code {logsExitCode}" );
}
executionContext . Warning ( $"Docker container {container.ContainerId} is not in running state." );
}
}
catch ( Exception ex )
{
// pull container log is best effort.
Trace . Error ( "Catch exception when check container log and container status." );
Trace . Error ( ex );
}
// Gather runtime container information
if (! container . IsJobContainer )
{
var service = new DictionaryContextData ()
{
["id"] = new StringContextData ( container . ContainerId ),
["ports"] = new DictionaryContextData (),
["network"] = new StringContextData ( container . ContainerNetwork )
};
2021-06-04 16:51:30 +02:00
container . AddPortMappings ( await _dockerManager . DockerPort ( executionContext , container . ContainerId ));
2019-10-10 00:52:42 -04:00
foreach ( var port in container . PortMappings )
{
( service [ "ports" ] as DictionaryContextData )[ port . ContainerPort ] = new StringContextData ( port . HostPort );
}
executionContext . JobContext . Services [ container . ContainerNetworkAlias ] = service ;
}
else
{
var configEnvFormat = "--format \"{{range .Config.Env}}{{println .}}{{end}}\"" ;
2021-06-04 16:51:30 +02:00
var containerEnv = await _dockerManager . DockerInspect ( executionContext , container . ContainerId , configEnvFormat );
2019-10-10 00:52:42 -04:00
container . ContainerRuntimePath = DockerUtil . ParsePathFromConfigEnv ( containerEnv );
executionContext . JobContext . Container [ "id" ] = new StringContextData ( container . ContainerId );
}
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[endgroup]" );
2019-10-10 00:52:42 -04:00
}
2022-06-10 15:51:20 +02:00
private void MountWellKnownDirectories ( IExecutionContext executionContext , ContainerInfo container )
{
// Configure job container - Mount workspace and tools, set up environment, and start long running process
var githubContext = executionContext . ExpressionValues [ "github" ] as GitHubContext ;
ArgUtil . NotNull ( githubContext , nameof ( githubContext ));
var workingDirectory = githubContext [ "workspace" ] as StringContextData ;
ArgUtil . NotNullOrEmpty ( workingDirectory , nameof ( workingDirectory ));
container . MountVolumes . Add ( new MountVolume ( HostContext . GetDirectory ( WellKnownDirectory . Work ), container . TranslateToContainerPath ( HostContext . GetDirectory ( WellKnownDirectory . Work ))));
#if OS_WINDOWS
container . MountVolumes . Add ( new MountVolume ( HostContext . GetDirectory ( WellKnownDirectory . Externals ), container . TranslateToContainerPath ( HostContext . GetDirectory ( WellKnownDirectory . Externals ))));
#else
container . MountVolumes . Add ( new MountVolume ( HostContext . GetDirectory ( WellKnownDirectory . Externals ), container . TranslateToContainerPath ( HostContext . GetDirectory ( WellKnownDirectory . Externals )), true ));
#endif
container . MountVolumes . Add ( new MountVolume ( HostContext . GetDirectory ( WellKnownDirectory . Temp ), container . TranslateToContainerPath ( HostContext . GetDirectory ( WellKnownDirectory . Temp ))));
container . MountVolumes . Add ( new MountVolume ( HostContext . GetDirectory ( WellKnownDirectory . Actions ), container . TranslateToContainerPath ( HostContext . GetDirectory ( WellKnownDirectory . Actions ))));
container . MountVolumes . Add ( new MountVolume ( HostContext . GetDirectory ( WellKnownDirectory . Tools ), container . TranslateToContainerPath ( HostContext . GetDirectory ( WellKnownDirectory . Tools ))));
var tempHomeDirectory = Path . Combine ( HostContext . GetDirectory ( WellKnownDirectory . Temp ), "_github_home" );
Directory . CreateDirectory ( tempHomeDirectory );
container . MountVolumes . Add ( new MountVolume ( tempHomeDirectory , "/github/home" ));
container . AddPathTranslateMapping ( tempHomeDirectory , "/github/home" );
container . ContainerEnvironmentVariables [ "HOME" ] = container . TranslateToContainerPath ( tempHomeDirectory );
var tempWorkflowDirectory = Path . Combine ( HostContext . GetDirectory ( WellKnownDirectory . Temp ), "_github_workflow" );
Directory . CreateDirectory ( tempWorkflowDirectory );
container . MountVolumes . Add ( new MountVolume ( tempWorkflowDirectory , "/github/workflow" ));
container . AddPathTranslateMapping ( tempWorkflowDirectory , "/github/workflow" );
container . ContainerWorkDirectory = container . TranslateToContainerPath ( workingDirectory );
if (! FeatureManager . IsContainerHooksEnabled ( executionContext . Global . Variables ))
{
container . ContainerEntryPoint = "tail" ;
container . ContainerEntryPointArgs = "\"-f\" \"/dev/null\"" ;
}
}
2019-10-10 00:52:42 -04:00
private async Task StopContainerAsync ( IExecutionContext executionContext , ContainerInfo container )
{
Trace . Entering ();
ArgUtil . NotNull ( executionContext , nameof ( executionContext ));
ArgUtil . NotNull ( container , nameof ( container ));
if (! string . IsNullOrEmpty ( container . ContainerId ))
{
2022-06-10 15:51:20 +02:00
if (! container . IsJobContainer )
2021-12-20 07:55:47 -08:00
{
2022-09-06 19:17:48 +00:00
var healthcheck = await Healthcheck ( executionContext , container );
if ( string . Equals ( healthcheck , "healthy" , StringComparison . OrdinalIgnoreCase )){
// Print logs for service container jobs (not the "action" job itself b/c that's already logged).
// Print them only if the service was healthy, else they were already logged via ContainerHealthCheckLogs.
executionContext . Output ( $"Print service container logs: {container.ContainerDisplayName}" );
2022-06-10 15:51:20 +02:00
2022-09-06 19:17:48 +00:00
int logsExitCode = await _dockerManager . DockerLogs ( executionContext , container . ContainerId );
if ( logsExitCode != 0 )
{
executionContext . Warning ( $"Docker logs fail with exit code {logsExitCode}" );
}
}
2021-12-20 07:55:47 -08:00
}
2019-10-10 00:52:42 -04:00
executionContext . Output ( $"Stop and remove container: {container.ContainerDisplayName}" );
2021-06-04 16:51:30 +02:00
int rmExitCode = await _dockerManager . DockerRemove ( executionContext , container . ContainerId );
2019-10-10 00:52:42 -04:00
if ( rmExitCode != 0 )
{
executionContext . Warning ( $"Docker rm fail with exit code {rmExitCode}" );
}
}
}
#if ! OS_WINDOWS
private async Task < List < string >> ExecuteCommandAsync ( IExecutionContext context , string command , string arg )
{
context . Command ( $"{command} {arg}" );
List < string > outputs = new List < string >();
object outputLock = new object ();
var processInvoker = HostContext . CreateService < IProcessInvoker >();
processInvoker . OutputDataReceived += delegate ( object sender , ProcessDataReceivedEventArgs message )
{
if (! string . IsNullOrEmpty ( message . Data ))
{
lock ( outputLock )
{
outputs . Add ( message . Data );
}
}
};
processInvoker . ErrorDataReceived += delegate ( object sender , ProcessDataReceivedEventArgs message )
{
if (! string . IsNullOrEmpty ( message . Data ))
{
lock ( outputLock )
{
outputs . Add ( message . Data );
}
}
};
await processInvoker . ExecuteAsync (
workingDirectory : HostContext . GetDirectory ( WellKnownDirectory . Work ),
fileName : command ,
arguments : arg ,
environment : null ,
requireExitCodeZero : true ,
outputEncoding : null ,
cancellationToken : CancellationToken . None );
foreach ( var outputLine in outputs )
{
context . Output ( outputLine );
}
return outputs ;
}
#endif
private async Task CreateContainerNetworkAsync ( IExecutionContext executionContext , string network )
{
Trace . Entering ();
ArgUtil . NotNull ( executionContext , nameof ( executionContext ));
2021-06-04 16:51:30 +02:00
int networkExitCode = await _dockerManager . DockerNetworkCreate ( executionContext , network );
2019-10-10 00:52:42 -04:00
if ( networkExitCode != 0 )
{
throw new InvalidOperationException ( $"Docker network create failed with exit code {networkExitCode}" );
}
}
private async Task RemoveContainerNetworkAsync ( IExecutionContext executionContext , string network )
{
Trace . Entering ();
ArgUtil . NotNull ( executionContext , nameof ( executionContext ));
ArgUtil . NotNull ( network , nameof ( network ));
executionContext . Output ( $"Remove container network: {network}" );
2021-06-04 16:51:30 +02:00
int removeExitCode = await _dockerManager . DockerNetworkRemove ( executionContext , network );
2019-10-10 00:52:42 -04:00
if ( removeExitCode != 0 )
{
executionContext . Warning ( $"Docker network rm failed with exit code {removeExitCode}" );
}
}
2022-08-31 10:05:44 +00:00
public async Task < string > Healthcheck ( IExecutionContext executionContext , ContainerInfo container ){
2019-10-10 00:52:42 -04:00
string healthCheck = "--format=\"{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}\"" ;
2021-06-04 16:51:30 +02:00
string serviceHealth = ( await _dockerManager . DockerInspect ( context : executionContext , dockerObject : container . ContainerId , options : healthCheck )). FirstOrDefault ();
2019-10-10 00:52:42 -04:00
if ( string . IsNullOrEmpty ( serviceHealth ))
{
// Container has no HEALTHCHECK
2022-08-30 12:17:35 +02:00
return String . Empty ;
2019-10-10 00:52:42 -04:00
}
var retryCount = 0 ;
while ( string . Equals ( serviceHealth , "starting" , StringComparison . OrdinalIgnoreCase ))
{
TimeSpan backoff = BackoffTimerHelper . GetExponentialBackoff ( retryCount , TimeSpan . FromSeconds ( 2 ), TimeSpan . FromSeconds ( 32 ), TimeSpan . FromSeconds ( 2 ));
executionContext . Output ( $"{container.ContainerNetworkAlias} service is starting, waiting {backoff.Seconds} seconds before checking again." );
await Task . Delay ( backoff , executionContext . CancellationToken );
2021-06-04 16:51:30 +02:00
serviceHealth = ( await _dockerManager . DockerInspect ( context : executionContext , dockerObject : container . ContainerId , options : healthCheck )). FirstOrDefault ();
2019-10-10 00:52:42 -04:00
retryCount ++;
}
2022-08-30 12:17:35 +02:00
return serviceHealth ;
}
2022-08-31 10:05:44 +00:00
public async Task ContainerHealthcheckLogs ( IExecutionContext executionContext , ContainerInfo container , string serviceHealth )
2022-08-30 12:17:35 +02:00
{
2019-10-10 00:52:42 -04:00
if ( string . Equals ( serviceHealth , "healthy" , StringComparison . OrdinalIgnoreCase ))
{
executionContext . Output ( $"{container.ContainerNetworkAlias} service is healthy." );
}
else
{
2022-09-06 17:21:01 +02:00
executionContext . Output ( $"##[group] Container {container.ContainerImage} failed healthchecks, printing logs:" );
2022-09-06 23:13:20 +02:00
await _dockerManager . DockerLogs ( context : executionContext , containerId : container . ContainerId );
2022-09-06 10:10:52 +02:00
executionContext . Output ( "##[endgroup]" );
2019-10-10 00:52:42 -04:00
}
}
2020-09-11 12:28:58 -04:00
private async Task < string > ContainerRegistryLogin ( IExecutionContext executionContext , ContainerInfo container )
{
if ( string . IsNullOrEmpty ( container . RegistryAuthUsername ) || string . IsNullOrEmpty ( container . RegistryAuthPassword ))
{
// No valid client config can be generated
return "" ;
}
var configLocation = Path . Combine ( HostContext . GetDirectory ( WellKnownDirectory . Temp ), $".docker_{Guid.NewGuid()}" );
try
{
var dirInfo = Directory . CreateDirectory ( configLocation );
}
catch ( Exception e )
{
throw new InvalidOperationException ( $"Failed to create directory to store registry client credentials: {e.Message}" );
}
2021-06-04 16:51:30 +02:00
var loginExitCode = await _dockerManager . DockerLogin (
2020-09-11 12:28:58 -04:00
executionContext ,
configLocation ,
container . RegistryServer ,
container . RegistryAuthUsername ,
container . RegistryAuthPassword );
if ( loginExitCode != 0 )
{
throw new InvalidOperationException ( $"Docker login for '{container.RegistryServer}' failed with exit code {loginExitCode}" );
}
return configLocation ;
}
private void ContainerRegistryLogout ( string configLocation )
{
try
{
if (! string . IsNullOrEmpty ( configLocation ) && Directory . Exists ( configLocation ))
{
Directory . Delete ( configLocation , recursive : true );
}
}
catch ( Exception e )
{
throw new InvalidOperationException ( $"Failed to remove directory containing Docker client credentials: {e.Message}" );
}
}
private void UpdateRegistryAuthForGitHubToken ( IExecutionContext executionContext , ContainerInfo container )
{
2021-02-19 03:55:58 +01:00
var registryIsTokenCompatible = container . RegistryServer . Equals ( "ghcr.io" , StringComparison . OrdinalIgnoreCase ) || container . RegistryServer . Equals ( "containers.pkg.github.com" , StringComparison . OrdinalIgnoreCase );
2021-08-30 11:52:12 +02:00
var isFallbackTokenFromHostedGithub = HostContext . GetService < IConfigurationStore >(). GetSettings (). IsHostedServer ;
if (! registryIsTokenCompatible || ! isFallbackTokenFromHostedGithub )
2020-09-11 12:28:58 -04:00
{
return ;
}
var registryCredentialsNotSupplied = string . IsNullOrEmpty ( container . RegistryAuthUsername ) && string . IsNullOrEmpty ( container . RegistryAuthPassword );
2021-02-19 03:55:58 +01:00
if ( registryCredentialsNotSupplied )
2020-09-11 12:28:58 -04:00
{
container . RegistryAuthUsername = executionContext . GetGitHubContext ( "actor" );
container . RegistryAuthPassword = executionContext . GetGitHubContext ( "token" );
}
}
2022-06-10 15:51:20 +02:00
private async Task AssertCompatibleOS ( IExecutionContext executionContext )
{
// Check whether we are inside a container.
// Our container feature requires to map working directory from host to the container.
// If we are already inside a container, we will not able to find out the real working direcotry path on the host.
#if OS_WINDOWS
#pragma warning disable CA1416
// service CExecSvc is Container Execution Agent.
ServiceController [] scServices = ServiceController . GetServices ();
if ( scServices . Any ( x => String . Equals ( x . ServiceName , "cexecsvc" , StringComparison . OrdinalIgnoreCase ) && x . Status == ServiceControllerStatus . Running ))
{
throw new NotSupportedException ( "Container feature is not supported when runner is already running inside container." );
}
#pragma warning restore CA1416
#else
var initProcessCgroup = File . ReadLines ( "/proc/1/cgroup" );
if ( initProcessCgroup . Any ( x => x . IndexOf ( ":/docker/" , StringComparison . OrdinalIgnoreCase ) >= 0 ))
{
throw new NotSupportedException ( "Container feature is not supported when runner is already running inside container." );
}
#endif
#if OS_WINDOWS
#pragma warning disable CA1416
// Check OS version (Windows server 1803 is required)
object windowsInstallationType = Registry . GetValue ( @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion" , "InstallationType" , defaultValue : null );
ArgUtil . NotNull ( windowsInstallationType , nameof ( windowsInstallationType ));
object windowsReleaseId = Registry . GetValue ( @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion" , "ReleaseId" , defaultValue : null );
ArgUtil . NotNull ( windowsReleaseId , nameof ( windowsReleaseId ));
executionContext . Debug ( $"Current Windows version: '{windowsReleaseId} ({windowsInstallationType})'" );
if ( int . TryParse ( windowsReleaseId . ToString (), out int releaseId ))
{
if (! windowsInstallationType . ToString (). StartsWith ( "Server" , StringComparison . OrdinalIgnoreCase ) || releaseId < 1803 )
{
throw new NotSupportedException ( "Container feature requires Windows Server 1803 or higher." );
}
}
else
{
throw new ArgumentOutOfRangeException ( @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ReleaseId" );
}
#pragma warning restore CA1416
#endif
// Check docker client/server version
executionContext . Output ( "##[group]Checking docker version" );
DockerVersion dockerVersion = await _dockerManager . DockerVersion ( executionContext );
executionContext . Output ( "##[endgroup]" );
ArgUtil . NotNull ( dockerVersion . ServerVersion , nameof ( dockerVersion . ServerVersion ));
ArgUtil . NotNull ( dockerVersion . ClientVersion , nameof ( dockerVersion . ClientVersion ));
#if OS_WINDOWS
Version requiredDockerEngineAPIVersion = new Version ( 1 , 30 ); // Docker-EE version 17.6
#else
Version requiredDockerEngineAPIVersion = new Version ( 1 , 35 ); // Docker-CE version 17.12
#endif
if ( dockerVersion . ServerVersion < requiredDockerEngineAPIVersion )
{
throw new NotSupportedException ( $"Min required docker engine API server version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManager.DockerPath}') server version is '{dockerVersion.ServerVersion}'" );
}
if ( dockerVersion . ClientVersion < requiredDockerEngineAPIVersion )
{
throw new NotSupportedException ( $"Min required docker engine API client version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManager.DockerPath}') client version is '{dockerVersion.ClientVersion}'" );
}
}
2019-10-10 00:52:42 -04:00
}
}