2023-06-17 03:53:52 +02:00
using System ;
2019-10-10 00:52:42 -04:00
using System.Collections.Generic ;
2023-06-17 03:53:52 +02:00
using System.Linq ;
2019-10-10 00:52:42 -04:00
using System.Text.RegularExpressions ;
namespace GitHub.Runner.Worker.Container
{
public class DockerUtil
{
2022-10-18 10:54:08 -04:00
private static readonly Regex QuoteEscape = new ( @"(\\*)" + "\"" , RegexOptions . Compiled ) ;
private static readonly Regex EndOfStringEscape = new ( @"(\\+)$" , RegexOptions . Compiled ) ;
2022-09-09 14:32:07 -04:00
2019-10-10 00:52:42 -04:00
public static List < PortMapping > ParseDockerPort ( IList < string > portMappingLines )
{
const string targetPort = "targetPort" ;
const string proto = "proto" ;
const string host = "host" ;
const string hostPort = "hostPort" ;
//"TARGET_PORT/PROTO -> HOST:HOST_PORT"
string pattern = $"^(?<{targetPort}>\\d+)/(?<{proto}>\\w+) -> (?<{host}>.+):(?<{hostPort}>\\d+)$" ;
2022-10-18 10:54:08 -04:00
List < PortMapping > portMappings = new ( ) ;
2022-08-23 07:42:29 -07:00
foreach ( var line in portMappingLines )
2019-10-10 00:52:42 -04:00
{
Match m = Regex . Match ( line , pattern , RegexOptions . None , TimeSpan . FromSeconds ( 1 ) ) ;
if ( m . Success )
{
portMappings . Add ( new PortMapping (
m . Groups [ hostPort ] . Value ,
m . Groups [ targetPort ] . Value ,
m . Groups [ proto ] . Value
) ) ;
}
}
return portMappings ;
}
public static string ParsePathFromConfigEnv ( IList < string > configEnvLines )
{
// Config format is VAR=value per line
foreach ( var line in configEnvLines )
{
var keyValue = line . Split ( "=" , 2 ) ;
if ( keyValue . Length = = 2 & & string . Equals ( keyValue [ 0 ] , "PATH" ) )
{
return keyValue [ 1 ] ;
}
}
return "" ;
}
2020-09-11 12:28:58 -04:00
public static string ParseRegistryHostnameFromImageName ( string name )
{
var nameSplit = name . Split ( '/' ) ;
// Single slash is implictly from Dockerhub, unless first part has .tld or :port
if ( nameSplit . Length = = 2 & & ( nameSplit [ 0 ] . Contains ( ":" ) | | nameSplit [ 0 ] . Contains ( "." ) ) )
{
return nameSplit [ 0 ] ;
}
// All other non Dockerhub registries
else if ( nameSplit . Length > 2 )
{
return nameSplit [ 0 ] ;
}
return "" ;
}
2022-08-23 07:42:29 -07:00
2023-06-17 03:53:52 +02:00
public static bool IsDockerfile ( string image )
{
if ( image . StartsWith ( "docker://" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
var imageWithoutPath = image . Split ( '/' ) . Last ( ) ;
return imageWithoutPath . StartsWith ( "Dockerfile." , StringComparison . OrdinalIgnoreCase ) | | imageWithoutPath . EndsWith ( "Dockerfile" , StringComparison . OrdinalIgnoreCase ) ;
}
2022-08-23 07:42:29 -07:00
public static string CreateEscapedOption ( string flag , string key )
{
if ( String . IsNullOrEmpty ( key ) )
{
return "" ;
}
2022-09-09 14:32:07 -04:00
return $"{flag} {EscapeString(key)}" ;
}
public static string CreateEscapedOption ( string flag , string key , string value )
{
if ( String . IsNullOrEmpty ( key ) )
{
return "" ;
}
var escapedString = EscapeString ( $"{key}={value}" ) ;
return $"{flag} {escapedString}" ;
2022-08-23 07:42:29 -07:00
}
private static string EscapeString ( string value )
{
2022-09-09 14:32:07 -04:00
if ( String . IsNullOrEmpty ( value ) )
{
return "" ;
}
// Dotnet escaping rules are weird here, we can only escape \ if it precedes a "
// If a double quotation mark follows two or an even number of backslashes, each proceeding backslash pair is replaced with one backslash and the double quotation mark is removed.
// If a double quotation mark follows an odd number of backslashes, including just one, each preceding pair is replaced with one backslash and the remaining backslash is removed; however, in this case the double quotation mark is not removed.
// https://docs.microsoft.com/en-us/dotnet/api/system.environment.getcommandlineargs?redirectedfrom=MSDN&view=net-6.0#remarks
// First, find any \ followed by a " and double the number of \ + 1.
2023-06-02 21:47:59 +02:00
value = QuoteEscape . Replace ( value , @"$1$1\" + "\"" ) ;
2022-09-09 14:32:07 -04:00
// Next, what if it ends in `\`, it would escape the end quote. So, we need to detect that at the end of the string and perform the same escape
// Luckily, we can just use the $ character with detects the end of string in regex
value = EndOfStringEscape . Replace ( value , @"$1$1" ) ;
// Finally, wrap it in quotes
return $"\" { value } \ "" ;
2022-08-23 07:42:29 -07:00
}
2019-10-10 00:52:42 -04:00
}
}