From 550702114fca1ed958e49ec6d06c07acab0495b9 Mon Sep 17 00:00:00 2001 From: La'Kaleigh Harris <35268101+Xlient@users.noreply.github.com> Date: Tue, 23 Nov 2021 05:03:56 -0500 Subject: [PATCH] Support multiple versions in single invocation (#240) --- .github/workflows/workflow.yml | 23 + .licenses/npm/@actions/core.dep.yml | Bin 1303 -> 1303 bytes .../npm/@actions/http-client-1.0.11.dep.yml | Bin 0 -> 1387 bytes ...ient.dep.yml => http-client-1.0.8.dep.yml} | Bin README.md | 15 +- __tests__/installer.test.ts | 22 + __tests__/sample-csproj/sample.csproj | 2 +- __tests__/verify-dotnet.ps1 | 37 +- action.yml | 2 +- dist/index.js | 1363 +++++++++++++++-- package-lock.json | 22 +- package.json | 2 +- src/installer.ts | 10 +- src/setup-dotnet.ts | 23 +- 14 files changed, 1387 insertions(+), 134 deletions(-) create mode 100644 .licenses/npm/@actions/http-client-1.0.11.dep.yml rename .licenses/npm/@actions/{http-client.dep.yml => http-client-1.0.8.dep.yml} (100%) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 9ee782a..fe98553 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -32,6 +32,29 @@ jobs: - name: Verify no unstaged changes if: runner.os != 'windows' run: __tests__/verify-no-unstaged-changes.sh + + test-setup-multiple-versions: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + - name: Setup dotnet 2.2.402 and 3.1.404 + uses: ./ + with: + dotnet-version: | + 2.2.402 + 3.1.404 + 3.0.x + - name: Verify dotnet + shell: pwsh + run: __tests__/verify-dotnet.ps1 2.2.402 3.1.404 '3.0' test-setup-full-version: runs-on: ${{ matrix.operating-system }} diff --git a/.licenses/npm/@actions/core.dep.yml b/.licenses/npm/@actions/core.dep.yml index b1152f59f0a1059022f84e3d4e1aab5af2a1d6e6..43cedcd28fb7854ab51d95d841e5a24594fe25fa 100644 GIT binary patch delta 14 VcmbQvHJxjMDzllM!A6buEC3(%1X}n9`v9s29juqRI<9A5eS+wZM z=5XeF4eiBZ5e;Rp)^PV%Svze8yK0>~FX~RK!QDk~YP48g!^86H^5G$JmoqjG=RUGi z-5!6z3obb5U!0buLfX>e1|awK{3nPSvtD{pt@ypGH+Sy^1vk0#Bdyy0&|4A zEyt6J!5Jur3!E`a959tD^`M6nlu)B2-|1R}vF7OB%TZx-69kvmnp&5L35}_z9ye0@ z9l=p|%ED*YD!41IQ+M})s8MAHdO$=4w<~zZ@Xh4BEjQ}0uQ)L1q3))}Yq_;_8l#_C zJj68*bTCtuKaU&P?cOg$uC?Lq-&h`h)NP zRmR@8&kA!w4cEA=ue{@X`qdG}b)3clX)zq^Xbi?TGa>V`6c~imH{4@sRQ1&dvFC~bXH+{XbZHXQ#g(>^+kRRjrrJF5uIaSu-W6-#Z zvl#>D?{=?GOQsZxd@G-cpuh^)i~Kv=&<%Vc1)jge@XTbIA0%LhAY8scz6HWx;2Yzc z80g=7L5l)%0c^L=7{wanNp{#U{sfOW$8#BWoiTjI2rP5(q*pjbadrzkDv}h@$s@~{ ze2HPpB=@*m1SYU2LbBwL5dr&y*yjaB2^)mwjBf=p(H-S-iOhkD=Rm(>0g9AlS)d~a zbRmMyB;UUX_LNFU^K3)0_(<`0O&&8k)1kQ}BWxGLhV00bS12Gy(IT|X3Iow|N<*1( z)B_}uEa%=%l5;8W7^7PuKgOR~L1Q3-6+WD;$dSTFixWOS?&mn2QGD!PdEoij5Vra7 e4j90PrVS+-;^De@xV$wlqxr?)zt%rLqW=K%u(U4# literal 0 HcmV?d00001 diff --git a/.licenses/npm/@actions/http-client.dep.yml b/.licenses/npm/@actions/http-client-1.0.8.dep.yml similarity index 100% rename from .licenses/npm/@actions/http-client.dep.yml rename to .licenses/npm/@actions/http-client-1.0.8.dep.yml diff --git a/README.md b/README.md index bfec8f1..ba94f92 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This action sets up a [.NET CLI](https://github.com/dotnet/sdk) environment for use in actions by: -- optionally downloading and caching a version of dotnet by SDK version and adding to PATH +- optionally downloading and caching a version(s) of dotnet by SDK version(s) and adding to PATH - registering problem matchers for error output - setting up authentication to private package sources like GitHub Packages @@ -29,7 +29,20 @@ steps: dotnet-version: '3.1.x' # SDK Version to use; x will use the latest version of the 3.1 channel - run: dotnet build ``` +Multiple versions: +> Note: In case multiple versions are installed, the latest .NET version will be used by default unless another version is specified in the `global.json` file. +```yml +steps: +- name: Setup dotnet +- uses: actions/checkout@v2 +- uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 5.0.x +- run: dotnet build +``` Preview version: ```yml steps: diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index a7db4ea..532da18 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -32,6 +32,27 @@ describe('installer tests', () => { } }, 30000); + it('Aquires multiple versions of dotnet', async () => { + const versions = ['2.2.207', '3.1.120']; + + for (const version of versions) { + await getDotnet(version); + } + expect(fs.existsSync(path.join(toolDir, 'sdk', '2.2.207'))).toBe(true); + expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.120'))).toBe(true); + + if (IS_WINDOWS) { + expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); + } else { + expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); + } + + expect(process.env.DOTNET_ROOT).toBeDefined; + expect(process.env.PATH).toBeDefined; + expect(process.env.DOTNET_ROOT).toBe(toolDir); + expect(process.env.PATH?.startsWith(toolDir)).toBe(true); + }, 600000); + it('Acquires version of dotnet if no matching version is installed', async () => { await getDotnet('3.1.201'); expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); @@ -126,4 +147,5 @@ function normalizeFileContents(contents: string): string { async function getDotnet(version: string): Promise { const dotnetInstaller = new installer.DotnetCoreInstaller(version); await dotnetInstaller.installDotnet(); + installer.DotnetCoreInstaller.addToPath(); } diff --git a/__tests__/sample-csproj/sample.csproj b/__tests__/sample-csproj/sample.csproj index 50fb5e4..be6d7ea 100644 --- a/__tests__/sample-csproj/sample.csproj +++ b/__tests__/sample-csproj/sample.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;netcoreapp2.2 + netcoreapp3.1;netcoreapp3.0;netcoreapp2.2 sample_csproj false diff --git a/__tests__/verify-dotnet.ps1 b/__tests__/verify-dotnet.ps1 index f9b8913..7068c87 100755 --- a/__tests__/verify-dotnet.ps1 +++ b/__tests__/verify-dotnet.ps1 @@ -6,12 +6,15 @@ if (!$args[0]) $dotnet = Get-Command dotnet | Select-Object -First 1 | ForEach-Object { $_.Path } Write-Host "Found '$dotnet'" -$version = & $dotnet --version | Out-String | ForEach-Object { $_.Trim() } -Write-Host "Version $version" -if (-not ($version.StartsWith($args[0].ToString()))) +if($args.count -eq 1) { - Write-Host "PATH='$env:PATH'" - throw "Unexpected version" + $version = & $dotnet --version | Out-String | ForEach-Object { $_.Trim() } + Write-Host "Version $version" + if (-not ($version.StartsWith($args[0].ToString()))) + { + Write-Host "PATH='$env:PATH'" + throw "Unexpected version" + } } if ($args[1]) @@ -19,16 +22,17 @@ if ($args[1]) # SDKs are listed on multiple lines with the path afterwards in square brackets $versions = & $dotnet --list-sdks | ForEach-Object { $_.SubString(0, $_.IndexOf('[')).Trim() } Write-Host "Installed versions: $versions" - $isInstalledVersion = $false - foreach ($version in $versions) - { - if ($version.StartsWith($args[1].ToString())) + $InstalledVersionCount = 0 + foreach($arg in $args){ + foreach ($version in $versions) { - $isInstalledVersion = $true - break + if ($version.StartsWith($arg.ToString())) + { + $InstalledVersionCount++ + } } - } - if (-not $isInstalledVersion) + } + if ( $InstalledVersionCount -ne $args.Count) { Write-Host "PATH='$env:PATH'" throw "Unexpected version" @@ -53,6 +57,13 @@ if ($args[1]) throw "Unexpected output" } } +if ($args[2]) +{ + if ($sample_output -notlike "*Test Run Successful.*Test Run Successful.*Test Run Successful.*") + { + throw "Unexpected output" + } +} else { if ($sample_output -notlike "*Test Run Successful.*") diff --git a/action.yml b/action.yml index 635e9d8..45199d3 100644 --- a/action.yml +++ b/action.yml @@ -6,7 +6,7 @@ branding: color: green inputs: dotnet-version: - description: 'Optional SDK version to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x' + description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x' source-url: description: 'Optional package source for which to set up authentication. Will consult any existing NuGet.config in the root of the repo and provide a temporary NuGet.config using the NUGET_AUTH_TOKEN environment variable as a ClearTextPassword' owner: diff --git a/dist/index.js b/dist/index.js index f510d10..74c9c1a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2942,6 +2942,7 @@ module.exports = function(tagname, parent, val) { // We use any as a valid input type /* eslint-disable @typescript-eslint/no-explicit-any */ Object.defineProperty(exports, "__esModule", { value: true }); +exports.toCommandProperties = exports.toCommandValue = void 0; /** * Sanitizes an input into a string so it can be passed into issueCommand safely * @param input input to sanitize into a string @@ -2956,6 +2957,26 @@ function toCommandValue(input) { return JSON.stringify(input); } exports.toCommandValue = toCommandValue; +/** + * + * @param annotationProperties + * @returns The command properties to send with the actual annotation command + * See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646 + */ +function toCommandProperties(annotationProperties) { + if (!Object.keys(annotationProperties).length) { + return {}; + } + return { + title: annotationProperties.title, + file: annotationProperties.file, + line: annotationProperties.startLine, + endLine: annotationProperties.endLine, + col: annotationProperties.startColumn, + endColumn: annotationProperties.endColumn + }; +} +exports.toCommandProperties = toCommandProperties; //# sourceMappingURL=utils.js.map /***/ }), @@ -2973,14 +2994,27 @@ module.exports = require("os"); "use strict"; // For internal use, subject to change. +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); +exports.issueCommand = void 0; // We use any as a valid input type /* eslint-disable @typescript-eslint/no-explicit-any */ const fs = __importStar(__webpack_require__(747)); @@ -3951,6 +3985,278 @@ module.exports = uniq; module.exports = require("child_process"); +/***/ }), + +/***/ 141: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +var net = __webpack_require__(631); +var tls = __webpack_require__(16); +var http = __webpack_require__(605); +var https = __webpack_require__(211); +var events = __webpack_require__(614); +var assert = __webpack_require__(357); +var util = __webpack_require__(669); + + +exports.httpOverHttp = httpOverHttp; +exports.httpsOverHttp = httpsOverHttp; +exports.httpOverHttps = httpOverHttps; +exports.httpsOverHttps = httpsOverHttps; + + +function httpOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + return agent; +} + +function httpsOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + +function httpOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + return agent; +} + +function httpsOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + + +function TunnelingAgent(options) { + var self = this; + self.options = options || {}; + self.proxyOptions = self.options.proxy || {}; + self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; + self.requests = []; + self.sockets = []; + + self.on('free', function onFree(socket, host, port, localAddress) { + var options = toOptions(host, port, localAddress); + for (var i = 0, len = self.requests.length; i < len; ++i) { + var pending = self.requests[i]; + if (pending.host === options.host && pending.port === options.port) { + // Detect the request to connect same origin server, + // reuse the connection. + self.requests.splice(i, 1); + pending.request.onSocket(socket); + return; + } + } + socket.destroy(); + self.removeSocket(socket); + }); +} +util.inherits(TunnelingAgent, events.EventEmitter); + +TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { + var self = this; + var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress)); + + if (self.sockets.length >= this.maxSockets) { + // We are over limit so we'll add it to the queue. + self.requests.push(options); + return; + } + + // If we are under maxSockets create a new one. + self.createSocket(options, function(socket) { + socket.on('free', onFree); + socket.on('close', onCloseOrRemove); + socket.on('agentRemove', onCloseOrRemove); + req.onSocket(socket); + + function onFree() { + self.emit('free', socket, options); + } + + function onCloseOrRemove(err) { + self.removeSocket(socket); + socket.removeListener('free', onFree); + socket.removeListener('close', onCloseOrRemove); + socket.removeListener('agentRemove', onCloseOrRemove); + } + }); +}; + +TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { + var self = this; + var placeholder = {}; + self.sockets.push(placeholder); + + var connectOptions = mergeOptions({}, self.proxyOptions, { + method: 'CONNECT', + path: options.host + ':' + options.port, + agent: false, + headers: { + host: options.host + ':' + options.port + } + }); + if (options.localAddress) { + connectOptions.localAddress = options.localAddress; + } + if (connectOptions.proxyAuth) { + connectOptions.headers = connectOptions.headers || {}; + connectOptions.headers['Proxy-Authorization'] = 'Basic ' + + new Buffer(connectOptions.proxyAuth).toString('base64'); + } + + debug('making CONNECT request'); + var connectReq = self.request(connectOptions); + connectReq.useChunkedEncodingByDefault = false; // for v0.6 + connectReq.once('response', onResponse); // for v0.6 + connectReq.once('upgrade', onUpgrade); // for v0.6 + connectReq.once('connect', onConnect); // for v0.7 or later + connectReq.once('error', onError); + connectReq.end(); + + function onResponse(res) { + // Very hacky. This is necessary to avoid http-parser leaks. + res.upgrade = true; + } + + function onUpgrade(res, socket, head) { + // Hacky. + process.nextTick(function() { + onConnect(res, socket, head); + }); + } + + function onConnect(res, socket, head) { + connectReq.removeAllListeners(); + socket.removeAllListeners(); + + if (res.statusCode !== 200) { + debug('tunneling socket could not be established, statusCode=%d', + res.statusCode); + socket.destroy(); + var error = new Error('tunneling socket could not be established, ' + + 'statusCode=' + res.statusCode); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + if (head.length > 0) { + debug('got illegal response body from proxy'); + socket.destroy(); + var error = new Error('got illegal response body from proxy'); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + debug('tunneling connection has established'); + self.sockets[self.sockets.indexOf(placeholder)] = socket; + return cb(socket); + } + + function onError(cause) { + connectReq.removeAllListeners(); + + debug('tunneling socket could not be established, cause=%s\n', + cause.message, cause.stack); + var error = new Error('tunneling socket could not be established, ' + + 'cause=' + cause.message); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + } +}; + +TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { + var pos = this.sockets.indexOf(socket) + if (pos === -1) { + return; + } + this.sockets.splice(pos, 1); + + var pending = this.requests.shift(); + if (pending) { + // If we have pending requests and a socket gets closed a new one + // needs to be created to take over in the pool for the one that closed. + this.createSocket(pending, function(socket) { + pending.request.onSocket(socket); + }); + } +}; + +function createSecureSocket(options, cb) { + var self = this; + TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { + var hostHeader = options.request.getHeader('host'); + var tlsOptions = mergeOptions({}, self.options, { + socket: socket, + servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host + }); + + // 0 is dummy port for v0.6 + var secureSocket = tls.connect(0, tlsOptions); + self.sockets[self.sockets.indexOf(socket)] = secureSocket; + cb(secureSocket); + }); +} + + +function toOptions(host, port, localAddress) { + if (typeof host === 'string') { // since v0.10 + return { + host: host, + port: port, + localAddress: localAddress + }; + } + return host; // for v0.11 or later +} + +function mergeOptions(target) { + for (var i = 1, len = arguments.length; i < len; ++i) { + var overrides = arguments[i]; + if (typeof overrides === 'object') { + var keys = Object.keys(overrides); + for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { + var k = keys[j]; + if (overrides[k] !== undefined) { + target[k] = overrides[k]; + } + } + } + } + return target; +} + + +var debug; +if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { + debug = function() { + var args = Array.prototype.slice.call(arguments); + if (typeof args[0] === 'string') { + args[0] = 'TUNNEL: ' + args[0]; + } else { + args.unshift('TUNNEL:'); + } + console.error.apply(console, args); + } +} else { + debug = function() {}; +} +exports.debug = debug; // for test + + /***/ }), /***/ 143: @@ -4481,6 +4787,551 @@ function authenticationPlugin (octokit, options) { } +/***/ }), + +/***/ 192: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const http = __webpack_require__(605); +const https = __webpack_require__(211); +const pm = __webpack_require__(651); +let tunnel; +var HttpCodes; +(function (HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; +})(HttpCodes = exports.HttpCodes || (exports.HttpCodes = {})); +var Headers; +(function (Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; +})(Headers = exports.Headers || (exports.Headers = {})); +var MediaTypes; +(function (MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; +})(MediaTypes = exports.MediaTypes || (exports.MediaTypes = {})); +/** + * Returns the proxy URL, depending upon the supplied url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ +function getProxyUrl(serverUrl) { + let proxyUrl = pm.getProxyUrl(new URL(serverUrl)); + return proxyUrl ? proxyUrl.href : ''; +} +exports.getProxyUrl = getProxyUrl; +const HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +]; +const HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +]; +const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; +const ExponentialBackoffCeiling = 10; +const ExponentialBackoffTimeSlice = 5; +class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = 'HttpClientError'; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } +} +exports.HttpClientError = HttpClientError; +class HttpClientResponse { + constructor(message) { + this.message = message; + } + readBody() { + return new Promise(async (resolve, reject) => { + let output = Buffer.alloc(0); + this.message.on('data', (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on('end', () => { + resolve(output.toString()); + }); + }); + } +} +exports.HttpClientResponse = HttpClientResponse; +function isHttps(requestUrl) { + let parsedUrl = new URL(requestUrl); + return parsedUrl.protocol === 'https:'; +} +exports.isHttps = isHttps; +class HttpClient { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = userAgent; + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) { + this._ignoreSslError = requestOptions.ignoreSslError; + } + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) { + this._allowRedirects = requestOptions.allowRedirects; + } + if (requestOptions.allowRedirectDowngrade != null) { + this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + } + if (requestOptions.maxRedirects != null) { + this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + } + if (requestOptions.keepAlive != null) { + this._keepAlive = requestOptions.keepAlive; + } + if (requestOptions.allowRetries != null) { + this._allowRetries = requestOptions.allowRetries; + } + if (requestOptions.maxRetries != null) { + this._maxRetries = requestOptions.maxRetries; + } + } + } + options(requestUrl, additionalHeaders) { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + } + get(requestUrl, additionalHeaders) { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + } + del(requestUrl, additionalHeaders) { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + } + post(requestUrl, data, additionalHeaders) { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + } + patch(requestUrl, data, additionalHeaders) { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + } + put(requestUrl, data, additionalHeaders) { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + } + head(requestUrl, additionalHeaders) { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return this.request(verb, requestUrl, stream, additionalHeaders); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + async getJson(requestUrl, additionalHeaders = {}) { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + let res = await this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + } + async postJson(requestUrl, obj, additionalHeaders = {}) { + let data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + let res = await this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + } + async putJson(requestUrl, obj, additionalHeaders = {}) { + let data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + let res = await this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + } + async patchJson(requestUrl, obj, additionalHeaders = {}) { + let data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + let res = await this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + async request(verb, requestUrl, data, headers) { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + let parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1 + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + while (numTries < maxTries) { + response = await this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (let i = 0; i < this.handlers.length; i++) { + if (this.handlers[i].canHandleAuthentication(response)) { + authenticationHandler = this.handlers[i]; + break; + } + } + if (authenticationHandler) { + return authenticationHandler.handleAuthentication(this, info, data); + } + else { + // We have received an unauthorized response but have no handlers to handle it. + // Let the response return to the caller. + return response; + } + } + let redirectsRemaining = this._maxRedirects; + while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1 && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + let parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol == 'https:' && + parsedUrl.protocol != parsedRedirectUrl.protocol && + !this._allowRedirectDowngrade) { + throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'); + } + // we need to finish reading the response before reassigning response + // which will leak the open socket. + await response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (let header in headers) { + // header names are case insensitive + if (header.toLowerCase() === 'authorization') { + delete headers[header]; + } + } + } + // let's make the request with the new redirectUrl + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = await this.requestRaw(info, data); + redirectsRemaining--; + } + if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + await response.readBody(); + await this._performExponentialBackoff(numTries); + } + } + return response; + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) { + this._agent.destroy(); + } + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return new Promise((resolve, reject) => { + let callbackForResult = function (err, res) { + if (err) { + reject(err); + } + resolve(res); + }; + this.requestRawWithCallback(info, data, callbackForResult); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + let socket; + if (typeof data === 'string') { + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + let handleResult = (err, res) => { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + }; + let req = info.httpModule.request(info.options, (msg) => { + let res = new HttpClientResponse(msg); + handleResult(null, res); + }); + req.on('socket', sock => { + socket = sock; + }); + // If we ever get disconnected, we want the socket to timeout eventually + req.setTimeout(this._socketTimeout || 3 * 60000, () => { + if (socket) { + socket.end(); + } + handleResult(new Error('Request timeout: ' + info.options.path), null); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err, null); + }); + if (data && typeof data === 'string') { + req.write(data, 'utf8'); + } + if (data && typeof data !== 'string') { + data.on('close', function () { + req.end(); + }); + data.pipe(req); + } + else { + req.end(); + } + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + let parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === 'https:'; + info.httpModule = usingSsl ? https : http; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port + ? parseInt(info.parsedUrl.port) + : defaultPort; + info.options.path = + (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) { + info.options.headers['user-agent'] = this.userAgent; + } + info.options.agent = this._getAgent(info.parsedUrl); + // gives handlers an opportunity to participate + if (this.handlers) { + this.handlers.forEach(handler => { + handler.prepareRequest(info.options); + }); + } + return info; + } + _mergeHeaders(headers) { + const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers)); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + let proxyUrl = pm.getProxyUrl(parsedUrl); + let useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) { + agent = this._proxyAgent; + } + if (this._keepAlive && !useProxy) { + agent = this._agent; + } + // if agent is already assigned use that agent. + if (!!agent) { + return agent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + let maxSockets = 100; + if (!!this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; + } + if (useProxy) { + // If using proxy, need tunnel + if (!tunnel) { + tunnel = __webpack_require__(413); + } + const agentOptions = { + maxSockets: maxSockets, + keepAlive: this._keepAlive, + proxy: { + ...((proxyUrl.username || proxyUrl.password) && { + proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` + }), + host: proxyUrl.hostname, + port: proxyUrl.port + } + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === 'https:'; + if (usingSsl) { + tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; + } + else { + tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; + } + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + // if reusing agent across request and tunneling agent isn't assigned create a new agent + if (this._keepAlive && !agent) { + const options = { keepAlive: this._keepAlive, maxSockets: maxSockets }; + agent = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent; + } + // if not using private agent and tunnel agent isn't setup then use global agent + if (!agent) { + agent = usingSsl ? https.globalAgent : http.globalAgent; + } + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options || {}, { + rejectUnauthorized: false + }); + } + return agent; + } + _performExponentialBackoff(retryNumber) { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + } + static dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + let a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + async _processResponse(res, options) { + return new Promise(async (resolve, reject) => { + const statusCode = res.message.statusCode; + const response = { + statusCode: statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode == HttpCodes.NotFound) { + resolve(response); + } + let obj; + let contents; + // get the result from the body + try { + contents = await res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, HttpClient.dateTimeDeserializer); + } + else { + obj = JSON.parse(contents); + } + response.result = obj; + } + response.headers = res.message.headers; + } + catch (err) { + // Invalid resource (contents not json); leaving result obj null + } + // note that 3xx redirects are handled by the http layer. + if (statusCode > 299) { + let msg; + // if exception/error in body, attempt to get better error + if (obj && obj.message) { + msg = obj.message; + } + else if (contents && contents.length > 0) { + // it may be the case that the exception is in the body message as string + msg = contents; + } + else { + msg = 'Failed request: (' + statusCode + ')'; + } + let err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + }); + } +} +exports.HttpClient = HttpClient; + + /***/ }), /***/ 197: @@ -7823,20 +8674,24 @@ function run() { // If a valid version still can't be identified, nothing will be installed. // Proxy, auth, (etc) are still set up, even if no version is identified // - let version = core.getInput('dotnet-version'); - if (!version) { + let versions = core.getMultilineInput('dotnet-version'); + if (!versions.length) { // Try to fall back to global.json core.debug('No version found, trying to find version from global.json'); const globalJsonPath = path.join(process.cwd(), 'global.json'); if (fs.existsSync(globalJsonPath)) { - version = getVersionFromGlobalJson(globalJsonPath); + versions.push(getVersionFromGlobalJson(globalJsonPath)); } } - if (version) { + if (versions.length) { const includePrerelease = (core.getInput('include-prerelease') || 'false').toLowerCase() === 'true'; - const dotnetInstaller = new installer.DotnetCoreInstaller(version, includePrerelease); - yield dotnetInstaller.installDotnet(); + let dotnetInstaller; + for (const version of versions) { + dotnetInstaller = new installer.DotnetCoreInstaller(version, includePrerelease); + yield dotnetInstaller.installDotnet(); + } + installer.DotnetCoreInstaller.addToPath(); } const sourceUrl = core.getInput('source-url'); const configFile = core.getInput('config-file'); @@ -8454,9 +9309,10 @@ function Octokit (plugins, options) { /***/ }), /***/ 413: -/***/ (function(module) { +/***/ (function(module, __unusedexports, __webpack_require__) { + +module.exports = __webpack_require__(141); -module.exports = require("stream"); /***/ }), @@ -9018,14 +9874,27 @@ function octokitValidate (octokit) { "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); +exports.issue = exports.issueCommand = void 0; const os = __importStar(__webpack_require__(87)); const utils_1 = __webpack_require__(82); /** @@ -9282,7 +10151,7 @@ Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } -var Stream = _interopDefault(__webpack_require__(413)); +var Stream = _interopDefault(__webpack_require__(794)); var http = _interopDefault(__webpack_require__(605)); var Url = _interopDefault(__webpack_require__(835)); var https = _interopDefault(__webpack_require__(211)); @@ -11301,6 +12170,25 @@ exports.GitHub = GitHub; "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -11310,19 +12198,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; - return result; -}; Object.defineProperty(exports, "__esModule", { value: true }); +exports.getIDToken = exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0; const command_1 = __webpack_require__(431); const file_command_1 = __webpack_require__(102); const utils_1 = __webpack_require__(82); const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); +const oidc_utils_1 = __webpack_require__(742); /** * The code to exit an action */ @@ -11384,7 +12267,9 @@ function addPath(inputPath) { } exports.addPath = addPath; /** - * Gets the value of an input. The value is also trimmed. + * Gets the value of an input. + * Unless trimWhitespace is set to false in InputOptions, the value is also trimmed. + * Returns an empty string if the value is not defined. * * @param name name of the input to get * @param options optional. See InputOptions. @@ -11395,9 +12280,49 @@ function getInput(name, options) { if (options && options.required && !val) { throw new Error(`Input required and not supplied: ${name}`); } + if (options && options.trimWhitespace === false) { + return val; + } return val.trim(); } exports.getInput = getInput; +/** + * Gets the values of an multiline input. Each value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string[] + * + */ +function getMultilineInput(name, options) { + const inputs = getInput(name, options) + .split('\n') + .filter(x => x !== ''); + return inputs; +} +exports.getMultilineInput = getMultilineInput; +/** + * Gets the input value of the boolean type in the YAML 1.2 "core schema" specification. + * Support boolean input list: `true | True | TRUE | false | False | FALSE` . + * The return value is also in boolean type. + * ref: https://yaml.org/spec/1.2/spec.html#id2804923 + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns boolean + */ +function getBooleanInput(name, options) { + const trueValue = ['true', 'True', 'TRUE']; + const falseValue = ['false', 'False', 'FALSE']; + const val = getInput(name, options); + if (trueValue.includes(val)) + return true; + if (falseValue.includes(val)) + return false; + throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` + + `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); +} +exports.getBooleanInput = getBooleanInput; /** * Sets the value of an output. * @@ -11406,6 +12331,7 @@ exports.getInput = getInput; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { + process.stdout.write(os.EOL); command_1.issueCommand('set-output', { name }, value); } exports.setOutput = setOutput; @@ -11452,19 +12378,30 @@ exports.debug = debug; /** * Adds an error issue * @param message error issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. */ -function error(message) { - command_1.issue('error', message instanceof Error ? message.toString() : message); +function error(message, properties = {}) { + command_1.issueCommand('error', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message); } exports.error = error; /** - * Adds an warning issue + * Adds a warning issue * @param message warning issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. */ -function warning(message) { - command_1.issue('warning', message instanceof Error ? message.toString() : message); +function warning(message, properties = {}) { + command_1.issueCommand('warning', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message); } exports.warning = warning; +/** + * Adds a notice issue + * @param message notice issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function notice(message, properties = {}) { + command_1.issueCommand('notice', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message); +} +exports.notice = notice; /** * Writes info to log with console.log. * @param message info message @@ -11537,6 +12474,12 @@ function getState(name) { return process.env[`STATE_${name}`] || ''; } exports.getState = getState; +function getIDToken(aud) { + return __awaiter(this, void 0, void 0, function* () { + return yield oidc_utils_1.OidcClient.getIDToken(aud); + }); +} +exports.getIDToken = getIDToken; //# sourceMappingURL=core.js.map /***/ }), @@ -14480,6 +15423,72 @@ module.exports.env = opts => { module.exports = require("path"); +/***/ }), + +/***/ 623: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + options.headers['Authorization'] = + 'Basic ' + + Buffer.from(this.username + ':' + this.password).toString('base64'); + } + // This handler cannot handle 401 + canHandleAuthentication(response) { + return false; + } + handleAuthentication(httpClient, requestInfo, objs) { + return null; + } +} +exports.BasicCredentialHandler = BasicCredentialHandler; +class BearerCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + options.headers['Authorization'] = 'Bearer ' + this.token; + } + // This handler cannot handle 401 + canHandleAuthentication(response) { + return false; + } + handleAuthentication(httpClient, requestInfo, objs) { + return null; + } +} +exports.BearerCredentialHandler = BearerCredentialHandler; +class PersonalAccessTokenCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + options.headers['Authorization'] = + 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64'); + } + // This handler cannot handle 401 + canHandleAuthentication(response) { + return false; + } + handleAuthentication(httpClient, requestInfo, objs) { + return null; + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; + + /***/ }), /***/ 626: @@ -14650,6 +15659,71 @@ function getLastPage (octokit, link, headers) { } +/***/ }), + +/***/ 651: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function getProxyUrl(reqUrl) { + let usingSsl = reqUrl.protocol === 'https:'; + let proxyUrl; + if (checkBypass(reqUrl)) { + return proxyUrl; + } + let proxyVar; + if (usingSsl) { + proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + if (proxyVar) { + proxyUrl = new URL(proxyVar); + } + return proxyUrl; +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; + if (!noProxy) { + return false; + } + // Determine the request port + let reqPort; + if (reqUrl.port) { + reqPort = Number(reqUrl.port); + } + else if (reqUrl.protocol === 'http:') { + reqPort = 80; + } + else if (reqUrl.protocol === 'https:') { + reqPort = 443; + } + // Format the request hostname and hostname with port + let upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (let upperNoProxyItem of noProxy + .split(',') + .map(x => x.trim().toUpperCase()) + .filter(x => x)) { + if (upperReqHosts.some(x => x === upperNoProxyItem)) { + return true; + } + } + return false; +} +exports.checkBypass = checkBypass; + + /***/ }), /***/ 654: @@ -16770,66 +17844,86 @@ exports.convertToJson = convertToJson; /***/ }), /***/ 742: -/***/ (function(module, __unusedexports, __webpack_require__) { +/***/ (function(__unusedmodule, exports, __webpack_require__) { -var fs = __webpack_require__(747) -var core -if (process.platform === 'win32' || global.TESTING_WINDOWS) { - core = __webpack_require__(818) -} else { - core = __webpack_require__(197) -} +"use strict"; -module.exports = isexe -isexe.sync = sync - -function isexe (path, options, cb) { - if (typeof options === 'function') { - cb = options - options = {} - } - - if (!cb) { - if (typeof Promise !== 'function') { - throw new TypeError('callback not provided') +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OidcClient = void 0; +const http_client_1 = __webpack_require__(192); +const auth_1 = __webpack_require__(623); +const core_1 = __webpack_require__(470); +class OidcClient { + static createHttpClient(allowRetry = true, maxRetry = 10) { + const requestOptions = { + allowRetries: allowRetry, + maxRetries: maxRetry + }; + return new http_client_1.HttpClient('actions/oidc-client', [new auth_1.BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions); } - - return new Promise(function (resolve, reject) { - isexe(path, options || {}, function (er, is) { - if (er) { - reject(er) - } else { - resolve(is) + static getRequestToken() { + const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']; + if (!token) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable'); } - }) - }) - } - - core(path, options || {}, function (er, is) { - // ignore EACCES because that just means we aren't allowed to run it - if (er) { - if (er.code === 'EACCES' || options && options.ignoreErrors) { - er = null - is = false - } + return token; } - cb(er, is) - }) -} - -function sync (path, options) { - // my kingdom for a filtered catch - try { - return core.sync(path, options || {}) - } catch (er) { - if (options && options.ignoreErrors || er.code === 'EACCES') { - return false - } else { - throw er + static getIDTokenUrl() { + const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']; + if (!runtimeUrl) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable'); + } + return runtimeUrl; + } + static getCall(id_token_url) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const httpclient = OidcClient.createHttpClient(); + const res = yield httpclient + .getJson(id_token_url) + .catch(error => { + throw new Error(`Failed to get ID Token. \n + Error Code : ${error.statusCode}\n + Error Message: ${error.result.message}`); + }); + const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value; + if (!id_token) { + throw new Error('Response json body do not have ID Token field'); + } + return id_token; + }); + } + static getIDToken(audience) { + return __awaiter(this, void 0, void 0, function* () { + try { + // New ID Token is requested from action service + let id_token_url = OidcClient.getIDTokenUrl(); + if (audience) { + const encodedAudience = encodeURIComponent(audience); + id_token_url = `${id_token_url}&audience=${encodedAudience}`; + } + core_1.debug(`ID token url is ${id_token_url}`); + const id_token = yield OidcClient.getCall(id_token_url); + core_1.setSecret(id_token); + return id_token; + } + catch (error) { + throw new Error(`Error message: ${error.message}`); + } + }); } - } } - +exports.OidcClient = OidcClient; +//# sourceMappingURL=oidc-utils.js.map /***/ }), @@ -17010,28 +18104,30 @@ class DotnetCoreInstaller { env: envVariables }); } - if (process.env['DOTNET_INSTALL_DIR']) { - core.addPath(process.env['DOTNET_INSTALL_DIR']); - core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']); - } - else { - if (IS_WINDOWS) { - // This is the default set in install-dotnet.ps1 - core.addPath(path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')); - core.exportVariable('DOTNET_ROOT', path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')); - } - else { - // This is the default set in install-dotnet.sh - core.addPath(path.join(process.env['HOME'] + '', '.dotnet')); - core.exportVariable('DOTNET_ROOT', path.join(process.env['HOME'] + '', '.dotnet')); - } - } - console.log(process.env['PATH']); if (resultCode != 0) { throw new Error(`Failed to install dotnet ${resultCode}. ${output}`); } }); } + static addToPath() { + if (process.env['DOTNET_INSTALL_DIR']) { + core.addPath(process.env['DOTNET_INSTALL_DIR']); + core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']); + } + else { + if (IS_WINDOWS) { + // This is the default set in install-dotnet.ps1 + core.addPath(path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')); + core.exportVariable('DOTNET_ROOT', path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')); + } + else { + // This is the default set in install-dotnet.sh + core.addPath(path.join(process.env['HOME'] + '', '.dotnet')); + core.exportVariable('DOTNET_ROOT', path.join(process.env['HOME'] + '', '.dotnet')); + } + } + console.log(process.env['PATH']); + } // versionInfo - versionInfo of the SDK/Runtime resolveVersion(versionInfo) { return __awaiter(this, void 0, void 0, function* () { @@ -17362,6 +18458,13 @@ function getFirstPage (octokit, link, headers) { } +/***/ }), + +/***/ 794: +/***/ (function(module) { + +module.exports = require("stream"); + /***/ }), /***/ 796: @@ -17817,6 +18920,70 @@ function gather (octokit, results, iterator, mapFn) { } +/***/ }), + +/***/ 813: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var fs = __webpack_require__(747) +var core +if (process.platform === 'win32' || global.TESTING_WINDOWS) { + core = __webpack_require__(818) +} else { + core = __webpack_require__(197) +} + +module.exports = isexe +isexe.sync = sync + +function isexe (path, options, cb) { + if (typeof options === 'function') { + cb = options + options = {} + } + + if (!cb) { + if (typeof Promise !== 'function') { + throw new TypeError('callback not provided') + } + + return new Promise(function (resolve, reject) { + isexe(path, options || {}, function (er, is) { + if (er) { + reject(er) + } else { + resolve(is) + } + }) + }) + } + + core(path, options || {}, function (er, is) { + // ignore EACCES because that just means we aren't allowed to run it + if (er) { + if (er.code === 'EACCES' || options && options.ignoreErrors) { + er = null + is = false + } + } + cb(er, is) + }) +} + +function sync (path, options) { + // my kingdom for a filtered catch + try { + return core.sync(path, options || {}) + } catch (er) { + if (options && options.ignoreErrors || er.code === 'EACCES') { + return false + } else { + throw er + } + } +} + + /***/ }), /***/ 814: @@ -17831,7 +18998,7 @@ var isWindows = process.platform === 'win32' || var path = __webpack_require__(622) var COLON = isWindows ? ';' : ':' -var isexe = __webpack_require__(742) +var isexe = __webpack_require__(813) function getNotFoundError (cmd) { var er = new Error('not found: ' + cmd) @@ -21459,7 +22626,7 @@ exports.convert2nimn = convert2nimn; "use strict"; -const {PassThrough} = __webpack_require__(413); +const {PassThrough} = __webpack_require__(794); module.exports = options => { options = Object.assign({}, options); diff --git a/package-lock.json b/package-lock.json index 72990f0..7d3f3a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,22 @@ "requires": true, "dependencies": { "@actions/core": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", - "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", + "integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==", + "requires": { + "@actions/http-client": "^1.0.11" + }, + "dependencies": { + "@actions/http-client": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", + "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", + "requires": { + "tunnel": "0.0.6" + } + } + } }, "@actions/exec": { "version": "1.0.4", @@ -3432,8 +3445,7 @@ "tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" }, "type-check": { "version": "0.3.2", diff --git a/package.json b/package.json index 6e1f1e2..7825f71 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@actions/core": "^1.2.6", + "@actions/core": "^1.6.0", "@actions/exec": "^1.0.4", "@actions/github": "^1.1.0", "@actions/http-client": "^1.0.8", diff --git a/src/installer.ts b/src/installer.ts index aa2b45c..4251260 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -165,6 +165,12 @@ export class DotnetCoreInstaller { }); } + if (resultCode != 0) { + throw new Error(`Failed to install dotnet ${resultCode}. ${output}`); + } + } + + static addToPath() { if (process.env['DOTNET_INSTALL_DIR']) { core.addPath(process.env['DOTNET_INSTALL_DIR']); core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']); @@ -189,10 +195,6 @@ export class DotnetCoreInstaller { } console.log(process.env['PATH']); - - if (resultCode != 0) { - throw new Error(`Failed to install dotnet ${resultCode}. ${output}`); - } } // versionInfo - versionInfo of the SDK/Runtime diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index 0d09998..f82bb14 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -13,26 +13,29 @@ export async function run() { // If a valid version still can't be identified, nothing will be installed. // Proxy, auth, (etc) are still set up, even if no version is identified // - let version = core.getInput('dotnet-version'); - if (!version) { + let versions = core.getMultilineInput('dotnet-version'); + if (!versions.length) { // Try to fall back to global.json core.debug('No version found, trying to find version from global.json'); const globalJsonPath = path.join(process.cwd(), 'global.json'); if (fs.existsSync(globalJsonPath)) { - version = getVersionFromGlobalJson(globalJsonPath); + versions.push(getVersionFromGlobalJson(globalJsonPath)); } } - if (version) { + if (versions.length) { const includePrerelease: boolean = (core.getInput('include-prerelease') || 'false').toLowerCase() === 'true'; - - const dotnetInstaller = new installer.DotnetCoreInstaller( - version, - includePrerelease - ); - await dotnetInstaller.installDotnet(); + let dotnetInstaller!: installer.DotnetCoreInstaller; + for (const version of versions) { + dotnetInstaller = new installer.DotnetCoreInstaller( + version, + includePrerelease + ); + await dotnetInstaller.installDotnet(); + } + installer.DotnetCoreInstaller.addToPath(); } const sourceUrl: string = core.getInput('source-url');