From 55f60a4ffc65dd9169381700a26120a150d0df5b Mon Sep 17 00:00:00 2001 From: Julio Barba Date: Mon, 17 Aug 2020 15:25:48 -0400 Subject: [PATCH 1/7] Prepare the release of 2.273.0 runner --- releaseNote.md | 16 +++++++--------- src/runnerversion | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/releaseNote.md b/releaseNote.md index fd009ee62..a8f89fe18 100644 --- a/releaseNote.md +++ b/releaseNote.md @@ -1,15 +1,13 @@ ## Features - - Composite Actions Support for Multiple Run Steps (#549, #557, #564, #568, #569, #578, #591, #599, #605, #609, #610, #615, #624) - - Prepare to switch GITHUB_ACTION to use ContextName instead of refname (#593) - - Fold logs for intermediate docker commands (#608) - - Add ability to register a runner to the non-default self-hosted runner group (#613) - + - Continued improvements to Composite Actions code and documentation (#616, #625, #626, #641, #645, #657, #658) + ## Bugs - - Double quotes around variable so CD works if path contains spaces (#602) - - Bump lodash in /src/Misc/expressionFunc/hashFiles (#603) - - Fix poor performance of process spawned from svc daemon (#614) + - Fix feature flag check; omit context for generated context names (#638) + - Fix endgroup maker (#640) + ## Misc - - Move shared ExecutionContext properties under .Global (#594) + - Adding help text for the new runnergroup feature (#626) + - Updating virtual environment terminology in readme.md (#651) ## Windows x64 We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows. diff --git a/src/runnerversion b/src/runnerversion index d807ea93e..59d8ca8e0 100644 --- a/src/runnerversion +++ b/src/runnerversion @@ -1 +1 @@ -2.272.0 +2.273.0 From a7f205593a0943aaffefe107c7600f2c540271ba Mon Sep 17 00:00:00 2001 From: Julio Barba Date: Tue, 18 Aug 2020 16:03:06 -0400 Subject: [PATCH 2/7] Update dotnet scripts --- src/Misc/dotnet-install.ps1 | 368 ++++++++++++++++++------------------ src/Misc/dotnet-install.sh | 0 2 files changed, 184 insertions(+), 184 deletions(-) mode change 100755 => 100644 src/Misc/dotnet-install.sh diff --git a/src/Misc/dotnet-install.ps1 b/src/Misc/dotnet-install.ps1 index 5c35542e5..30fb492d6 100644 --- a/src/Misc/dotnet-install.ps1 +++ b/src/Misc/dotnet-install.ps1 @@ -98,7 +98,7 @@ param( [string]$FeedCredential, [string]$ProxyAddress, [switch]$ProxyUseDefaultCredentials, - [string[]]$ProxyBypassList, + [string[]]$ProxyBypassList=@(), [switch]$SkipNonVersionedFiles, [switch]$NoCdn ) @@ -718,194 +718,194 @@ Say "Installation finished" exit 0 # SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# MIIjlgYJKoZIhvcNAQcCoIIjhzCCI4MCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCpfA/pR7OjIFT3 -# AofDlc6nOYGKjwNIAy3Eyvb16wpECqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCXdb9pJ+MI1iFd +# 2hUVOaNmZYt6e48+bQNJm9/Rbj3u3qCCDYUwggYDMIID66ADAgECAhMzAAABiK9S +# 1rmSbej5AAAAAAGIMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ4WhcNMjEwMzAzMTgzOTQ4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgzc+/PrE5 -# 4VVWEHbuPUTelP1qUjpv3t9Tr6VG3NE7g5EwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQBxJUwvSKHR/18WcqMt7X5KZ2EEH2C3C6OZcJ10VwIQ -# uwqgtGg1ZzaTtbBjdU58wK0URWPdiWv+DI2pLLy/qO6VsAieHd9h1Feqe1JExlh+ -# s43iGMFqiMpZp9qJ29MZe/I/4uC0ZTGatO97ld93tPGiRpbyXxZHbqkxPr1IMjns -# hVxoq0QH8ia699zw/6K6uCfdRlu49ZbyVoZbRRh9Cx3xEVVwiGO2/i2vMBOO0Xwc -# j0SBp/G2vUcmddBnf+DxcBbBmeDpNzJdggF+YtBVubLv1In2MHYOmfB8CXPAi0IS -# 57nptq/BQ1edeN9ytizPOc+gi8pRuwWt4rLTmr2JTEt/oYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIPZ740SKNZOpkM9U1riD6qS3XQ9TMXQJTBjlSj6W -# 6sYjAgZfFz3Hj4kYEzIwMjAwNzI5MTIyMDEwLjkyNVowBIACAfSggdSkgdEwgc4x +# AQCSCNryE+Cewy2m4t/a74wZ7C9YTwv1PyC4BvM/kSWPNs8n0RTe+FvYfU+E9uf0 +# t7nYlAzHjK+plif2BhD+NgdhIUQ8sVwWO39tjvQRHjP2//vSvIfmmkRoML1Ihnjs +# 9kQiZQzYRDYYRp9xSQYmRwQjk5hl8/U7RgOiQDitVHaU7BT1MI92lfZRuIIDDYBd +# vXtbclYJMVOwqZtv0O9zQCret6R+fRSGaDNfEEpcILL+D7RV3M4uaJE4Ta6KAOdv +# V+MVaJp1YXFTZPKtpjHO6d9pHQPZiG7NdC6QbnRGmsa48uNQrb6AfmLKDI1Lp31W +# MogTaX5tZf+CZT9PSuvjOCLNAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUj9RJL9zNrPcL10RZdMQIXZN7MG8w +# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh +# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1ODM4NjAfBgNVHSMEGDAW +# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v +# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw +# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov +# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx +# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +# ACnXo8hjp7FeT+H6iQlV3CcGnkSbFvIpKYafgzYCFo3UHY1VHYJVb5jHEO8oG26Q +# qBELmak6MTI+ra3WKMTGhE1sEIlowTcp4IAs8a5wpCh6Vf4Z/bAtIppP3p3gXk2X +# 8UXTc+WxjQYsDkFiSzo/OBa5hkdW1g4EpO43l9mjToBdqEPtIXsZ7Hi1/6y4gK0P +# mMiwG8LMpSn0n/oSHGjrUNBgHJPxgs63Slf58QGBznuXiRaXmfTUDdrvhRocdxIM +# i8nXQwWACMiQzJSRzBP5S2wUq7nMAqjaTbeXhJqD2SFVHdUYlKruvtPSwbnqSRWT +# GI8s4FEXt+TL3w5JnwVZmZkUFoioQDMMjFyaKurdJ6pnzbr1h6QW0R97fWc8xEIz +# LIOiU2rjwWAtlQqFO8KNiykjYGyEf5LyAJKAO+rJd9fsYR+VBauIEQoYmjnUbTXM +# SY2Lf5KMluWlDOGVh8q6XjmBccpaT+8tCfxpaVYPi1ncnwTwaPQvVq8RjWDRB7Pa +# 8ruHgj2HJFi69+hcq7mWx5nTUtzzFa7RSZfE5a1a5AuBmGNRr7f8cNfa01+tiWjV +# Kk1a+gJUBSP0sIxecFbVSXTZ7bqeal45XSDIisZBkWb+83TbXdTGMDSUFKTAdtC+ +# r35GfsN8QVy59Hb5ZYzAXczhgRmk7NyE6jD0Ym5TKiW5MIIHejCCBWKgAwIBAgIK +# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV +# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv +# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm +# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw +# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD +# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG +# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la +# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc +# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D +# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ +# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk +# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 +# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd +# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL +# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd +# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 +# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS +# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI +# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL +# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD +# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv +# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF +# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h +# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA +# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn +# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 +# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b +# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ +# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy +# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp +# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi +# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb +# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS +# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL +# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX +# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWcwghVjAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABLqjSGQeT9GvoAAAA -# AAEuMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTUwNVoXDTIxMDMxNzAxMTUwNVowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJE -# LUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7TTKJRU196LFIjMQ9q -# /UjpPhz43m5RnHgHAVp2YGni74+ltsYoO1nZ58rTbJhCQ8GYHy8B4devgbqqYPQN -# U3i+drpEtEcNLbsMr4MEq3SM+vO3a6QMFd1lDRy7IQLPJNLKvcM69Nt7ku1YyM5N -# nPNDcRJsnUb/8Yx/zcW5cWjnoj8s9fQ93BPf/J74qM1ql2CdzQV74PBisMP/tppA -# nSuNwo8I7+uWr6vfpBynSWDvJeMDrcsa62Xsm7DbB1NnSsPGAGt3RzlBV9KViciz -# e4U3fo4chdoB2+QLu17PaEmj07qq700CG5XJkpEYOjedNFiByApF7YRvQrOZQ07Q -# YiMCAwEAAaOCARswggEXMB0GA1UdDgQWBBSGmokmTguJN7uqSTQ1UhLwt1RObDAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCN4ARqpzCuutNqY2nWJDDXj35iaidl -# gtJ/bspYsAX8atJl19IfUKIzTuuSVU3caXZ6/YvMMYMcbsNa/4J28us23K6PWZAl -# jIj0G8QtwDMlQHjrKnrcr4FBAz6ZqvB6SrN3/Wbb0QSK/OlxsU0mfD7z87R2JM4g -# wKJvH6EILuAEtjwUGSB1NKm3Twrm51fCD0jxvWxzaUS2etvMPrh8DNrrHLJBR3UH -# vg/NXS2IzdQn20xjjsW0BUAiTf+NCRpxUvu/j80Nb1++vnejibfpQJ2IlXiJdIi+ -# Hb+OL3XOr8MaDDSYOaRFAIfcoq3VPi4BkvSC8QGrvhjAZafkE7R6L5FJMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoz -# MkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUA+1/CN6BILeU1yDGo+b6WkpLoQpuggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOLLn2AwIhgPMjAyMDA3MjkxMTEwMjRaGA8yMDIwMDczMDExMTAyNFowdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA4sufYAIBADAKAgEAAgImPAIB/zAHAgEAAgIRsTAK -# AgUA4szw4AIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAI1W0gMAkyYoYB5t -# BiO10MENTa3AeN9Mc5rqjrw99nmLr9S7zq9oTprZ59SQ2eomQpBaIO+33xWK2met -# +NR+bq2vNh+m6W2dacmZ2Ki8jlMfUajJaWX0xbHWaGMpL+9nlgZnNUcH7whVwXxp -# FlMaPFf0CKv8Uo11B4rlZat8fFE4MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEuqNIZB5P0a+gAAAAAAS4wDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQgJ7FDhQl++eQw1izf/wfOB3EPDCi12dQQe1YoFx8iEckwgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCDa/s3O8YhWiqpVN0kTeK+x2m0RAh17JpR6DiFo -# TILJKTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# LqjSGQeT9GvoAAAAAAEuMCIEIIpoWQMeh7Zef+LuYZIDjkaUw/O5NUX/s7oGyVZj -# W4khMA0GCSqGSIb3DQEBCwUABIIBAJ1oCV3CVki6wRkqMYV9scx3xjFJoNah1g4X -# FPXqezxKaG534hToWOZUmXfvnJwCt136zoR8Z0JSEE6PnWVU7e7EhTrqIkMSHSSL -# GGg5HIHS/nbmsu6VCigAHZn0okwXXy8ftOKRFhMVaOSLqa7qQgrRMHBBduHvq6xw -# bv/aqK6i2TE5Sv/QrhsgVDKWNL3oyhVlp+tJzCAuHILSVkClGXHNjrRDrEHkeWP7 -# xrbtgxDGHATlqra2bW2bAp2B46X9qjZkBmHopXS/VOfSQltQWef9VyuB4wzrNuxG -# gF4uSZOf20eeD1svHaZvTVNwIdLq6WvPVhUB4s6GWFofm1dcDpQ= +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p +# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA +# AYgwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw +# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIM9C +# NU8DMdIjlVSldghA1uP8Jf60AlCYNoHBHHW3pscjMEIGCisGAQQBgjcCAQwxNDAy +# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20wDQYJKoZIhvcNAQEBBQAEggEAFwdPmnUSAnwqMM8b4QthX44z3UnhPYm1EtjC +# /PnpTA5xkFMaoOUhGdiR5tpGPWNgiNRqD5ZSL1JVUqUOpNfybZZqZPz/LnZdS1XB +# +aj4Orh1Lkbaqq74PQxgRrUR3eyOVHcNTcohPNIb/ZYHqr6cwhqZitGuNEHNtqCk +# lSRCrfiNlW8PNrpPvUWwIC1Fd+OpgRdGhKFIHTx31if1BH8omViGm4iFdlb5dGz3 +# ibeOm6FfXWwmKJVqVb/vhhemMel8tYNONTl2e+UjPOCy4f7myLiD61irA5T1a0vn +# vcIV0dRSwh8U5h8JYOEJxn4nydVKlJ5UGMS8eQiKdd42CGs93KGCEvEwghLtBgor +# BgEEAYI3AwMBMYIS3TCCEtkGCSqGSIb3DQEHAqCCEsowghLGAgEDMQ8wDQYJYIZI +# AWUDBAIBBQAwggFVBgsqhkiG9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGE +# WQoDATAxMA0GCWCGSAFlAwQCAQUABCCVM7LRYercP7cfHmTrb7lPfKaZCdVbtga7 +# UOM/oLAsHgIGXxb9UghEGBMyMDIwMDgxMzEyMjIwNS40NjZaMASAAgH0oIHUpIHR +# MIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQL +# EyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhh +# bGVzIFRTUyBFU046RjdBNi1FMjUxLTE1MEExJTAjBgNVBAMTHE1pY3Jvc29mdCBU +# aW1lLVN0YW1wIFNlcnZpY2Wggg5EMIIE9TCCA92gAwIBAgITMwAAASWL3otsciYx +# 3QAAAAABJTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg +# MjAxMDAeFw0xOTEyMTkwMTE0NThaFw0yMTAzMTcwMTE0NThaMIHOMQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg +# T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046 +# RjdBNi1FMjUxLTE1MEExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl +# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQex9jdmBb7OHJ +# wSYmMUorZNwAcv8Vy36TlJuzcVx7G+lFqt2zjWOMlSOMkm1XoAuJ8VZ5ShBedADX +# DGDKxHNZhLu3EW8x5ot/IOk6izLTlAFtvIXOgzXs/HaOM72XHKykMZHAdL/fpZtA +# SM5PalmsXX4Ol8lXkm9jR55K56C7q9+hDU+2tjGHaE1ZWlablNUXBhaZgtCJCd60 +# UyZvgI7/uNzcafj0/Vw2bait9nDAVd24yt/XCZnHY3yX7ZsHjIuHpsl+PpDXai1D +# we9p0ryCZsl9SOMHextIHe9qlTbtWYJ8WtWLoH9dEMQxVLnmPPDOVmBj7LZhSji3 +# 8N9Vpz/FAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQU86rK5Qcm+QE5NBXGCPIiCBdD +# JPgwHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBL +# oEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv +# TWljVGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr +# BgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAK +# BggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAkxxZPGEgIgAhsqZNTZk58V1v +# QiJ5ja2xHl5TqGA6Hwj5SioLg3FSLiTmGV+BtFlpYUtkneB4jrZsuNpMtfbTMdG7 +# p/xAyIVtwvXnTXqKlCD1T9Lcr94pVedzHGJzL1TYNQyZJBouCfzkgkzccOuFOfeW +# PfnMTiI5UBW5OdmoyHPQWDSGHoboW1dTKqXeJtuVDTYbHTKs4zjfCBMFjmylRu52 +# Zpiz+9MBeRj4iAeou0F/3xvIzepoIKgUWCZ9mmViWEkVwCtTGbV8eK73KeEE0tfM +# U/YY2UmoGPc8YwburDEfelegLW+YHkfrcGAGlftCmqtOdOLeghLoG0Ubx/B7sTCC +# BnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNV +# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w +# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29m +# dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1 +# NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw +# ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/ +# aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxh +# MFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhH +# hjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tk +# iVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox +# 8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJN +# AgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIox +# kPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0P +# BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9 +# lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQu +# Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3Js +# MFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3Nv +# ZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAG +# A1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRw +# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAG +# CCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEA +# dABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXED +# PZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgr +# UYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c +# 8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFw +# nzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFt +# w5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk +# 7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9d +# dJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zG +# y9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3 +# yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7c +# RDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wkn +# HNWzfjUeCLraNtvTX4/edIhJEqGCAtIwggI7AgEBMIH8oYHUpIHRMIHOMQswCQYD +# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe +# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3Nv +# ZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBF +# U046RjdBNi1FMjUxLTE1MEExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAEXTL+FQbc2G+3MXXvIRKVr2oXCnoIGD +# MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV +# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG +# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF +# BQACBQDi3yR1MCIYDzIwMjAwODEzMDYzMTE3WhgPMjAyMDA4MTQwNjMxMTdaMHcw +# PQYKKwYBBAGEWQoEATEvMC0wCgIFAOLfJHUCAQAwCgIBAAICKbYCAf8wBwIBAAIC +# EkQwCgIFAOLgdfUCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK +# MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQBI2hPSmSPK +# XurK36pE46s0uBEW23aGxotfubZR3iQCxDZ+dcZEN83t2JE4wh4a9HGpzXta/1Yz +# fgoIxgsI5wogRQF20sCD7x7ZTbpMweqxFCQSGRE8Z2B0FmntXXrEvQtS1ee0PC/1 +# +eD7oAsVwmsSWdQHKfOVBqz51g2S+ImuzTGCAw0wggMJAgEBMIGTMHwxCzAJBgNV +# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w +# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m +# dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJYvei2xyJjHdAAAAAAElMA0GCWCG +# SAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZI +# hvcNAQkEMSIEIJICFqJn2Gtkce4xbJqSJCqpNLdz4fjym2OW0Ac8zI+nMIH6Bgsq +# hkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgXd/Gsi5vMF/6iX2CDh+VfmL5RvqaFkFw +# luiyje9B9w4wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv +# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT +# MwAAASWL3otsciYx3QAAAAABJTAiBCBSjc2CBOdr7iaTswYVN8f7KwiN5s4uBEO+ +# JVI8WLhgFzANBgkqhkiG9w0BAQsFAASCAQCfsvzXMzAN1kylt4eAKSH4ryFIJqBH +# O7jcx7iIA9X6OPTuUmBniZGf2fmFG61V4HlmRgGOXuisJdpU3kiC7EZyFX6ZJoIj +# kgvCQf4BPu/cLtn2w6odZ68OrTHs7BfBKBr6eQKKcZ/kgRSsjMNinh8tHPlrxE63 +# Zha3mUFfsnX5bi+F4VPhluGvRuA7q3IqMzfA/dTxON9WH5L+t3TwW61VebBaSPkT +# YevYlj0TTlCw1B3zk0ztU37uulqDi4rFr67VaoR3qrhL/xZ/DsaNXg1V/RXqQRrw +# eCag1OFRASAQOUOlWSi0QtYgUDl5FKKzxaJTEd946+6mJIkNXZB3nmA1 # SIG # End signature block diff --git a/src/Misc/dotnet-install.sh b/src/Misc/dotnet-install.sh old mode 100755 new mode 100644 From 65e3ec86b413dd0d772c8fd789b6c3f03934835d Mon Sep 17 00:00:00 2001 From: Julio Barba Date: Tue, 18 Aug 2020 16:09:04 -0400 Subject: [PATCH 3/7] Set executable bit --- src/Misc/dotnet-install.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/Misc/dotnet-install.sh diff --git a/src/Misc/dotnet-install.sh b/src/Misc/dotnet-install.sh old mode 100644 new mode 100755 From d900654c425da82d8ba60d83f7c810f8cfafb8f6 Mon Sep 17 00:00:00 2001 From: Thomas Brumley Date: Tue, 25 Aug 2020 12:02:29 -0400 Subject: [PATCH 4/7] Add in Log line numbers for streaming logs (#663) * Add in Log line Co-authored-by: yaananth (Yash) --- src/Runner.Common/JobServer.cs | 7 +++++ src/Runner.Common/JobServerQueue.cs | 31 ++++++++++++------- src/Runner.Worker/ExecutionContext.cs | 3 +- src/Sdk/DTWebApi/WebApi/TaskHttpClient.cs | 24 +++++++++++++- .../WebApi/TimelineRecordFeedLinesWrapper.cs | 9 ++++++ .../DTWebApi/WebApi/TimelineRecordLogLine.cs | 29 +++++++++++++++++ src/Test/L0/Worker/ExecutionContextL0.cs | 8 ++--- 7 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 src/Sdk/DTWebApi/WebApi/TimelineRecordLogLine.cs diff --git a/src/Runner.Common/JobServer.cs b/src/Runner.Common/JobServer.cs index e3e0f551b..7d069bf97 100644 --- a/src/Runner.Common/JobServer.cs +++ b/src/Runner.Common/JobServer.cs @@ -16,6 +16,7 @@ namespace GitHub.Runner.Common // logging and console Task AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken); Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList lines, CancellationToken cancellationToken); + Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList lines, long startLine, CancellationToken cancellationToken); Task CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken); Task CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken); Task CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken); @@ -79,6 +80,12 @@ namespace GitHub.Runner.Common return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken); } + public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList lines, long startLine, CancellationToken cancellationToken) + { + CheckConnection(); + return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, startLine, cancellationToken: cancellationToken); + } + public Task CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken) { CheckConnection(); diff --git a/src/Runner.Common/JobServerQueue.cs b/src/Runner.Common/JobServerQueue.cs index 787daeffd..5cabca2a9 100644 --- a/src/Runner.Common/JobServerQueue.cs +++ b/src/Runner.Common/JobServerQueue.cs @@ -18,7 +18,7 @@ namespace GitHub.Runner.Common event EventHandler JobServerQueueThrottling; Task ShutdownAsync(); void Start(Pipelines.AgentJobRequestMessage jobRequest); - void QueueWebConsoleLine(Guid stepRecordId, string line); + void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null); void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource); void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord); } @@ -155,10 +155,10 @@ namespace GitHub.Runner.Common Trace.Info("All queue process tasks have been stopped, and all queues are drained."); } - public void QueueWebConsoleLine(Guid stepRecordId, string line) + public void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber) { Trace.Verbose("Enqueue web console line queue: {0}", line); - _webConsoleLineQueue.Enqueue(new ConsoleLineInfo(stepRecordId, line)); + _webConsoleLineQueue.Enqueue(new ConsoleLineInfo(stepRecordId, line, lineNumber)); } public void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource) @@ -214,7 +214,7 @@ namespace GitHub.Runner.Common } // Group consolelines by timeline record of each step - Dictionary> stepsConsoleLines = new Dictionary>(); + Dictionary> stepsConsoleLines = new Dictionary>(); List stepRecordIds = new List(); // We need to keep lines in order int linesCounter = 0; ConsoleLineInfo lineInfo; @@ -222,7 +222,7 @@ namespace GitHub.Runner.Common { if (!stepsConsoleLines.ContainsKey(lineInfo.StepRecordId)) { - stepsConsoleLines[lineInfo.StepRecordId] = new List(); + stepsConsoleLines[lineInfo.StepRecordId] = new List(); stepRecordIds.Add(lineInfo.StepRecordId); } @@ -232,7 +232,7 @@ namespace GitHub.Runner.Common lineInfo.Line = $"{lineInfo.Line.Substring(0, 1024)}..."; } - stepsConsoleLines[lineInfo.StepRecordId].Add(lineInfo.Line); + stepsConsoleLines[lineInfo.StepRecordId].Add(new TimelineRecordLogLine(lineInfo.Line, lineInfo.LineNumber)); linesCounter++; // process at most about 500 lines of web console line during regular timer dequeue task. @@ -247,13 +247,13 @@ namespace GitHub.Runner.Common { // Split consolelines into batch, each batch will container at most 100 lines. int batchCounter = 0; - List> batchedLines = new List>(); + List> batchedLines = new List>(); foreach (var line in stepsConsoleLines[stepRecordId]) { var currentBatch = batchedLines.ElementAtOrDefault(batchCounter); if (currentBatch == null) { - batchedLines.Add(new List()); + batchedLines.Add(new List()); currentBatch = batchedLines.ElementAt(batchCounter); } @@ -275,7 +275,6 @@ namespace GitHub.Runner.Common { Trace.Info($"Skip {batchedLines.Count - 2} batches web console lines for last run"); batchedLines = batchedLines.TakeLast(2).ToList(); - batchedLines[0].Insert(0, "..."); } int errorCount = 0; @@ -284,7 +283,15 @@ namespace GitHub.Runner.Common try { // we will not requeue failed batch, since the web console lines are time sensitive. - await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch, default(CancellationToken)); + if (batch[0].LineNumber.HasValue) + { + await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber.Value, default(CancellationToken)); + } + else + { + await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), default(CancellationToken)); + } + if (_firstConsoleOutputs) { HostContext.WritePerfCounter($"WorkerJobServerQueueAppendFirstConsoleOutput_{_planId.ToString()}"); @@ -653,13 +660,15 @@ namespace GitHub.Runner.Common internal class ConsoleLineInfo { - public ConsoleLineInfo(Guid recordId, string line) + public ConsoleLineInfo(Guid recordId, string line, long? lineNumber) { this.StepRecordId = recordId; this.Line = line; + this.LineNumber = lineNumber; } public Guid StepRecordId { get; set; } public string Line { get; set; } + public long? LineNumber { get; set; } } } diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 224b33b0e..46f516999 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -717,7 +717,8 @@ namespace GitHub.Runner.Worker } } - _jobServerQueue.QueueWebConsoleLine(_record.Id, msg); + _jobServerQueue.QueueWebConsoleLine(_record.Id, msg, totalLines); + return totalLines; } diff --git a/src/Sdk/DTWebApi/WebApi/TaskHttpClient.cs b/src/Sdk/DTWebApi/WebApi/TaskHttpClient.cs index fcc1ab6ce..6a5515552 100644 --- a/src/Sdk/DTWebApi/WebApi/TaskHttpClient.cs +++ b/src/Sdk/DTWebApi/WebApi/TaskHttpClient.cs @@ -50,7 +50,7 @@ namespace GitHub.DistributedTask.WebApi : base(baseUrl, pipeline, disposeHandler) { } - + public Task AppendTimelineRecordFeedAsync( Guid scopeIdentifier, String planType, @@ -91,6 +91,28 @@ namespace GitHub.DistributedTask.WebApi userState, cancellationToken); } + + public Task AppendTimelineRecordFeedAsync( + Guid scopeIdentifier, + String planType, + Guid planId, + Guid timelineId, + Guid recordId, + Guid stepId, + IList lines, + long startLine, + CancellationToken cancellationToken = default(CancellationToken), + Object userState = null) + { + return AppendTimelineRecordFeedAsync(scopeIdentifier, + planType, + planId, + timelineId, + recordId, + new TimelineRecordFeedLinesWrapper(stepId, lines, startLine), + userState, + cancellationToken); + } public async Task RaisePlanEventAsync( Guid scopeIdentifier, diff --git a/src/Sdk/DTWebApi/WebApi/TimelineRecordFeedLinesWrapper.cs b/src/Sdk/DTWebApi/WebApi/TimelineRecordFeedLinesWrapper.cs index c628852bd..edaaf314a 100644 --- a/src/Sdk/DTWebApi/WebApi/TimelineRecordFeedLinesWrapper.cs +++ b/src/Sdk/DTWebApi/WebApi/TimelineRecordFeedLinesWrapper.cs @@ -20,6 +20,12 @@ namespace GitHub.DistributedTask.WebApi this.Count = lines.Count; } + public TimelineRecordFeedLinesWrapper(Guid stepId, IList lines, Int64 startLine) + : this(stepId, lines) + { + this.StartLine = startLine; + } + [DataMember(Order = 0)] public Int32 Count { get; private set; } @@ -31,5 +37,8 @@ namespace GitHub.DistributedTask.WebApi [DataMember(EmitDefaultValue = false)] public Guid StepId { get; set; } + + [DataMember (EmitDefaultValue = false)] + public Int64? StartLine { get; private set; } } } diff --git a/src/Sdk/DTWebApi/WebApi/TimelineRecordLogLine.cs b/src/Sdk/DTWebApi/WebApi/TimelineRecordLogLine.cs new file mode 100644 index 000000000..2761390c9 --- /dev/null +++ b/src/Sdk/DTWebApi/WebApi/TimelineRecordLogLine.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.Serialization; + +namespace GitHub.DistributedTask.WebApi +{ + [DataContract] + public sealed class TimelineRecordLogLine + { + public TimelineRecordLogLine(String line, long? lineNumber) + { + this.Line = line; + this.LineNumber = lineNumber; + } + + [DataMember] + public String Line + { + get; + set; + } + + [DataMember (EmitDefaultValue = false)] + public long? LineNumber + { + get; + set; + } + } +} diff --git a/src/Test/L0/Worker/ExecutionContextL0.cs b/src/Test/L0/Worker/ExecutionContextL0.cs index 0efd4bfe5..39dbb2dda 100644 --- a/src/Test/L0/Worker/ExecutionContextL0.cs +++ b/src/Test/L0/Worker/ExecutionContextL0.cs @@ -116,7 +116,7 @@ namespace GitHub.Runner.Common.Tests.Worker var pagingLogger = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); - jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny())).Callback((Guid id, string msg) => { hc.GetTrace().Info(msg); }); + jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(),It.IsAny())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); }); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); @@ -137,7 +137,7 @@ namespace GitHub.Runner.Common.Tests.Worker ec.Complete(); - jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny()), Times.Exactly(10)); + jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(10)); } } @@ -171,7 +171,7 @@ namespace GitHub.Runner.Common.Tests.Worker var pagingLogger5 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); - jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny())).Callback((Guid id, string msg) => { hc.GetTrace().Info(msg); }); + jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); }); var actionRunner1 = new ActionRunner(); actionRunner1.Initialize(hc); @@ -269,7 +269,7 @@ namespace GitHub.Runner.Common.Tests.Worker var pagingLogger5 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); - jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny())).Callback((Guid id, string msg) => { hc.GetTrace().Info(msg); }); + jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); }); var actionRunner1 = new ActionRunner(); actionRunner1.Initialize(hc); From 9976cb92a0cfa919ee6be75a0c587cc16362f604 Mon Sep 17 00:00:00 2001 From: Thomas Boop <52323235+thboop@users.noreply.github.com> Date: Fri, 28 Aug 2020 15:32:25 -0400 Subject: [PATCH 5/7] Add Runner File Commands (#684) * Add File Runner Commands --- src/Runner.Common/ExtensionManager.cs | 4 + src/Runner.Worker/ActionRunner.cs | 8 + src/Runner.Worker/FileCommandManager.cs | 140 ++++++++++++++++++ src/Runner.Worker/GitHubContext.cs | 2 + .../Handlers/ContainerActionHandler.cs | 5 + src/Test/L0/ServiceInterfacesL0.cs | 1 + src/Test/L0/Worker/ActionRunnerL0.cs | 5 + 7 files changed, 165 insertions(+) create mode 100644 src/Runner.Worker/FileCommandManager.cs diff --git a/src/Runner.Common/ExtensionManager.cs b/src/Runner.Common/ExtensionManager.cs index c53fc3647..9b7171c73 100644 --- a/src/Runner.Common/ExtensionManager.cs +++ b/src/Runner.Common/ExtensionManager.cs @@ -56,6 +56,10 @@ namespace GitHub.Runner.Common Add(extensions, "GitHub.Runner.Worker.EndGroupCommandExtension, Runner.Worker"); Add(extensions, "GitHub.Runner.Worker.EchoCommandExtension, Runner.Worker"); break; + case "GitHub.Runner.Worker.IFileCommandExtension": + Add(extensions, "GitHub.Runner.Worker.AddPathFileCommand, Runner.Worker"); + Add(extensions, "GitHub.Runner.Worker.SetEnvFileCommand, Runner.Worker"); + break; default: // This should never happen. throw new NotSupportedException($"Unexpected extension type: '{typeof(T).FullName}'"); diff --git a/src/Runner.Worker/ActionRunner.cs b/src/Runner.Worker/ActionRunner.cs index 8a12b9561..f946573e4 100644 --- a/src/Runner.Worker/ActionRunner.cs +++ b/src/Runner.Worker/ActionRunner.cs @@ -145,6 +145,12 @@ namespace GitHub.Runner.Worker stepHost = containerStepHost; } + // Setup File Command Manager + var fileCommandManager = HostContext.CreateService(); + // Container Action Handler will handle the conversion for Container Actions + var container = handlerData.ExecutionType == ActionExecutionType.Container ? null : ExecutionContext.Global.Container; + fileCommandManager.InitializeFiles(ExecutionContext, container); + // Load the inputs. ExecutionContext.Debug("Loading inputs"); var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator(); @@ -239,6 +245,8 @@ namespace GitHub.Runner.Worker // Run the task. await handler.RunAsync(Stage); + fileCommandManager.TryProcessFiles(ExecutionContext, ExecutionContext.Global.Container); + } public bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context) diff --git a/src/Runner.Worker/FileCommandManager.cs b/src/Runner.Worker/FileCommandManager.cs new file mode 100644 index 000000000..5fdcb2377 --- /dev/null +++ b/src/Runner.Worker/FileCommandManager.cs @@ -0,0 +1,140 @@ +using GitHub.Runner.Worker.Container; +using System; +using System.Text; +using System.IO; +using GitHub.Runner.Common; +using GitHub.Runner.Sdk; +using System.Collections; +using System.Collections.Generic; + +namespace GitHub.Runner.Worker +{ + [ServiceLocator(Default = typeof(FileCommandManager))] + public interface IFileCommandManager : IRunnerService + { + void InitializeFiles(IExecutionContext context, ContainerInfo container); + void TryProcessFiles(IExecutionContext context, ContainerInfo container); + + } + + public sealed class FileCommandManager : RunnerService, IFileCommandManager + { + private const string _folderName = "_runner_file_commands"; + private List _commandExtensions; + private string _fileSuffix = String.Empty; + private string _fileCommandDirectory; + private Tracing _trace; + + public override void Initialize(IHostContext hostContext) + { + base.Initialize(hostContext); + _trace = HostContext.GetTrace(nameof(FileCommandManager)); + + _fileCommandDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Temp), _folderName); + if (!Directory.Exists(_fileCommandDirectory)) + { + Directory.CreateDirectory(_fileCommandDirectory); + } + + var extensionManager = hostContext.GetService(); + _commandExtensions = extensionManager.GetExtensions() ?? new List(); + + } + + public void InitializeFiles(IExecutionContext context, ContainerInfo container) + { + var oldSuffix = _fileSuffix; + _fileSuffix = Guid.NewGuid().ToString(); + foreach (var fileCommand in _commandExtensions) + { + var oldPath = Path.Combine(_fileCommandDirectory, fileCommand.FileName + oldSuffix); + if (oldSuffix != String.Empty && File.Exists(oldPath)) + { + TryDeleteFile(oldPath); + } + + var newPath = Path.Combine(_fileCommandDirectory, fileCommand.FileName + _fileSuffix); + TryDeleteFile(newPath); + File.Create(newPath).Dispose(); + + var pathToSet = container != null ? container.TranslateToContainerPath(newPath) : newPath; + context.SetGitHubContext(fileCommand.ContextName, pathToSet); + } + } + + public void TryProcessFiles(IExecutionContext context, ContainerInfo container) + { + foreach (var fileCommand in _commandExtensions) + { + fileCommand.ProcessCommand(context, Path.Combine(_fileCommandDirectory, fileCommand.FileName + _fileSuffix),container); + } + } + + private bool TryDeleteFile(string path) + { + if (!File.Exists(path)) + { + return true; + } + try + { + File.Delete(path); + } + catch (Exception e) + { + _trace.Warning($"Unable to delete file {path} for reason: {e.ToString()}"); + return false; + } + return true; + } + } + + public interface IFileCommandExtension : IExtension + { + string ContextName { get; } + string FileName { get; } + + void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container); + } + + public sealed class AddPathFileCommand : RunnerService, IFileCommandExtension + { + public string ContextName => "path"; + public string FileName => "add_path_"; + + public Type ExtensionType => typeof(IFileCommandExtension); + + public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container) + { + if (File.Exists(filePath)) + { + var lines = File.ReadAllLines(filePath, Encoding.UTF8); + foreach(var line in lines) + { + if (line == string.Empty) + { + continue; + } + context.Global.PrependPath.RemoveAll(x => string.Equals(x, line, StringComparison.CurrentCulture)); + context.Global.PrependPath.Add(line); + } + } + } + } + + public sealed class SetEnvFileCommand : RunnerService, IFileCommandExtension + { + public string ContextName => "env"; + public string FileName => "set_env_"; + + public Type ExtensionType => typeof(IFileCommandExtension); + + public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container) + { + if (File.Exists(filePath)) + { + // TODO Process this file + } + } + } +} diff --git a/src/Runner.Worker/GitHubContext.cs b/src/Runner.Worker/GitHubContext.cs index 1ecca19f0..0e4b6d472 100644 --- a/src/Runner.Worker/GitHubContext.cs +++ b/src/Runner.Worker/GitHubContext.cs @@ -13,11 +13,13 @@ namespace GitHub.Runner.Worker "actor", "api_url", "base_ref", + "env", "event_name", "event_path", "graphql_url", "head_ref", "job", + "path", "ref", "repository", "repository_owner", diff --git a/src/Runner.Worker/Handlers/ContainerActionHandler.cs b/src/Runner.Worker/Handlers/ContainerActionHandler.cs index 6e8624510..b25383459 100644 --- a/src/Runner.Worker/Handlers/ContainerActionHandler.cs +++ b/src/Runner.Worker/Handlers/ContainerActionHandler.cs @@ -161,16 +161,21 @@ namespace GitHub.Runner.Worker.Handlers Directory.CreateDirectory(tempHomeDirectory); this.Environment["HOME"] = tempHomeDirectory; + var tempFileCommandDirectory = Path.Combine(tempDirectory, "_runner_file_commands"); + ArgUtil.Directory(tempFileCommandDirectory, nameof(tempFileCommandDirectory)); + var tempWorkflowDirectory = Path.Combine(tempDirectory, "_github_workflow"); ArgUtil.Directory(tempWorkflowDirectory, nameof(tempWorkflowDirectory)); container.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock")); container.MountVolumes.Add(new MountVolume(tempHomeDirectory, "/github/home")); container.MountVolumes.Add(new MountVolume(tempWorkflowDirectory, "/github/workflow")); + container.MountVolumes.Add(new MountVolume(tempFileCommandDirectory, "/github/file_commands")); container.MountVolumes.Add(new MountVolume(defaultWorkingDirectory, "/github/workspace")); container.AddPathTranslateMapping(tempHomeDirectory, "/github/home"); container.AddPathTranslateMapping(tempWorkflowDirectory, "/github/workflow"); + container.AddPathTranslateMapping(tempFileCommandDirectory, "/github/file_commands"); container.AddPathTranslateMapping(defaultWorkingDirectory, "/github/workspace"); container.ContainerWorkDirectory = "/github/workspace"; diff --git a/src/Test/L0/ServiceInterfacesL0.cs b/src/Test/L0/ServiceInterfacesL0.cs index 63c01b17d..b3535e40a 100644 --- a/src/Test/L0/ServiceInterfacesL0.cs +++ b/src/Test/L0/ServiceInterfacesL0.cs @@ -60,6 +60,7 @@ namespace GitHub.Runner.Common.Tests { typeof(IActionCommandExtension), typeof(IExecutionContext), + typeof(IFileCommandExtension), typeof(IHandler), typeof(IJobExtension), typeof(IStep), diff --git a/src/Test/L0/Worker/ActionRunnerL0.cs b/src/Test/L0/Worker/ActionRunnerL0.cs index c5c6426e4..371a6253b 100644 --- a/src/Test/L0/Worker/ActionRunnerL0.cs +++ b/src/Test/L0/Worker/ActionRunnerL0.cs @@ -32,6 +32,8 @@ namespace GitHub.Runner.Common.Tests.Worker private TestHostContext _hc; private ActionRunner _actionRunner; private IActionManifestManager _actionManifestManager; + private Mock _fileCommandManager; + private DictionaryContextData _context = new DictionaryContextData(); [Fact] @@ -362,6 +364,7 @@ namespace GitHub.Runner.Common.Tests.Worker _handlerFactory = new Mock(); _defaultStepHost = new Mock(); _actionManifestManager = new ActionManifestManager(); + _fileCommandManager = new Mock(); _actionManifestManager.Initialize(_hc); var githubContext = new GitHubContext(); @@ -394,6 +397,8 @@ namespace GitHub.Runner.Common.Tests.Worker _hc.EnqueueInstance(_defaultStepHost.Object); + _hc.EnqueueInstance(_fileCommandManager.Object); + // Instance to test. _actionRunner = new ActionRunner(); _actionRunner.Initialize(_hc); From 3a76a2e291b764ebb4401cf176add4257a082e06 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Sat, 29 Aug 2020 23:18:35 -0400 Subject: [PATCH 6/7] read env file (#683) --- src/Runner.Worker/FileCommandManager.cs | 123 +++++- src/Runner.Worker/GitHubContext.cs | 12 + .../Handlers/CompositeActionHandler.cs | 18 +- src/Runner.Worker/StepsRunner.cs | 6 + src/Test/L0/Worker/SetEnvFileCommandL0.cs | 390 ++++++++++++++++++ src/Test/L0/Worker/StepsRunnerL0.cs | 9 +- 6 files changed, 541 insertions(+), 17 deletions(-) create mode 100644 src/Test/L0/Worker/SetEnvFileCommandL0.cs diff --git a/src/Runner.Worker/FileCommandManager.cs b/src/Runner.Worker/FileCommandManager.cs index 5fdcb2377..b6251112c 100644 --- a/src/Runner.Worker/FileCommandManager.cs +++ b/src/Runner.Worker/FileCommandManager.cs @@ -38,7 +38,6 @@ namespace GitHub.Runner.Worker var extensionManager = hostContext.GetService(); _commandExtensions = extensionManager.GetExtensions() ?? new List(); - } public void InitializeFiles(IExecutionContext context, ContainerInfo container) @@ -131,10 +130,128 @@ namespace GitHub.Runner.Worker public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container) { - if (File.Exists(filePath)) + try { - // TODO Process this file + var text = File.ReadAllText(filePath) ?? string.Empty; + var index = 0; + var line = ReadLine(text, ref index); + while (line != null) + { + if (!string.IsNullOrEmpty(line)) + { + var equalsIndex = line.IndexOf("=", StringComparison.Ordinal); + var heredocIndex = line.IndexOf("<<", StringComparison.Ordinal); + + // Normal style NAME=VALUE + if (equalsIndex >= 0 && (heredocIndex < 0 || equalsIndex < heredocIndex)) + { + var split = line.Split(new[] { '=' }, 2, StringSplitOptions.None); + if (string.IsNullOrEmpty(line)) + { + throw new Exception($"Invalid environment variable format '{line}'. Environment variable name must not be empty"); + } + SetEnvironmentVariable(context, split[0], split[1]); + } + // Heredoc style NAME<= 0 && (equalsIndex < 0 || heredocIndex < equalsIndex)) + { + var split = line.Split(new[] { "<<" }, 2, StringSplitOptions.None); + if (string.IsNullOrEmpty(split[0]) || string.IsNullOrEmpty(split[1])) + { + throw new Exception($"Invalid environment variable format '{line}'. Environment variable name must not be empty and delimiter must not be empty"); + } + var name = split[0]; + var delimiter = split[1]; + var startIndex = index; // Start index of the value (inclusive) + var endIndex = index; // End index of the value (exclusive) + var tempLine = ReadLine(text, ref index, out var newline); + while (!string.Equals(tempLine, delimiter, StringComparison.Ordinal)) + { + if (tempLine == null) + { + throw new Exception($"Invalid environment variable value. Matching delimiter not found '{delimiter}'"); + } + endIndex = index - newline.Length; + tempLine = ReadLine(text, ref index, out newline); + } + + var value = endIndex > startIndex ? text.Substring(startIndex, endIndex - startIndex) : string.Empty; + SetEnvironmentVariable(context, name, value); + } + else + { + throw new Exception($"Invalid environment variable format '{line}'"); + } + } + + line = ReadLine(text, ref index); + } } + catch (DirectoryNotFoundException) + { + context.Debug($"Environment variables file does not exist '{filePath}'"); + } + catch (FileNotFoundException) + { + context.Debug($"Environment variables file does not exist '{filePath}'"); + } + catch (Exception ex) + { + context.Error($"Failed to read environment variables file '{filePath}'"); + context.Error(ex); + } + } + + private static void SetEnvironmentVariable( + IExecutionContext context, + string name, + string value) + { + context.Global.EnvironmentVariables[name] = value; + context.SetEnvContext(name, value); + context.Debug($"{name}='{value}'"); + } + + private static string ReadLine( + string text, + ref int index) + { + return ReadLine(text, ref index, out _); + } + + private static string ReadLine( + string text, + ref int index, + out string newline) + { + if (index >= text.Length) + { + newline = null; + return null; + } + + var originalIndex = index; + var lfIndex = text.IndexOf("\n", index, StringComparison.Ordinal); + if (lfIndex < 0) + { + index = text.Length; + newline = null; + return text.Substring(originalIndex); + } + +#if OS_WINDOWS + var crLFIndex = text.IndexOf("\r\n", index, StringComparison.Ordinal); + if (crLFIndex >= 0 && crLFIndex < lfIndex) + { + index = crLFIndex + 2; // Skip over CRLF + newline = "\r\n"; + return text.Substring(originalIndex, crLFIndex - originalIndex); + } +#endif + + index = lfIndex + 1; // Skip over LF + newline = "\n"; + return text.Substring(originalIndex, lfIndex - originalIndex); } } } diff --git a/src/Runner.Worker/GitHubContext.cs b/src/Runner.Worker/GitHubContext.cs index 0e4b6d472..4eb98f770 100644 --- a/src/Runner.Worker/GitHubContext.cs +++ b/src/Runner.Worker/GitHubContext.cs @@ -41,5 +41,17 @@ namespace GitHub.Runner.Worker } } } + + public GitHubContext ShallowCopy() + { + var copy = new GitHubContext(); + + foreach (var pair in this) + { + copy[pair.Key] = pair.Value; + } + + return copy; + } } } \ No newline at end of file diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 254277112..d7f92b0c0 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -32,9 +32,6 @@ namespace GitHub.Runner.Worker.Handlers ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.NotNull(Data.Steps, nameof(Data.Steps)); - var githubContext = ExecutionContext.ExpressionValues["github"] as GitHubContext; - ArgUtil.NotNull(githubContext, nameof(githubContext)); - // Resolve action steps var actionSteps = Data.Steps; @@ -56,14 +53,6 @@ namespace GitHub.Runner.Worker.Handlers childScopeName = $"__{Guid.NewGuid()}"; } - // Copy the github context so that we don't modify the original pointer - // We can't use PipelineContextData.Clone() since that creates a null pointer exception for copying a GitHubContext - var compositeGitHubContext = new GitHubContext(); - foreach (var pair in githubContext) - { - compositeGitHubContext[pair.Key] = pair.Value; - } - foreach (Pipelines.ActionStep actionStep in actionSteps) { var actionRunner = HostContext.CreateService(); @@ -73,8 +62,13 @@ namespace GitHub.Runner.Worker.Handlers var step = ExecutionContext.CreateCompositeStep(childScopeName, actionRunner, inputsData, Environment); + // Shallow copy github context + var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext; + ArgUtil.NotNull(gitHubContext, nameof(gitHubContext)); + gitHubContext = gitHubContext.ShallowCopy(); + step.ExecutionContext.ExpressionValues["github"] = gitHubContext; + // Set GITHUB_ACTION_PATH - step.ExecutionContext.ExpressionValues["github"] = compositeGitHubContext; step.ExecutionContext.SetGitHubContext("action_path", ActionDirectory); compositeSteps.Add(step); diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index 58845206b..c0ab4f339 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -103,6 +103,12 @@ namespace GitHub.Runner.Worker bool evaluateStepEnvFailed = false; if (step is IActionRunner actionStep) { + // Shallow copy github context + var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext; + ArgUtil.NotNull(gitHubContext, nameof(gitHubContext)); + gitHubContext = gitHubContext.ShallowCopy(); + step.ExecutionContext.ExpressionValues["github"] = gitHubContext; + // Set GITHUB_ACTION step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name); diff --git a/src/Test/L0/Worker/SetEnvFileCommandL0.cs b/src/Test/L0/Worker/SetEnvFileCommandL0.cs new file mode 100644 index 000000000..c655fb676 --- /dev/null +++ b/src/Test/L0/Worker/SetEnvFileCommandL0.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Runtime.CompilerServices; +using GitHub.Runner.Common.Util; +using GitHub.Runner.Sdk; +using GitHub.Runner.Worker; +using GitHub.Runner.Worker.Container; +using GitHub.Runner.Worker.Handlers; +using Moq; +using Xunit; +using DTWebApi = GitHub.DistributedTask.WebApi; + +namespace GitHub.Runner.Common.Tests.Worker +{ + public sealed class SetEnvFileCommandL0 + { + private Mock _executionContext; + private List> _issues; + private string _rootDirectory; + private SetEnvFileCommand _setEnvFileCommand; + private ITraceWriter _trace; + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_DirectoryNotFound() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "directory-not-found", "env"); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_NotFound() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "file-not-found"); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_EmptyFile() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "empty-file"); + var content = new List(); + WriteContent(envFile, content); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_Simple() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "simple"); + var content = new List + { + "MY_ENV=MY VALUE", + }; + WriteContent(envFile, content); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count); + Assert.Equal("MY VALUE", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_Simple_SkipEmptyLines() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "simple"); + var content = new List + { + string.Empty, + "MY_ENV=my value", + string.Empty, + "MY_ENV_2=my second value", + string.Empty, + }; + WriteContent(envFile, content); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(2, _executionContext.Object.Global.EnvironmentVariables.Count); + Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); + Assert.Equal("my second value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_Simple_EmptyValue() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "simple-empty-value"); + var content = new List + { + "MY_ENV=", + }; + WriteContent(envFile, content); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count); + Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_Simple_MultipleValues() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "simple"); + var content = new List + { + "MY_ENV=my value", + "MY_ENV_2=", + "MY_ENV_3=my third value", + }; + WriteContent(envFile, content); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count); + Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); + Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]); + Assert.Equal("my third value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_Simple_SpecialCharacters() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "simple"); + var content = new List + { + "MY_ENV==abc", + "MY_ENV_2=def=ghi", + "MY_ENV_3=jkl=", + }; + WriteContent(envFile, content); + _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); + Assert.Equal(0, _issues.Count); + Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count); + Assert.Equal("=abc", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); + Assert.Equal("def=ghi", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]); + Assert.Equal("jkl=", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void SetEnvFileCommand_Heredoc() + { + using (var hostContext = Setup()) + { + var envFile = Path.Combine(_rootDirectory, "heredoc"); + var content = new List + { + "MY_ENV< + { + "MY_ENV< + { + string.Empty, + "MY_ENV< + { + "MY_ENV<<=EOF", + "hello", + "one", + "=EOF", + "MY_ENV_2<< + { + "MY_ENV< content, + string newline = null) + { + if (string.IsNullOrEmpty(newline)) + { + newline = Environment.NewLine; + } + + var encoding = new UTF8Encoding(true); // Emit BOM + var contentStr = string.Join(newline, content); + File.WriteAllText(path, contentStr, encoding); + } + + private TestHostContext Setup([CallerMemberName] string name = "") + { + _issues = new List>(); + + var hostContext = new TestHostContext(this, name); + + // Trace + _trace = hostContext.GetTrace(); + + // Directory for test data + var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work); + ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory)); + Directory.CreateDirectory(workDirectory); + _rootDirectory = Path.Combine(workDirectory, nameof(SetEnvFileCommandL0)); + Directory.CreateDirectory(_rootDirectory); + + // Execution context + _executionContext = new Mock(); + _executionContext.Setup(x => x.Global) + .Returns(new GlobalContext + { + EnvironmentVariables = new Dictionary(VarUtil.EnvironmentVariableKeyComparer), + WriteDebug = true, + }); + _executionContext.Setup(x => x.AddIssue(It.IsAny(), It.IsAny())) + .Callback((DTWebApi.Issue issue, string logMessage) => + { + _issues.Add(new Tuple(issue, logMessage)); + var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message; + _trace.Info($"Issue '{issue.Type}': {message}"); + }); + _executionContext.Setup(x => x.Write(It.IsAny(), It.IsAny())) + .Callback((string tag, string message) => + { + _trace.Info($"{tag}{message}"); + }); + + // SetEnvFileCommand + _setEnvFileCommand = new SetEnvFileCommand(); + _setEnvFileCommand.Initialize(hostContext); + + return hostContext; + } + } +} diff --git a/src/Test/L0/Worker/StepsRunnerL0.cs b/src/Test/L0/Worker/StepsRunnerL0.cs index 3d97b0110..86843a083 100644 --- a/src/Test/L0/Worker/StepsRunnerL0.cs +++ b/src/Test/L0/Worker/StepsRunnerL0.cs @@ -44,7 +44,7 @@ namespace GitHub.Runner.Common.Tests.Worker _contexts = new DictionaryContextData(); _jobContext = new JobContext(); - _contexts["github"] = new DictionaryContextData(); + _contexts["github"] = new GitHubContext(); _contexts["runner"] = new DictionaryContextData(); _contexts["job"] = _jobContext; _ec.Setup(x => x.ExpressionValues).Returns(_contexts); @@ -602,7 +602,12 @@ namespace GitHub.Runner.Common.Tests.Worker var stepContext = new Mock(); stepContext.SetupAllProperties(); stepContext.Setup(x => x.Global).Returns(() => _ec.Object.Global); - stepContext.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData()); + var expressionValues = new DictionaryContextData(); + foreach (var pair in _ec.Object.ExpressionValues) + { + expressionValues[pair.Key] = pair.Value; + } + stepContext.Setup(x => x.ExpressionValues).Returns(expressionValues); stepContext.Setup(x => x.ExpressionFunctions).Returns(new List()); stepContext.Setup(x => x.JobContext).Returns(_jobContext); stepContext.Setup(x => x.ContextName).Returns(step.Object.Action.ContextName); From e6eb9e381ddacc2a05c83ded5bb3ba755b035264 Mon Sep 17 00:00:00 2001 From: Thomas Boop <52323235+thboop@users.noreply.github.com> Date: Fri, 4 Sep 2020 15:35:36 -0400 Subject: [PATCH 7/7] Cleanup FileCommands (#693) --- src/Runner.Worker/ActionRunner.cs | 14 +++++---- src/Runner.Worker/FileCommandManager.cs | 39 ++++++++++++++----------- src/Runner.Worker/GitHubContext.cs | 4 +-- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Runner.Worker/ActionRunner.cs b/src/Runner.Worker/ActionRunner.cs index f946573e4..d9387286a 100644 --- a/src/Runner.Worker/ActionRunner.cs +++ b/src/Runner.Worker/ActionRunner.cs @@ -147,9 +147,7 @@ namespace GitHub.Runner.Worker // Setup File Command Manager var fileCommandManager = HostContext.CreateService(); - // Container Action Handler will handle the conversion for Container Actions - var container = handlerData.ExecutionType == ActionExecutionType.Container ? null : ExecutionContext.Global.Container; - fileCommandManager.InitializeFiles(ExecutionContext, container); + fileCommandManager.InitializeFiles(ExecutionContext, null); // Load the inputs. ExecutionContext.Debug("Loading inputs"); @@ -244,8 +242,14 @@ namespace GitHub.Runner.Worker handler.PrintActionDetails(Stage); // Run the task. - await handler.RunAsync(Stage); - fileCommandManager.TryProcessFiles(ExecutionContext, ExecutionContext.Global.Container); + try + { + await handler.RunAsync(Stage); + } + finally + { + fileCommandManager.ProcessFiles(ExecutionContext, ExecutionContext.Global.Container); + } } diff --git a/src/Runner.Worker/FileCommandManager.cs b/src/Runner.Worker/FileCommandManager.cs index b6251112c..aea76f10a 100644 --- a/src/Runner.Worker/FileCommandManager.cs +++ b/src/Runner.Worker/FileCommandManager.cs @@ -1,11 +1,12 @@ -using GitHub.Runner.Worker.Container; -using System; -using System.Text; -using System.IO; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Worker.Container; using GitHub.Runner.Common; using GitHub.Runner.Sdk; +using System; using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Text; namespace GitHub.Runner.Worker { @@ -13,7 +14,7 @@ namespace GitHub.Runner.Worker public interface IFileCommandManager : IRunnerService { void InitializeFiles(IExecutionContext context, ContainerInfo container); - void TryProcessFiles(IExecutionContext context, ContainerInfo container); + void ProcessFiles(IExecutionContext context, ContainerInfo container); } @@ -46,13 +47,13 @@ namespace GitHub.Runner.Worker _fileSuffix = Guid.NewGuid().ToString(); foreach (var fileCommand in _commandExtensions) { - var oldPath = Path.Combine(_fileCommandDirectory, fileCommand.FileName + oldSuffix); + var oldPath = Path.Combine(_fileCommandDirectory, fileCommand.FilePrefix + oldSuffix); if (oldSuffix != String.Empty && File.Exists(oldPath)) { TryDeleteFile(oldPath); } - var newPath = Path.Combine(_fileCommandDirectory, fileCommand.FileName + _fileSuffix); + var newPath = Path.Combine(_fileCommandDirectory, fileCommand.FilePrefix + _fileSuffix); TryDeleteFile(newPath); File.Create(newPath).Dispose(); @@ -61,11 +62,20 @@ namespace GitHub.Runner.Worker } } - public void TryProcessFiles(IExecutionContext context, ContainerInfo container) + public void ProcessFiles(IExecutionContext context, ContainerInfo container) { foreach (var fileCommand in _commandExtensions) { - fileCommand.ProcessCommand(context, Path.Combine(_fileCommandDirectory, fileCommand.FileName + _fileSuffix),container); + try + { + fileCommand.ProcessCommand(context, Path.Combine(_fileCommandDirectory, fileCommand.FilePrefix + _fileSuffix),container); + } + catch (Exception ex) + { + context.Error($"Unable to process file command '{fileCommand.ContextName}' successfully."); + context.Error(ex); + context.CommandResult = TaskResult.Failed; + } } } @@ -91,7 +101,7 @@ namespace GitHub.Runner.Worker public interface IFileCommandExtension : IExtension { string ContextName { get; } - string FileName { get; } + string FilePrefix { get; } void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container); } @@ -99,7 +109,7 @@ namespace GitHub.Runner.Worker public sealed class AddPathFileCommand : RunnerService, IFileCommandExtension { public string ContextName => "path"; - public string FileName => "add_path_"; + public string FilePrefix => "add_path_"; public Type ExtensionType => typeof(IFileCommandExtension); @@ -124,7 +134,7 @@ namespace GitHub.Runner.Worker public sealed class SetEnvFileCommand : RunnerService, IFileCommandExtension { public string ContextName => "env"; - public string FileName => "set_env_"; + public string FilePrefix => "set_env_"; public Type ExtensionType => typeof(IFileCommandExtension); @@ -195,11 +205,6 @@ namespace GitHub.Runner.Worker { context.Debug($"Environment variables file does not exist '{filePath}'"); } - catch (Exception ex) - { - context.Error($"Failed to read environment variables file '{filePath}'"); - context.Error(ex); - } } private static void SetEnvironmentVariable( diff --git a/src/Runner.Worker/GitHubContext.cs b/src/Runner.Worker/GitHubContext.cs index 4eb98f770..5079206cf 100644 --- a/src/Runner.Worker/GitHubContext.cs +++ b/src/Runner.Worker/GitHubContext.cs @@ -6,7 +6,7 @@ namespace GitHub.Runner.Worker { public sealed class GitHubContext : DictionaryContextData, IEnvironmentContextData { - private readonly HashSet _contextEnvWhitelist = new HashSet(StringComparer.OrdinalIgnoreCase) + private readonly HashSet _contextEnvAllowlist = new HashSet(StringComparer.OrdinalIgnoreCase) { "action", "action_path", @@ -35,7 +35,7 @@ namespace GitHub.Runner.Worker { foreach (var data in this) { - if (_contextEnvWhitelist.Contains(data.Key) && data.Value is StringContextData value) + if (_contextEnvAllowlist.Contains(data.Key) && data.Value is StringContextData value) { yield return new KeyValuePair($"GITHUB_{data.Key.ToUpperInvariant()}", value); }