2019-10-10 00:52:42 -04:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using GitHub.Runner.Common.Util ;
using GitHub.Runner.Common ;
using GitHub.Runner.Sdk ;
2020-01-13 15:24:57 -05:00
using Pipelines = GitHub . DistributedTask . Pipelines ;
2019-10-10 00:52:42 -04:00
namespace GitHub.Runner.Worker.Container
{
public class ContainerInfo
{
private IDictionary < string , string > _userMountVolumes ;
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 , 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 ;
#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 )
{
UserMountVolumes [ volume ] = volume ;
}
}
2020-01-27 10:56:18 -05:00
UpdateWebProxyEnv ( hostContext . WebProxy ) ;
2019-10-10 00:52:42 -04:00
}
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 bool IsJobContainer { get ; set ; }
public IDictionary < string , string > ContainerEnvironmentVariables
{
get
{
if ( _environmentVariables = = null )
{
_environmentVariables = new Dictionary < string , string > ( ) ;
}
return _environmentVariables ;
}
}
public IDictionary < string , string > UserMountVolumes
{
get
{
if ( _userMountVolumes = = null )
{
_userMountVolumes = new Dictionary < string , string > ( ) ;
}
return _userMountVolumes ;
}
}
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 AddPathTranslateMapping ( string hostCommonPath , string containerCommonPath )
{
_pathMappings . Insert ( 0 , new PathMapping ( hostCommonPath , containerCommonPath ) ) ;
}
2020-01-27 10:56:18 -05:00
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 ) ;
}
}
2019-10-10 00:52:42 -04:00
}
public class MountVolume
{
public MountVolume ( string sourceVolumePath , string targetVolumePath , bool readOnly = false )
{
this . SourceVolumePath = sourceVolumePath ;
this . TargetVolumePath = targetVolumePath ;
this . ReadOnly = readOnly ;
}
public MountVolume ( string fromString )
{
ParseVolumeString ( 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 , 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 ; }
}
}