2019-10-10 00:52:42 -04:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.ServiceProcess ;
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 ;
using Microsoft.Win32 ;
2019-11-04 13:19:21 -05:00
using GitHub.DistributedTask.Pipelines.ObjectTemplating ;
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 ;
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 > ( ) ;
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 ) ;
2019-11-04 13:19:21 -05:00
2019-10-10 00:52:42 -04:00
// 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
// 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." ) ;
}
#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
// 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" ) ;
}
#endif
// Check docker client/server version
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[group]Checking docker version" ) ;
2021-06-04 16:51:30 +02:00
DockerVersion dockerVersion = await _dockerManager . DockerVersion ( executionContext ) ;
2020-07-22 14:55:49 -04:00
executionContext . Output ( "##[endgroup]" ) ;
2019-10-10 00:52:42 -04:00
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 )
{
2021-06-04 16:51:30 +02:00
throw new NotSupportedException ( $"Min required docker engine API server version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManager.DockerPath}') server version is '{dockerVersion.ServerVersion}'" ) ;
2019-10-10 00:52:42 -04:00
}
if ( dockerVersion . ClientVersion < requiredDockerEngineAPIVersion )
{
2021-06-04 16:51:30 +02:00
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
}
// 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 ) )
{
await ContainerHealthcheck ( executionContext , container ) ;
}
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 ) ) ;
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}" ) ;
}
foreach ( var volume in container . UserMountVolumes )
{
Trace . Info ( $"User provided volume: {volume.Value}" ) ;
2020-04-08 11:17:54 -04:00
var mount = new MountVolume ( volume . Value ) ;
if ( string . Equals ( mount . SourceVolumePath , "/" , StringComparison . OrdinalIgnoreCase ) )
{
executionContext . Warning ( $"Volume mount {volume.Value} 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." ) ;
}
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 )
{
// 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 ) ;
container . ContainerEntryPoint = "tail" ;
container . ContainerEntryPointArgs = "\"-f\" \"/dev/null\"" ;
}
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
}
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 ) )
{
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}" ) ;
}
}
private async Task ContainerHealthcheck ( IExecutionContext executionContext , ContainerInfo container )
{
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
return ;
}
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 + + ;
}
if ( string . Equals ( serviceHealth , "healthy" , StringComparison . OrdinalIgnoreCase ) )
{
executionContext . Output ( $"{container.ContainerNetworkAlias} service is healthy." ) ;
}
else
{
throw new InvalidOperationException ( $"Failed to initialize, {container.ContainerNetworkAlias} service is {serviceHealth}." ) ;
}
}
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" ) ;
}
}
2019-10-10 00:52:42 -04:00
}
}