From e9cec5c5986db3ca076584a0e8c9984496701886 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sun, 15 Sep 2019 01:14:12 +0200 Subject: [PATCH 01/77] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5743bbe --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 webfactory GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From e181806200805a1b73d626e059836863e4b697c9 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sun, 15 Sep 2019 09:32:43 +0200 Subject: [PATCH 02/77] Use `$HOME` to support OS X as well (#2) This fixes #1. Windows is currently not supported. --- .github/workflows/demo.yml | 19 +++++++++++++++++++ README.md | 4 ++++ dist/index.js | 11 +++++++---- index.js | 11 +++++++---- 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/demo.yml diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml new file mode 100644 index 0000000..0d860f1 --- /dev/null +++ b/.github/workflows/demo.yml @@ -0,0 +1,19 @@ +on: [push, pull_request] + +jobs: + load_key_demo: + strategy: + matrix: + os: [ubuntu-latest, macOS-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v1 + - name: Setup key + uses: ./ + with: + ssh-private-key: ${{ secrets.DEMO_KEY }} + - run: | + ssh-add -l + echo SSH_AUTH_SOCK is at $SSH_AUTH_SOCK + + diff --git a/README.md b/README.md index 4d9c861..b445621 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ jobs: ## Known issues and limitations +### Currently OS X and Linux only + +This action has not been tested for the Windows virtual environment. If you can provide the steps necessary to setup (even install?) OpenSSH on the Windows machine, please open an issue. + ### Works for the current job only Since each job [runs in a fresh instance](https://help.github.com/en/articles/about-github-actions#job) of the virtual environment, the SSH key will only be available in the job where this action has been referenced. You can, of course, add the action in multiple jobs or even workflows. All instances can use the same `SSH_PRIVATE_KEY` secret. diff --git a/dist/index.js b/dist/index.js index 0903223..2ae7540 100644 --- a/dist/index.js +++ b/dist/index.js @@ -58,10 +58,13 @@ const child_process = __webpack_require__(129); const fs = __webpack_require__(747); try { - console.log("Adding GitHub.com keys to /home/runner/ssh_known_hosts"); - fs.mkdirSync('/home/runner/.ssh', { recursive: true}); - fs.appendFileSync('/home/runner/.ssh/known_hosts', '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); - fs.appendFileSync('/home/runner/.ssh/known_hosts', '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); + const home = process.env['HOME']; + const homeSsh = home + '/.ssh'; + + console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); + fs.mkdirSync(homeSsh, { recursive: true}); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); console.log("Starting ssh-agent"); const authSock = core.getInput('ssh-auth-sock'); diff --git a/index.js b/index.js index ec8c378..141ad08 100644 --- a/index.js +++ b/index.js @@ -3,10 +3,13 @@ const child_process = require('child_process'); const fs = require('fs'); try { - console.log("Adding GitHub.com keys to /home/runner/ssh_known_hosts"); - fs.mkdirSync('/home/runner/.ssh', { recursive: true}); - fs.appendFileSync('/home/runner/.ssh/known_hosts', '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); - fs.appendFileSync('/home/runner/.ssh/known_hosts', '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); + const home = process.env['HOME']; + const homeSsh = home + '/.ssh'; + + console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); + fs.mkdirSync(homeSsh, { recursive: true}); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); console.log("Starting ssh-agent"); const authSock = core.getInput('ssh-auth-sock'); From 1a9af8e8e040e1ca1a7da67d6a45e13ba9890630 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 24 Sep 2019 22:08:56 +0000 Subject: [PATCH 03/77] Update README to show correct version number in example Supersedes #3; thank you @kod-kristoff! --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b445621..98a455e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,9 @@ jobs: ... steps: - actions/checkout@v1 - - uses: webfactory/ssh-agent@v0.1 + # Make sure the @v0.1.1 matches the current version of the + # action + - uses: webfactory/ssh-agent@v0.1.1 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -76,6 +78,7 @@ As a note to my future self, in order to work on this repo: * `node index.js` (inputs are passed through `INPUT_` env vars, but how to set `ssh-private-key`?) * Run `./node_modules/.bin/ncc build index.js` to update `dist/index.js`, which is the file actually run * Read https://help.github.com/en/articles/creating-a-javascript-action if unsure. +* Maybe update the README example when publishing a new version. ## Credits, Copyright and License From ea39f521c5e59e2e0a73686497d303bb2b499e03 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Thu, 28 Nov 2019 17:52:10 +0100 Subject: [PATCH 04/77] Write down what this Action cannot do A few notes on what has been raised as issues recently. --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 98a455e..59c90e7 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,24 @@ If the private key is not in the `PEM` format, you will see an `Error loading ke Use `ssh-keygen -p -f path/to/your/key -m pem` to convert your key file to `PEM`, but be sure to make a backup of the file first 😉. +## What this Action *cannot* do for you + +The following items are not issues, but beyond what this Action is supposed to do. + +### Work on remote machines + +When using `ssh` to connect from the GitHub Action worker node to another machine, you *can* forward the SSH Agent socket and use your private key on the other (remote) machine. However, this Action will not configure `known_hosts` or other SSH settings on the remote machine for you. + +### Provide the SSH key as a file + +This Action is designed to pass the SSH directly into `ssh-agent`; that is, the key is available in memory on the GitHub Action worker node, but never written to disk. As a consequence, you _cannot_ pass the key as a build argument or a mounted file into Docker containers that you build or run on the worker node. You _can_, however, mount the `ssh-agent` Unix socket into a Docker container that you _run_, set up the `SSH_AUTH_SOCK` env var and then use SSH from within the container (see #11). + +### Run `ssh-keyscan` to add host keys for additional hosts + +If you want to use `ssh-keyscan` to add additional hosts (that you own/know) to the `known_hosts` file, you can do so with a single shell line in your Action definition. You don't really need this Action to do this for you. + +As a side note, using `ssh-keyscan` without proper key verification is susceptible to man-in-the-middle attacks. You might prefer putting your _known_ SSH host key in your own Action files to add it to the `known_hosts` file. The SSH host key is not secret and can safely be committed into the repo. + ## Creating SSH keys In order to create a new SSH key, run `ssh-keygen -t rsa -b 4096 -m pem -f path/to/keyfile`. This will prompt you for a key passphrase and save the key in `path/to/keyfile`. From 6cf6299d23acd1c656b7bbfe53dcd254ad7c134a Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 14 Jan 2020 10:21:11 +0100 Subject: [PATCH 05/77] Support multiple SSH keys (#14) * Support concatenation of multiple private keys in the given secret * Add a changelog --- .github/workflows/demo.yml | 22 +++++++++++++++----- CHANGELOG.md | 14 +++++++++++++ README.md | 42 +++++++++++++++++++++++++++++--------- dist/index.js | 6 +++++- index.js | 6 +++++- 5 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 0d860f1..c7c7511 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -1,7 +1,21 @@ on: [push, pull_request] jobs: - load_key_demo: + single_key_demo: + strategy: + matrix: + os: [ubuntu-latest, macOS-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v1 + - name: Setup key + uses: ./ + with: + ssh-private-key: | + ${{ secrets.DEMO_KEY }} + ${{ secrets.DEMO_KEY_2 }} + + multiple_keys_demo: strategy: matrix: os: [ubuntu-latest, macOS-latest] @@ -12,8 +26,6 @@ jobs: uses: ./ with: ssh-private-key: ${{ secrets.DEMO_KEY }} - - run: | - ssh-add -l - echo SSH_AUTH_SOCK is at $SSH_AUTH_SOCK - + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7d07023 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +* Multiple SSH keys can now be provided (#14, closes #7). Thanks to + @webknjaz and @bradmartin for support and tests. + diff --git a/README.md b/README.md index 59c90e7..81484bb 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ GitHub Actions only have access to the repository they run for. So, in order to ## Usage 1. Create an SSH key with sufficient access privileges. For security reasons, don't use your personal SSH key but set up a dedicated one for use in GitHub Actions. See below for a few hints if you are unsure about this step. -2. In your repository, go to the *Settings > Secrets* menu and create a new secret called `SSH_PRIVATE_KEY`. Put the *unencrypted private* SSH key in `PEM` format into the contents field.
- This key should start with `-----BEGIN RSA PRIVATE KEY-----`, consist of many lines and ends with `-----END RSA PRIVATE KEY-----`. - You can just copy the key as-is from the private key file. -3. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v1` line. +2. Make sure you don't have a passphrase set on the private key. +3. In your repository, go to the *Settings > Secrets* menu and create a new secret. In this example, we'll call it `SSH_PRIVATE_KEY`. Put the contents of the *private* SSH key file into the contents field.
+ This key should start with `-----BEGIN ... PRIVATE KEY-----`, consist of many lines and ends with `-----END ... PRIVATE KEY-----`. +4. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v1` line. ```yaml # .github/workflows/my-workflow.yml @@ -34,7 +34,28 @@ jobs: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps ``` -4. If, for some reason, you need to change the location of the SSH agent socket, you can use the `ssh-auth-sock` input to provide a path. +5. If, for some reason, you need to change the location of the SSH agent socket, you can use the `ssh-auth-sock` input to provide a path. + +### Using multiple keys + +There are cases where you might need to use multiple keys. For example, "deployment keys" might be limited to a single repository each. + +In that case, you can set-up the different keys as multiple secrets and pass them all to the action like so: + +```yaml +# ... contens as before + - uses: webfactory/ssh-agent@v0.1.1 + with: + ssh-private-key: | + ${{ secrets.FIRST_KEY }} + ${{ secrets.NEXT_KEY }} + ${{ secrets.ANOTHER_KEY }} +``` + +The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections. + +There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have +six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. ## Known issues and limitations @@ -72,11 +93,12 @@ As a side note, using `ssh-keyscan` without proper key verification is susceptib ## Creating SSH keys -In order to create a new SSH key, run `ssh-keygen -t rsa -b 4096 -m pem -f path/to/keyfile`. This will prompt you for a key passphrase and save the key in `path/to/keyfile`. +In order to create a new SSH key, run `ssh-keygen -t ed25519 -a 100 -f path/to/keyfile`, as suggested in [this blog post](https://stribika.github.io/2015/01/04/secure-secure-shell.html). +If you need to work with some older server software and need RSA keys, tr `ssh-keygen -t rsa -b 4096 -o -f path/to/keyfile` instead. -Having a passphrase is a good thing, since it will keep the key encrypted on your disk. When configuring the secret `SSH_PRIVATE_KEY` value in your repository, however, you will need the private key *unencrypted*. - -To show the private key unencrypted, run `openssl rsa -in path/to/key -outform pem`. +Both commands will prompt you for a key passphrase and save the key in `path/to/keyfile`. +In general, having a passphrase is a good thing, since it will keep the key encrypted on your disk. When using the key with this action, however, you need to make sure you don't +specify a passphrase: The key must be usable without reading the passphrase from input. Since the key itself is stored using GitHub's "Secret" feature, it should be fairly safe anyway. ## Authorizing a key @@ -93,7 +115,7 @@ As a note to my future self, in order to work on this repo: * Clone it * Run `npm install` to fetch dependencies * _hack hack hack_ -* `node index.js` (inputs are passed through `INPUT_` env vars, but how to set `ssh-private-key`?) +* `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased. Use `env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js` for this action. * Run `./node_modules/.bin/ncc build index.js` to update `dist/index.js`, which is the file actually run * Read https://help.github.com/en/articles/creating-a-javascript-action if unsure. * Maybe update the README example when publishing a new version. diff --git a/dist/index.js b/dist/index.js index 2ae7540..602df64 100644 --- a/dist/index.js +++ b/dist/index.js @@ -72,7 +72,11 @@ try { core.exportVariable('SSH_AUTH_SOCK', authSock); console.log("Adding private key to agent"); - child_process.execSync('ssh-add -', { input: core.getInput('ssh-private-key') }); + core.getInput('ssh-private-key').split(/(?=-----BEGIN)/).forEach(function(key) { + child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); + }); + console.log("Keys added:"); + child_process.execSync('ssh-add -l', { stdio: 'inherit' }); } catch (error) { core.setFailed(error.message); } diff --git a/index.js b/index.js index 141ad08..140afb4 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,11 @@ try { core.exportVariable('SSH_AUTH_SOCK', authSock); console.log("Adding private key to agent"); - child_process.execSync('ssh-add -', { input: core.getInput('ssh-private-key') }); + core.getInput('ssh-private-key').split(/(?=-----BEGIN)/).forEach(function(key) { + child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); + }); + console.log("Keys added:"); + child_process.execSync('ssh-add -l', { stdio: 'inherit' }); } catch (error) { core.setFailed(error.message); } From c6eb7ee1d8c524a5f411bccbd84e4a3712759837 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 14 Jan 2020 09:29:16 +0000 Subject: [PATCH 06/77] Exit with a helpful error message when the secret has not been configured --- CHANGELOG.md | 2 ++ dist/index.js | 13 ++++++++++++- index.js | 13 ++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d07023..ac50e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Multiple SSH keys can now be provided (#14, closes #7). Thanks to @webknjaz and @bradmartin for support and tests. +* Catch empty ssh-private-key input values and exit with a helpful + error message right away. diff --git a/dist/index.js b/dist/index.js index 602df64..5ee5d2f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -58,9 +58,18 @@ const child_process = __webpack_require__(129); const fs = __webpack_require__(747); try { + const home = process.env['HOME']; const homeSsh = home + '/.ssh'; + const privateKey = core.getInput('ssh-private-key').trim(); + + if (!privateKey) { + core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); + + return; + } + console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); fs.mkdirSync(homeSsh, { recursive: true}); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); @@ -72,11 +81,13 @@ try { core.exportVariable('SSH_AUTH_SOCK', authSock); console.log("Adding private key to agent"); - core.getInput('ssh-private-key').split(/(?=-----BEGIN)/).forEach(function(key) { + privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); }); + console.log("Keys added:"); child_process.execSync('ssh-add -l', { stdio: 'inherit' }); + } catch (error) { core.setFailed(error.message); } diff --git a/index.js b/index.js index 140afb4..2da939f 100644 --- a/index.js +++ b/index.js @@ -3,9 +3,18 @@ const child_process = require('child_process'); const fs = require('fs'); try { + const home = process.env['HOME']; const homeSsh = home + '/.ssh'; + const privateKey = core.getInput('ssh-private-key').trim(); + + if (!privateKey) { + core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); + + return; + } + console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); fs.mkdirSync(homeSsh, { recursive: true}); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); @@ -17,11 +26,13 @@ try { core.exportVariable('SSH_AUTH_SOCK', authSock); console.log("Adding private key to agent"); - core.getInput('ssh-private-key').split(/(?=-----BEGIN)/).forEach(function(key) { + privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); }); + console.log("Keys added:"); child_process.execSync('ssh-add -l', { stdio: 'inherit' }); + } catch (error) { core.setFailed(error.message); } From b6c65becb03b5d9487d1ddb2ab9f28bdffa1fa77 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 14 Jan 2020 09:32:40 +0000 Subject: [PATCH 07/77] Prepare the v0.2.0 release --- CHANGELOG.md | 2 ++ README.md | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac50e04..7b560e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.2.0 + ### Added * Multiple SSH keys can now be provided (#14, closes #7). Thanks to diff --git a/README.md b/README.md index 81484bb..6001c32 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ jobs: ... steps: - actions/checkout@v1 - # Make sure the @v0.1.1 matches the current version of the + # Make sure the @v0.2.0 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.1.1 + - uses: webfactory/ssh-agent@v0.2.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -44,7 +44,7 @@ In that case, you can set-up the different keys as multiple secrets and pass the ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.1.1 + - uses: webfactory/ssh-agent@v0.2.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 9d13200510e53c3eef5a4bac9032fef1718be5c5 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 14 Jan 2020 09:35:08 +0000 Subject: [PATCH 08/77] Bump copyright year in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6001c32..ae8f836 100644 --- a/README.md +++ b/README.md @@ -129,4 +129,4 @@ developer looking for new challenges, we'd like to hear from you! - - -Copyright 2019 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). +Copyright 2019 – 2020 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). From cf56a519af6f624c12f7d8150752d45390e6d8a4 Mon Sep 17 00:00:00 2001 From: Josh Mandel Date: Thu, 6 Feb 2020 12:09:44 -0600 Subject: [PATCH 09/77] Remove redundant .trim() --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 2da939f..8490d93 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ try { const home = process.env['HOME']; const homeSsh = home + '/.ssh'; - const privateKey = core.getInput('ssh-private-key').trim(); + const privateKey = core.getInput('ssh-private-key'); if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); From 0a38ab092644d38c804058b34ce188a319bacee7 Mon Sep 17 00:00:00 2001 From: Benjamin Borowski Date: Mon, 2 Mar 2020 16:41:12 -0800 Subject: [PATCH 10/77] style: lint just reviewing and noticed a missing space --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 8490d93..e141876 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,7 @@ try { } console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); - fs.mkdirSync(homeSsh, { recursive: true}); + fs.mkdirSync(homeSsh, { recursive: true }); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); From a3b3049f433c20313243d6448726ab202d0020d3 Mon Sep 17 00:00:00 2001 From: Jan Pieter Waagmeester Date: Tue, 31 Mar 2020 12:23:36 +0200 Subject: [PATCH 11/77] add omitted 'key' word --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae8f836..df220e1 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ When using `ssh` to connect from the GitHub Action worker node to another machin ### Provide the SSH key as a file -This Action is designed to pass the SSH directly into `ssh-agent`; that is, the key is available in memory on the GitHub Action worker node, but never written to disk. As a consequence, you _cannot_ pass the key as a build argument or a mounted file into Docker containers that you build or run on the worker node. You _can_, however, mount the `ssh-agent` Unix socket into a Docker container that you _run_, set up the `SSH_AUTH_SOCK` env var and then use SSH from within the container (see #11). +This Action is designed to pass the SSH key directly into `ssh-agent`; that is, the key is available in memory on the GitHub Action worker node, but never written to disk. As a consequence, you _cannot_ pass the key as a build argument or a mounted file into Docker containers that you build or run on the worker node. You _can_, however, mount the `ssh-agent` Unix socket into a Docker container that you _run_, set up the `SSH_AUTH_SOCK` env var and then use SSH from within the container (see #11). ### Run `ssh-keyscan` to add host keys for additional hosts From 4fcb25e7ef89d0ad885e957928ddfe0a78aad59e Mon Sep 17 00:00:00 2001 From: Thorben Nissen Date: Mon, 18 May 2020 09:08:29 +0200 Subject: [PATCH 12/77] Randomize SSH auth socket, kill agent to support non-ephemeral, self hosted runners (@thommyhh, #27) Thanks to @thommyhh for this contribution! Unless the `SSH_AUTH_SOCK` is configured explicitly, this change will make the SSH agent use a random file name for the socket. That way, multiple, concurrent SSH agents can be used on non-ephemeral, self-hosted runners. A new post-action step will automatically clean up the running agent at the end of a job. Be aware of the possible security implications: Two jobs running on the same runner might be able to access each other's socket and thus access repositories and/or hosts. --- README.md | 7 +- action.yml | 2 +- cleanup.js | 10 ++ dist/cleanup.js | 332 ++++++++++++++++++++++++++++++++++++++++++++++ dist/index.js | 21 ++- index.js | 17 ++- package-lock.json | 12 +- package.json | 8 +- scripts/build.js | 35 +++++ 9 files changed, 424 insertions(+), 20 deletions(-) create mode 100644 cleanup.js create mode 100644 dist/cleanup.js create mode 100644 scripts/build.js diff --git a/README.md b/README.md index df220e1..ac87662 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,11 @@ The `ssh-agent` will load all of the keys and try each one in order when establi There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. +## Exported variables +The action exports `SSH_AUTH_SOCK` and `SSH_AGENT_PID` through the Github Actions core module. +The `$SSH_AUTH_SOCK` is used by several applications like git or rsync to connect to the SSH authentication agent. +The `$SSH_AGENT_PID` contains the process id of the agent. This is used to kill the agent in post job action. + ## Known issues and limitations ### Currently OS X and Linux only @@ -116,7 +121,7 @@ As a note to my future self, in order to work on this repo: * Run `npm install` to fetch dependencies * _hack hack hack_ * `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased. Use `env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js` for this action. -* Run `./node_modules/.bin/ncc build index.js` to update `dist/index.js`, which is the file actually run +* Run `npm run build` to update `dist/*`, which holds the files actually run * Read https://help.github.com/en/articles/creating-a-javascript-action if unsure. * Maybe update the README example when publishing a new version. diff --git a/action.yml b/action.yml index bdeeaa6..e43c44f 100644 --- a/action.yml +++ b/action.yml @@ -6,10 +6,10 @@ inputs: required: true ssh-auth-sock: description: 'Where to place the SSH Agent auth socket' - default: /tmp/ssh-auth.sock runs: using: 'node12' main: 'dist/index.js' + post: 'dist/cleanup.js' branding: icon: loader color: 'yellow' diff --git a/cleanup.js b/cleanup.js new file mode 100644 index 0000000..89955a6 --- /dev/null +++ b/cleanup.js @@ -0,0 +1,10 @@ +const core = require('@actions/core') +const { execSync } = require('child_process') + +try { + // Kill the started SSH agent + console.log('Stopping SSH agent') + execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' }) +} catch (error) { + core.setFailed(error.message) +} diff --git a/dist/cleanup.js b/dist/cleanup.js new file mode 100644 index 0000000..a678e24 --- /dev/null +++ b/dist/cleanup.js @@ -0,0 +1,332 @@ +module.exports = +/******/ (function(modules, runtime) { // webpackBootstrap +/******/ "use strict"; +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ __webpack_require__.ab = __dirname + "/"; +/******/ +/******/ // the startup function +/******/ function startup() { +/******/ // Load entry module and return exports +/******/ return __webpack_require__(175); +/******/ }; +/******/ +/******/ // run startup +/******/ return startup(); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ 87: +/***/ (function(module) { + +module.exports = require("os"); + +/***/ }), + +/***/ 129: +/***/ (function(module) { + +module.exports = require("child_process"); + +/***/ }), + +/***/ 175: +/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { + +const core = __webpack_require__(470) +const { execSync } = __webpack_require__(129) + +try { + // Kill the started SSH agent + console.log('Stopping SSH agent') + execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' }) +} catch (error) { + core.setFailed(error.message) +} + + +/***/ }), + +/***/ 431: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const os = __webpack_require__(87); +/** + * Commands + * + * Command Format: + * ##[name key=value;key=value]message + * + * Examples: + * ##[warning]This is the user warning message + * ##[set-secret name=mypassword]definitelyNotAPassword! + */ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os.EOL); +} +exports.issueCommand = issueCommand; +function issue(name, message = '') { + issueCommand(name, {}, message); +} +exports.issue = issue; +const CMD_PREFIX = '##['; +class Command { + constructor(command, properties, message) { + if (!command) { + command = 'missing.command'; + } + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_PREFIX + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + for (const key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + // safely append the val - avoid blowing up when attempting to + // call .replace() if message is not a string for some reason + cmdStr += `${key}=${escape(`${val || ''}`)};`; + } + } + } + } + cmdStr += ']'; + // safely append the message - avoid blowing up when attempting to + // call .replace() if message is not a string for some reason + const message = `${this.message || ''}`; + cmdStr += escapeData(message); + return cmdStr; + } +} +function escapeData(s) { + return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A'); +} +function escape(s) { + return s + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/]/g, '%5D') + .replace(/;/g, '%3B'); +} +//# sourceMappingURL=command.js.map + +/***/ }), + +/***/ 470: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +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 }); +const command_1 = __webpack_require__(431); +const path = __webpack_require__(622); +/** + * The code to exit an action + */ +var ExitCode; +(function (ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode = exports.ExitCode || (exports.ExitCode = {})); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- +/** + * sets env variable for this action and future actions in the job + * @param name the name of the variable to set + * @param val the value of the variable + */ +function exportVariable(name, val) { + process.env[name] = val; + command_1.issueCommand('set-env', { name }, val); +} +exports.exportVariable = exportVariable; +/** + * exports the variable and registers a secret which will get masked from logs + * @param name the name of the variable to set + * @param val value of the secret + */ +function exportSecret(name, val) { + exportVariable(name, val); + // the runner will error with not implemented + // leaving the function but raising the error earlier + command_1.issueCommand('set-secret', {}, val); + throw new Error('Not implemented.'); +} +exports.exportSecret = exportSecret; +/** + * Prepends inputPath to the PATH (for this action and future actions) + * @param inputPath + */ +function addPath(inputPath) { + command_1.issueCommand('add-path', {}, inputPath); + process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; +} +exports.addPath = addPath; +/** + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || ''; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + return val.trim(); +} +exports.getInput = getInput; +/** + * Sets the value of an output. + * + * @param name name of the output to set + * @param value value to store + */ +function setOutput(name, value) { + command_1.issueCommand('set-output', { name }, value); +} +exports.setOutput = setOutput; +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +exports.setFailed = setFailed; +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- +/** + * Writes debug message to user log + * @param message debug message + */ +function debug(message) { + command_1.issueCommand('debug', {}, message); +} +exports.debug = debug; +/** + * Adds an error issue + * @param message error issue message + */ +function error(message) { + command_1.issue('error', message); +} +exports.error = error; +/** + * Adds an warning issue + * @param message warning issue message + */ +function warning(message) { + command_1.issue('warning', message); +} +exports.warning = warning; +/** + * Begin an output group. + * + * Output until the next `groupEnd` will be foldable in this group + * + * @param name The name of the output group + */ +function startGroup(name) { + command_1.issue('group', name); +} +exports.startGroup = startGroup; +/** + * End an output group. + */ +function endGroup() { + command_1.issue('endgroup'); +} +exports.endGroup = endGroup; +/** + * Wrap an asynchronous function call in a group. + * + * Returns the same type as the function itself. + * + * @param name The name of the group + * @param fn The function to wrap in the group + */ +function group(name, fn) { + return __awaiter(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } + finally { + endGroup(); + } + return result; + }); +} +exports.group = group; +//# sourceMappingURL=core.js.map + +/***/ }), + +/***/ 622: +/***/ (function(module) { + +module.exports = require("path"); + +/***/ }) + +/******/ }); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 5ee5d2f..f7a65cd 100644 --- a/dist/index.js +++ b/dist/index.js @@ -62,7 +62,7 @@ try { const home = process.env['HOME']; const homeSsh = home + '/.ssh'; - const privateKey = core.getInput('ssh-private-key').trim(); + const privateKey = core.getInput('ssh-private-key'); if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); @@ -71,14 +71,27 @@ try { } console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); - fs.mkdirSync(homeSsh, { recursive: true}); + fs.mkdirSync(homeSsh, { recursive: true }); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); console.log("Starting ssh-agent"); const authSock = core.getInput('ssh-auth-sock'); - child_process.execFileSync('ssh-agent', ['-a', authSock]); - core.exportVariable('SSH_AUTH_SOCK', authSock); + let sshAgentOutput = '' + if (authSock && authSock.length > 0) { + sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]); + } else { + sshAgentOutput = child_process.execFileSync('ssh-agent') + } + + // Extract auth socket path and agent pid and set them as job variables + const lines = sshAgentOutput.toString().split("\n") + for (const lineNumber in lines) { + const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(lines[lineNumber]) + if (matches && matches.length > 0) { + core.exportVariable(matches[1], matches[2]) + } + } console.log("Adding private key to agent"); privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { diff --git a/index.js b/index.js index e141876..7ee6fe7 100644 --- a/index.js +++ b/index.js @@ -22,8 +22,21 @@ try { console.log("Starting ssh-agent"); const authSock = core.getInput('ssh-auth-sock'); - child_process.execFileSync('ssh-agent', ['-a', authSock]); - core.exportVariable('SSH_AUTH_SOCK', authSock); + let sshAgentOutput = '' + if (authSock && authSock.length > 0) { + sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]); + } else { + sshAgentOutput = child_process.execFileSync('ssh-agent') + } + + // Extract auth socket path and agent pid and set them as job variables + const lines = sshAgentOutput.toString().split("\n") + for (const lineNumber in lines) { + const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(lines[lineNumber]) + if (matches && matches.length > 0) { + core.exportVariable(matches[1], matches[2]) + } + } console.log("Adding private key to agent"); privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { diff --git a/package-lock.json b/package-lock.json index 829664d..12699b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@actions/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.1.0.tgz", - "integrity": "sha512-KKpo3xzo0Zsikni9tbOsEQkxZBGDsYSJZNkTvmo0gPSXrc98TBOcdTvKwwjitjkjHkreTggWdB1ACiAFVgsuzA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.4.tgz", + "integrity": "sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg==", "dev": true }, "@zeit/ncc": { @@ -15,12 +15,6 @@ "resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz", "integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==", "dev": true - }, - "child_process": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", - "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o=", - "dev": true } } } diff --git a/package.json b/package.json index 35a0c5c..bb68cef 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,10 @@ "author": "webfactory GmbH ", "license": "MIT", "devDependencies": { - "@actions/core": "^1.1.0", - "@zeit/ncc": "^0.20.5", - "child_process": "^1.0.2" + "@actions/core": "^1.2.4", + "@zeit/ncc": "^0.20.5" + }, + "scripts": { + "build": "node scripts/build.js" } } diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..eecd192 --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,35 @@ +const { execSync } = require('child_process') +const path = require('path') +const fs = require('fs') + +const buildDir = path.join(process.cwd(), 'build') +const distDir = path.join(process.cwd(), 'dist') + +const buildIndexJs = path.join(buildDir, 'index.js') +const distIndexJs = path.join(distDir, 'index.js') +const distCleanupJs = path.join(distDir, 'cleanup.js') + +if (!fs.existsSync(buildDir)) { + fs.mkdirSync(buildDir) +} + +// Build the main index.js file +console.log('Building index.js...') +execSync(`./node_modules/.bin/ncc build index.js -q -o ${buildDir}`) +if (fs.existsSync(distIndexJs)) { + fs.unlinkSync(distIndexJs) +} +fs.renameSync(buildIndexJs, distIndexJs) + +// Build the cleanup.js file +console.log('Building cleanup.js...') +execSync(`./node_modules/.bin/ncc build cleanup.js -q -o ${buildDir}`) +if (fs.existsSync(distCleanupJs)) { + fs.unlinkSync(distCleanupJs) +} +fs.renameSync(buildIndexJs, distCleanupJs) + +console.log('Cleaning up...') +fs.rmdirSync(buildDir) + +console.log('Done') From 8789658b02527d79b3939a5bfc202ea55f91ab32 Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Fri, 5 Jun 2020 06:45:30 +0200 Subject: [PATCH 13/77] Update actions version in examples (#29) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ac87662..bf9fb13 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ jobs: ... steps: - actions/checkout@v1 - # Make sure the @v0.2.0 matches the current version of the + # Make sure the @v0.3.0 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.2.0 + - uses: webfactory/ssh-agent@v0.3.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -44,7 +44,7 @@ In that case, you can set-up the different keys as multiple secrets and pass the ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.2.0 + - uses: webfactory/ssh-agent@v0.3.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 5ef9e0334a9b8bcfe6bab2d1c15d6f2ebd5a5c8c Mon Sep 17 00:00:00 2001 From: Ryan Zidago <46972947+ryanzidago@users.noreply.github.com> Date: Tue, 23 Jun 2020 12:56:50 +0200 Subject: [PATCH 14/77] updated README.md to including further instructions regarding where to add deploy key (#34) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf9fb13..cac624b 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ specify a passphrase: The key must be usable without reading the passphrase from To actually grant the SSH key access, you can – on GitHub – use at least two ways: -* [Deploy keys](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) can be added to individual GitHub repositories. They can give read and/or write access to the particular repository. When pulling a lot of dependencies, however, you'll end up adding the key in many places. Rotating the key probably becomes difficult. +* [Deploy keys](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) can be added to individual GitHub repositories. They can give read and/or write access to the particular repository. When pulling a lot of dependencies, however, you'll end up adding the key in many places. Rotating the key probably becomes difficult. The deploy key needs to be added to the private repository that is being fetched as a private dependency. * A [machine user](https://developer.github.com/v3/guides/managing-deploy-keys/#machine-users) can be used for more fine-grained permissions management and have access to multiple repositories with just one instance of the key being registered. It will, however, count against your number of users on paid GitHub plans. From ef0ce0cab8e3ad0aa0f3e1fb209b0811b68c0c51 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 24 Jun 2020 08:31:28 +0200 Subject: [PATCH 15/77] Ignore failures when trying to kill the `ssh-agent` (#33) --- CHANGELOG.md | 22 ++++++- README.md | 12 ++-- cleanup.js | 3 +- dist/cleanup.js | 165 ++++++++++++++++++++++++++++++++++------------ dist/index.js | 162 +++++++++++++++++++++++++++++++++------------ package-lock.json | 20 ------ yarn.lock | 13 ++++ 7 files changed, 286 insertions(+), 111 deletions(-) delete mode 100644 package-lock.json create mode 100644 yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b560e3..c297211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## v0.2.0 +## v0.4.0 + +### Changed + +* A failure to kill the agent in the post-action step will no longer fail the workflow run. That way, you can kill the agent yourself when necessary (#33). + +## v0.3.0 [2020-05-18] + +### Added + +* A new post-action step will automatically clean up the running agent at the end of a job. This helps with self-hosted runners, which are non-ephemeral. (@thommyhh, #27) + +### Changed + +* Unless the SSH_AUTH_SOCK is configured explicitly, the SSH agent will now use a random file name for the socket. That way, multiple, concurrent SSH agents can be used on self-hosted runners. (@thommyhh, #27) + +## v0.2.0 [2020-01-14] ### Added @@ -16,3 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Catch empty ssh-private-key input values and exit with a helpful error message right away. + +## v0.1.0 [2019-09-15] + +Initial release. diff --git a/README.md b/README.md index cac624b..282c9cf 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ jobs: ... steps: - actions/checkout@v1 - # Make sure the @v0.3.0 matches the current version of the + # Make sure the @v0.4.0 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.3.0 + - uses: webfactory/ssh-agent@v0.4.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -44,7 +44,7 @@ In that case, you can set-up the different keys as multiple secrets and pass the ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.3.0 + - uses: webfactory/ssh-agent@v0.4.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} @@ -55,10 +55,10 @@ In that case, you can set-up the different keys as multiple secrets and pass the The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections. There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have -six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. +six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. If you don't need all of the keys at the same time, you could try to `run: kill $SSH_AGENT_PID` to kill the currently running `ssh-agent` and use the action again in a following step to start another instance. ## Exported variables -The action exports `SSH_AUTH_SOCK` and `SSH_AGENT_PID` through the Github Actions core module. +The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables through the Github Actions core module. The `$SSH_AUTH_SOCK` is used by several applications like git or rsync to connect to the SSH authentication agent. The `$SSH_AGENT_PID` contains the process id of the agent. This is used to kill the agent in post job action. @@ -118,7 +118,7 @@ To actually grant the SSH key access, you can – on GitHub – use at least two As a note to my future self, in order to work on this repo: * Clone it -* Run `npm install` to fetch dependencies +* Run `yarn install` to fetch dependencies * _hack hack hack_ * `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased. Use `env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js` for this action. * Run `npm run build` to update `dist/*`, which holds the files actually run diff --git a/cleanup.js b/cleanup.js index 89955a6..f90cddd 100644 --- a/cleanup.js +++ b/cleanup.js @@ -6,5 +6,6 @@ try { console.log('Stopping SSH agent') execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' }) } catch (error) { - core.setFailed(error.message) + console.log(error.message); + console.log('Error stopping the SSH agent, proceeding anyway'); } diff --git a/dist/cleanup.js b/dist/cleanup.js index a678e24..e33f897 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -68,7 +68,8 @@ try { console.log('Stopping SSH agent') execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' }) } catch (error) { - core.setFailed(error.message) + console.log(error.message); + console.log('Error stopping the SSH agent, proceeding anyway'); } @@ -79,17 +80,24 @@ try { "use strict"; +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 }); -const os = __webpack_require__(87); +const os = __importStar(__webpack_require__(87)); /** * Commands * * Command Format: - * ##[name key=value;key=value]message + * ::name key=value,key=value::message * * Examples: - * ##[warning]This is the user warning message - * ##[set-secret name=mypassword]definitelyNotAPassword! + * ::warning::This is the message + * ::set-env name=MY_VAR::some value */ function issueCommand(command, properties, message) { const cmd = new Command(command, properties, message); @@ -100,7 +108,7 @@ function issue(name, message = '') { issueCommand(name, {}, message); } exports.issue = issue; -const CMD_PREFIX = '##['; +const CMD_STRING = '::'; class Command { constructor(command, properties, message) { if (!command) { @@ -111,37 +119,56 @@ class Command { this.message = message; } toString() { - let cmdStr = CMD_PREFIX + this.command; + let cmdStr = CMD_STRING + this.command; if (this.properties && Object.keys(this.properties).length > 0) { cmdStr += ' '; + let first = true; for (const key in this.properties) { if (this.properties.hasOwnProperty(key)) { const val = this.properties[key]; if (val) { - // safely append the val - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - cmdStr += `${key}=${escape(`${val || ''}`)};`; + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; } } } } - cmdStr += ']'; - // safely append the message - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - const message = `${this.message || ''}`; - cmdStr += escapeData(message); + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; return cmdStr; } } -function escapeData(s) { - return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A'); +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); } -function escape(s) { - return s +exports.toCommandValue = toCommandValue; +function escapeData(s) { + return toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return toCommandValue(s) + .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') - .replace(/]/g, '%5D') - .replace(/;/g, '%3B'); + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); } //# sourceMappingURL=command.js.map @@ -161,9 +188,17 @@ 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 }); const command_1 = __webpack_require__(431); -const path = __webpack_require__(622); +const os = __importStar(__webpack_require__(87)); +const path = __importStar(__webpack_require__(622)); /** * The code to exit an action */ @@ -182,28 +217,25 @@ var ExitCode; // Variables //----------------------------------------------------------------------- /** - * sets env variable for this action and future actions in the job + * Sets env variable for this action and future actions in the job * @param name the name of the variable to set - * @param val the value of the variable + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any function exportVariable(name, val) { - process.env[name] = val; - command_1.issueCommand('set-env', { name }, val); + const convertedVal = command_1.toCommandValue(val); + process.env[name] = convertedVal; + command_1.issueCommand('set-env', { name }, convertedVal); } exports.exportVariable = exportVariable; /** - * exports the variable and registers a secret which will get masked from logs - * @param name the name of the variable to set - * @param val value of the secret + * Registers a secret which will get masked from logs + * @param secret value of the secret */ -function exportSecret(name, val) { - exportVariable(name, val); - // the runner will error with not implemented - // leaving the function but raising the error earlier - command_1.issueCommand('set-secret', {}, val); - throw new Error('Not implemented.'); +function setSecret(secret) { + command_1.issueCommand('add-mask', {}, secret); } -exports.exportSecret = exportSecret; +exports.setSecret = setSecret; /** * Prepends inputPath to the PATH (for this action and future actions) * @param inputPath @@ -221,7 +253,7 @@ exports.addPath = addPath; * @returns string */ function getInput(name, options) { - const val = process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || ''; + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; if (options && options.required && !val) { throw new Error(`Input required and not supplied: ${name}`); } @@ -232,12 +264,22 @@ exports.getInput = getInput; * Sets the value of an output. * * @param name name of the output to set - * @param value value to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { command_1.issueCommand('set-output', { name }, value); } exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + command_1.issue('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; //----------------------------------------------------------------------- // Results //----------------------------------------------------------------------- @@ -254,6 +296,13 @@ exports.setFailed = setFailed; //----------------------------------------------------------------------- // Logging Commands //----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; /** * Writes debug message to user log * @param message debug message @@ -264,20 +313,28 @@ function debug(message) { exports.debug = debug; /** * Adds an error issue - * @param message error issue message + * @param message error issue message. Errors will be converted to string via toString() */ function error(message) { - command_1.issue('error', message); + command_1.issue('error', message instanceof Error ? message.toString() : message); } exports.error = error; /** * Adds an warning issue - * @param message warning issue message + * @param message warning issue message. Errors will be converted to string via toString() */ function warning(message) { - command_1.issue('warning', message); + command_1.issue('warning', message instanceof Error ? message.toString() : message); } exports.warning = warning; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; /** * Begin an output group. * @@ -318,6 +375,30 @@ function group(name, fn) { }); } exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + command_1.issueCommand('save-state', { name }, value); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; //# sourceMappingURL=core.js.map /***/ }), diff --git a/dist/index.js b/dist/index.js index f7a65cd..e12897d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -120,17 +120,24 @@ module.exports = require("child_process"); "use strict"; +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 }); -const os = __webpack_require__(87); +const os = __importStar(__webpack_require__(87)); /** * Commands * * Command Format: - * ##[name key=value;key=value]message + * ::name key=value,key=value::message * * Examples: - * ##[warning]This is the user warning message - * ##[set-secret name=mypassword]definitelyNotAPassword! + * ::warning::This is the message + * ::set-env name=MY_VAR::some value */ function issueCommand(command, properties, message) { const cmd = new Command(command, properties, message); @@ -141,7 +148,7 @@ function issue(name, message = '') { issueCommand(name, {}, message); } exports.issue = issue; -const CMD_PREFIX = '##['; +const CMD_STRING = '::'; class Command { constructor(command, properties, message) { if (!command) { @@ -152,37 +159,56 @@ class Command { this.message = message; } toString() { - let cmdStr = CMD_PREFIX + this.command; + let cmdStr = CMD_STRING + this.command; if (this.properties && Object.keys(this.properties).length > 0) { cmdStr += ' '; + let first = true; for (const key in this.properties) { if (this.properties.hasOwnProperty(key)) { const val = this.properties[key]; if (val) { - // safely append the val - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - cmdStr += `${key}=${escape(`${val || ''}`)};`; + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; } } } } - cmdStr += ']'; - // safely append the message - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - const message = `${this.message || ''}`; - cmdStr += escapeData(message); + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; return cmdStr; } } -function escapeData(s) { - return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A'); +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); } -function escape(s) { - return s +exports.toCommandValue = toCommandValue; +function escapeData(s) { + return toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return toCommandValue(s) + .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') - .replace(/]/g, '%5D') - .replace(/;/g, '%3B'); + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); } //# sourceMappingURL=command.js.map @@ -202,9 +228,17 @@ 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 }); const command_1 = __webpack_require__(431); -const path = __webpack_require__(622); +const os = __importStar(__webpack_require__(87)); +const path = __importStar(__webpack_require__(622)); /** * The code to exit an action */ @@ -223,28 +257,25 @@ var ExitCode; // Variables //----------------------------------------------------------------------- /** - * sets env variable for this action and future actions in the job + * Sets env variable for this action and future actions in the job * @param name the name of the variable to set - * @param val the value of the variable + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any function exportVariable(name, val) { - process.env[name] = val; - command_1.issueCommand('set-env', { name }, val); + const convertedVal = command_1.toCommandValue(val); + process.env[name] = convertedVal; + command_1.issueCommand('set-env', { name }, convertedVal); } exports.exportVariable = exportVariable; /** - * exports the variable and registers a secret which will get masked from logs - * @param name the name of the variable to set - * @param val value of the secret + * Registers a secret which will get masked from logs + * @param secret value of the secret */ -function exportSecret(name, val) { - exportVariable(name, val); - // the runner will error with not implemented - // leaving the function but raising the error earlier - command_1.issueCommand('set-secret', {}, val); - throw new Error('Not implemented.'); +function setSecret(secret) { + command_1.issueCommand('add-mask', {}, secret); } -exports.exportSecret = exportSecret; +exports.setSecret = setSecret; /** * Prepends inputPath to the PATH (for this action and future actions) * @param inputPath @@ -262,7 +293,7 @@ exports.addPath = addPath; * @returns string */ function getInput(name, options) { - const val = process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || ''; + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; if (options && options.required && !val) { throw new Error(`Input required and not supplied: ${name}`); } @@ -273,12 +304,22 @@ exports.getInput = getInput; * Sets the value of an output. * * @param name name of the output to set - * @param value value to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { command_1.issueCommand('set-output', { name }, value); } exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + command_1.issue('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; //----------------------------------------------------------------------- // Results //----------------------------------------------------------------------- @@ -295,6 +336,13 @@ exports.setFailed = setFailed; //----------------------------------------------------------------------- // Logging Commands //----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; /** * Writes debug message to user log * @param message debug message @@ -305,20 +353,28 @@ function debug(message) { exports.debug = debug; /** * Adds an error issue - * @param message error issue message + * @param message error issue message. Errors will be converted to string via toString() */ function error(message) { - command_1.issue('error', message); + command_1.issue('error', message instanceof Error ? message.toString() : message); } exports.error = error; /** * Adds an warning issue - * @param message warning issue message + * @param message warning issue message. Errors will be converted to string via toString() */ function warning(message) { - command_1.issue('warning', message); + command_1.issue('warning', message instanceof Error ? message.toString() : message); } exports.warning = warning; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; /** * Begin an output group. * @@ -359,6 +415,30 @@ function group(name, fn) { }); } exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + command_1.issueCommand('save-state', { name }, value); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; //# sourceMappingURL=core.js.map /***/ }), diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 12699b6..0000000 --- a/package-lock.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "webfactory-action-ssh-agent", - "version": "0.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@actions/core": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.4.tgz", - "integrity": "sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg==", - "dev": true - }, - "@zeit/ncc": { - "version": "0.20.5", - "resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz", - "integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==", - "dev": true - } - } -} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..267f5d3 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,13 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@actions/core@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.4.tgz#96179dbf9f8d951dd74b40a0dbd5c22555d186ab" + integrity sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg== + +"@zeit/ncc@^0.20.5": + version "0.20.5" + resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.20.5.tgz#a41af6e6bcab4a58f4612bae6137f70bce0192e3" + integrity sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw== From 9e5c1c7a9b63cddacf3175ea971055789bdbd9ac Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 7 Sep 2020 16:57:21 +0200 Subject: [PATCH 16/77] Point to blog post explaining how to use multiple GitHub deploy keys --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 282c9cf..232050c 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ jobs: ### Using multiple keys -There are cases where you might need to use multiple keys. For example, "deployment keys" might be limited to a single repository each. +There are cases where you might need to use multiple keys. For example, "[deploy keys](https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys)" might be limited to a single repository, so you'll need several of them. -In that case, you can set-up the different keys as multiple secrets and pass them all to the action like so: +You can set up different keys as different secrets and pass them all to the action like so: ```yaml # ... contens as before @@ -55,7 +55,11 @@ In that case, you can set-up the different keys as multiple secrets and pass the The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections. There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have -six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. If you don't need all of the keys at the same time, you could try to `run: kill $SSH_AGENT_PID` to kill the currently running `ssh-agent` and use the action again in a following step to start another instance. +six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. + +Also, when using **Github deploy keys**, GitHub servers will accept the first known key. But since deploy keys are scoped to a single repository, you might get the error message `fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.` if the wrong key/repository combination is tried. + +In both cases, you might want to [try a wrapper script around `ssh`](https://gist.github.com/mpdude/e56fcae5bc541b95187fa764aafb5e6d) that can pick the right key, based on key comments. See [our blog post](https://www.webfactory.de/blog/using-multiple-ssh-deploy-keys-with-github) for the full story. ## Exported variables The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables through the Github Actions core module. From 3dd57c80a6b4328a5de9b32057f2f2d4f2723243 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 7 Oct 2020 23:06:49 +0200 Subject: [PATCH 17/77] Update dependencies (#43) --- dist/cleanup.js | 110 +++++++++++++++++++++++++++++++++++++++--------- dist/index.js | 103 ++++++++++++++++++++++++++++++++++++--------- yarn.lock | 6 +-- 3 files changed, 178 insertions(+), 41 deletions(-) diff --git a/dist/cleanup.js b/dist/cleanup.js index e33f897..c8081be 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -43,6 +43,32 @@ module.exports = /************************************************************************/ /******/ ({ +/***/ 82: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +//# sourceMappingURL=utils.js.map + +/***/ }), + /***/ 87: /***/ (function(module) { @@ -50,6 +76,42 @@ module.exports = require("os"); /***/ }), +/***/ 102: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +// For internal use, subject to change. +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 }); +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const fs = __importStar(__webpack_require__(747)); +const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); +function issueCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueCommand = issueCommand; +//# sourceMappingURL=file-command.js.map + +/***/ }), + /***/ 129: /***/ (function(module) { @@ -89,6 +151,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); /** * Commands * @@ -142,28 +205,14 @@ class Command { return cmdStr; } } -/** - * Sanitizes an input into a string so it can be passed into issueCommand safely - * @param input input to sanitize into a string - */ -function toCommandValue(input) { - if (input === null || input === undefined) { - return ''; - } - else if (typeof input === 'string' || input instanceof String) { - return input; - } - return JSON.stringify(input); -} -exports.toCommandValue = toCommandValue; function escapeData(s) { - return toCommandValue(s) + return utils_1.toCommandValue(s) .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A'); } function escapeProperty(s) { - return toCommandValue(s) + return utils_1.toCommandValue(s) .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') @@ -197,6 +246,8 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); 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)); /** @@ -223,9 +274,17 @@ var ExitCode; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function exportVariable(name, val) { - const convertedVal = command_1.toCommandValue(val); + const convertedVal = utils_1.toCommandValue(val); process.env[name] = convertedVal; - command_1.issueCommand('set-env', { name }, convertedVal); + const filePath = process.env['GITHUB_ENV'] || ''; + if (filePath) { + const delimiter = '_GitHubActionsFileCommandDelimeter_'; + const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; + file_command_1.issueCommand('ENV', commandValue); + } + else { + command_1.issueCommand('set-env', { name }, convertedVal); + } } exports.exportVariable = exportVariable; /** @@ -241,7 +300,13 @@ exports.setSecret = setSecret; * @param inputPath */ function addPath(inputPath) { - command_1.issueCommand('add-path', {}, inputPath); + const filePath = process.env['GITHUB_PATH'] || ''; + if (filePath) { + file_command_1.issueCommand('PATH', inputPath); + } + else { + command_1.issueCommand('add-path', {}, inputPath); + } process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; } exports.addPath = addPath; @@ -408,6 +473,13 @@ exports.getState = getState; module.exports = require("path"); +/***/ }), + +/***/ 747: +/***/ (function(module) { + +module.exports = require("fs"); + /***/ }) /******/ }); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index e12897d..173d8ea 100644 --- a/dist/index.js +++ b/dist/index.js @@ -43,6 +43,32 @@ module.exports = /************************************************************************/ /******/ ({ +/***/ 82: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +//# sourceMappingURL=utils.js.map + +/***/ }), + /***/ 87: /***/ (function(module) { @@ -50,6 +76,42 @@ module.exports = require("os"); /***/ }), +/***/ 102: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +// For internal use, subject to change. +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 }); +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const fs = __importStar(__webpack_require__(747)); +const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); +function issueCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueCommand = issueCommand; +//# sourceMappingURL=file-command.js.map + +/***/ }), + /***/ 104: /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { @@ -129,6 +191,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); /** * Commands * @@ -182,28 +245,14 @@ class Command { return cmdStr; } } -/** - * Sanitizes an input into a string so it can be passed into issueCommand safely - * @param input input to sanitize into a string - */ -function toCommandValue(input) { - if (input === null || input === undefined) { - return ''; - } - else if (typeof input === 'string' || input instanceof String) { - return input; - } - return JSON.stringify(input); -} -exports.toCommandValue = toCommandValue; function escapeData(s) { - return toCommandValue(s) + return utils_1.toCommandValue(s) .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A'); } function escapeProperty(s) { - return toCommandValue(s) + return utils_1.toCommandValue(s) .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') @@ -237,6 +286,8 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); 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)); /** @@ -263,9 +314,17 @@ var ExitCode; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function exportVariable(name, val) { - const convertedVal = command_1.toCommandValue(val); + const convertedVal = utils_1.toCommandValue(val); process.env[name] = convertedVal; - command_1.issueCommand('set-env', { name }, convertedVal); + const filePath = process.env['GITHUB_ENV'] || ''; + if (filePath) { + const delimiter = '_GitHubActionsFileCommandDelimeter_'; + const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; + file_command_1.issueCommand('ENV', commandValue); + } + else { + command_1.issueCommand('set-env', { name }, convertedVal); + } } exports.exportVariable = exportVariable; /** @@ -281,7 +340,13 @@ exports.setSecret = setSecret; * @param inputPath */ function addPath(inputPath) { - command_1.issueCommand('add-path', {}, inputPath); + const filePath = process.env['GITHUB_PATH'] || ''; + if (filePath) { + file_command_1.issueCommand('PATH', inputPath); + } + else { + command_1.issueCommand('add-path', {}, inputPath); + } process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; } exports.addPath = addPath; diff --git a/yarn.lock b/yarn.lock index 267f5d3..83381c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,9 +3,9 @@ "@actions/core@^1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.4.tgz#96179dbf9f8d951dd74b40a0dbd5c22555d186ab" - integrity sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg== + version "1.2.6" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.6.tgz#a78d49f41a4def18e88ce47c2cac615d5694bf09" + integrity sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA== "@zeit/ncc@^0.20.5": version "0.20.5" From ee29fafb6aa450493bac9136b346e51ea60a8b5e Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 7 Oct 2020 21:08:38 +0000 Subject: [PATCH 18/77] Update README for a 0.4.1 release --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 232050c..18e26de 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ jobs: ... steps: - actions/checkout@v1 - # Make sure the @v0.4.0 matches the current version of the + # Make sure the @v0.4.1 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.4.0 + - uses: webfactory/ssh-agent@v0.4.1 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -44,7 +44,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.4.0 + - uses: webfactory/ssh-agent@v0.4.1 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 43c9b3548b7e9b548546a3b2691e099deab9bd63 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 10 Nov 2020 20:28:49 +0100 Subject: [PATCH 19/77] Nutze Composer v1, vermeide ::set-env (Case 115156, Case 115161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anpassungen ausgeführt mit automatischer Migration. Co-authored-by: Fabian Schmick --- .github/workflows/demo.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index c7c7511..5fb24b6 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -26,6 +26,3 @@ jobs: uses: ./ with: ssh-private-key: ${{ secrets.DEMO_KEY }} - - - From 780d0ee9a319b8fb504cc74e4c9257ee45b94f04 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 17 Nov 2020 02:17:43 -0600 Subject: [PATCH 20/77] document v0.4.1 changes in the changelog (#47) --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c297211..618aa24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.4.1 [2020-10-07] + +### Fixed + +* This action no longer relies on `set-env`, which has been deprecated. + ## v0.4.0 ### Changed From 5fedeb584e8c6b8608a5a06dbca73b68ea95fea7 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 12 Feb 2021 18:02:24 +0000 Subject: [PATCH 21/77] Fix scripts/build.js to work on Windows Suggested by @shaunco in #38. Co-authored-by: Shaun Cooley --- scripts/build.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index eecd192..61728e4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,6 +1,7 @@ const { execSync } = require('child_process') const path = require('path') const fs = require('fs') +const process = require('process') const buildDir = path.join(process.cwd(), 'build') const distDir = path.join(process.cwd(), 'dist') @@ -9,13 +10,18 @@ const buildIndexJs = path.join(buildDir, 'index.js') const distIndexJs = path.join(distDir, 'index.js') const distCleanupJs = path.join(distDir, 'cleanup.js') +var ncc = `./node_modules/.bin/ncc`; +if (process.platform === "win32") { + ncc = `.\\node_modules\\.bin\\ncc.cmd`; +} + if (!fs.existsSync(buildDir)) { fs.mkdirSync(buildDir) } // Build the main index.js file console.log('Building index.js...') -execSync(`./node_modules/.bin/ncc build index.js -q -o ${buildDir}`) +execSync(`${ncc} build index.js -q -o ${buildDir}`) if (fs.existsSync(distIndexJs)) { fs.unlinkSync(distIndexJs) } @@ -23,7 +29,7 @@ fs.renameSync(buildIndexJs, distIndexJs) // Build the cleanup.js file console.log('Building cleanup.js...') -execSync(`./node_modules/.bin/ncc build cleanup.js -q -o ${buildDir}`) +execSync(`${ncc} build cleanup.js -q -o ${buildDir}`) if (fs.existsSync(distCleanupJs)) { fs.unlinkSync(distCleanupJs) } From 79096d29b0f40e1983882f44797336f0d75a3aed Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 12 Feb 2021 18:04:53 +0000 Subject: [PATCH 22/77] Document how to pass input arguments during local development Suggested by @shaunco in #38. Co-authored-by: Shaun Cooley --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18e26de..4f1a250 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,24 @@ As a note to my future self, in order to work on this repo: * Clone it * Run `yarn install` to fetch dependencies * _hack hack hack_ -* `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased. Use `env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js` for this action. +* `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased. + + On *nix use: + ```bash + env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js + ``` + + On Windows (cmd): + ```cmd + set /P INPUT_SSH-PRIVATE-KEY=< file + node index.js + ``` + + On Windows (PowerShell): + ```ps + ${env:INPUT_SSH-PRIVATE-KEY} = (Get-Content .\test-keys -Raw); node index.js + node index.js + ``` * Run `npm run build` to update `dist/*`, which holds the files actually run * Read https://help.github.com/en/articles/creating-a-javascript-action if unsure. * Maybe update the README example when publishing a new version. From edc2fe4f2e658683169a6b534ed1bea9f4c2704b Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 13 Feb 2021 21:02:34 +0100 Subject: [PATCH 23/77] Support container-based workflows and Windows (#17) --- .github/workflows/demo.yml | 16 +++++++++++++++- dist/index.js | 20 ++++++++++++++++---- index.js | 20 ++++++++++++++++---- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 5fb24b6..2b34888 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -4,7 +4,7 @@ jobs: single_key_demo: strategy: matrix: - os: [ubuntu-latest, macOS-latest] + os: [ubuntu-latest, macOS-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 @@ -26,3 +26,17 @@ jobs: uses: ./ with: ssh-private-key: ${{ secrets.DEMO_KEY }} + + docker_demo: + runs-on: ubuntu-latest + container: + image: ubuntu:latest + steps: + - uses: actions/checkout@v1 + - run: apt update && apt install -y openssh-client + - name: Setup key + uses: ./ + with: + ssh-private-key: | + ${{ secrets.DEMO_KEY }} + ${{ secrets.DEMO_KEY_2 }} diff --git a/dist/index.js b/dist/index.js index 173d8ea..e90c339 100644 --- a/dist/index.js +++ b/dist/index.js @@ -118,12 +118,9 @@ exports.issueCommand = issueCommand; const core = __webpack_require__(470); const child_process = __webpack_require__(129); const fs = __webpack_require__(747); +const os = __webpack_require__(87); try { - - const home = process.env['HOME']; - const homeSsh = home + '/.ssh'; - const privateKey = core.getInput('ssh-private-key'); if (!privateKey) { @@ -132,6 +129,21 @@ try { return; } + var home; + + if (process.env['OS'] == 'Windows_NT') { + console.log('Preparing ssh-agent service on Windows'); + child_process.execSync('sc config ssh-agent start=demand', { stdio: 'inherit' }); + + home = os.homedir(); + } else { + // Use getent() system call, since this is what ssh does; makes a difference in Docker-based + // Action runs, where $HOME is different from the pwent + var { homedir: home } = os.userInfo(); + } + + const homeSsh = home + '/.ssh'; + console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); fs.mkdirSync(homeSsh, { recursive: true }); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); diff --git a/index.js b/index.js index 7ee6fe7..c4f5049 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,9 @@ const core = require('@actions/core'); const child_process = require('child_process'); const fs = require('fs'); +const os = require('os'); try { - - const home = process.env['HOME']; - const homeSsh = home + '/.ssh'; - const privateKey = core.getInput('ssh-private-key'); if (!privateKey) { @@ -15,6 +12,21 @@ try { return; } + var home; + + if (process.env['OS'] == 'Windows_NT') { + console.log('Preparing ssh-agent service on Windows'); + child_process.execSync('sc config ssh-agent start=demand', { stdio: 'inherit' }); + + home = os.homedir(); + } else { + // Use getent() system call, since this is what ssh does; makes a difference in Docker-based + // Action runs, where $HOME is different from the pwent + var { homedir: home } = os.userInfo(); + } + + const homeSsh = home + '/.ssh'; + console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); fs.mkdirSync(homeSsh, { recursive: true }); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); From 23e7ede81cd7139128472527c9bda88f1c97c2bb Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 13 Feb 2021 20:03:25 +0000 Subject: [PATCH 24/77] Bump copyright in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f1a250..e90d243 100644 --- a/README.md +++ b/README.md @@ -155,4 +155,4 @@ developer looking for new challenges, we'd like to hear from you! - - -Copyright 2019 – 2020 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). +Copyright 2019 – 2021 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). From e5df661fc43194fa93fd3d05933ff3ae8c80d05f Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 13 Feb 2021 20:25:00 +0000 Subject: [PATCH 25/77] Update README to mention Windows and/or Docker support --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e90d243..1be36f2 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ This action * loads a private SSH key into the agent and * configures `known_hosts` for GitHub.com. +It should work in all GitHub Actions virtual environments, including container-based workflows. Windows and Docker support is, however, somewhat new. Since we have little feedback from the field, things might not run so smooth for you as we'd hope. If Windows and/or Docker-based workflows work well for you, leave a :+1: at #17. + ## Why? When running a GitHub Action workflow to stage your project, run tests or build images, you might need to fetch additional libraries or _vendors_ from private repositories. @@ -68,10 +70,6 @@ The `$SSH_AGENT_PID` contains the process id of the agent. This is used to kill ## Known issues and limitations -### Currently OS X and Linux only - -This action has not been tested for the Windows virtual environment. If you can provide the steps necessary to setup (even install?) OpenSSH on the Windows machine, please open an issue. - ### Works for the current job only Since each job [runs in a fresh instance](https://help.github.com/en/articles/about-github-actions#job) of the virtual environment, the SSH key will only be available in the job where this action has been referenced. You can, of course, add the action in multiple jobs or even workflows. All instances can use the same `SSH_PRIVATE_KEY` secret. From 85353917a25aa1f197c5dff70d3ab6ef5aa88cd4 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 13 Feb 2021 21:26:12 +0100 Subject: [PATCH 26/77] Tweak README formatting --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1be36f2..5b18052 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ This action * loads a private SSH key into the agent and * configures `known_hosts` for GitHub.com. -It should work in all GitHub Actions virtual environments, including container-based workflows. Windows and Docker support is, however, somewhat new. Since we have little feedback from the field, things might not run so smooth for you as we'd hope. If Windows and/or Docker-based workflows work well for you, leave a :+1: at #17. +It should work in all GitHub Actions virtual environments, including container-based workflows. + +Windows and Docker support is, however, somewhat new. Since we have little feedback from the field, things might not run so smooth for you as we'd hope. If Windows and/or Docker-based workflows work well for you, leave a :+1: at https://github.com/webfactory/ssh-agent/pull/17. ## Why? From 4d06ea6a33df77ae7dcbe00fe177ea999103c2f3 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 19 Feb 2021 14:37:34 +0100 Subject: [PATCH 27/77] Add support for GitHub Deployment Keys through key comments (#59) Fixes #30, closes #38. --- .github/workflows/demo.yml | 24 +++++++++++++++++++++--- README.md | 22 +++++++++++++++------- dist/index.js | 35 +++++++++++++++++++++++++++++++++++ index.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 2b34888..c3bb009 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -7,7 +7,7 @@ jobs: os: [ubuntu-latest, macOS-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Setup key uses: ./ with: @@ -21,7 +21,7 @@ jobs: os: [ubuntu-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Setup key uses: ./ with: @@ -32,7 +32,7 @@ jobs: container: image: ubuntu:latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - run: apt update && apt install -y openssh-client - name: Setup key uses: ./ @@ -40,3 +40,21 @@ jobs: ssh-private-key: | ${{ secrets.DEMO_KEY }} ${{ secrets.DEMO_KEY_2 }} + + deployment_keys_demo: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup key + uses: ./ + with: + ssh-private-key: | + ${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }} + ${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }} + - run: | + git clone https://github.com/mpdude/test-1.git test-1-http + git clone git@github.com:mpdude/test-1.git test-1-git + git clone ssh://git@github.com/mpdude/test-1.git test-1-git-ssh + git clone https://github.com/mpdude/test-2.git test-2-http + git clone git@github.com:mpdude/test-2.git test-2-git + git clone ssh://git@github.com/mpdude/test-2.git test-2-git-ssh diff --git a/README.md b/README.md index 5b18052..d5273bd 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,15 @@ This action * starts the `ssh-agent`, * exports the `SSH_AUTH_SOCK` environment variable, -* loads a private SSH key into the agent and +* loads one or several private SSH key into the agent and * configures `known_hosts` for GitHub.com. It should work in all GitHub Actions virtual environments, including container-based workflows. Windows and Docker support is, however, somewhat new. Since we have little feedback from the field, things might not run so smooth for you as we'd hope. If Windows and/or Docker-based workflows work well for you, leave a :+1: at https://github.com/webfactory/ssh-agent/pull/17. +Also, using multiple GitHub deployment keys is supported; keys are mapped to repositories by using SSH key comments (see below). + ## Why? When running a GitHub Action workflow to stage your project, run tests or build images, you might need to fetch additional libraries or _vendors_ from private repositories. @@ -22,7 +24,7 @@ GitHub Actions only have access to the repository they run for. So, in order to 2. Make sure you don't have a passphrase set on the private key. 3. In your repository, go to the *Settings > Secrets* menu and create a new secret. In this example, we'll call it `SSH_PRIVATE_KEY`. Put the contents of the *private* SSH key file into the contents field.
This key should start with `-----BEGIN ... PRIVATE KEY-----`, consist of many lines and ends with `-----END ... PRIVATE KEY-----`. -4. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v1` line. +4. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v2` line. ```yaml # .github/workflows/my-workflow.yml @@ -30,7 +32,7 @@ jobs: my_job: ... steps: - - actions/checkout@v1 + - actions/checkout@v2 # Make sure the @v0.4.1 matches the current version of the # action - uses: webfactory/ssh-agent@v0.4.1 @@ -58,12 +60,18 @@ You can set up different keys as different secrets and pass them all to the acti The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections. -There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have -six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. +There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. But when you're using GitHub Deploy Keys, read on! -Also, when using **Github deploy keys**, GitHub servers will accept the first known key. But since deploy keys are scoped to a single repository, you might get the error message `fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.` if the wrong key/repository combination is tried. +### Support for GitHub Deploy Keys -In both cases, you might want to [try a wrapper script around `ssh`](https://gist.github.com/mpdude/e56fcae5bc541b95187fa764aafb5e6d) that can pick the right key, based on key comments. See [our blog post](https://www.webfactory.de/blog/using-multiple-ssh-deploy-keys-with-github) for the full story. +When using **Github deploy keys**, GitHub servers will accept the _first_ known key. But since deploy keys are scoped to a single repository, this might not be the key needed to access a particular repository. Thus, you will get the error message `fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.` if the wrong key/repository combination is tried. + +To support picking the right key in this use case, this action scans _key comments_ and will set up extra Git and SSH configuration to make things work. + +1. When creating the deploy key for a repository like `git@github.com:owner/repo.git` or `https://github.com/owner/repo`, put that URL into the key comment. +2. After keys have been added to the agent, this action will scan the key comments. +3. For key comments containing such URLs, a Git config setting is written that uses [`url..insteadof`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf). It will redirect `git` requests to URLs starting with either `https://github.com/owner/repo` or `git@github.com:owner/repo` to a fake hostname/URL like `git@...some.hash...:owner/repo`. +4. An SSH configuration section is generated that applies to the fake hostname. It will map the SSH connection back to `github.com`, while at the same time pointing SSH to a file containing the appropriate key's public part. That will make SSH use the right key when connecting to GitHub.com. ## Exported variables The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables through the Github Actions core module. diff --git a/dist/index.js b/dist/index.js index e90c339..ac64344 100644 --- a/dist/index.js +++ b/dist/index.js @@ -119,6 +119,7 @@ const core = __webpack_require__(470); const child_process = __webpack_require__(129); const fs = __webpack_require__(747); const os = __webpack_require__(87); +const crypto = __webpack_require__(417); try { const privateKey = core.getInput('ssh-private-key'); @@ -175,6 +176,33 @@ try { console.log("Keys added:"); child_process.execSync('ssh-add -l', { stdio: 'inherit' }); + child_process.execFileSync('ssh-add', ['-L']).toString().split(/\r?\n/).forEach(function(key) { + let parts = key.match(/\bgithub.com[:/](.*)(?:\.git)?\b/); + + if (parts == null) { + return; + } + + let ownerAndRepo = parts[1]; + let sha256 = crypto.createHash('sha256').update(key).digest('hex'); + + fs.writeFileSync(`${homeSsh}/${sha256}`, key + "\n", { mode: '600' }); + + child_process.execSync(`git config --global --replace-all url."git@${sha256}:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + + let sshConfig = `\nHost ${sha256}\n` + + ` HostName github.com\n` + + ` User git\n` + + ` IdentityFile ${homeSsh}/${sha256}\n` + + ` IdentitiesOnly yes\n`; + + fs.appendFileSync(`${homeSsh}/config`, sshConfig); + + console.log(`Added deploy-key mapping: Use key "${key}" for GitHub repository ${ownerAndRepo}`); + }); + } catch (error) { core.setFailed(error.message); } @@ -189,6 +217,13 @@ module.exports = require("child_process"); /***/ }), +/***/ 417: +/***/ (function(module) { + +module.exports = require("crypto"); + +/***/ }), + /***/ 431: /***/ (function(__unusedmodule, exports, __webpack_require__) { diff --git a/index.js b/index.js index c4f5049..cf7b562 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const core = require('@actions/core'); const child_process = require('child_process'); const fs = require('fs'); const os = require('os'); +const crypto = require('crypto'); try { const privateKey = core.getInput('ssh-private-key'); @@ -58,6 +59,33 @@ try { console.log("Keys added:"); child_process.execSync('ssh-add -l', { stdio: 'inherit' }); + child_process.execFileSync('ssh-add', ['-L']).toString().split(/\r?\n/).forEach(function(key) { + let parts = key.match(/\bgithub.com[:/](.*)(?:\.git)?\b/); + + if (parts == null) { + return; + } + + let ownerAndRepo = parts[1]; + let sha256 = crypto.createHash('sha256').update(key).digest('hex'); + + fs.writeFileSync(`${homeSsh}/${sha256}`, key + "\n", { mode: '600' }); + + child_process.execSync(`git config --global --replace-all url."git@${sha256}:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + + let sshConfig = `\nHost ${sha256}\n` + + ` HostName github.com\n` + + ` User git\n` + + ` IdentityFile ${homeSsh}/${sha256}\n` + + ` IdentitiesOnly yes\n`; + + fs.appendFileSync(`${homeSsh}/config`, sshConfig); + + console.log(`Added deploy-key mapping: Use key "${key}" for GitHub repository ${ownerAndRepo}`); + }); + } catch (error) { core.setFailed(error.message); } From 6b2f2c5354ff41f1edbbf7a17ea9b6178c89be9f Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 19 Feb 2021 14:41:23 +0100 Subject: [PATCH 28/77] Prepare README for the upcoming 0.5.0 release --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d5273bd..63fccf8 100644 --- a/README.md +++ b/README.md @@ -33,16 +33,16 @@ jobs: ... steps: - actions/checkout@v2 - # Make sure the @v0.4.1 matches the current version of the + # Make sure the @v0.5.0 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.4.1 + - uses: webfactory/ssh-agent@v0.5.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps ``` 5. If, for some reason, you need to change the location of the SSH agent socket, you can use the `ssh-auth-sock` input to provide a path. -### Using multiple keys +### Using Multiple Keys There are cases where you might need to use multiple keys. For example, "[deploy keys](https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys)" might be limited to a single repository, so you'll need several of them. @@ -50,7 +50,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.4.1 + - uses: webfactory/ssh-agent@v0.5.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} @@ -78,13 +78,13 @@ The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables The `$SSH_AUTH_SOCK` is used by several applications like git or rsync to connect to the SSH authentication agent. The `$SSH_AGENT_PID` contains the process id of the agent. This is used to kill the agent in post job action. -## Known issues and limitations +## Known Issues and Limitations -### Works for the current job only +### Works for the Current Job Only Since each job [runs in a fresh instance](https://help.github.com/en/articles/about-github-actions#job) of the virtual environment, the SSH key will only be available in the job where this action has been referenced. You can, of course, add the action in multiple jobs or even workflows. All instances can use the same `SSH_PRIVATE_KEY` secret. -### SSH private key format +### SSH Private Key Format If the private key is not in the `PEM` format, you will see an `Error loading key "(stdin)": invalid format` message. @@ -94,21 +94,21 @@ Use `ssh-keygen -p -f path/to/your/key -m pem` to convert your key file to `PEM` The following items are not issues, but beyond what this Action is supposed to do. -### Work on remote machines +### Work on Remote Machines When using `ssh` to connect from the GitHub Action worker node to another machine, you *can* forward the SSH Agent socket and use your private key on the other (remote) machine. However, this Action will not configure `known_hosts` or other SSH settings on the remote machine for you. -### Provide the SSH key as a file +### Provide the SSH Key as a File This Action is designed to pass the SSH key directly into `ssh-agent`; that is, the key is available in memory on the GitHub Action worker node, but never written to disk. As a consequence, you _cannot_ pass the key as a build argument or a mounted file into Docker containers that you build or run on the worker node. You _can_, however, mount the `ssh-agent` Unix socket into a Docker container that you _run_, set up the `SSH_AUTH_SOCK` env var and then use SSH from within the container (see #11). -### Run `ssh-keyscan` to add host keys for additional hosts +### Run `ssh-keyscan` to Add Host Keys for Additional Hosts If you want to use `ssh-keyscan` to add additional hosts (that you own/know) to the `known_hosts` file, you can do so with a single shell line in your Action definition. You don't really need this Action to do this for you. As a side note, using `ssh-keyscan` without proper key verification is susceptible to man-in-the-middle attacks. You might prefer putting your _known_ SSH host key in your own Action files to add it to the `known_hosts` file. The SSH host key is not secret and can safely be committed into the repo. -## Creating SSH keys +## Creating SSH Keys In order to create a new SSH key, run `ssh-keygen -t ed25519 -a 100 -f path/to/keyfile`, as suggested in [this blog post](https://stribika.github.io/2015/01/04/secure-secure-shell.html). If you need to work with some older server software and need RSA keys, tr `ssh-keygen -t rsa -b 4096 -o -f path/to/keyfile` instead. @@ -117,7 +117,7 @@ Both commands will prompt you for a key passphrase and save the key in `path/to/ In general, having a passphrase is a good thing, since it will keep the key encrypted on your disk. When using the key with this action, however, you need to make sure you don't specify a passphrase: The key must be usable without reading the passphrase from input. Since the key itself is stored using GitHub's "Secret" feature, it should be fairly safe anyway. -## Authorizing a key +## Authorizing a Key To actually grant the SSH key access, you can – on GitHub – use at least two ways: From 5f95203cead80c02aad86c732037fc0ddaf887d9 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 4 Mar 2021 12:49:35 +0100 Subject: [PATCH 29/77] Add note about using cargo with private dependencies (#64) * Add note about using cargo with private dependencies * Update doc to mention Windows only * Add alternative workaround * Create extra main section for tips and information regarding different languages/tools Co-authored-by: Matthias Pigulla --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 63fccf8..0a49485 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,38 @@ If the private key is not in the `PEM` format, you will see an `Error loading ke Use `ssh-keygen -p -f path/to/your/key -m pem` to convert your key file to `PEM`, but be sure to make a backup of the file first 😉. +## Additional Information for Particular Tools or Platforms + +If you know that your favorite tool or platform of choice requires extra tweaks or has some caveats when running with SSH, feel free to open a PR to amend this section here. + +### Cargo's (Rust) Private Dependencies on Windows + +If you are using private repositories in your dependencies like this: + +``` +stuff = { git = "ssh://git@github.com/myorg/stuff.git", branch = "main" } +``` + +... you will need to change a configuration in the workflow for Windows machines in order to make cargo able to clone private repositories. + +There are 2 ways you can achieve this: + +1. Add this step once in your job **before** any cargo command: + +``` + - name: Update cargo config to use Git CLI + run: Set-Content -Path $env:USERPROFILE\.cargo\config.toml "[net]`ngit-fetch-with-cli = true" +``` + +This will configure Cargo to use the Git CLI as explained in the [Cargo's documentation](https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli). + +2. Alternatively you can set it to the environment variables for the entire workflow: + +``` +env: + CARGO_NET_GIT_FETCH_WITH_CLI: true +``` + ## What this Action *cannot* do for you The following items are not issues, but beyond what this Action is supposed to do. From 65d1ea3d907cda10122160c201a380dbad15dbd0 Mon Sep 17 00:00:00 2001 From: Shashank Patidar <74622220+shashank11p@users.noreply.github.com> Date: Fri, 5 Mar 2021 04:47:34 +0530 Subject: [PATCH 30/77] Mention that container-based workflows need to have ssh packages installed Co-authored-by: Shashank Patidar <74622220+shashank11p@users.noreply.github.com> --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0a49485..ec02d43 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,10 @@ Use `ssh-keygen -p -f path/to/your/key -m pem` to convert your key file to `PEM` If you know that your favorite tool or platform of choice requires extra tweaks or has some caveats when running with SSH, feel free to open a PR to amend this section here. +### Container-based Workflows + +If you are using this action on container-based workflows, make sure the container has the necessary SSH binaries or package(s) installed. + ### Cargo's (Rust) Private Dependencies on Windows If you are using private repositories in your dependencies like this: From 598c7ea89465463c5f9784314410a9d75362d74f Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 5 Mar 2021 20:17:14 +0000 Subject: [PATCH 31/77] Handle ENOENT exceptions with a graceful message --- dist/index.js | 6 ++++++ index.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/dist/index.js b/dist/index.js index ac64344..6b1ea20 100644 --- a/dist/index.js +++ b/dist/index.js @@ -204,6 +204,12 @@ try { }); } catch (error) { + + if (error.code == 'ENOENT') { + console.log(`The '${error.path}' executable could not be found. Please make sure it is on your PATH and/or the necessary packages are installed.`); + console.log(`PATH is set to: ${process.env.PATH}`); + } + core.setFailed(error.message); } diff --git a/index.js b/index.js index cf7b562..199438a 100644 --- a/index.js +++ b/index.js @@ -87,5 +87,11 @@ try { }); } catch (error) { + + if (error.code == 'ENOENT') { + console.log(`The '${error.path}' executable could not be found. Please make sure it is on your PATH and/or the necessary packages are installed.`); + console.log(`PATH is set to: ${process.env.PATH}`); + } + core.setFailed(error.message); } From 795485730f43d85b86350c37087f13cba34660d6 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 10 Mar 2021 08:17:18 +0100 Subject: [PATCH 32/77] Prepare 0.5.1 release --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec02d43..30c266d 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ jobs: ... steps: - actions/checkout@v2 - # Make sure the @v0.5.0 matches the current version of the + # Make sure the @v0.5.1 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.5.0 + - uses: webfactory/ssh-agent@v0.5.1 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -50,7 +50,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.5.0 + - uses: webfactory/ssh-agent@v0.5.1 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 4b6f4eb000f167422aa955c5a7e848a432931c6f Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 10 Mar 2021 08:19:17 +0100 Subject: [PATCH 33/77] Windows virtual environment: Use SSH binaries from the Git suite (#63) * Use SSH binaries from the Git suite * Try to kill the ssh-agent upon action termination on Windows as well --- .github/workflows/demo.yml | 82 ++++++++++++----------------- cleanup.js | 10 ++-- dist/cleanup.js | 35 +++++++++++-- dist/index.js | 104 +++++++++++++++++++++---------------- index.js | 79 ++++++++++++---------------- paths.js | 18 +++++++ 6 files changed, 182 insertions(+), 146 deletions(-) create mode 100644 paths.js diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index c3bb009..9d8fad0 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -1,60 +1,46 @@ -on: [push, pull_request] +on: [ push, pull_request ] jobs: - single_key_demo: + deployment_keys_demo: strategy: + fail-fast: false matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] + os: [ ubuntu-latest, macOS-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - name: Setup key - uses: ./ - with: - ssh-private-key: | - ${{ secrets.DEMO_KEY }} - ${{ secrets.DEMO_KEY_2 }} - - multiple_keys_demo: - strategy: - matrix: - os: [ubuntu-latest, macOS-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - name: Setup key - uses: ./ - with: - ssh-private-key: ${{ secrets.DEMO_KEY }} + - uses: actions/checkout@v2 + - name: Setup key + uses: ./ + with: + ssh-private-key: | + ${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }} + ${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }} + - run: | + git clone https://github.com/mpdude/test-1.git test-1-http + git clone git@github.com:mpdude/test-1.git test-1-git + git clone ssh://git@github.com/mpdude/test-1.git test-1-git-ssh + git clone https://github.com/mpdude/test-2.git test-2-http + git clone git@github.com:mpdude/test-2.git test-2-git + git clone ssh://git@github.com/mpdude/test-2.git test-2-git-ssh docker_demo: - runs-on: ubuntu-latest + runs-on: ubuntu-latest container: image: ubuntu:latest steps: - - uses: actions/checkout@v2 - - run: apt update && apt install -y openssh-client - - name: Setup key - uses: ./ - with: - ssh-private-key: | - ${{ secrets.DEMO_KEY }} - ${{ secrets.DEMO_KEY_2 }} + - uses: actions/checkout@v2 + - run: apt update && apt install -y openssh-client git + - name: Setup key + uses: ./ + with: + ssh-private-key: | + ${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }} + ${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }} + - run: | + git clone https://github.com/mpdude/test-1.git test-1-http + git clone git@github.com:mpdude/test-1.git test-1-git + git clone ssh://git@github.com/mpdude/test-1.git test-1-git-ssh + git clone https://github.com/mpdude/test-2.git test-2-http + git clone git@github.com:mpdude/test-2.git test-2-git + git clone ssh://git@github.com/mpdude/test-2.git test-2-git-ssh - deployment_keys_demo: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup key - uses: ./ - with: - ssh-private-key: | - ${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }} - ${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }} - - run: | - git clone https://github.com/mpdude/test-1.git test-1-http - git clone git@github.com:mpdude/test-1.git test-1-git - git clone ssh://git@github.com/mpdude/test-1.git test-1-git-ssh - git clone https://github.com/mpdude/test-2.git test-2-http - git clone git@github.com:mpdude/test-2.git test-2-git - git clone ssh://git@github.com/mpdude/test-2.git test-2-git-ssh diff --git a/cleanup.js b/cleanup.js index f90cddd..529fbe8 100644 --- a/cleanup.js +++ b/cleanup.js @@ -1,10 +1,12 @@ -const core = require('@actions/core') -const { execSync } = require('child_process') +const core = require('@actions/core'); +const { execSync } = require('child_process'); +const { sshAgent } = require('./paths.js'); try { // Kill the started SSH agent - console.log('Stopping SSH agent') - execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' }) + console.log('Stopping SSH agent'); + execSync(sshAgent, ['-k'], { stdio: 'inherit' }); + } catch (error) { console.log(error.message); console.log('Error stopping the SSH agent, proceeding anyway'); diff --git a/dist/cleanup.js b/dist/cleanup.js index c8081be..49024b4 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -122,13 +122,15 @@ module.exports = require("child_process"); /***/ 175: /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { -const core = __webpack_require__(470) -const { execSync } = __webpack_require__(129) +const core = __webpack_require__(470); +const { execSync } = __webpack_require__(129); +const { sshAgent } = __webpack_require__(972); try { // Kill the started SSH agent - console.log('Stopping SSH agent') - execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' }) + console.log('Stopping SSH agent'); + execSync(sshAgent, ['-k'], { stdio: 'inherit' }); + } catch (error) { console.log(error.message); console.log('Error stopping the SSH agent, proceeding anyway'); @@ -480,6 +482,31 @@ module.exports = require("path"); module.exports = require("fs"); +/***/ }), + +/***/ 972: +/***/ (function(module, __unusedexports, __webpack_require__) { + +const os = __webpack_require__(87); + +module.exports = (process.env['OS'] != 'Windows_NT') ? { + + // Use getent() system call, since this is what ssh does; makes a difference in Docker-based + // Action runs, where $HOME is different from the pwent + home: os.userInfo().homedir, + sshAgent: 'ssh-agent', + sshAdd: 'ssh-add' + +} : { + + home: os.homedir(), + sshAgent: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAdd: 'c://progra~1//git//usr//bin//ssh-add.exe' + +}; + + + /***/ }) /******/ }); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 6b1ea20..f75d5b2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -118,8 +118,8 @@ exports.issueCommand = issueCommand; const core = __webpack_require__(470); const child_process = __webpack_require__(129); const fs = __webpack_require__(747); -const os = __webpack_require__(87); const crypto = __webpack_require__(417); +const { home, sshAgent, sshAdd } = __webpack_require__(972); try { const privateKey = core.getInput('ssh-private-key'); @@ -130,77 +130,66 @@ try { return; } - var home; - - if (process.env['OS'] == 'Windows_NT') { - console.log('Preparing ssh-agent service on Windows'); - child_process.execSync('sc config ssh-agent start=demand', { stdio: 'inherit' }); - - home = os.homedir(); - } else { - // Use getent() system call, since this is what ssh does; makes a difference in Docker-based - // Action runs, where $HOME is different from the pwent - var { homedir: home } = os.userInfo(); - } - const homeSsh = home + '/.ssh'; console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); + fs.mkdirSync(homeSsh, { recursive: true }); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); console.log("Starting ssh-agent"); + const authSock = core.getInput('ssh-auth-sock'); - let sshAgentOutput = '' - if (authSock && authSock.length > 0) { - sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]); - } else { - sshAgentOutput = child_process.execFileSync('ssh-agent') - } + const sshAgentArgs = (authSock && authSock.length > 0) ? ['-a', authSock] : []; // Extract auth socket path and agent pid and set them as job variables - const lines = sshAgentOutput.toString().split("\n") - for (const lineNumber in lines) { - const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(lines[lineNumber]) - if (matches && matches.length > 0) { - core.exportVariable(matches[1], matches[2]) - } - } + child_process.execFileSync(sshAgent, sshAgentArgs).toString().split("\n").forEach(function(line) { + const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(line); - console.log("Adding private key to agent"); - privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { - child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); + if (matches && matches.length > 0) { + // This will also set process.env accordingly, so changes take effect for this script + core.exportVariable(matches[1], matches[2]) + console.log(`${matches[1]}=${matches[2]}`); + } }); - console.log("Keys added:"); - child_process.execSync('ssh-add -l', { stdio: 'inherit' }); + console.log("Adding private key(s) to agent"); - child_process.execFileSync('ssh-add', ['-L']).toString().split(/\r?\n/).forEach(function(key) { - let parts = key.match(/\bgithub.com[:/](.*)(?:\.git)?\b/); + privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { + child_process.execFileSync(sshAdd, ['-'], { input: key.trim() + "\n" }); + }); - if (parts == null) { + console.log("Key(s) added:"); + + child_process.execFileSync(sshAdd, ['-l'], { stdio: 'inherit' }); + + console.log('Configuring deployment key(s)'); + + child_process.execFileSync(sshAdd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { + const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/); + + if (!parts) { return; } - let ownerAndRepo = parts[1]; - let sha256 = crypto.createHash('sha256').update(key).digest('hex'); + const sha256 = crypto.createHash('sha256').update(key).digest('hex'); + const ownerAndRepo = parts[1].replace(/\.git$/, ''); - fs.writeFileSync(`${homeSsh}/${sha256}`, key + "\n", { mode: '600' }); + fs.writeFileSync(`${homeSsh}/key-${sha256}`, key + "\n", { mode: '600' }); - child_process.execSync(`git config --global --replace-all url."git@${sha256}:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + child_process.execSync(`git config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); - let sshConfig = `\nHost ${sha256}\n` + const sshConfig = `\nHost key-${sha256}.github.com\n` + ` HostName github.com\n` - + ` User git\n` - + ` IdentityFile ${homeSsh}/${sha256}\n` + + ` IdentityFile ${homeSsh}/key-${sha256}\n` + ` IdentitiesOnly yes\n`; fs.appendFileSync(`${homeSsh}/config`, sshConfig); - console.log(`Added deploy-key mapping: Use key "${key}" for GitHub repository ${ownerAndRepo}`); + console.log(`Added deploy-key mapping: Use identity '${homeSsh}/key-${sha256}' for GitHub repository ${ownerAndRepo}`); }); } catch (error) { @@ -573,6 +562,31 @@ module.exports = require("path"); module.exports = require("fs"); +/***/ }), + +/***/ 972: +/***/ (function(module, __unusedexports, __webpack_require__) { + +const os = __webpack_require__(87); + +module.exports = (process.env['OS'] != 'Windows_NT') ? { + + // Use getent() system call, since this is what ssh does; makes a difference in Docker-based + // Action runs, where $HOME is different from the pwent + home: os.userInfo().homedir, + sshAgent: 'ssh-agent', + sshAdd: 'ssh-add' + +} : { + + home: os.homedir(), + sshAgent: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAdd: 'c://progra~1//git//usr//bin//ssh-add.exe' + +}; + + + /***/ }) /******/ }); \ No newline at end of file diff --git a/index.js b/index.js index 199438a..5243e61 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,8 @@ const core = require('@actions/core'); const child_process = require('child_process'); const fs = require('fs'); -const os = require('os'); const crypto = require('crypto'); +const { home, sshAgent, sshAdd } = require('./paths.js'); try { const privateKey = core.getInput('ssh-private-key'); @@ -13,77 +13,66 @@ try { return; } - var home; - - if (process.env['OS'] == 'Windows_NT') { - console.log('Preparing ssh-agent service on Windows'); - child_process.execSync('sc config ssh-agent start=demand', { stdio: 'inherit' }); - - home = os.homedir(); - } else { - // Use getent() system call, since this is what ssh does; makes a difference in Docker-based - // Action runs, where $HOME is different from the pwent - var { homedir: home } = os.userInfo(); - } - const homeSsh = home + '/.ssh'; console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); + fs.mkdirSync(homeSsh, { recursive: true }); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); console.log("Starting ssh-agent"); + const authSock = core.getInput('ssh-auth-sock'); - let sshAgentOutput = '' - if (authSock && authSock.length > 0) { - sshAgentOutput = child_process.execFileSync('ssh-agent', ['-a', authSock]); - } else { - sshAgentOutput = child_process.execFileSync('ssh-agent') - } + const sshAgentArgs = (authSock && authSock.length > 0) ? ['-a', authSock] : []; // Extract auth socket path and agent pid and set them as job variables - const lines = sshAgentOutput.toString().split("\n") - for (const lineNumber in lines) { - const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(lines[lineNumber]) - if (matches && matches.length > 0) { - core.exportVariable(matches[1], matches[2]) - } - } + child_process.execFileSync(sshAgent, sshAgentArgs).toString().split("\n").forEach(function(line) { + const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(line); - console.log("Adding private key to agent"); - privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { - child_process.execSync('ssh-add -', { input: key.trim() + "\n" }); + if (matches && matches.length > 0) { + // This will also set process.env accordingly, so changes take effect for this script + core.exportVariable(matches[1], matches[2]) + console.log(`${matches[1]}=${matches[2]}`); + } }); - console.log("Keys added:"); - child_process.execSync('ssh-add -l', { stdio: 'inherit' }); + console.log("Adding private key(s) to agent"); - child_process.execFileSync('ssh-add', ['-L']).toString().split(/\r?\n/).forEach(function(key) { - let parts = key.match(/\bgithub.com[:/](.*)(?:\.git)?\b/); + privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { + child_process.execFileSync(sshAdd, ['-'], { input: key.trim() + "\n" }); + }); - if (parts == null) { + console.log("Key(s) added:"); + + child_process.execFileSync(sshAdd, ['-l'], { stdio: 'inherit' }); + + console.log('Configuring deployment key(s)'); + + child_process.execFileSync(sshAdd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { + const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/); + + if (!parts) { return; } - let ownerAndRepo = parts[1]; - let sha256 = crypto.createHash('sha256').update(key).digest('hex'); + const sha256 = crypto.createHash('sha256').update(key).digest('hex'); + const ownerAndRepo = parts[1].replace(/\.git$/, ''); - fs.writeFileSync(`${homeSsh}/${sha256}`, key + "\n", { mode: '600' }); + fs.writeFileSync(`${homeSsh}/key-${sha256}`, key + "\n", { mode: '600' }); - child_process.execSync(`git config --global --replace-all url."git@${sha256}:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@${sha256}:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + child_process.execSync(`git config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); - let sshConfig = `\nHost ${sha256}\n` + const sshConfig = `\nHost key-${sha256}.github.com\n` + ` HostName github.com\n` - + ` User git\n` - + ` IdentityFile ${homeSsh}/${sha256}\n` + + ` IdentityFile ${homeSsh}/key-${sha256}\n` + ` IdentitiesOnly yes\n`; fs.appendFileSync(`${homeSsh}/config`, sshConfig); - console.log(`Added deploy-key mapping: Use key "${key}" for GitHub repository ${ownerAndRepo}`); + console.log(`Added deploy-key mapping: Use identity '${homeSsh}/key-${sha256}' for GitHub repository ${ownerAndRepo}`); }); } catch (error) { diff --git a/paths.js b/paths.js new file mode 100644 index 0000000..8ee7afd --- /dev/null +++ b/paths.js @@ -0,0 +1,18 @@ +const os = require('os'); + +module.exports = (process.env['OS'] != 'Windows_NT') ? { + + // Use getent() system call, since this is what ssh does; makes a difference in Docker-based + // Action runs, where $HOME is different from the pwent + home: os.userInfo().homedir, + sshAgent: 'ssh-agent', + sshAdd: 'ssh-add' + +} : { + + home: os.homedir(), + sshAgent: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAdd: 'c://progra~1//git//usr//bin//ssh-add.exe' + +}; + From 4681241867865c064d220082c7b6cbe128c16171 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 17 Mar 2021 18:27:52 +0000 Subject: [PATCH 34/77] Use case-insensitive regex matching when scanning key comments Resolves #68, closes #70, closes #71. Co-authored-by: Sean Killeen --- dist/index.js | 2 +- index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index f75d5b2..883d7c7 100644 --- a/dist/index.js +++ b/dist/index.js @@ -167,7 +167,7 @@ try { console.log('Configuring deployment key(s)'); child_process.execFileSync(sshAdd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { - const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/); + const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { return; diff --git a/index.js b/index.js index 5243e61..6c8d969 100644 --- a/index.js +++ b/index.js @@ -50,7 +50,7 @@ try { console.log('Configuring deployment key(s)'); child_process.execFileSync(sshAdd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { - const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/); + const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { return; From aed5400f20865ee7ae916e3283597c9a56577778 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 17 Mar 2021 18:50:49 +0000 Subject: [PATCH 35/77] Log when a key is _not_ used as a deploy key Resolves #69. Co-authored-by: Sean Killeen --- dist/index.js | 2 ++ index.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dist/index.js b/dist/index.js index 883d7c7..6dbf026 100644 --- a/dist/index.js +++ b/dist/index.js @@ -170,6 +170,8 @@ try { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { + console.log(`Comment for key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + return; } diff --git a/index.js b/index.js index 6c8d969..1a4835e 100644 --- a/index.js +++ b/index.js @@ -53,6 +53,8 @@ try { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { + console.log(`Comment for key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + return; } From cb8b21017acfd319f0f4eb320ae495f22c36d0a7 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 7 Apr 2021 12:30:27 +0200 Subject: [PATCH 36/77] Update version numbers in README for the next bugfix release --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 30c266d..41d5e4b 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ jobs: ... steps: - actions/checkout@v2 - # Make sure the @v0.5.1 matches the current version of the + # Make sure the @v0.5.2 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.5.1 + - uses: webfactory/ssh-agent@v0.5.2 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -50,7 +50,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.5.1 + - uses: webfactory/ssh-agent@v0.5.2 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 98f76b1158e862ff1ed07602f86cb11dd3716345 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Thu, 27 May 2021 20:05:28 +0000 Subject: [PATCH 37/77] Give an example of how to add a key comment Co-authored-by: rr-james-hickman --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41d5e4b..452b122 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ When using **Github deploy keys**, GitHub servers will accept the _first_ known To support picking the right key in this use case, this action scans _key comments_ and will set up extra Git and SSH configuration to make things work. -1. When creating the deploy key for a repository like `git@github.com:owner/repo.git` or `https://github.com/owner/repo`, put that URL into the key comment. +1. When creating the deploy key for a repository like `git@github.com:owner/repo.git` or `https://github.com/owner/repo`, put that URL into the key comment. (Hint: Try `ssh-keygen ... -C "git@github.com:owner/repo.git"`.) 2. After keys have been added to the agent, this action will scan the key comments. 3. For key comments containing such URLs, a Git config setting is written that uses [`url..insteadof`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf). It will redirect `git` requests to URLs starting with either `https://github.com/owner/repo` or `git@github.com:owner/repo` to a fake hostname/URL like `git@...some.hash...:owner/repo`. 4. An SSH configuration section is generated that applies to the fake hostname. It will map the SSH connection back to `github.com`, while at the same time pointing SSH to a file containing the appropriate key's public part. That will make SSH use the right key when connecting to GitHub.com. From 8569bedfe095bb3c5b3c636aab03add3ec45cf27 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Thu, 27 May 2021 20:11:56 +0000 Subject: [PATCH 38/77] Mention "-scmProvider system" for XCode builds/Swift Package Manager Co-authored-by: rr-james-hickman --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 452b122..ed299e1 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,10 @@ env: CARGO_NET_GIT_FETCH_WITH_CLI: true ``` +### Using Deploy Keys with Swift Package Manager + +`xcodebuild` by default uses Xcode's built-it Git tooling. If you want to use GitHub Deploy Keys as supported by this action, however, that version of Git will lack the necessary URL remapping. In this case, pass `-scmProvider system` to the `xcodebuild` command, as mentioned in [Apple's documentation](https://developer.apple.com/documentation/swift_packages/building_swift_packages_or_apps_that_use_them_in_continuous_integration_workflows#3680255). + ## What this Action *cannot* do for you The following items are not issues, but beyond what this Action is supposed to do. From 515d164e78d902edeb5c201b2090a14a17549447 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 2 Jun 2021 22:15:05 +0200 Subject: [PATCH 39/77] Run cleanup (post) step also on failure (#79) According to https://github.com/actions/runner/issues/987, this should run the post step (cleanup.js) also when a workflow fails. Probably most important on self-hosted runners that are not ephemeral, to terminate SSH agents from failed jobs as well. --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index e43c44f..16aaba6 100644 --- a/action.yml +++ b/action.yml @@ -10,6 +10,7 @@ runs: using: 'node12' main: 'dist/index.js' post: 'dist/cleanup.js' + post-if: 'always()' branding: icon: loader color: 'yellow' From 81d965f2bd14ec88844371f6b5cd8e6ab8071e83 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Thu, 3 Jun 2021 23:33:40 +0200 Subject: [PATCH 40/77] Tix a fypo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed299e1..b20a09b 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ env: ### Using Deploy Keys with Swift Package Manager -`xcodebuild` by default uses Xcode's built-it Git tooling. If you want to use GitHub Deploy Keys as supported by this action, however, that version of Git will lack the necessary URL remapping. In this case, pass `-scmProvider system` to the `xcodebuild` command, as mentioned in [Apple's documentation](https://developer.apple.com/documentation/swift_packages/building_swift_packages_or_apps_that_use_them_in_continuous_integration_workflows#3680255). +`xcodebuild` by default uses Xcode's built-in Git tooling. If you want to use GitHub Deploy Keys as supported by this action, however, that version of Git will lack the necessary URL remapping. In this case, pass `-scmProvider system` to the `xcodebuild` command, as mentioned in [Apple's documentation](https://developer.apple.com/documentation/swift_packages/building_swift_packages_or_apps_that_use_them_in_continuous_integration_workflows#3680255). ## What this Action *cannot* do for you From a45226bfaf7df5d2db6d1a46d0a280d9185a4402 Mon Sep 17 00:00:00 2001 From: Maciej Pasternacki <52241383+maciejp-ro@users.noreply.github.com> Date: Fri, 11 Jun 2021 15:17:22 +0200 Subject: [PATCH 41/77] Use execFileSync to clean up (#80) execSync just started a second ssh-agent. `['-k']` argument was treated as options, it didn't have `stdio` set, so stdio was piped and returned (and ignored). --- cleanup.js | 5 ++--- dist/cleanup.js | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cleanup.js b/cleanup.js index 529fbe8..34b2bbc 100644 --- a/cleanup.js +++ b/cleanup.js @@ -1,12 +1,11 @@ const core = require('@actions/core'); -const { execSync } = require('child_process'); +const { execFileSync } = require('child_process'); const { sshAgent } = require('./paths.js'); try { // Kill the started SSH agent console.log('Stopping SSH agent'); - execSync(sshAgent, ['-k'], { stdio: 'inherit' }); - + execFileSync(sshAgent, ['-k'], { stdio: 'inherit' }); } catch (error) { console.log(error.message); console.log('Error stopping the SSH agent, proceeding anyway'); diff --git a/dist/cleanup.js b/dist/cleanup.js index 49024b4..38a3d56 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -123,14 +123,13 @@ module.exports = require("child_process"); /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { const core = __webpack_require__(470); -const { execSync } = __webpack_require__(129); +const { execFileSync } = __webpack_require__(129); const { sshAgent } = __webpack_require__(972); try { // Kill the started SSH agent console.log('Stopping SSH agent'); - execSync(sshAgent, ['-k'], { stdio: 'inherit' }); - + execFileSync(sshAgent, ['-k'], { stdio: 'inherit' }); } catch (error) { console.log(error.message); console.log('Error stopping the SSH agent, proceeding anyway'); From 5f066a372ec13036ab7cb9a8adf18c936f8d2043 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 11 Jun 2021 15:18:45 +0200 Subject: [PATCH 42/77] Prepare a 0.5.3 release --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b20a09b..87c63a9 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ jobs: ... steps: - actions/checkout@v2 - # Make sure the @v0.5.2 matches the current version of the + # Make sure the @v0.5.3 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.5.2 + - uses: webfactory/ssh-agent@v0.5.3 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -50,7 +50,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.5.2 + - uses: webfactory/ssh-agent@v0.5.3 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 1711bb1971ca6a9e7706524325d8e2853a191e6e Mon Sep 17 00:00:00 2001 From: Michael Hipp Date: Wed, 1 Sep 2021 11:40:04 -0700 Subject: [PATCH 43/77] Update GitHub deploy key log comment to specify public key --- dist/index.js | 2 +- index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 6dbf026..1de91a0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -170,7 +170,7 @@ try { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { - console.log(`Comment for key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); return; } diff --git a/index.js b/index.js index 1a4835e..1f2ebad 100644 --- a/index.js +++ b/index.js @@ -53,7 +53,7 @@ try { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { - console.log(`Comment for key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); return; } From bbd5513ed5e1a794add6cfc57776b346873803df Mon Sep 17 00:00:00 2001 From: Ror <47309835+rorcores@users.noreply.github.com> Date: Thu, 18 Nov 2021 02:58:33 -0800 Subject: [PATCH 44/77] Better explain in the README how to set up keys in repositories (#96) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 87c63a9..7725d64 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,14 @@ GitHub Actions only have access to the repository they run for. So, in order to ## Usage -1. Create an SSH key with sufficient access privileges. For security reasons, don't use your personal SSH key but set up a dedicated one for use in GitHub Actions. See below for a few hints if you are unsure about this step. +1. Generate a new SSH key with sufficient access privileges. For security reasons, don't use your personal SSH key but set up a dedicated one for use in GitHub Actions. See below for a few hints if you are unsure about this step. 2. Make sure you don't have a passphrase set on the private key. -3. In your repository, go to the *Settings > Secrets* menu and create a new secret. In this example, we'll call it `SSH_PRIVATE_KEY`. Put the contents of the *private* SSH key file into the contents field.
- This key should start with `-----BEGIN ... PRIVATE KEY-----`, consist of many lines and ends with `-----END ... PRIVATE KEY-----`. -4. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v2` line. +3. Add the public SSH key to the private repository you are pulling from during the Github Action as a 'Deploy Key'. +4. Add the private SSH key to the repository triggering the Github Action: + * In your repository, go to the *Settings > Secrets* menu and create a new secret. In this example, we'll call it `SSH_PRIVATE_KEY`. + * Put the contents of the *private* SSH key file into the contents field.
+ * This key should start with `-----BEGIN ... PRIVATE KEY-----`, consist of many lines and ends with `-----END ... PRIVATE KEY-----`. +5. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v2` line. ```yaml # .github/workflows/my-workflow.yml From 97348a2ec66ccf4c78659d447a6d34c87ddbb333 Mon Sep 17 00:00:00 2001 From: John Hamelink Date: Thu, 18 Nov 2021 11:27:08 +0000 Subject: [PATCH 45/77] Document on how to integrate with `docker/build-push-action` (#90) This change adds some extra clarification to the documentation to show how to setup the `docker/build-push-action` step with this action. This is very helpful when using buildkit's `RUN --mount=type=ssh`. We found this to be a little confusing and the GH issues we found on the matter didn't help! Co-authored-by: Matthias Pigulla --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 7725d64..9ad8ca8 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,19 @@ If you know that your favorite tool or platform of choice requires extra tweaks If you are using this action on container-based workflows, make sure the container has the necessary SSH binaries or package(s) installed. +### Using the `docker/build-push-action` Action + +If you are using the `docker/build-push-action`, and would like to pass the SSH key, you can do so by adding the following config to pass the socket file through: + +``` + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + ssh: | + default=${{ env.SSH_AUTH_SOCK }} +``` + ### Cargo's (Rust) Private Dependencies on Windows If you are using private repositories in your dependencies like this: From dc622c59e471395a5b1e0d96279fe835c87a218b Mon Sep 17 00:00:00 2001 From: "J.R. Mash" <2574997+jrmash@users.noreply.github.com> Date: Sat, 20 Nov 2021 03:21:38 -0800 Subject: [PATCH 46/77] Update to Reflect Supported GitHub Host Keys (#102) Closes #101 as well. Co-authored-by: Matthias Pigulla --- dist/index.js | 3 ++- index.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 1de91a0..8ab265a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -135,8 +135,9 @@ try { console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); fs.mkdirSync(homeSsh, { recursive: true }); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n'); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n'); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); console.log("Starting ssh-agent"); diff --git a/index.js b/index.js index 1f2ebad..e08d46f 100644 --- a/index.js +++ b/index.js @@ -18,8 +18,9 @@ try { console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); fs.mkdirSync(homeSsh, { recursive: true }); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n'); + fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n'); fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-dss AAAAB3NzaC1kc3MAAACBANGFW2P9xlGU3zWrymJgI/lKo//ZW2WfVtmbsUZJ5uyKArtlQOT2+WRhcg4979aFxgKdcsqAYW3/LS1T2km3jYW/vr4Uzn+dXWODVk5VlUiZ1HFOHf6s6ITcZvjvdbp6ZbpM+DuJT7Bw+h5Fx8Qt8I16oCZYmAPJRtu46o9C2zk1AAAAFQC4gdFGcSbp5Gr0Wd5Ay/jtcldMewAAAIATTgn4sY4Nem/FQE+XJlyUQptPWMem5fwOcWtSXiTKaaN0lkk2p2snz+EJvAGXGq9dTSWHyLJSM2W6ZdQDqWJ1k+cL8CARAqL+UMwF84CR0m3hj+wtVGD/J4G5kW2DBAf4/bqzP4469lT+dF2FRQ2L9JKXrCWcnhMtJUvua8dvnwAAAIB6C4nQfAA7x8oLta6tT+oCk2WQcydNsyugE8vLrHlogoWEicla6cWPk7oXSspbzUcfkjN3Qa6e74PhRkc7JdSdAlFzU3m7LMkXo1MHgkqNX8glxWNVqBSc0YRdbFdTkL0C6gtpklilhvuHQCdbgB3LBAikcRkDp+FCVkUgPC/7Rw==\n'); console.log("Starting ssh-agent"); From bc6614de941d01d2db028645df55d9375c4b9e77 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 20 Nov 2021 11:43:02 +0000 Subject: [PATCH 47/77] Update recent changes in the CHANGELOG file --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 618aa24..5012027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,65 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.5.4 [2021-11-21] + +### Fixed + + * Update changed GitHub Host Keys (#102, #101) + +### Changed + + * Various documentation (README) improvements and additions + * Change logging to more precisely state that _public_ keys are being printed + +## v0.5.3 [2021-06-11] + +### Fixed + + * Fixed cleanup phase to really terminate the ssh-agent (#80) + * Fix termination of ssh-agent also on workflow faiulre (#79) + +### Changed + + * Various documentation (README) improvements and additions + +## v0.5.2 [2021-04-07] + +### Fixed + + * Use case-insensitive regex matching when scanning key comments (#68, #70, #71) + +### Changed + + * Log when a key is _not_ used as a deploy key (#69) + +## v0.5.1 [2021-03-10] + +### Fixed + + * Fix deployment key mapping on Windows virtual environment by using SSH binaries from the Git + suite, terminate ssh-agent upon actio termination on Windows as well (#63) + * Handle ENOENT exceptions with a graceful message + +### Changed + + * Various documentation (README) improvements and additions + +## v0.5.0 [2021-02-19] + +### Added + + * Add support for GitHub Deployment Keys through key comments (#59). Fixes #30, closes #38. + * Support for container-based workflows and Windows (#17) + +### Fixed + + * Fix scripts/build.js to work on Windows (#38) + +### Changed + + * Various documentation (README) improvements and additions + ## v0.4.1 [2020-10-07] ### Fixed From fc49353b67b2b7c1e0e6a600572d01a69f2672dd Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Sat, 20 Nov 2021 11:43:25 +0000 Subject: [PATCH 48/77] Bump example version numbers in the README file --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9ad8ca8..a0acf8a 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ jobs: ... steps: - actions/checkout@v2 - # Make sure the @v0.5.3 matches the current version of the + # Make sure the @v0.5.4 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.5.3 + - uses: webfactory/ssh-agent@v0.5.4 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -53,7 +53,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.5.3 + - uses: webfactory/ssh-agent@v0.5.4 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 19a5c6646fa9e16855fdacd6fe051076a5efd78e Mon Sep 17 00:00:00 2001 From: Koshi Eguchi Date: Thu, 10 Feb 2022 15:26:18 +0900 Subject: [PATCH 49/77] Fix a typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0acf8a..7b5d206 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ As a side note, using `ssh-keyscan` without proper key verification is susceptib ## Creating SSH Keys In order to create a new SSH key, run `ssh-keygen -t ed25519 -a 100 -f path/to/keyfile`, as suggested in [this blog post](https://stribika.github.io/2015/01/04/secure-secure-shell.html). -If you need to work with some older server software and need RSA keys, tr `ssh-keygen -t rsa -b 4096 -o -f path/to/keyfile` instead. +If you need to work with some older server software and need RSA keys, try `ssh-keygen -t rsa -b 4096 -o -f path/to/keyfile` instead. Both commands will prompt you for a key passphrase and save the key in `path/to/keyfile`. In general, having a passphrase is a good thing, since it will keep the key encrypted on your disk. When using the key with this action, however, you need to make sure you don't From 5a6c248f3f4047fcb1c82fc9d5d86c6dc118e3c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 09:02:13 +0200 Subject: [PATCH 50/77] Bump @actions/core from 1.2.6 to 1.9.1 (#125) * Bump @actions/core from 1.2.6 to 1.9.1 Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.2.6 to 1.9.1. - [Release notes](https://github.com/actions/toolkit/releases) - [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md) - [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core) --- updated-dependencies: - dependency-name: "@actions/core" dependency-type: direct:development ... Signed-off-by: dependabot[bot] * Bump the copyright year * yarn.lock syntax updates * Rebuild dist/ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matthias Pigulla --- README.md | 2 +- dist/cleanup.js | 2371 ++++++++++++++++++++++++++++++++++++++++++++++- dist/index.js | 2364 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- yarn.lock | 34 +- 5 files changed, 4728 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 7b5d206..980cb40 100644 --- a/README.md +++ b/README.md @@ -219,4 +219,4 @@ developer looking for new challenges, we'd like to hear from you! - - -Copyright 2019 – 2021 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). +Copyright 2019 – 2022 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). diff --git a/dist/cleanup.js b/dist/cleanup.js index 38a3d56..dc2c19b 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -43,6 +43,175 @@ module.exports = /************************************************************************/ /******/ ({ +/***/ 16: +/***/ (function(module) { + +module.exports = require("tls"); + +/***/ }), + +/***/ 22: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function parse(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + let v; + const arr = new Uint8Array(16); // Parse ########-....-....-....-............ + + arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; + arr[1] = v >>> 16 & 0xff; + arr[2] = v >>> 8 & 0xff; + arr[3] = v & 0xff; // Parse ........-####-....-....-............ + + arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; + arr[5] = v & 0xff; // Parse ........-....-####-....-............ + + arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; + arr[7] = v & 0xff; // Parse ........-....-....-####-............ + + arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; + arr[9] = v & 0xff; // Parse ........-....-....-....-############ + // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) + + arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff; + arr[11] = v / 0x100000000 & 0xff; + arr[12] = v >>> 24 & 0xff; + arr[13] = v >>> 16 & 0xff; + arr[14] = v >>> 8 & 0xff; + arr[15] = v & 0xff; + return arr; +} + +var _default = parse; +exports.default = _default; + +/***/ }), + +/***/ 62: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "v1", { + enumerable: true, + get: function () { + return _v.default; + } +}); +Object.defineProperty(exports, "v3", { + enumerable: true, + get: function () { + return _v2.default; + } +}); +Object.defineProperty(exports, "v4", { + enumerable: true, + get: function () { + return _v3.default; + } +}); +Object.defineProperty(exports, "v5", { + enumerable: true, + get: function () { + return _v4.default; + } +}); +Object.defineProperty(exports, "NIL", { + enumerable: true, + get: function () { + return _nil.default; + } +}); +Object.defineProperty(exports, "version", { + enumerable: true, + get: function () { + return _version.default; + } +}); +Object.defineProperty(exports, "validate", { + enumerable: true, + get: function () { + return _validate.default; + } +}); +Object.defineProperty(exports, "stringify", { + enumerable: true, + get: function () { + return _stringify.default; + } +}); +Object.defineProperty(exports, "parse", { + enumerable: true, + get: function () { + return _parse.default; + } +}); + +var _v = _interopRequireDefault(__webpack_require__(893)); + +var _v2 = _interopRequireDefault(__webpack_require__(209)); + +var _v3 = _interopRequireDefault(__webpack_require__(733)); + +var _v4 = _interopRequireDefault(__webpack_require__(384)); + +var _nil = _interopRequireDefault(__webpack_require__(327)); + +var _version = _interopRequireDefault(__webpack_require__(695)); + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +var _parse = _interopRequireDefault(__webpack_require__(22)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/***/ }), + +/***/ 78: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _regex = _interopRequireDefault(__webpack_require__(456)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function validate(uuid) { + return typeof uuid === 'string' && _regex.default.test(uuid); +} + +var _default = validate; +exports.default = _default; + +/***/ }), + /***/ 82: /***/ (function(__unusedmodule, exports) { @@ -51,6 +220,7 @@ module.exports = // 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 @@ -65,6 +235,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 /***/ }), @@ -82,14 +272,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)); @@ -117,6 +320,278 @@ exports.issueCommand = issueCommand; 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 + + /***/ }), /***/ 175: @@ -136,6 +611,907 @@ try { } +/***/ }), + +/***/ 177: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkBypass = exports.getProxyUrl = void 0; +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === 'https:'; + if (checkBypass(reqUrl)) { + return undefined; + } + const proxyVar = (() => { + if (usingSsl) { + return process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + return process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + })(); + if (proxyVar) { + return new URL(proxyVar); + } + else { + return undefined; + } +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + const 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 + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (const 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; +//# sourceMappingURL=proxy.js.map + +/***/ }), + +/***/ 209: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _v = _interopRequireDefault(__webpack_require__(212)); + +var _md = _interopRequireDefault(__webpack_require__(803)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v3 = (0, _v.default)('v3', 0x30, _md.default); +var _default = v3; +exports.default = _default; + +/***/ }), + +/***/ 211: +/***/ (function(module) { + +module.exports = require("https"); + +/***/ }), + +/***/ 212: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = _default; +exports.URL = exports.DNS = void 0; + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +var _parse = _interopRequireDefault(__webpack_require__(22)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function stringToBytes(str) { + str = unescape(encodeURIComponent(str)); // UTF8 escape + + const bytes = []; + + for (let i = 0; i < str.length; ++i) { + bytes.push(str.charCodeAt(i)); + } + + return bytes; +} + +const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; +exports.DNS = DNS; +const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; +exports.URL = URL; + +function _default(name, version, hashfunc) { + function generateUUID(value, namespace, buf, offset) { + if (typeof value === 'string') { + value = stringToBytes(value); + } + + if (typeof namespace === 'string') { + namespace = (0, _parse.default)(namespace); + } + + if (namespace.length !== 16) { + throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)'); + } // Compute hash of namespace and value, Per 4.3 + // Future: Use spread syntax when supported on all platforms, e.g. `bytes = + // hashfunc([...namespace, ... value])` + + + let bytes = new Uint8Array(16 + value.length); + bytes.set(namespace); + bytes.set(value, namespace.length); + bytes = hashfunc(bytes); + bytes[6] = bytes[6] & 0x0f | version; + bytes[8] = bytes[8] & 0x3f | 0x80; + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = bytes[i]; + } + + return buf; + } + + return (0, _stringify.default)(bytes); + } // Function#name is not settable on some platforms (#270) + + + try { + generateUUID.name = name; // eslint-disable-next-line no-empty + } catch (err) {} // For CommonJS default export support + + + generateUUID.DNS = DNS; + generateUUID.URL = URL; + return generateUUID; +} + +/***/ }), + +/***/ 327: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _default = '00000000-0000-0000-0000-000000000000'; +exports.default = _default; + +/***/ }), + +/***/ 357: +/***/ (function(module) { + +module.exports = require("assert"); + +/***/ }), + +/***/ 384: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _v = _interopRequireDefault(__webpack_require__(212)); + +var _sha = _interopRequireDefault(__webpack_require__(498)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v5 = (0, _v.default)('v5', 0x50, _sha.default); +var _default = v5; +exports.default = _default; + +/***/ }), + +/***/ 411: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ +const byteToHex = []; + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).substr(1)); +} + +function stringify(arr, offset = 0) { + // Note: Be careful editing this code! It's been tuned for performance + // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 + const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID. If this throws, it's likely due to one + // of the following: + // - One or more input array values don't map to a hex octet (leading to + // "undefined" in the uuid) + // - Invalid input values for the RFC `version` or `variant` fields + + if (!(0, _validate.default)(uuid)) { + throw TypeError('Stringified UUID is invalid'); + } + + return uuid; +} + +var _default = stringify; +exports.default = _default; + +/***/ }), + +/***/ 413: +/***/ (function(module, __unusedexports, __webpack_require__) { + +module.exports = __webpack_require__(141); + + +/***/ }), + +/***/ 417: +/***/ (function(module) { + +module.exports = require("crypto"); + +/***/ }), + +/***/ 425: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +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) { + 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.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; +const http = __importStar(__webpack_require__(605)); +const https = __importStar(__webpack_require__(211)); +const pm = __importStar(__webpack_require__(177)); +const tunnel = __importStar(__webpack_require__(413)); +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) { + const 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 __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + 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) { + const 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 __awaiter(this, void 0, void 0, function* () { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + 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 + */ + getJson(requestUrl, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const 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); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const 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); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const 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); + const res = yield 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 + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) { + if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + 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 (response.message.statusCode && + HttpRedirectCodes.includes(response.message.statusCode) && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + const 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. + yield response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const 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 = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || + !HttpResponseRetryCodes.includes(response.message.statusCode)) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + 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 __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) { + reject(err); + } + else if (!res) { + // If `err` is not passed, then `res` must be passed. + reject(new Error('Unknown error')); + } + else { + resolve(res); + } + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === 'string') { + if (!info.options.headers) { + info.options.headers = {}; + } + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + const res = new HttpClientResponse(msg); + handleResult(undefined, res); + }); + let socket; + 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}`)); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err); + }); + 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) { + const 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) { + for (const handler of this.handlers) { + handler.prepareRequest(info.options); + } + } + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const 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; + } + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, ((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 }; + 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) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode === HttpCodes.NotFound) { + resolve(response); + } + // get the result from the body + function dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + const a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, 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})`; + } + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + })); + }); + } +} +exports.HttpClient = HttpClient; +const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); +//# sourceMappingURL=index.js.map + /***/ }), /***/ 431: @@ -143,14 +1519,27 @@ try { "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); /** @@ -224,11 +1613,45 @@ function escapeProperty(s) { /***/ }), +/***/ 456: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +exports.default = _default; + +/***/ }), + /***/ 470: /***/ (function(__unusedmodule, exports, __webpack_require__) { "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) { @@ -238,19 +1661,15 @@ 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 uuid_1 = __webpack_require__(62); +const oidc_utils_1 = __webpack_require__(742); /** * The code to exit an action */ @@ -279,7 +1698,14 @@ function exportVariable(name, val) { process.env[name] = convertedVal; const filePath = process.env['GITHUB_ENV'] || ''; if (filePath) { - const delimiter = '_GitHubActionsFileCommandDelimeter_'; + const delimiter = `ghadelimiter_${uuid_1.v4()}`; + // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter. + if (name.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedVal.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; file_command_1.issueCommand('ENV', commandValue); } @@ -312,7 +1738,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. @@ -323,9 +1751,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. * @@ -334,6 +1802,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; @@ -380,19 +1849,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 @@ -465,10 +1945,230 @@ 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; +/** + * Summary exports + */ +var summary_1 = __webpack_require__(665); +Object.defineProperty(exports, "summary", { enumerable: true, get: function () { return summary_1.summary; } }); +/** + * @deprecated use core.summary + */ +var summary_2 = __webpack_require__(665); +Object.defineProperty(exports, "markdownSummary", { enumerable: true, get: function () { return summary_2.markdownSummary; } }); +/** + * Path exports + */ +var path_utils_1 = __webpack_require__(573); +Object.defineProperty(exports, "toPosixPath", { enumerable: true, get: function () { return path_utils_1.toPosixPath; } }); +Object.defineProperty(exports, "toWin32Path", { enumerable: true, get: function () { return path_utils_1.toWin32Path; } }); +Object.defineProperty(exports, "toPlatformPath", { enumerable: true, get: function () { return path_utils_1.toPlatformPath; } }); //# sourceMappingURL=core.js.map /***/ }), +/***/ 498: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _crypto = _interopRequireDefault(__webpack_require__(417)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function sha1(bytes) { + if (Array.isArray(bytes)) { + bytes = Buffer.from(bytes); + } else if (typeof bytes === 'string') { + bytes = Buffer.from(bytes, 'utf8'); + } + + return _crypto.default.createHash('sha1').update(bytes).digest(); +} + +var _default = sha1; +exports.default = _default; + +/***/ }), + +/***/ 554: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +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.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +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) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Bearer ${this.token}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +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) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; +//# sourceMappingURL=auth.js.map + +/***/ }), + +/***/ 573: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"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; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = void 0; +const path = __importStar(__webpack_require__(622)); +/** + * toPosixPath converts the given path to the posix form. On Windows, \\ will be + * replaced with /. + * + * @param pth. Path to transform. + * @return string Posix path. + */ +function toPosixPath(pth) { + return pth.replace(/[\\]/g, '/'); +} +exports.toPosixPath = toPosixPath; +/** + * toWin32Path converts the given path to the win32 form. On Linux, / will be + * replaced with \\. + * + * @param pth. Path to transform. + * @return string Win32 path. + */ +function toWin32Path(pth) { + return pth.replace(/[/]/g, '\\'); +} +exports.toWin32Path = toWin32Path; +/** + * toPlatformPath converts the given path to a platform-specific path. It does + * this by replacing instances of / and \ with the platform-specific path + * separator. + * + * @param pth The path to platformize. + * @return string The platform-specific path. + */ +function toPlatformPath(pth) { + return pth.replace(/[/\\]/g, path.sep); +} +exports.toPlatformPath = toPlatformPath; +//# sourceMappingURL=path-utils.js.map + +/***/ }), + +/***/ 605: +/***/ (function(module) { + +module.exports = require("http"); + +/***/ }), + +/***/ 614: +/***/ (function(module) { + +module.exports = require("events"); + +/***/ }), + /***/ 622: /***/ (function(module) { @@ -476,6 +2176,466 @@ module.exports = require("path"); /***/ }), +/***/ 631: +/***/ (function(module) { + +module.exports = require("net"); + +/***/ }), + +/***/ 665: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +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.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0; +const os_1 = __webpack_require__(87); +const fs_1 = __webpack_require__(747); +const { access, appendFile, writeFile } = fs_1.promises; +exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'; +exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'; +class Summary { + constructor() { + this._buffer = ''; + } + /** + * Finds the summary file path from the environment, rejects if env var is not found or file does not exist + * Also checks r/w permissions. + * + * @returns step summary file path + */ + filePath() { + return __awaiter(this, void 0, void 0, function* () { + if (this._filePath) { + return this._filePath; + } + const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR]; + if (!pathFromEnv) { + throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`); + } + try { + yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK); + } + catch (_a) { + throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`); + } + this._filePath = pathFromEnv; + return this._filePath; + }); + } + /** + * Wraps content in an HTML tag, adding any HTML attributes + * + * @param {string} tag HTML tag to wrap + * @param {string | null} content content within the tag + * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add + * + * @returns {string} content wrapped in HTML element + */ + wrap(tag, content, attrs = {}) { + const htmlAttrs = Object.entries(attrs) + .map(([key, value]) => ` ${key}="${value}"`) + .join(''); + if (!content) { + return `<${tag}${htmlAttrs}>`; + } + return `<${tag}${htmlAttrs}>${content}`; + } + /** + * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default. + * + * @param {SummaryWriteOptions} [options] (optional) options for write operation + * + * @returns {Promise} summary instance + */ + write(options) { + return __awaiter(this, void 0, void 0, function* () { + const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite); + const filePath = yield this.filePath(); + const writeFunc = overwrite ? writeFile : appendFile; + yield writeFunc(filePath, this._buffer, { encoding: 'utf8' }); + return this.emptyBuffer(); + }); + } + /** + * Clears the summary buffer and wipes the summary file + * + * @returns {Summary} summary instance + */ + clear() { + return __awaiter(this, void 0, void 0, function* () { + return this.emptyBuffer().write({ overwrite: true }); + }); + } + /** + * Returns the current summary buffer as a string + * + * @returns {string} string of summary buffer + */ + stringify() { + return this._buffer; + } + /** + * If the summary buffer is empty + * + * @returns {boolen} true if the buffer is empty + */ + isEmptyBuffer() { + return this._buffer.length === 0; + } + /** + * Resets the summary buffer without writing to summary file + * + * @returns {Summary} summary instance + */ + emptyBuffer() { + this._buffer = ''; + return this; + } + /** + * Adds raw text to the summary buffer + * + * @param {string} text content to add + * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) + * + * @returns {Summary} summary instance + */ + addRaw(text, addEOL = false) { + this._buffer += text; + return addEOL ? this.addEOL() : this; + } + /** + * Adds the operating system-specific end-of-line marker to the buffer + * + * @returns {Summary} summary instance + */ + addEOL() { + return this.addRaw(os_1.EOL); + } + /** + * Adds an HTML codeblock to the summary buffer + * + * @param {string} code content to render within fenced code block + * @param {string} lang (optional) language to syntax highlight code + * + * @returns {Summary} summary instance + */ + addCodeBlock(code, lang) { + const attrs = Object.assign({}, (lang && { lang })); + const element = this.wrap('pre', this.wrap('code', code), attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML list to the summary buffer + * + * @param {string[]} items list of items to render + * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) + * + * @returns {Summary} summary instance + */ + addList(items, ordered = false) { + const tag = ordered ? 'ol' : 'ul'; + const listItems = items.map(item => this.wrap('li', item)).join(''); + const element = this.wrap(tag, listItems); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML table to the summary buffer + * + * @param {SummaryTableCell[]} rows table rows + * + * @returns {Summary} summary instance + */ + addTable(rows) { + const tableBody = rows + .map(row => { + const cells = row + .map(cell => { + if (typeof cell === 'string') { + return this.wrap('td', cell); + } + const { header, data, colspan, rowspan } = cell; + const tag = header ? 'th' : 'td'; + const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan })); + return this.wrap(tag, data, attrs); + }) + .join(''); + return this.wrap('tr', cells); + }) + .join(''); + const element = this.wrap('table', tableBody); + return this.addRaw(element).addEOL(); + } + /** + * Adds a collapsable HTML details element to the summary buffer + * + * @param {string} label text for the closed state + * @param {string} content collapsable content + * + * @returns {Summary} summary instance + */ + addDetails(label, content) { + const element = this.wrap('details', this.wrap('summary', label) + content); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML image tag to the summary buffer + * + * @param {string} src path to the image you to embed + * @param {string} alt text description of the image + * @param {SummaryImageOptions} options (optional) addition image attributes + * + * @returns {Summary} summary instance + */ + addImage(src, alt, options) { + const { width, height } = options || {}; + const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height })); + const element = this.wrap('img', null, Object.assign({ src, alt }, attrs)); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML section heading element + * + * @param {string} text heading text + * @param {number | string} [level=1] (optional) the heading level, default: 1 + * + * @returns {Summary} summary instance + */ + addHeading(text, level) { + const tag = `h${level}`; + const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag) + ? tag + : 'h1'; + const element = this.wrap(allowedTag, text); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML thematic break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addSeparator() { + const element = this.wrap('hr', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML line break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addBreak() { + const element = this.wrap('br', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML blockquote to the summary buffer + * + * @param {string} text quote text + * @param {string} cite (optional) citation url + * + * @returns {Summary} summary instance + */ + addQuote(text, cite) { + const attrs = Object.assign({}, (cite && { cite })); + const element = this.wrap('blockquote', text, attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML anchor tag to the summary buffer + * + * @param {string} text link text/content + * @param {string} href hyperlink + * + * @returns {Summary} summary instance + */ + addLink(text, href) { + const element = this.wrap('a', text, { href }); + return this.addRaw(element).addEOL(); + } +} +const _summary = new Summary(); +/** + * @deprecated use `core.summary` + */ +exports.markdownSummary = _summary; +exports.summary = _summary; +//# sourceMappingURL=summary.js.map + +/***/ }), + +/***/ 669: +/***/ (function(module) { + +module.exports = require("util"); + +/***/ }), + +/***/ 695: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function version(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + return parseInt(uuid.substr(14, 1), 16); +} + +var _default = version; +exports.default = _default; + +/***/ }), + +/***/ 733: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _rng = _interopRequireDefault(__webpack_require__(844)); + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function v4(options, buf, offset) { + options = options || {}; + + const rnds = options.random || (options.rng || _rng.default)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + + + rnds[6] = rnds[6] & 0x0f | 0x40; + rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + + return buf; + } + + return (0, _stringify.default)(rnds); +} + +var _default = v4; +exports.default = _default; + +/***/ }), + +/***/ 742: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +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__(425); +const auth_1 = __webpack_require__(554); +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); + } + 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'); + } + return token; + } + 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 + +/***/ }), + /***/ 747: /***/ (function(module) { @@ -483,6 +2643,181 @@ module.exports = require("fs"); /***/ }), +/***/ 803: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _crypto = _interopRequireDefault(__webpack_require__(417)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function md5(bytes) { + if (Array.isArray(bytes)) { + bytes = Buffer.from(bytes); + } else if (typeof bytes === 'string') { + bytes = Buffer.from(bytes, 'utf8'); + } + + return _crypto.default.createHash('md5').update(bytes).digest(); +} + +var _default = md5; +exports.default = _default; + +/***/ }), + +/***/ 844: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = rng; + +var _crypto = _interopRequireDefault(__webpack_require__(417)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const rnds8Pool = new Uint8Array(256); // # of random values to pre-allocate + +let poolPtr = rnds8Pool.length; + +function rng() { + if (poolPtr > rnds8Pool.length - 16) { + _crypto.default.randomFillSync(rnds8Pool); + + poolPtr = 0; + } + + return rnds8Pool.slice(poolPtr, poolPtr += 16); +} + +/***/ }), + +/***/ 893: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _rng = _interopRequireDefault(__webpack_require__(844)); + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// **`v1()` - Generate time-based UUID** +// +// Inspired by https://github.com/LiosK/UUID.js +// and http://docs.python.org/library/uuid.html +let _nodeId; + +let _clockseq; // Previous uuid creation time + + +let _lastMSecs = 0; +let _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details + +function v1(options, buf, offset) { + let i = buf && offset || 0; + const b = buf || new Array(16); + options = options || {}; + let node = options.node || _nodeId; + let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not + // specified. We do this lazily to minimize issues related to insufficient + // system entropy. See #189 + + if (node == null || clockseq == null) { + const seedBytes = options.random || (options.rng || _rng.default)(); + + if (node == null) { + // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) + node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]]; + } + + if (clockseq == null) { + // Per 4.2.2, randomize (14 bit) clockseq + clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff; + } + } // UUID timestamps are 100 nano-second units since the Gregorian epoch, + // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so + // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' + // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. + + + let msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock + // cycle to simulate higher resolution clock + + let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs) + + const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression + + if (dt < 0 && options.clockseq === undefined) { + clockseq = clockseq + 1 & 0x3fff; + } // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new + // time interval + + + if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { + nsecs = 0; + } // Per 4.2.1.2 Throw error if too many uuids are requested + + + if (nsecs >= 10000) { + throw new Error("uuid.v1(): Can't create more than 10M uuids/sec"); + } + + _lastMSecs = msecs; + _lastNSecs = nsecs; + _clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + + msecs += 12219292800000; // `time_low` + + const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + b[i++] = tl >>> 24 & 0xff; + b[i++] = tl >>> 16 & 0xff; + b[i++] = tl >>> 8 & 0xff; + b[i++] = tl & 0xff; // `time_mid` + + const tmh = msecs / 0x100000000 * 10000 & 0xfffffff; + b[i++] = tmh >>> 8 & 0xff; + b[i++] = tmh & 0xff; // `time_high_and_version` + + b[i++] = tmh >>> 24 & 0xf | 0x10; // include version + + b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) + + b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low` + + b[i++] = clockseq & 0xff; // `node` + + for (let n = 0; n < 6; ++n) { + b[i + n] = node[n]; + } + + return buf || (0, _stringify.default)(b); +} + +var _default = v1; +exports.default = _default; + +/***/ }), + /***/ 972: /***/ (function(module, __unusedexports, __webpack_require__) { diff --git a/dist/index.js b/dist/index.js index 8ab265a..fe01c67 100644 --- a/dist/index.js +++ b/dist/index.js @@ -43,6 +43,175 @@ module.exports = /************************************************************************/ /******/ ({ +/***/ 16: +/***/ (function(module) { + +module.exports = require("tls"); + +/***/ }), + +/***/ 22: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function parse(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + let v; + const arr = new Uint8Array(16); // Parse ########-....-....-....-............ + + arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; + arr[1] = v >>> 16 & 0xff; + arr[2] = v >>> 8 & 0xff; + arr[3] = v & 0xff; // Parse ........-####-....-....-............ + + arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; + arr[5] = v & 0xff; // Parse ........-....-####-....-............ + + arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; + arr[7] = v & 0xff; // Parse ........-....-....-####-............ + + arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; + arr[9] = v & 0xff; // Parse ........-....-....-....-############ + // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) + + arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff; + arr[11] = v / 0x100000000 & 0xff; + arr[12] = v >>> 24 & 0xff; + arr[13] = v >>> 16 & 0xff; + arr[14] = v >>> 8 & 0xff; + arr[15] = v & 0xff; + return arr; +} + +var _default = parse; +exports.default = _default; + +/***/ }), + +/***/ 62: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "v1", { + enumerable: true, + get: function () { + return _v.default; + } +}); +Object.defineProperty(exports, "v3", { + enumerable: true, + get: function () { + return _v2.default; + } +}); +Object.defineProperty(exports, "v4", { + enumerable: true, + get: function () { + return _v3.default; + } +}); +Object.defineProperty(exports, "v5", { + enumerable: true, + get: function () { + return _v4.default; + } +}); +Object.defineProperty(exports, "NIL", { + enumerable: true, + get: function () { + return _nil.default; + } +}); +Object.defineProperty(exports, "version", { + enumerable: true, + get: function () { + return _version.default; + } +}); +Object.defineProperty(exports, "validate", { + enumerable: true, + get: function () { + return _validate.default; + } +}); +Object.defineProperty(exports, "stringify", { + enumerable: true, + get: function () { + return _stringify.default; + } +}); +Object.defineProperty(exports, "parse", { + enumerable: true, + get: function () { + return _parse.default; + } +}); + +var _v = _interopRequireDefault(__webpack_require__(893)); + +var _v2 = _interopRequireDefault(__webpack_require__(209)); + +var _v3 = _interopRequireDefault(__webpack_require__(733)); + +var _v4 = _interopRequireDefault(__webpack_require__(384)); + +var _nil = _interopRequireDefault(__webpack_require__(327)); + +var _version = _interopRequireDefault(__webpack_require__(695)); + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +var _parse = _interopRequireDefault(__webpack_require__(22)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/***/ }), + +/***/ 78: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _regex = _interopRequireDefault(__webpack_require__(456)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function validate(uuid) { + return typeof uuid === 'string' && _regex.default.test(uuid); +} + +var _default = validate; +exports.default = _default; + +/***/ }), + /***/ 82: /***/ (function(__unusedmodule, exports) { @@ -51,6 +220,7 @@ module.exports = // 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 @@ -65,6 +235,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 /***/ }), @@ -82,14 +272,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)); @@ -213,6 +416,560 @@ try { 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 + + +/***/ }), + +/***/ 177: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkBypass = exports.getProxyUrl = void 0; +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === 'https:'; + if (checkBypass(reqUrl)) { + return undefined; + } + const proxyVar = (() => { + if (usingSsl) { + return process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + return process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + })(); + if (proxyVar) { + return new URL(proxyVar); + } + else { + return undefined; + } +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + const 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 + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (const 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; +//# sourceMappingURL=proxy.js.map + +/***/ }), + +/***/ 209: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _v = _interopRequireDefault(__webpack_require__(212)); + +var _md = _interopRequireDefault(__webpack_require__(803)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v3 = (0, _v.default)('v3', 0x30, _md.default); +var _default = v3; +exports.default = _default; + +/***/ }), + +/***/ 211: +/***/ (function(module) { + +module.exports = require("https"); + +/***/ }), + +/***/ 212: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = _default; +exports.URL = exports.DNS = void 0; + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +var _parse = _interopRequireDefault(__webpack_require__(22)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function stringToBytes(str) { + str = unescape(encodeURIComponent(str)); // UTF8 escape + + const bytes = []; + + for (let i = 0; i < str.length; ++i) { + bytes.push(str.charCodeAt(i)); + } + + return bytes; +} + +const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; +exports.DNS = DNS; +const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; +exports.URL = URL; + +function _default(name, version, hashfunc) { + function generateUUID(value, namespace, buf, offset) { + if (typeof value === 'string') { + value = stringToBytes(value); + } + + if (typeof namespace === 'string') { + namespace = (0, _parse.default)(namespace); + } + + if (namespace.length !== 16) { + throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)'); + } // Compute hash of namespace and value, Per 4.3 + // Future: Use spread syntax when supported on all platforms, e.g. `bytes = + // hashfunc([...namespace, ... value])` + + + let bytes = new Uint8Array(16 + value.length); + bytes.set(namespace); + bytes.set(value, namespace.length); + bytes = hashfunc(bytes); + bytes[6] = bytes[6] & 0x0f | version; + bytes[8] = bytes[8] & 0x3f | 0x80; + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = bytes[i]; + } + + return buf; + } + + return (0, _stringify.default)(bytes); + } // Function#name is not settable on some platforms (#270) + + + try { + generateUUID.name = name; // eslint-disable-next-line no-empty + } catch (err) {} // For CommonJS default export support + + + generateUUID.DNS = DNS; + generateUUID.URL = URL; + return generateUUID; +} + +/***/ }), + +/***/ 327: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _default = '00000000-0000-0000-0000-000000000000'; +exports.default = _default; + +/***/ }), + +/***/ 357: +/***/ (function(module) { + +module.exports = require("assert"); + +/***/ }), + +/***/ 384: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _v = _interopRequireDefault(__webpack_require__(212)); + +var _sha = _interopRequireDefault(__webpack_require__(498)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const v5 = (0, _v.default)('v5', 0x50, _sha.default); +var _default = v5; +exports.default = _default; + +/***/ }), + +/***/ 411: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ +const byteToHex = []; + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).substr(1)); +} + +function stringify(arr, offset = 0) { + // Note: Be careful editing this code! It's been tuned for performance + // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 + const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID. If this throws, it's likely due to one + // of the following: + // - One or more input array values don't map to a hex octet (leading to + // "undefined" in the uuid) + // - Invalid input values for the RFC `version` or `variant` fields + + if (!(0, _validate.default)(uuid)) { + throw TypeError('Stringified UUID is invalid'); + } + + return uuid; +} + +var _default = stringify; +exports.default = _default; + +/***/ }), + +/***/ 413: +/***/ (function(module, __unusedexports, __webpack_require__) { + +module.exports = __webpack_require__(141); + + /***/ }), /***/ 417: @@ -222,19 +979,644 @@ module.exports = require("crypto"); /***/ }), +/***/ 425: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +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) { + 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.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; +const http = __importStar(__webpack_require__(605)); +const https = __importStar(__webpack_require__(211)); +const pm = __importStar(__webpack_require__(177)); +const tunnel = __importStar(__webpack_require__(413)); +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) { + const 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 __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + 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) { + const 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 __awaiter(this, void 0, void 0, function* () { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + 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 + */ + getJson(requestUrl, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const 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); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const 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); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const 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); + const res = yield 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 + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) { + if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + 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 (response.message.statusCode && + HttpRedirectCodes.includes(response.message.statusCode) && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + const 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. + yield response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const 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 = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || + !HttpResponseRetryCodes.includes(response.message.statusCode)) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + 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 __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) { + reject(err); + } + else if (!res) { + // If `err` is not passed, then `res` must be passed. + reject(new Error('Unknown error')); + } + else { + resolve(res); + } + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === 'string') { + if (!info.options.headers) { + info.options.headers = {}; + } + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + const res = new HttpClientResponse(msg); + handleResult(undefined, res); + }); + let socket; + 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}`)); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err); + }); + 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) { + const 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) { + for (const handler of this.handlers) { + handler.prepareRequest(info.options); + } + } + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const 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; + } + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, ((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 }; + 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) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode === HttpCodes.NotFound) { + resolve(response); + } + // get the result from the body + function dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + const a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, 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})`; + } + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + })); + }); + } +} +exports.HttpClient = HttpClient; +const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); +//# sourceMappingURL=index.js.map + +/***/ }), + /***/ 431: /***/ (function(__unusedmodule, exports, __webpack_require__) { "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); /** @@ -308,11 +1690,45 @@ function escapeProperty(s) { /***/ }), +/***/ 456: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +exports.default = _default; + +/***/ }), + /***/ 470: /***/ (function(__unusedmodule, exports, __webpack_require__) { "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) { @@ -322,19 +1738,15 @@ 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 uuid_1 = __webpack_require__(62); +const oidc_utils_1 = __webpack_require__(742); /** * The code to exit an action */ @@ -363,7 +1775,14 @@ function exportVariable(name, val) { process.env[name] = convertedVal; const filePath = process.env['GITHUB_ENV'] || ''; if (filePath) { - const delimiter = '_GitHubActionsFileCommandDelimeter_'; + const delimiter = `ghadelimiter_${uuid_1.v4()}`; + // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter. + if (name.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedVal.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; file_command_1.issueCommand('ENV', commandValue); } @@ -396,7 +1815,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. @@ -407,9 +1828,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. * @@ -418,6 +1879,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; @@ -464,19 +1926,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 @@ -549,10 +2022,230 @@ 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; +/** + * Summary exports + */ +var summary_1 = __webpack_require__(665); +Object.defineProperty(exports, "summary", { enumerable: true, get: function () { return summary_1.summary; } }); +/** + * @deprecated use core.summary + */ +var summary_2 = __webpack_require__(665); +Object.defineProperty(exports, "markdownSummary", { enumerable: true, get: function () { return summary_2.markdownSummary; } }); +/** + * Path exports + */ +var path_utils_1 = __webpack_require__(573); +Object.defineProperty(exports, "toPosixPath", { enumerable: true, get: function () { return path_utils_1.toPosixPath; } }); +Object.defineProperty(exports, "toWin32Path", { enumerable: true, get: function () { return path_utils_1.toWin32Path; } }); +Object.defineProperty(exports, "toPlatformPath", { enumerable: true, get: function () { return path_utils_1.toPlatformPath; } }); //# sourceMappingURL=core.js.map /***/ }), +/***/ 498: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _crypto = _interopRequireDefault(__webpack_require__(417)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function sha1(bytes) { + if (Array.isArray(bytes)) { + bytes = Buffer.from(bytes); + } else if (typeof bytes === 'string') { + bytes = Buffer.from(bytes, 'utf8'); + } + + return _crypto.default.createHash('sha1').update(bytes).digest(); +} + +var _default = sha1; +exports.default = _default; + +/***/ }), + +/***/ 554: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +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.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +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) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Bearer ${this.token}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +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) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; +//# sourceMappingURL=auth.js.map + +/***/ }), + +/***/ 573: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"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; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = void 0; +const path = __importStar(__webpack_require__(622)); +/** + * toPosixPath converts the given path to the posix form. On Windows, \\ will be + * replaced with /. + * + * @param pth. Path to transform. + * @return string Posix path. + */ +function toPosixPath(pth) { + return pth.replace(/[\\]/g, '/'); +} +exports.toPosixPath = toPosixPath; +/** + * toWin32Path converts the given path to the win32 form. On Linux, / will be + * replaced with \\. + * + * @param pth. Path to transform. + * @return string Win32 path. + */ +function toWin32Path(pth) { + return pth.replace(/[/]/g, '\\'); +} +exports.toWin32Path = toWin32Path; +/** + * toPlatformPath converts the given path to a platform-specific path. It does + * this by replacing instances of / and \ with the platform-specific path + * separator. + * + * @param pth The path to platformize. + * @return string The platform-specific path. + */ +function toPlatformPath(pth) { + return pth.replace(/[/\\]/g, path.sep); +} +exports.toPlatformPath = toPlatformPath; +//# sourceMappingURL=path-utils.js.map + +/***/ }), + +/***/ 605: +/***/ (function(module) { + +module.exports = require("http"); + +/***/ }), + +/***/ 614: +/***/ (function(module) { + +module.exports = require("events"); + +/***/ }), + /***/ 622: /***/ (function(module) { @@ -560,6 +2253,466 @@ module.exports = require("path"); /***/ }), +/***/ 631: +/***/ (function(module) { + +module.exports = require("net"); + +/***/ }), + +/***/ 665: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +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.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0; +const os_1 = __webpack_require__(87); +const fs_1 = __webpack_require__(747); +const { access, appendFile, writeFile } = fs_1.promises; +exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'; +exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'; +class Summary { + constructor() { + this._buffer = ''; + } + /** + * Finds the summary file path from the environment, rejects if env var is not found or file does not exist + * Also checks r/w permissions. + * + * @returns step summary file path + */ + filePath() { + return __awaiter(this, void 0, void 0, function* () { + if (this._filePath) { + return this._filePath; + } + const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR]; + if (!pathFromEnv) { + throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`); + } + try { + yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK); + } + catch (_a) { + throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`); + } + this._filePath = pathFromEnv; + return this._filePath; + }); + } + /** + * Wraps content in an HTML tag, adding any HTML attributes + * + * @param {string} tag HTML tag to wrap + * @param {string | null} content content within the tag + * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add + * + * @returns {string} content wrapped in HTML element + */ + wrap(tag, content, attrs = {}) { + const htmlAttrs = Object.entries(attrs) + .map(([key, value]) => ` ${key}="${value}"`) + .join(''); + if (!content) { + return `<${tag}${htmlAttrs}>`; + } + return `<${tag}${htmlAttrs}>${content}`; + } + /** + * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default. + * + * @param {SummaryWriteOptions} [options] (optional) options for write operation + * + * @returns {Promise} summary instance + */ + write(options) { + return __awaiter(this, void 0, void 0, function* () { + const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite); + const filePath = yield this.filePath(); + const writeFunc = overwrite ? writeFile : appendFile; + yield writeFunc(filePath, this._buffer, { encoding: 'utf8' }); + return this.emptyBuffer(); + }); + } + /** + * Clears the summary buffer and wipes the summary file + * + * @returns {Summary} summary instance + */ + clear() { + return __awaiter(this, void 0, void 0, function* () { + return this.emptyBuffer().write({ overwrite: true }); + }); + } + /** + * Returns the current summary buffer as a string + * + * @returns {string} string of summary buffer + */ + stringify() { + return this._buffer; + } + /** + * If the summary buffer is empty + * + * @returns {boolen} true if the buffer is empty + */ + isEmptyBuffer() { + return this._buffer.length === 0; + } + /** + * Resets the summary buffer without writing to summary file + * + * @returns {Summary} summary instance + */ + emptyBuffer() { + this._buffer = ''; + return this; + } + /** + * Adds raw text to the summary buffer + * + * @param {string} text content to add + * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) + * + * @returns {Summary} summary instance + */ + addRaw(text, addEOL = false) { + this._buffer += text; + return addEOL ? this.addEOL() : this; + } + /** + * Adds the operating system-specific end-of-line marker to the buffer + * + * @returns {Summary} summary instance + */ + addEOL() { + return this.addRaw(os_1.EOL); + } + /** + * Adds an HTML codeblock to the summary buffer + * + * @param {string} code content to render within fenced code block + * @param {string} lang (optional) language to syntax highlight code + * + * @returns {Summary} summary instance + */ + addCodeBlock(code, lang) { + const attrs = Object.assign({}, (lang && { lang })); + const element = this.wrap('pre', this.wrap('code', code), attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML list to the summary buffer + * + * @param {string[]} items list of items to render + * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) + * + * @returns {Summary} summary instance + */ + addList(items, ordered = false) { + const tag = ordered ? 'ol' : 'ul'; + const listItems = items.map(item => this.wrap('li', item)).join(''); + const element = this.wrap(tag, listItems); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML table to the summary buffer + * + * @param {SummaryTableCell[]} rows table rows + * + * @returns {Summary} summary instance + */ + addTable(rows) { + const tableBody = rows + .map(row => { + const cells = row + .map(cell => { + if (typeof cell === 'string') { + return this.wrap('td', cell); + } + const { header, data, colspan, rowspan } = cell; + const tag = header ? 'th' : 'td'; + const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan })); + return this.wrap(tag, data, attrs); + }) + .join(''); + return this.wrap('tr', cells); + }) + .join(''); + const element = this.wrap('table', tableBody); + return this.addRaw(element).addEOL(); + } + /** + * Adds a collapsable HTML details element to the summary buffer + * + * @param {string} label text for the closed state + * @param {string} content collapsable content + * + * @returns {Summary} summary instance + */ + addDetails(label, content) { + const element = this.wrap('details', this.wrap('summary', label) + content); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML image tag to the summary buffer + * + * @param {string} src path to the image you to embed + * @param {string} alt text description of the image + * @param {SummaryImageOptions} options (optional) addition image attributes + * + * @returns {Summary} summary instance + */ + addImage(src, alt, options) { + const { width, height } = options || {}; + const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height })); + const element = this.wrap('img', null, Object.assign({ src, alt }, attrs)); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML section heading element + * + * @param {string} text heading text + * @param {number | string} [level=1] (optional) the heading level, default: 1 + * + * @returns {Summary} summary instance + */ + addHeading(text, level) { + const tag = `h${level}`; + const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag) + ? tag + : 'h1'; + const element = this.wrap(allowedTag, text); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML thematic break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addSeparator() { + const element = this.wrap('hr', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML line break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addBreak() { + const element = this.wrap('br', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML blockquote to the summary buffer + * + * @param {string} text quote text + * @param {string} cite (optional) citation url + * + * @returns {Summary} summary instance + */ + addQuote(text, cite) { + const attrs = Object.assign({}, (cite && { cite })); + const element = this.wrap('blockquote', text, attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML anchor tag to the summary buffer + * + * @param {string} text link text/content + * @param {string} href hyperlink + * + * @returns {Summary} summary instance + */ + addLink(text, href) { + const element = this.wrap('a', text, { href }); + return this.addRaw(element).addEOL(); + } +} +const _summary = new Summary(); +/** + * @deprecated use `core.summary` + */ +exports.markdownSummary = _summary; +exports.summary = _summary; +//# sourceMappingURL=summary.js.map + +/***/ }), + +/***/ 669: +/***/ (function(module) { + +module.exports = require("util"); + +/***/ }), + +/***/ 695: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _validate = _interopRequireDefault(__webpack_require__(78)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function version(uuid) { + if (!(0, _validate.default)(uuid)) { + throw TypeError('Invalid UUID'); + } + + return parseInt(uuid.substr(14, 1), 16); +} + +var _default = version; +exports.default = _default; + +/***/ }), + +/***/ 733: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _rng = _interopRequireDefault(__webpack_require__(844)); + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function v4(options, buf, offset) { + options = options || {}; + + const rnds = options.random || (options.rng || _rng.default)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + + + rnds[6] = rnds[6] & 0x0f | 0x40; + rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + + return buf; + } + + return (0, _stringify.default)(rnds); +} + +var _default = v4; +exports.default = _default; + +/***/ }), + +/***/ 742: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +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__(425); +const auth_1 = __webpack_require__(554); +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); + } + 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'); + } + return token; + } + 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 + +/***/ }), + /***/ 747: /***/ (function(module) { @@ -567,6 +2720,181 @@ module.exports = require("fs"); /***/ }), +/***/ 803: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _crypto = _interopRequireDefault(__webpack_require__(417)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function md5(bytes) { + if (Array.isArray(bytes)) { + bytes = Buffer.from(bytes); + } else if (typeof bytes === 'string') { + bytes = Buffer.from(bytes, 'utf8'); + } + + return _crypto.default.createHash('md5').update(bytes).digest(); +} + +var _default = md5; +exports.default = _default; + +/***/ }), + +/***/ 844: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = rng; + +var _crypto = _interopRequireDefault(__webpack_require__(417)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const rnds8Pool = new Uint8Array(256); // # of random values to pre-allocate + +let poolPtr = rnds8Pool.length; + +function rng() { + if (poolPtr > rnds8Pool.length - 16) { + _crypto.default.randomFillSync(rnds8Pool); + + poolPtr = 0; + } + + return rnds8Pool.slice(poolPtr, poolPtr += 16); +} + +/***/ }), + +/***/ 893: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _rng = _interopRequireDefault(__webpack_require__(844)); + +var _stringify = _interopRequireDefault(__webpack_require__(411)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// **`v1()` - Generate time-based UUID** +// +// Inspired by https://github.com/LiosK/UUID.js +// and http://docs.python.org/library/uuid.html +let _nodeId; + +let _clockseq; // Previous uuid creation time + + +let _lastMSecs = 0; +let _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details + +function v1(options, buf, offset) { + let i = buf && offset || 0; + const b = buf || new Array(16); + options = options || {}; + let node = options.node || _nodeId; + let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not + // specified. We do this lazily to minimize issues related to insufficient + // system entropy. See #189 + + if (node == null || clockseq == null) { + const seedBytes = options.random || (options.rng || _rng.default)(); + + if (node == null) { + // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) + node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]]; + } + + if (clockseq == null) { + // Per 4.2.2, randomize (14 bit) clockseq + clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff; + } + } // UUID timestamps are 100 nano-second units since the Gregorian epoch, + // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so + // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs' + // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00. + + + let msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock + // cycle to simulate higher resolution clock + + let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs) + + const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression + + if (dt < 0 && options.clockseq === undefined) { + clockseq = clockseq + 1 & 0x3fff; + } // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new + // time interval + + + if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) { + nsecs = 0; + } // Per 4.2.1.2 Throw error if too many uuids are requested + + + if (nsecs >= 10000) { + throw new Error("uuid.v1(): Can't create more than 10M uuids/sec"); + } + + _lastMSecs = msecs; + _lastNSecs = nsecs; + _clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + + msecs += 12219292800000; // `time_low` + + const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + b[i++] = tl >>> 24 & 0xff; + b[i++] = tl >>> 16 & 0xff; + b[i++] = tl >>> 8 & 0xff; + b[i++] = tl & 0xff; // `time_mid` + + const tmh = msecs / 0x100000000 * 10000 & 0xfffffff; + b[i++] = tmh >>> 8 & 0xff; + b[i++] = tmh & 0xff; // `time_high_and_version` + + b[i++] = tmh >>> 24 & 0xf | 0x10; // include version + + b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) + + b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low` + + b[i++] = clockseq & 0xff; // `node` + + for (let n = 0; n < 6; ++n) { + b[i + n] = node[n]; + } + + return buf || (0, _stringify.default)(b); +} + +var _default = v1; +exports.default = _default; + +/***/ }), + /***/ 972: /***/ (function(module, __unusedexports, __webpack_require__) { diff --git a/package.json b/package.json index bb68cef..cd99a7c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "author": "webfactory GmbH ", "license": "MIT", "devDependencies": { - "@actions/core": "^1.2.4", + "@actions/core": "^1.9.1", "@zeit/ncc": "^0.20.5" }, "scripts": { diff --git a/yarn.lock b/yarn.lock index 83381c3..25bef76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,12 +2,32 @@ # yarn lockfile v1 -"@actions/core@^1.2.4": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.6.tgz#a78d49f41a4def18e88ce47c2cac615d5694bf09" - integrity sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA== +"@actions/core@^1.9.1": + "integrity" "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==" + "resolved" "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz" + "version" "1.9.1" + dependencies: + "@actions/http-client" "^2.0.1" + "uuid" "^8.3.2" + +"@actions/http-client@^2.0.1": + "integrity" "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==" + "resolved" "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "tunnel" "^0.0.6" "@zeit/ncc@^0.20.5": - version "0.20.5" - resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.20.5.tgz#a41af6e6bcab4a58f4612bae6137f70bce0192e3" - integrity sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw== + "integrity" "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==" + "resolved" "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz" + "version" "0.20.5" + +"tunnel@^0.0.6": + "integrity" "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + "resolved" "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz" + "version" "0.0.6" + +"uuid@^8.3.2": + "integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + "version" "8.3.2" From 26e485b72da53538f103f191ddd325d2f2ef3771 Mon Sep 17 00:00:00 2001 From: Dan Walkes Date: Thu, 1 Sep 2022 01:03:13 -0600 Subject: [PATCH 51/77] Fix link to an issue in the README file (#120) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 980cb40..af8c95c 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ When using `ssh` to connect from the GitHub Action worker node to another machin ### Provide the SSH Key as a File -This Action is designed to pass the SSH key directly into `ssh-agent`; that is, the key is available in memory on the GitHub Action worker node, but never written to disk. As a consequence, you _cannot_ pass the key as a build argument or a mounted file into Docker containers that you build or run on the worker node. You _can_, however, mount the `ssh-agent` Unix socket into a Docker container that you _run_, set up the `SSH_AUTH_SOCK` env var and then use SSH from within the container (see #11). +This Action is designed to pass the SSH key directly into `ssh-agent`; that is, the key is available in memory on the GitHub Action worker node, but never written to disk. As a consequence, you _cannot_ pass the key as a build argument or a mounted file into Docker containers that you build or run on the worker node. You _can_, however, mount the `ssh-agent` Unix socket into a Docker container that you _run_, set up the `SSH_AUTH_SOCK` env var and then use SSH from within the container (see https://github.com/webfactory/ssh-agent/issues/11). ### Run `ssh-keyscan` to Add Host Keys for Additional Hosts From ea4c593dc940bb9cfd4199b2c55ea694da6c3cd0 Mon Sep 17 00:00:00 2001 From: Chung Tran Date: Wed, 19 Oct 2022 15:16:47 +0700 Subject: [PATCH 52/77] Update node from node12 to node16 (#132) This addresses the deprecation of Node 12 in GHA (https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/). --- CHANGELOG.md | 4 ++++ README.md | 6 +++--- action.yml | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5012027..6375e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.6.0 [2022-10-11] +### Changed +* Update node to node16 + ## v0.5.4 [2021-11-21] ### Fixed diff --git a/README.md b/README.md index af8c95c..328182b 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ jobs: ... steps: - actions/checkout@v2 - # Make sure the @v0.5.4 matches the current version of the + # Make sure the @v0.6.0 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.5.4 + - uses: webfactory/ssh-agent@v0.6.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -53,7 +53,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.5.4 + - uses: webfactory/ssh-agent@v0.6.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} diff --git a/action.yml b/action.yml index 16aaba6..5e782f5 100644 --- a/action.yml +++ b/action.yml @@ -7,7 +7,7 @@ inputs: ssh-auth-sock: description: 'Where to place the SSH Agent auth socket' runs: - using: 'node12' + using: 'node16' main: 'dist/index.js' post: 'dist/cleanup.js' post-if: 'always()' From 28cb4d85053f47c0effcf0d08da669f65683d1e5 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 19 Oct 2022 08:12:38 +0000 Subject: [PATCH 53/77] Prepare a 0.6.0 release --- CHANGELOG.md | 6 ++++-- package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6375e72..5075807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## v0.6.0 [2022-10-11] +## v0.6.0 [2022-10-19] + ### Changed -* Update node to node16 + + * Update the version of Node used by the action from 12 to 16 (https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/). ## v0.5.4 [2021-11-21] diff --git a/package.json b/package.json index cd99a7c..4086ca4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "webfactory-action-ssh-agent", "repository": "git@github.com:webfactory/ssh-agent.git", "description": "GitHub Action to set up ssh-agent with a private SSH key", - "version": "0.1.0", + "version": "0.6.0", "main": "index.js", "author": "webfactory GmbH ", "license": "MIT", From fbef2c7bd0be9b978b85dd4bca7a228ef9aef7eb Mon Sep 17 00:00:00 2001 From: Camilo Celis Guzman Date: Wed, 19 Oct 2022 10:41:11 +0000 Subject: [PATCH 54/77] Add an action input/flag to disable logging of public key information (#122) This commit adds the new `log-public-key` action input. Closes #122 (contains the suggested changes plus a few tweaks and documentation), fixes #100. Co-authored-by: Matthias Pigulla --- CHANGELOG.md | 2 ++ README.md | 9 +++++++++ action.yml | 4 ++++ dist/index.js | 6 ++++-- index.js | 6 ++++-- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5075807..2e27463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] + * Add the `log-public-key` input that can be used to turn off logging key identities (#122) + ## v0.6.0 [2022-10-19] ### Changed diff --git a/README.md b/README.md index 328182b..7924d98 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,16 @@ To support picking the right key in this use case, this action scans _key commen 3. For key comments containing such URLs, a Git config setting is written that uses [`url..insteadof`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf). It will redirect `git` requests to URLs starting with either `https://github.com/owner/repo` or `git@github.com:owner/repo` to a fake hostname/URL like `git@...some.hash...:owner/repo`. 4. An SSH configuration section is generated that applies to the fake hostname. It will map the SSH connection back to `github.com`, while at the same time pointing SSH to a file containing the appropriate key's public part. That will make SSH use the right key when connecting to GitHub.com. +## Action Inputs + +The following inputs can be used to control the action's behavior: + +* `ssh-private-key`: Required. Use this to provide the key(s) to load as GitHub Actions secrets. +* `ssh-auth-sock`: Can be used to control where the SSH agent socket will be placed. Ultimately affects the `$SSH_AUTH_SOCK` environment variable. +* `log-public-key`: Set this to `false` if you want to suppress logging of _public_ key information. To simplify debugging and since it contains public key information only, this is turned on by default. + ## Exported variables + The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables through the Github Actions core module. The `$SSH_AUTH_SOCK` is used by several applications like git or rsync to connect to the SSH authentication agent. The `$SSH_AGENT_PID` contains the process id of the agent. This is used to kill the agent in post job action. diff --git a/action.yml b/action.yml index 5e782f5..ec3dfd9 100644 --- a/action.yml +++ b/action.yml @@ -6,6 +6,10 @@ inputs: required: true ssh-auth-sock: description: 'Where to place the SSH Agent auth socket' + log-public-key: + description: 'Whether or not to log public key fingerprints' + required: false + default: true runs: using: 'node16' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index fe01c67..46f6582 100644 --- a/dist/index.js +++ b/dist/index.js @@ -326,6 +326,7 @@ const { home, sshAgent, sshAdd } = __webpack_require__(972); try { const privateKey = core.getInput('ssh-private-key'); + const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); @@ -374,8 +375,9 @@ try { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { - console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); - + if (logPublicKey) { + console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + } return; } diff --git a/index.js b/index.js index e08d46f..5dbc831 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ const { home, sshAgent, sshAdd } = require('./paths.js'); try { const privateKey = core.getInput('ssh-private-key'); + const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); @@ -54,8 +55,9 @@ try { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { - console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); - + if (logPublicKey) { + console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + } return; } From df2f741a87868c176697d790b56786571c94a82f Mon Sep 17 00:00:00 2001 From: Oktawian Chojnacki Date: Wed, 19 Oct 2022 13:27:50 +0200 Subject: [PATCH 55/77] Provide `gitPath` for Windows to avoid failures on `windows-2022` (GitHub-hosted runner) (#137) ### Problem: Observed error on `windows-2022` ([GitHub-hosted runner](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources)) that `git` command cannot be found. ### Issue: Cannot find git executable on on windows-2022 (GitHub-hosted runner) #136 ### Solution: This path improvement makes use of existing `path.js` to resolve and return correct `git.exe` path for Windows, leaving the executable name as it was for other operating systems. ### Caveats: No idea how and why this `c://progra~1//git//usr//bin//git.exe` mumbo-jumbo works but it apparently did for other executables so figured it should work for `git.exe` (and it does). --- cleanup.js | 4 ++-- dist/cleanup.js | 24 +++++++++++------------- dist/index.js | 38 ++++++++++++++++++-------------------- index.js | 18 +++++++++--------- paths.js | 20 +++++++++----------- 5 files changed, 49 insertions(+), 55 deletions(-) diff --git a/cleanup.js b/cleanup.js index 34b2bbc..6b7ab7d 100644 --- a/cleanup.js +++ b/cleanup.js @@ -1,11 +1,11 @@ const core = require('@actions/core'); const { execFileSync } = require('child_process'); -const { sshAgent } = require('./paths.js'); +const { sshAgentCmd } = require('./paths.js'); try { // Kill the started SSH agent console.log('Stopping SSH agent'); - execFileSync(sshAgent, ['-k'], { stdio: 'inherit' }); + execFileSync(sshAgentCmd, ['-k'], { stdio: 'inherit' }); } catch (error) { console.log(error.message); console.log('Error stopping the SSH agent, proceeding anyway'); diff --git a/dist/cleanup.js b/dist/cleanup.js index dc2c19b..f978a12 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -599,12 +599,12 @@ exports.debug = debug; // for test const core = __webpack_require__(470); const { execFileSync } = __webpack_require__(129); -const { sshAgent } = __webpack_require__(972); +const { sshAgentCmd } = __webpack_require__(972); try { // Kill the started SSH agent console.log('Stopping SSH agent'); - execFileSync(sshAgent, ['-k'], { stdio: 'inherit' }); + execFileSync(sshAgentCmd, ['-k'], { stdio: 'inherit' }); } catch (error) { console.log(error.message); console.log('Error stopping the SSH agent, proceeding anyway'); @@ -2824,23 +2824,21 @@ exports.default = _default; const os = __webpack_require__(87); module.exports = (process.env['OS'] != 'Windows_NT') ? { - // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent - home: os.userInfo().homedir, - sshAgent: 'ssh-agent', - sshAdd: 'ssh-add' - + homePath: os.userInfo().homedir, + sshAgentCmd: 'ssh-agent', + sshAddCmd: 'ssh-add', + gitCmd: 'git' } : { - - home: os.homedir(), - sshAgent: 'c://progra~1//git//usr//bin//ssh-agent.exe', - sshAdd: 'c://progra~1//git//usr//bin//ssh-add.exe' - + // Assuming GitHub hosted `windows-*` runners for now + homePath: os.homedir(), + sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', + gitCmd: 'c://progra~1//git//usr//bin//git.exe' }; - /***/ }) /******/ }); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 46f6582..20f7d61 100644 --- a/dist/index.js +++ b/dist/index.js @@ -322,7 +322,7 @@ const core = __webpack_require__(470); const child_process = __webpack_require__(129); const fs = __webpack_require__(747); const crypto = __webpack_require__(417); -const { home, sshAgent, sshAdd } = __webpack_require__(972); +const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = __webpack_require__(972); try { const privateKey = core.getInput('ssh-private-key'); @@ -334,7 +334,7 @@ try { return; } - const homeSsh = home + '/.ssh'; + const homeSsh = homePath + '/.ssh'; console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); @@ -349,7 +349,7 @@ try { const sshAgentArgs = (authSock && authSock.length > 0) ? ['-a', authSock] : []; // Extract auth socket path and agent pid and set them as job variables - child_process.execFileSync(sshAgent, sshAgentArgs).toString().split("\n").forEach(function(line) { + child_process.execFileSync(sshAgentCmd, sshAgentArgs).toString().split("\n").forEach(function(line) { const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(line); if (matches && matches.length > 0) { @@ -362,16 +362,16 @@ try { console.log("Adding private key(s) to agent"); privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { - child_process.execFileSync(sshAdd, ['-'], { input: key.trim() + "\n" }); + child_process.execFileSync(sshAddCmd, ['-'], { input: key.trim() + "\n" }); }); console.log("Key(s) added:"); - child_process.execFileSync(sshAdd, ['-l'], { stdio: 'inherit' }); + child_process.execFileSync(sshAddCmd, ['-l'], { stdio: 'inherit' }); console.log('Configuring deployment key(s)'); - child_process.execFileSync(sshAdd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { + child_process.execFileSync(sshAddCmd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { @@ -386,9 +386,9 @@ try { fs.writeFileSync(`${homeSsh}/key-${sha256}`, key + "\n", { mode: '600' }); - child_process.execSync(`git config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); const sshConfig = `\nHost key-${sha256}.github.com\n` + ` HostName github.com\n` @@ -2903,23 +2903,21 @@ exports.default = _default; const os = __webpack_require__(87); module.exports = (process.env['OS'] != 'Windows_NT') ? { - // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent - home: os.userInfo().homedir, - sshAgent: 'ssh-agent', - sshAdd: 'ssh-add' - + homePath: os.userInfo().homedir, + sshAgentCmd: 'ssh-agent', + sshAddCmd: 'ssh-add', + gitCmd: 'git' } : { - - home: os.homedir(), - sshAgent: 'c://progra~1//git//usr//bin//ssh-agent.exe', - sshAdd: 'c://progra~1//git//usr//bin//ssh-add.exe' - + // Assuming GitHub hosted `windows-*` runners for now + homePath: os.homedir(), + sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', + gitCmd: 'c://progra~1//git//usr//bin//git.exe' }; - /***/ }) /******/ }); \ No newline at end of file diff --git a/index.js b/index.js index 5dbc831..0384fc4 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ const core = require('@actions/core'); const child_process = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const { home, sshAgent, sshAdd } = require('./paths.js'); +const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = require('./paths.js'); try { const privateKey = core.getInput('ssh-private-key'); @@ -14,7 +14,7 @@ try { return; } - const homeSsh = home + '/.ssh'; + const homeSsh = homePath + '/.ssh'; console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); @@ -29,7 +29,7 @@ try { const sshAgentArgs = (authSock && authSock.length > 0) ? ['-a', authSock] : []; // Extract auth socket path and agent pid and set them as job variables - child_process.execFileSync(sshAgent, sshAgentArgs).toString().split("\n").forEach(function(line) { + child_process.execFileSync(sshAgentCmd, sshAgentArgs).toString().split("\n").forEach(function(line) { const matches = /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=(.*); export \1/.exec(line); if (matches && matches.length > 0) { @@ -42,16 +42,16 @@ try { console.log("Adding private key(s) to agent"); privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { - child_process.execFileSync(sshAdd, ['-'], { input: key.trim() + "\n" }); + child_process.execFileSync(sshAddCmd, ['-'], { input: key.trim() + "\n" }); }); console.log("Key(s) added:"); - child_process.execFileSync(sshAdd, ['-l'], { stdio: 'inherit' }); + child_process.execFileSync(sshAddCmd, ['-l'], { stdio: 'inherit' }); console.log('Configuring deployment key(s)'); - child_process.execFileSync(sshAdd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { + child_process.execFileSync(sshAddCmd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { @@ -66,9 +66,9 @@ try { fs.writeFileSync(`${homeSsh}/key-${sha256}`, key + "\n", { mode: '600' }); - child_process.execSync(`git config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); - child_process.execSync(`git config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); const sshConfig = `\nHost key-${sha256}.github.com\n` + ` HostName github.com\n` diff --git a/paths.js b/paths.js index 8ee7afd..edeb197 100644 --- a/paths.js +++ b/paths.js @@ -1,18 +1,16 @@ const os = require('os'); module.exports = (process.env['OS'] != 'Windows_NT') ? { - // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent - home: os.userInfo().homedir, - sshAgent: 'ssh-agent', - sshAdd: 'ssh-add' - + homePath: os.userInfo().homedir, + sshAgentCmd: 'ssh-agent', + sshAddCmd: 'ssh-add', + gitCmd: 'git' } : { - - home: os.homedir(), - sshAgent: 'c://progra~1//git//usr//bin//ssh-agent.exe', - sshAdd: 'c://progra~1//git//usr//bin//ssh-add.exe' - + // Assuming GitHub hosted `windows-*` runners for now + homePath: os.homedir(), + sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', + gitCmd: 'c://progra~1//git//usr//bin//git.exe' }; - From 2c78a1c5d15acc4372e150ef44d488decbd74863 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 19 Oct 2022 11:28:57 +0000 Subject: [PATCH 56/77] Update the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e27463..742160a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] * Add the `log-public-key` input that can be used to turn off logging key identities (#122) + * Fix path to `git` binary on Windows, assuming GitHub-hosted runners (#136, #137) ## v0.6.0 [2022-10-19] From b19b28d091cde6811b86fbb6ef3a8322a40e4dc7 Mon Sep 17 00:00:00 2001 From: j-riebe <52535122+j-riebe@users.noreply.github.com> Date: Wed, 19 Oct 2022 13:32:32 +0200 Subject: [PATCH 57/77] Explain how to use `docker/build-push-action` with deployment-key configs (#133) This PR adds a recipe for using `docker/build-push-action` with multiple Deploy Keys (#78) to the docs. --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7924d98..63d86fd 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ If you are using this action on container-based workflows, make sure the contain If you are using the `docker/build-push-action`, and would like to pass the SSH key, you can do so by adding the following config to pass the socket file through: -``` +```yml - name: Build and push id: docker_build uses: docker/build-push-action@v2 @@ -123,6 +123,36 @@ If you are using the `docker/build-push-action`, and would like to pass the SSH default=${{ env.SSH_AUTH_SOCK }} ``` +### Using the `docker/build-push-action` Action together with multiple Deploy Keys + +If you use the `docker/build-push-action` and want to use multiple GitHub deploy keys, you need to copy the git and ssh configuration to the container during the build. Otherwise, the Docker build process would still not know how to handle multiple deploy keys. Even if the ssh agent was set up correctly on the runner. + +This requires an additional step in the actions workflow and two additional lines in the Dockerfile. + +Workflow: +```yml + - name: Prepare git and ssh config for build context + run: | + mkdir root-config + cp -r ~/.gitconfig ~/.ssh root-config/ + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + ssh: | + default=${{ env.SSH_AUTH_SOCK }} +``` + +Dockerfile: +```Dockerfile +COPY root-config /root/ +RUN sed 's|/home/runner|/root|g' -i.bak /root/.ssh/config +``` + +Have in mind that the Dockerfile now contains customized git and ssh configurations. If you don't want that in your final image, use multi-stage builds. + + ### Cargo's (Rust) Private Dependencies on Windows If you are using private repositories in your dependencies like this: From 0a7dc1c09fbbc386f7a8adb41c325e5a077eb8d3 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 19 Oct 2022 14:54:52 +0200 Subject: [PATCH 58/77] Avoid nonsensical log message (#139) This change avoids the `Comment for (public) key '' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.` log message that was caused by inappropriate parsing of `ssh-add -L` output and confused a lot of users already. --- dist/index.js | 2 +- index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 20f7d61..3326288 100644 --- a/dist/index.js +++ b/dist/index.js @@ -371,7 +371,7 @@ try { console.log('Configuring deployment key(s)'); - child_process.execFileSync(sshAddCmd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { + child_process.execFileSync(sshAddCmd, ['-L']).toString().trim().split(/\r?\n/).forEach(function(key) { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { diff --git a/index.js b/index.js index 0384fc4..add5f7c 100644 --- a/index.js +++ b/index.js @@ -51,7 +51,7 @@ try { console.log('Configuring deployment key(s)'); - child_process.execFileSync(sshAddCmd, ['-L']).toString().split(/\r?\n/).forEach(function(key) { + child_process.execFileSync(sshAddCmd, ['-L']).toString().trim().split(/\r?\n/).forEach(function(key) { const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); if (!parts) { From 8a9e20a5860cb7f6b3a1d92a74799581e054d850 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 19 Oct 2022 14:55:54 +0200 Subject: [PATCH 59/77] Fix path to `git` binary on Windows runners (#140) This PR fixes an apparently wrong path to the `git` binary that was added in #136. According to https://github.com/actions/checkout/discussions/928#discussioncomment-3861581, the path should not contain the `usr/` part, although for `ssh-add` and `ssh-agent`, it has to. --- dist/cleanup.js | 2 +- dist/index.js | 2 +- paths.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/cleanup.js b/dist/cleanup.js index f978a12..8af40c8 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -2835,7 +2835,7 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { homePath: os.homedir(), sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', - gitCmd: 'c://progra~1//git//usr//bin//git.exe' + gitCmd: 'c://progra~1//git//bin//git.exe' }; diff --git a/dist/index.js b/dist/index.js index 3326288..3039a0b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2914,7 +2914,7 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { homePath: os.homedir(), sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', - gitCmd: 'c://progra~1//git//usr//bin//git.exe' + gitCmd: 'c://progra~1//git//bin//git.exe' }; diff --git a/paths.js b/paths.js index edeb197..fa2c366 100644 --- a/paths.js +++ b/paths.js @@ -12,5 +12,5 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { homePath: os.homedir(), sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', - gitCmd: 'c://progra~1//git//usr//bin//git.exe' + gitCmd: 'c://progra~1//git//bin//git.exe' }; From 836c84ec59a0e7bc0eabc79988384eb567561ee2 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 19 Oct 2022 13:52:25 +0000 Subject: [PATCH 60/77] Prepare a v0.7.0 release --- CHANGELOG.md | 10 +++++++++- package.json | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 742160a..811af67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.7.0 [2022-10-19] + +### Added + * Add the `log-public-key` input that can be used to turn off logging key identities (#122) + +### Fixed + * Fix path to `git` binary on Windows, assuming GitHub-hosted runners (#136, #137) + * Fix a nonsensical log message (#139) ## v0.6.0 [2022-10-19] @@ -32,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * Fixed cleanup phase to really terminate the ssh-agent (#80) - * Fix termination of ssh-agent also on workflow faiulre (#79) + * Fix termination of ssh-agent also on workflow failure (#79) ### Changed diff --git a/package.json b/package.json index 4086ca4..e78f51b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "webfactory-action-ssh-agent", "repository": "git@github.com:webfactory/ssh-agent.git", "description": "GitHub Action to set up ssh-agent with a private SSH key", - "version": "0.6.0", + "version": "0.7.0", "main": "index.js", "author": "webfactory GmbH ", "license": "MIT", From 4512be8010f262a32b74d4b4a59300d76bdd43c6 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 25 Oct 2022 17:23:31 +0200 Subject: [PATCH 61/77] Update to `actions/checkout@v3` (#143) --- .github/workflows/demo.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 9d8fad0..1cfd2a1 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -8,7 +8,7 @@ jobs: os: [ ubuntu-latest, macOS-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup key uses: ./ with: @@ -28,7 +28,7 @@ jobs: container: image: ubuntu:latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: apt update && apt install -y openssh-client git - name: Setup key uses: ./ From 2996779c087b05cb911c8fefc8403a8af8d8f2d4 Mon Sep 17 00:00:00 2001 From: Patrick Higgins Date: Fri, 25 Nov 2022 10:40:39 -0800 Subject: [PATCH 62/77] Replace 0.6.0 references with 0.7.0 in README.md (#153) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 63d86fd..22c6a25 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ jobs: ... steps: - actions/checkout@v2 - # Make sure the @v0.6.0 matches the current version of the + # Make sure the @v0.7.0 matches the current version of the # action - - uses: webfactory/ssh-agent@v0.6.0 + - uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - ... other steps @@ -53,7 +53,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contens as before - - uses: webfactory/ssh-agent@v0.6.0 + - uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 18ff7066d37af765c4268af534ab1500bc3ea7f9 Mon Sep 17 00:00:00 2001 From: kjarkur <108679036+kjarkur@users.noreply.github.com> Date: Fri, 25 Nov 2022 10:44:41 -0800 Subject: [PATCH 63/77] Update README.md (#147) Update `actions/checkout` to `@v3` and make it syntactically correct in order to allow copy and paste. Co-authored-by: Matthias Pigulla --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 22c6a25..c2bdecb 100644 --- a/README.md +++ b/README.md @@ -35,13 +35,12 @@ jobs: my_job: ... steps: - - actions/checkout@v2 - # Make sure the @v0.7.0 matches the current version of the - # action + - uses: actions/checkout@v3 + # Make sure the @v0.7.0 matches the current version of the action - uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - ... other steps + # ... other steps ``` 5. If, for some reason, you need to change the location of the SSH agent socket, you can use the `ssh-auth-sock` input to provide a path. From 209e2d72ff4a448964d26610aceaaf1b3f8764c6 Mon Sep 17 00:00:00 2001 From: kjarkur <108679036+kjarkur@users.noreply.github.com> Date: Fri, 25 Nov 2022 10:45:57 -0800 Subject: [PATCH 64/77] Fix a typo in the README.md (#146) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2bdecb..c7f826a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ There are cases where you might need to use multiple keys. For example, "[deploy You can set up different keys as different secrets and pass them all to the action like so: ```yaml -# ... contens as before +# ... contents as before - uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: | From 6f828ccb51b0d042f4783ff45710decae9836d37 Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Fri, 27 Jan 2023 12:09:18 -0500 Subject: [PATCH 65/77] Allow the user to override the commands for `git`, `ssh-agent`, and `ssh-add` (#154) On my self-hosted Windows runners, the `git`, `ssh-agent`, and `ssh-add` commands are not located in the locations that are currently hard-coded in `paths.js`. With this PR, I am able to get this action to work on my runners as follows: ```yaml - uses: webfactory/ssh-agent@... with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} git-cmd: git ssh-agent-cmd: ssh-agent ssh-add-cmd: ssh-add ``` --- README.md | 3 +++ action.yml | 9 +++++++++ dist/cleanup.js | 12 ++++++------ dist/index.js | 22 +++++++++++++++------- index.js | 10 +++++++++- paths.js | 12 ++++++------ 6 files changed, 48 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c7f826a..8e2416b 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ The following inputs can be used to control the action's behavior: * `ssh-private-key`: Required. Use this to provide the key(s) to load as GitHub Actions secrets. * `ssh-auth-sock`: Can be used to control where the SSH agent socket will be placed. Ultimately affects the `$SSH_AUTH_SOCK` environment variable. * `log-public-key`: Set this to `false` if you want to suppress logging of _public_ key information. To simplify debugging and since it contains public key information only, this is turned on by default. +* `ssh-agent-cmd`: Optional. Use this to specify a custom location for the `ssh-agent` binary. +* `ssh-add-cmd`: Optional. Use this to specify a custom location for the `ssh-add` binary. +* `git-cmd`: Optional. Use this to specify a custom location for the `git` binary. ## Exported variables diff --git a/action.yml b/action.yml index ec3dfd9..4c54ef5 100644 --- a/action.yml +++ b/action.yml @@ -10,6 +10,15 @@ inputs: description: 'Whether or not to log public key fingerprints' required: false default: true + ssh-agent-cmd: + description: 'ssh-agent command' + required: false + ssh-add-cmd: + description: 'ssh-add command' + required: false + git-cmd: + description: 'git command' + required: false runs: using: 'node16' main: 'dist/index.js' diff --git a/dist/cleanup.js b/dist/cleanup.js index 8af40c8..bc904c0 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -2827,15 +2827,15 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent homePath: os.userInfo().homedir, - sshAgentCmd: 'ssh-agent', - sshAddCmd: 'ssh-add', - gitCmd: 'git' + sshAgentCmdDefault: 'ssh-agent', + sshAddCmdDefault: 'ssh-add', + gitCmdDefault: 'git' } : { // Assuming GitHub hosted `windows-*` runners for now homePath: os.homedir(), - sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', - sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', - gitCmd: 'c://progra~1//git//bin//git.exe' + sshAgentCmdDefault: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAddCmdDefault: 'c://progra~1//git//usr//bin//ssh-add.exe', + gitCmdDefault: 'c://progra~1//git//bin//git.exe' }; diff --git a/dist/index.js b/dist/index.js index 3039a0b..ce61449 100644 --- a/dist/index.js +++ b/dist/index.js @@ -322,12 +322,20 @@ const core = __webpack_require__(470); const child_process = __webpack_require__(129); const fs = __webpack_require__(747); const crypto = __webpack_require__(417); -const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = __webpack_require__(972); +const { homePath, sshAgentCmdDefault, sshAddCmdDefault, gitCmdDefault } = __webpack_require__(972); try { const privateKey = core.getInput('ssh-private-key'); const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); + const sshAgentCmdInput = core.getInput('ssh-agent-cmd'); + const sshAddCmdInput = core.getInput('ssh-add-cmd'); + const gitCmdInput = core.getInput('git-cmd'); + + const sshAgentCmd = sshAgentCmdInput ? sshAgentCmdInput : sshAgentCmdDefault + const sshAddCmd = sshAddCmdInput ? sshAddCmdInput : sshAddCmdDefault + const gitCmd = gitCmdInput ? gitCmdInput : gitCmdDefault + if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); @@ -2906,15 +2914,15 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent homePath: os.userInfo().homedir, - sshAgentCmd: 'ssh-agent', - sshAddCmd: 'ssh-add', - gitCmd: 'git' + sshAgentCmdDefault: 'ssh-agent', + sshAddCmdDefault: 'ssh-add', + gitCmdDefault: 'git' } : { // Assuming GitHub hosted `windows-*` runners for now homePath: os.homedir(), - sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', - sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', - gitCmd: 'c://progra~1//git//bin//git.exe' + sshAgentCmdDefault: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAddCmdDefault: 'c://progra~1//git//usr//bin//ssh-add.exe', + gitCmdDefault: 'c://progra~1//git//bin//git.exe' }; diff --git a/index.js b/index.js index add5f7c..549bc00 100644 --- a/index.js +++ b/index.js @@ -2,12 +2,20 @@ const core = require('@actions/core'); const child_process = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = require('./paths.js'); +const { homePath, sshAgentCmdDefault, sshAddCmdDefault, gitCmdDefault } = require('./paths.js'); try { const privateKey = core.getInput('ssh-private-key'); const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); + const sshAgentCmdInput = core.getInput('ssh-agent-cmd'); + const sshAddCmdInput = core.getInput('ssh-add-cmd'); + const gitCmdInput = core.getInput('git-cmd'); + + const sshAgentCmd = sshAgentCmdInput ? sshAgentCmdInput : sshAgentCmdDefault; + const sshAddCmd = sshAddCmdInput ? sshAddCmdInput : sshAddCmdDefault; + const gitCmd = gitCmdInput ? gitCmdInput : gitCmdDefault; + if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); diff --git a/paths.js b/paths.js index fa2c366..1c6fbf0 100644 --- a/paths.js +++ b/paths.js @@ -4,13 +4,13 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent homePath: os.userInfo().homedir, - sshAgentCmd: 'ssh-agent', - sshAddCmd: 'ssh-add', - gitCmd: 'git' + sshAgentCmdDefault: 'ssh-agent', + sshAddCmdDefault: 'ssh-add', + gitCmdDefault: 'git' } : { // Assuming GitHub hosted `windows-*` runners for now homePath: os.homedir(), - sshAgentCmd: 'c://progra~1//git//usr//bin//ssh-agent.exe', - sshAddCmd: 'c://progra~1//git//usr//bin//ssh-add.exe', - gitCmd: 'c://progra~1//git//bin//git.exe' + sshAgentCmdDefault: 'c://progra~1//git//usr//bin//ssh-agent.exe', + sshAddCmdDefault: 'c://progra~1//git//usr//bin//ssh-add.exe', + gitCmdDefault: 'c://progra~1//git//bin//git.exe' }; From 9fbc246995ce195568ea31d1110c161283c89684 Mon Sep 17 00:00:00 2001 From: j-riebe <52535122+j-riebe@users.noreply.github.com> Date: Fri, 27 Jan 2023 18:32:35 +0100 Subject: [PATCH 66/77] Clarify usage for Docker build processes, especially with deployment keys (#145) The current docs mention only `docker/build-push-action` in conjunction with deploy keys. This might mislead users to believe, that this only applies to said Action. But the concept applies to all workflows that somehow use `docker build` with deploy keys. This PR clarifies the relevant section. Co-authored-by: Matthias Pigulla --- README.md | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8e2416b..fdf936f 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,11 @@ If you know that your favorite tool or platform of choice requires extra tweaks If you are using this action on container-based workflows, make sure the container has the necessary SSH binaries or package(s) installed. -### Using the `docker/build-push-action` Action +### Building Docker Images and/or Using the `docker/build-push-action` Action -If you are using the `docker/build-push-action`, and would like to pass the SSH key, you can do so by adding the following config to pass the socket file through: +When you are building Docker images with `docker build` or `docker compose build` and need to provide the SSH keys to the build, don't forget to pass `--ssh default=${{ env.SSH_AUTH_SOCK }}` on the command line to pass the SSH agent socket through. See the [Docker documentation](https://docs.docker.com/engine/reference/commandline/buildx_build/#ssh) for more information on this option. + +If you are using the `docker/build-push-action`, you can do so by adding the following config. ```yml - name: Build and push @@ -125,35 +127,45 @@ If you are using the `docker/build-push-action`, and would like to pass the SSH default=${{ env.SSH_AUTH_SOCK }} ``` -### Using the `docker/build-push-action` Action together with multiple Deploy Keys +Make sure not to miss the next section, though. -If you use the `docker/build-push-action` and want to use multiple GitHub deploy keys, you need to copy the git and ssh configuration to the container during the build. Otherwise, the Docker build process would still not know how to handle multiple deploy keys. Even if the ssh agent was set up correctly on the runner. +### Using Multiple Deploy Keys Inside Docker Builds -This requires an additional step in the actions workflow and two additional lines in the Dockerfile. +When you pass the SSH agent socket to the Docker build environment _and_ want to use multiple GitHub deploy keys, you need to copy the Git and SSH configuration files to the build environment as well. This is necessary _in addition to_ forwarding the SSH agent socket into the build process. The config files are required so that Git can pick the right one from your deployment keys. + +This requires an additional step in the workflow file **after** the `ssh-agent` step and **before** the Docker build step. You also need two additional lines in the `Dockerfile` to actually copy the configs. + +The following example will: +* collect the necessary Git and SSH configuration files in a directory that must be part of the Docker build context so that... +* ... the files can be copied into the Docker image (or an intermediate build stage). Workflow: + ```yml - - name: Prepare git and ssh config for build context + - name: ssh-agent setup + ... + + - name: Collect Git and SSH config files in a directory that is part of the Docker build context run: | mkdir root-config cp -r ~/.gitconfig ~/.ssh root-config/ - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - ssh: | - default=${{ env.SSH_AUTH_SOCK }} + - name: Docker build + # build-push-action | docker [compose] build | etc. + ... ``` Dockerfile: + ```Dockerfile +# Copy the two files in place and fix different path/locations inside the Docker image COPY root-config /root/ RUN sed 's|/home/runner|/root|g' -i.bak /root/.ssh/config ``` -Have in mind that the Dockerfile now contains customized git and ssh configurations. If you don't want that in your final image, use multi-stage builds. +Keep in mind that the resulting Docker image now might contain these customized Git and SSH configuration files! Your private SSH keys are never written to files anywhere, just loaded into the SSH agent and forwarded into the container. The config files might, however, give away details about your build or development process and contain the names and URLs of your (private) repositories. You might want to use a multi-staged build to make sure these files do not end up in the final image. +If you still get the error message: `fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.`, you most likely forgot one of the steps above. ### Cargo's (Rust) Private Dependencies on Windows From ea17a056b908d85ca1f46c275ab91389eb65c9fa Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Sat, 28 Jan 2023 02:20:24 -0500 Subject: [PATCH 67/77] Add missing semicolons (#159) Follow-up to #154 --- dist/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index ce61449..bdebdb8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -332,9 +332,9 @@ try { const sshAddCmdInput = core.getInput('ssh-add-cmd'); const gitCmdInput = core.getInput('git-cmd'); - const sshAgentCmd = sshAgentCmdInput ? sshAgentCmdInput : sshAgentCmdDefault - const sshAddCmd = sshAddCmdInput ? sshAddCmdInput : sshAddCmdDefault - const gitCmd = gitCmdInput ? gitCmdInput : gitCmdDefault + const sshAgentCmd = sshAgentCmdInput ? sshAgentCmdInput : sshAgentCmdDefault; + const sshAddCmd = sshAddCmdInput ? sshAddCmdInput : sshAddCmdDefault; + const gitCmd = gitCmdInput ? gitCmdInput : gitCmdDefault; if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); From d4b9b8ff72958532804b70bbe600ad43b36d5f2e Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 24 Mar 2023 12:15:25 +0100 Subject: [PATCH 68/77] Stop adding GitHub SSH keys (#171) We need to fix the SSH keys shipped with this action: https://github.blog/2023-03-23-we-updated-our-rsa-ssh-host-key/ But, we have another issue (https://github.com/webfactory/ssh-agent/pull/108) with regards to host keys: On self-hosted runners which are not ephemeral the known_host file fills up with repeated entries, because every action run adds a new line with the same host keys. Also, on those machines, the old key will still be in the `known_hosts` file. IMHO this action should not be repsonsible for shipping SSH host keys, that's too much responsibility. This section in the code is a leftover from early days when GitHub provided runners did not include SSH keys at all. For a long time already, GH takes care of placing their SSH keys in their runner images. For self-hosted runners, those people setting up the runner should fetch and verify SSH keys themselves and put it into the `known_hosts` file. I know this is a breaking change and is going to annoy users. But on the other hand, there is no better opportunity to drop this feature than with an emergency-style key revocation as today. Closes #106, closes #129, closes #169, closes #170, closes #172. --- README.md | 2 +- dist/cleanup.js | 57 +++++++++++++++++++++++++++----------------- dist/index.js | 63 ++++++++++++++++++++++++++++--------------------- index.js | 6 ----- 4 files changed, 73 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index fdf936f..46c74a5 100644 --- a/README.md +++ b/README.md @@ -272,4 +272,4 @@ developer looking for new challenges, we'd like to hear from you! - - -Copyright 2019 – 2022 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). +Copyright 2019 – 2023 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE). diff --git a/dist/cleanup.js b/dist/cleanup.js index bc904c0..61fc276 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -292,13 +292,14 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.issueCommand = void 0; +exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; // We use any as a valid input type /* eslint-disable @typescript-eslint/no-explicit-any */ const fs = __importStar(__webpack_require__(747)); const os = __importStar(__webpack_require__(87)); +const uuid_1 = __webpack_require__(62); const utils_1 = __webpack_require__(82); -function issueCommand(command, message) { +function issueFileCommand(command, message) { const filePath = process.env[`GITHUB_${command}`]; if (!filePath) { throw new Error(`Unable to find environment variable for file command ${command}`); @@ -310,7 +311,22 @@ function issueCommand(command, message) { encoding: 'utf8' }); } -exports.issueCommand = issueCommand; +exports.issueFileCommand = issueFileCommand; +function prepareKeyValueMessage(key, value) { + const delimiter = `ghadelimiter_${uuid_1.v4()}`; + const convertedValue = utils_1.toCommandValue(value); + // These should realistically never happen, but just in case someone finds a + // way to exploit uuid generation let's not allow keys or values that contain + // the delimiter. + if (key.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedValue.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } + return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; +} +exports.prepareKeyValueMessage = prepareKeyValueMessage; //# sourceMappingURL=file-command.js.map /***/ }), @@ -1668,7 +1684,6 @@ 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 uuid_1 = __webpack_require__(62); const oidc_utils_1 = __webpack_require__(742); /** * The code to exit an action @@ -1698,20 +1713,9 @@ function exportVariable(name, val) { process.env[name] = convertedVal; const filePath = process.env['GITHUB_ENV'] || ''; if (filePath) { - const delimiter = `ghadelimiter_${uuid_1.v4()}`; - // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter. - if (name.includes(delimiter)) { - throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); - } - if (convertedVal.includes(delimiter)) { - throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); - } - const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; - file_command_1.issueCommand('ENV', commandValue); - } - else { - command_1.issueCommand('set-env', { name }, convertedVal); + return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val)); } + command_1.issueCommand('set-env', { name }, convertedVal); } exports.exportVariable = exportVariable; /** @@ -1729,7 +1733,7 @@ exports.setSecret = setSecret; function addPath(inputPath) { const filePath = process.env['GITHUB_PATH'] || ''; if (filePath) { - file_command_1.issueCommand('PATH', inputPath); + file_command_1.issueFileCommand('PATH', inputPath); } else { command_1.issueCommand('add-path', {}, inputPath); @@ -1769,7 +1773,10 @@ function getMultilineInput(name, options) { const inputs = getInput(name, options) .split('\n') .filter(x => x !== ''); - return inputs; + if (options && options.trimWhitespace === false) { + return inputs; + } + return inputs.map(input => input.trim()); } exports.getMultilineInput = getMultilineInput; /** @@ -1802,8 +1809,12 @@ exports.getBooleanInput = getBooleanInput; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { + const filePath = process.env['GITHUB_OUTPUT'] || ''; + if (filePath) { + return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value)); + } process.stdout.write(os.EOL); - command_1.issueCommand('set-output', { name }, value); + command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value)); } exports.setOutput = setOutput; /** @@ -1932,7 +1943,11 @@ exports.group = group; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function saveState(name, value) { - command_1.issueCommand('save-state', { name }, value); + const filePath = process.env['GITHUB_STATE'] || ''; + if (filePath) { + return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value)); + } + command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value)); } exports.saveState = saveState; /** diff --git a/dist/index.js b/dist/index.js index bdebdb8..2c23f23 100644 --- a/dist/index.js +++ b/dist/index.js @@ -292,13 +292,14 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.issueCommand = void 0; +exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; // We use any as a valid input type /* eslint-disable @typescript-eslint/no-explicit-any */ const fs = __importStar(__webpack_require__(747)); const os = __importStar(__webpack_require__(87)); +const uuid_1 = __webpack_require__(62); const utils_1 = __webpack_require__(82); -function issueCommand(command, message) { +function issueFileCommand(command, message) { const filePath = process.env[`GITHUB_${command}`]; if (!filePath) { throw new Error(`Unable to find environment variable for file command ${command}`); @@ -310,7 +311,22 @@ function issueCommand(command, message) { encoding: 'utf8' }); } -exports.issueCommand = issueCommand; +exports.issueFileCommand = issueFileCommand; +function prepareKeyValueMessage(key, value) { + const delimiter = `ghadelimiter_${uuid_1.v4()}`; + const convertedValue = utils_1.toCommandValue(value); + // These should realistically never happen, but just in case someone finds a + // way to exploit uuid generation let's not allow keys or values that contain + // the delimiter. + if (key.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedValue.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } + return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; +} +exports.prepareKeyValueMessage = prepareKeyValueMessage; //# sourceMappingURL=file-command.js.map /***/ }), @@ -343,13 +359,7 @@ try { } const homeSsh = homePath + '/.ssh'; - - console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); - fs.mkdirSync(homeSsh, { recursive: true }); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n'); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n'); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); console.log("Starting ssh-agent"); @@ -1755,7 +1765,6 @@ 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 uuid_1 = __webpack_require__(62); const oidc_utils_1 = __webpack_require__(742); /** * The code to exit an action @@ -1785,20 +1794,9 @@ function exportVariable(name, val) { process.env[name] = convertedVal; const filePath = process.env['GITHUB_ENV'] || ''; if (filePath) { - const delimiter = `ghadelimiter_${uuid_1.v4()}`; - // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter. - if (name.includes(delimiter)) { - throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); - } - if (convertedVal.includes(delimiter)) { - throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); - } - const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; - file_command_1.issueCommand('ENV', commandValue); - } - else { - command_1.issueCommand('set-env', { name }, convertedVal); + return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val)); } + command_1.issueCommand('set-env', { name }, convertedVal); } exports.exportVariable = exportVariable; /** @@ -1816,7 +1814,7 @@ exports.setSecret = setSecret; function addPath(inputPath) { const filePath = process.env['GITHUB_PATH'] || ''; if (filePath) { - file_command_1.issueCommand('PATH', inputPath); + file_command_1.issueFileCommand('PATH', inputPath); } else { command_1.issueCommand('add-path', {}, inputPath); @@ -1856,7 +1854,10 @@ function getMultilineInput(name, options) { const inputs = getInput(name, options) .split('\n') .filter(x => x !== ''); - return inputs; + if (options && options.trimWhitespace === false) { + return inputs; + } + return inputs.map(input => input.trim()); } exports.getMultilineInput = getMultilineInput; /** @@ -1889,8 +1890,12 @@ exports.getBooleanInput = getBooleanInput; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { + const filePath = process.env['GITHUB_OUTPUT'] || ''; + if (filePath) { + return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value)); + } process.stdout.write(os.EOL); - command_1.issueCommand('set-output', { name }, value); + command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value)); } exports.setOutput = setOutput; /** @@ -2019,7 +2024,11 @@ exports.group = group; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function saveState(name, value) { - command_1.issueCommand('save-state', { name }, value); + const filePath = process.env['GITHUB_STATE'] || ''; + if (filePath) { + return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value)); + } + command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value)); } exports.saveState = saveState; /** diff --git a/index.js b/index.js index 549bc00..0c2e08b 100644 --- a/index.js +++ b/index.js @@ -23,13 +23,7 @@ try { } const homeSsh = homePath + '/.ssh'; - - console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); - fs.mkdirSync(homeSsh, { recursive: true }); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n'); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n'); - fs.appendFileSync(`${homeSsh}/known_hosts`, '\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n'); console.log("Starting ssh-agent"); From fd34b8dee206fe74b288a5e61bc95fba2f1911eb Mon Sep 17 00:00:00 2001 From: wolf++ Date: Thu, 2 Nov 2023 09:03:13 -0700 Subject: [PATCH 69/77] Update README.md to reflect latest version (#196) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 46c74a5..96f0dae 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ jobs: ... steps: - uses: actions/checkout@v3 - # Make sure the @v0.7.0 matches the current version of the action - - uses: webfactory/ssh-agent@v0.7.0 + # Make sure the @v0.8.0 matches the current version of the action + - uses: webfactory/ssh-agent@v0.8.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} # ... other steps @@ -52,7 +52,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contents as before - - uses: webfactory/ssh-agent@v0.7.0 + - uses: webfactory/ssh-agent@v0.8.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From 2e59dd7d06f6a994bb6366e0639c13fb756e8bb6 Mon Sep 17 00:00:00 2001 From: Benjamin Ragheb Date: Mon, 5 Feb 2024 01:37:14 -0500 Subject: [PATCH 70/77] Remove outdated claim from README (#206) Since #171 was merged, this action no longer touches `known_hosts`; this line should have been removed from the README at that time. --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 96f0dae..253a405 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # `ssh-agent` GitHub Action -This action -* starts the `ssh-agent`, -* exports the `SSH_AUTH_SOCK` environment variable, -* loads one or several private SSH key into the agent and -* configures `known_hosts` for GitHub.com. +This action +* starts the `ssh-agent`, +* exports the `SSH_AUTH_SOCK` environment variable, and +* loads one or several private SSH key into the agent. It should work in all GitHub Actions virtual environments, including container-based workflows. From 9f6f312a31523794f99d8264ed8b069d8a395733 Mon Sep 17 00:00:00 2001 From: Felix Seifert Date: Tue, 6 Feb 2024 09:38:02 +0100 Subject: [PATCH 71/77] chore: update all versions of `actions/checkout` to v4 (#199) In this PR, I update the `actions/checkout` versions. When I recently started using this action, I was confused about whether it works with the newest checkout action. I tested it and everything is fine. For future users, we should display examples with the newest versions. --- .github/workflows/demo.yml | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 1cfd2a1..42a1e26 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -8,7 +8,7 @@ jobs: os: [ ubuntu-latest, macOS-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup key uses: ./ with: @@ -28,7 +28,7 @@ jobs: container: image: ubuntu:latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: apt update && apt install -y openssh-client git - name: Setup key uses: ./ diff --git a/README.md b/README.md index 253a405..cc19dbc 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ GitHub Actions only have access to the repository they run for. So, in order to * In your repository, go to the *Settings > Secrets* menu and create a new secret. In this example, we'll call it `SSH_PRIVATE_KEY`. * Put the contents of the *private* SSH key file into the contents field.
* This key should start with `-----BEGIN ... PRIVATE KEY-----`, consist of many lines and ends with `-----END ... PRIVATE KEY-----`. -5. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v2` line. +5. In your workflow definition file, add the following step. Preferably this would be rather on top, near the `actions/checkout@v4` line. ```yaml # .github/workflows/my-workflow.yml @@ -34,7 +34,7 @@ jobs: my_job: ... steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Make sure the @v0.8.0 matches the current version of the action - uses: webfactory/ssh-agent@v0.8.0 with: From 204eb35a4ecff7628304146c9d326f72b532fc1f Mon Sep 17 00:00:00 2001 From: Kevin Glavin Date: Tue, 6 Feb 2024 04:26:21 -0500 Subject: [PATCH 72/77] Bump to `node20` (#201) Fix for deprecated node16 Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: webfactory/ssh-agent@v0.8.0. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/ --- action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 4c54ef5..8f32cc8 100644 --- a/action.yml +++ b/action.yml @@ -20,10 +20,11 @@ inputs: description: 'git command' required: false runs: - using: 'node16' + using: 'node20' main: 'dist/index.js' post: 'dist/cleanup.js' post-if: 'always()' + branding: icon: loader color: 'yellow' From dc588b651fe13675774614f8e6a936a468676387 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Tue, 6 Feb 2024 10:28:20 +0100 Subject: [PATCH 73/77] Update version numbers in the README examples --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cc19dbc..3028201 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ jobs: ... steps: - uses: actions/checkout@v4 - # Make sure the @v0.8.0 matches the current version of the action - - uses: webfactory/ssh-agent@v0.8.0 + # Make sure the @v0.9.0 matches the current version of the action + - uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} # ... other steps @@ -51,7 +51,7 @@ You can set up different keys as different secrets and pass them all to the acti ```yaml # ... contents as before - - uses: webfactory/ssh-agent@v0.8.0 + - uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: | ${{ secrets.FIRST_KEY }} From b504c19775343714e11b8c754e4fe1f02dc7b8e7 Mon Sep 17 00:00:00 2001 From: Jano Paetzold Date: Wed, 8 Jan 2025 17:52:14 +0100 Subject: [PATCH 74/77] Update CHANGELOG.md --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 811af67..572a902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.9.0 [2024-02-06] + +### Changed + +* Update all versions of `actions/checkout` to v4 (#199) +* Update to Node 20 (#201) + +## v0.8.0 [2023-03-24] + +### Changed + +* No longer writing GitHub's SSH host keys to `known_hosts` (#171) +* Update to actions/checkout@v3 (#143) +* Allow the user to override the commands for git, ssh-agent, and ssh-add (#154) + ## v0.7.0 [2022-10-19] ### Added From e3f1a8e046525bfed3725ef54a31ca91aed399f4 Mon Sep 17 00:00:00 2001 From: Jano Paetzold Date: Wed, 8 Jan 2025 17:59:51 +0100 Subject: [PATCH 75/77] Acknowledge custom command inputs in cleanup.js (#235) Refactored version of https://github.com/webfactory/ssh-agent/pull/183. Fixes: https://github.com/webfactory/ssh-agent/issues/208 --- CHANGELOG.md | 4 +++ cleanup.js | 1 - dist/cleanup.js | 72 +++++++++++++++++++++---------------------- dist/index.js | 81 +++++++++++++++++++++---------------------------- index.js | 10 +----- paths.js | 14 ++++++++- 6 files changed, 87 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 572a902..2c733d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +* Fix path used to execute ssh-agent in cleanup.js to respect custom paths set by input (#235) + ## v0.9.0 [2024-02-06] ### Changed diff --git a/cleanup.js b/cleanup.js index 6b7ab7d..71c657b 100644 --- a/cleanup.js +++ b/cleanup.js @@ -1,4 +1,3 @@ -const core = require('@actions/core'); const { execFileSync } = require('child_process'); const { sshAgentCmd } = require('./paths.js'); diff --git a/dist/cleanup.js b/dist/cleanup.js index 61fc276..7e478b7 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -292,14 +292,13 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; +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)); const os = __importStar(__webpack_require__(87)); -const uuid_1 = __webpack_require__(62); const utils_1 = __webpack_require__(82); -function issueFileCommand(command, message) { +function issueCommand(command, message) { const filePath = process.env[`GITHUB_${command}`]; if (!filePath) { throw new Error(`Unable to find environment variable for file command ${command}`); @@ -311,22 +310,7 @@ function issueFileCommand(command, message) { encoding: 'utf8' }); } -exports.issueFileCommand = issueFileCommand; -function prepareKeyValueMessage(key, value) { - const delimiter = `ghadelimiter_${uuid_1.v4()}`; - const convertedValue = utils_1.toCommandValue(value); - // These should realistically never happen, but just in case someone finds a - // way to exploit uuid generation let's not allow keys or values that contain - // the delimiter. - if (key.includes(delimiter)) { - throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); - } - if (convertedValue.includes(delimiter)) { - throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); - } - return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; -} -exports.prepareKeyValueMessage = prepareKeyValueMessage; +exports.issueCommand = issueCommand; //# sourceMappingURL=file-command.js.map /***/ }), @@ -613,7 +597,6 @@ exports.debug = debug; // for test /***/ 175: /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { -const core = __webpack_require__(470); const { execFileSync } = __webpack_require__(129); const { sshAgentCmd } = __webpack_require__(972); @@ -1684,6 +1667,7 @@ 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 uuid_1 = __webpack_require__(62); const oidc_utils_1 = __webpack_require__(742); /** * The code to exit an action @@ -1713,9 +1697,20 @@ function exportVariable(name, val) { process.env[name] = convertedVal; const filePath = process.env['GITHUB_ENV'] || ''; if (filePath) { - return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val)); + const delimiter = `ghadelimiter_${uuid_1.v4()}`; + // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter. + if (name.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedVal.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } + const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; + file_command_1.issueCommand('ENV', commandValue); + } + else { + command_1.issueCommand('set-env', { name }, convertedVal); } - command_1.issueCommand('set-env', { name }, convertedVal); } exports.exportVariable = exportVariable; /** @@ -1733,7 +1728,7 @@ exports.setSecret = setSecret; function addPath(inputPath) { const filePath = process.env['GITHUB_PATH'] || ''; if (filePath) { - file_command_1.issueFileCommand('PATH', inputPath); + file_command_1.issueCommand('PATH', inputPath); } else { command_1.issueCommand('add-path', {}, inputPath); @@ -1773,10 +1768,7 @@ function getMultilineInput(name, options) { const inputs = getInput(name, options) .split('\n') .filter(x => x !== ''); - if (options && options.trimWhitespace === false) { - return inputs; - } - return inputs.map(input => input.trim()); + return inputs; } exports.getMultilineInput = getMultilineInput; /** @@ -1809,12 +1801,8 @@ exports.getBooleanInput = getBooleanInput; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { - const filePath = process.env['GITHUB_OUTPUT'] || ''; - if (filePath) { - return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value)); - } process.stdout.write(os.EOL); - command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value)); + command_1.issueCommand('set-output', { name }, value); } exports.setOutput = setOutput; /** @@ -1943,11 +1931,7 @@ exports.group = group; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function saveState(name, value) { - const filePath = process.env['GITHUB_STATE'] || ''; - if (filePath) { - return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value)); - } - command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value)); + command_1.issueCommand('save-state', { name }, value); } exports.saveState = saveState; /** @@ -2837,8 +2821,9 @@ exports.default = _default; /***/ (function(module, __unusedexports, __webpack_require__) { const os = __webpack_require__(87); +const core = __webpack_require__(470); -module.exports = (process.env['OS'] != 'Windows_NT') ? { +const defaults = (process.env['OS'] != 'Windows_NT') ? { // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent homePath: os.userInfo().homedir, @@ -2853,6 +2838,17 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { gitCmdDefault: 'c://progra~1//git//bin//git.exe' }; +const sshAgentCmdInput = core.getInput('ssh-agent-cmd'); +const sshAddCmdInput = core.getInput('ssh-add-cmd'); +const gitCmdInput = core.getInput('git-cmd'); + +module.exports = { + homePath: defaults.homePath, + sshAgentCmd: sshAgentCmdInput !== '' ? sshAgentCmdInput : defaults.sshAgentCmdDefault, + sshAddCmd: sshAddCmdInput !== '' ? sshAddCmdInput : defaults.sshAddCmdDefault, + gitCmd: gitCmdInput !== '' ? gitCmdInput : defaults.gitCmdDefault, +}; + /***/ }) diff --git a/dist/index.js b/dist/index.js index 2c23f23..3f288c7 100644 --- a/dist/index.js +++ b/dist/index.js @@ -292,14 +292,13 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; +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)); const os = __importStar(__webpack_require__(87)); -const uuid_1 = __webpack_require__(62); const utils_1 = __webpack_require__(82); -function issueFileCommand(command, message) { +function issueCommand(command, message) { const filePath = process.env[`GITHUB_${command}`]; if (!filePath) { throw new Error(`Unable to find environment variable for file command ${command}`); @@ -311,22 +310,7 @@ function issueFileCommand(command, message) { encoding: 'utf8' }); } -exports.issueFileCommand = issueFileCommand; -function prepareKeyValueMessage(key, value) { - const delimiter = `ghadelimiter_${uuid_1.v4()}`; - const convertedValue = utils_1.toCommandValue(value); - // These should realistically never happen, but just in case someone finds a - // way to exploit uuid generation let's not allow keys or values that contain - // the delimiter. - if (key.includes(delimiter)) { - throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); - } - if (convertedValue.includes(delimiter)) { - throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); - } - return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; -} -exports.prepareKeyValueMessage = prepareKeyValueMessage; +exports.issueCommand = issueCommand; //# sourceMappingURL=file-command.js.map /***/ }), @@ -338,20 +322,12 @@ const core = __webpack_require__(470); const child_process = __webpack_require__(129); const fs = __webpack_require__(747); const crypto = __webpack_require__(417); -const { homePath, sshAgentCmdDefault, sshAddCmdDefault, gitCmdDefault } = __webpack_require__(972); +const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = __webpack_require__(972); try { const privateKey = core.getInput('ssh-private-key'); const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); - const sshAgentCmdInput = core.getInput('ssh-agent-cmd'); - const sshAddCmdInput = core.getInput('ssh-add-cmd'); - const gitCmdInput = core.getInput('git-cmd'); - - const sshAgentCmd = sshAgentCmdInput ? sshAgentCmdInput : sshAgentCmdDefault; - const sshAddCmd = sshAddCmdInput ? sshAddCmdInput : sshAddCmdDefault; - const gitCmd = gitCmdInput ? gitCmdInput : gitCmdDefault; - if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); @@ -1765,6 +1741,7 @@ 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 uuid_1 = __webpack_require__(62); const oidc_utils_1 = __webpack_require__(742); /** * The code to exit an action @@ -1794,9 +1771,20 @@ function exportVariable(name, val) { process.env[name] = convertedVal; const filePath = process.env['GITHUB_ENV'] || ''; if (filePath) { - return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val)); + const delimiter = `ghadelimiter_${uuid_1.v4()}`; + // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter. + if (name.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedVal.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } + const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; + file_command_1.issueCommand('ENV', commandValue); + } + else { + command_1.issueCommand('set-env', { name }, convertedVal); } - command_1.issueCommand('set-env', { name }, convertedVal); } exports.exportVariable = exportVariable; /** @@ -1814,7 +1802,7 @@ exports.setSecret = setSecret; function addPath(inputPath) { const filePath = process.env['GITHUB_PATH'] || ''; if (filePath) { - file_command_1.issueFileCommand('PATH', inputPath); + file_command_1.issueCommand('PATH', inputPath); } else { command_1.issueCommand('add-path', {}, inputPath); @@ -1854,10 +1842,7 @@ function getMultilineInput(name, options) { const inputs = getInput(name, options) .split('\n') .filter(x => x !== ''); - if (options && options.trimWhitespace === false) { - return inputs; - } - return inputs.map(input => input.trim()); + return inputs; } exports.getMultilineInput = getMultilineInput; /** @@ -1890,12 +1875,8 @@ exports.getBooleanInput = getBooleanInput; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { - const filePath = process.env['GITHUB_OUTPUT'] || ''; - if (filePath) { - return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value)); - } process.stdout.write(os.EOL); - command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value)); + command_1.issueCommand('set-output', { name }, value); } exports.setOutput = setOutput; /** @@ -2024,11 +2005,7 @@ exports.group = group; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function saveState(name, value) { - const filePath = process.env['GITHUB_STATE'] || ''; - if (filePath) { - return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value)); - } - command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value)); + command_1.issueCommand('save-state', { name }, value); } exports.saveState = saveState; /** @@ -2918,8 +2895,9 @@ exports.default = _default; /***/ (function(module, __unusedexports, __webpack_require__) { const os = __webpack_require__(87); +const core = __webpack_require__(470); -module.exports = (process.env['OS'] != 'Windows_NT') ? { +const defaults = (process.env['OS'] != 'Windows_NT') ? { // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent homePath: os.userInfo().homedir, @@ -2934,6 +2912,17 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { gitCmdDefault: 'c://progra~1//git//bin//git.exe' }; +const sshAgentCmdInput = core.getInput('ssh-agent-cmd'); +const sshAddCmdInput = core.getInput('ssh-add-cmd'); +const gitCmdInput = core.getInput('git-cmd'); + +module.exports = { + homePath: defaults.homePath, + sshAgentCmd: sshAgentCmdInput !== '' ? sshAgentCmdInput : defaults.sshAgentCmdDefault, + sshAddCmd: sshAddCmdInput !== '' ? sshAddCmdInput : defaults.sshAddCmdDefault, + gitCmd: gitCmdInput !== '' ? gitCmdInput : defaults.gitCmdDefault, +}; + /***/ }) diff --git a/index.js b/index.js index 0c2e08b..d28a764 100644 --- a/index.js +++ b/index.js @@ -2,20 +2,12 @@ const core = require('@actions/core'); const child_process = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const { homePath, sshAgentCmdDefault, sshAddCmdDefault, gitCmdDefault } = require('./paths.js'); +const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = require('./paths.js'); try { const privateKey = core.getInput('ssh-private-key'); const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); - const sshAgentCmdInput = core.getInput('ssh-agent-cmd'); - const sshAddCmdInput = core.getInput('ssh-add-cmd'); - const gitCmdInput = core.getInput('git-cmd'); - - const sshAgentCmd = sshAgentCmdInput ? sshAgentCmdInput : sshAgentCmdDefault; - const sshAddCmd = sshAddCmdInput ? sshAddCmdInput : sshAddCmdDefault; - const gitCmd = gitCmdInput ? gitCmdInput : gitCmdDefault; - if (!privateKey) { core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); diff --git a/paths.js b/paths.js index 1c6fbf0..bd4cd30 100644 --- a/paths.js +++ b/paths.js @@ -1,6 +1,7 @@ const os = require('os'); +const core = require('@actions/core'); -module.exports = (process.env['OS'] != 'Windows_NT') ? { +const defaults = (process.env['OS'] != 'Windows_NT') ? { // Use getent() system call, since this is what ssh does; makes a difference in Docker-based // Action runs, where $HOME is different from the pwent homePath: os.userInfo().homedir, @@ -14,3 +15,14 @@ module.exports = (process.env['OS'] != 'Windows_NT') ? { sshAddCmdDefault: 'c://progra~1//git//usr//bin//ssh-add.exe', gitCmdDefault: 'c://progra~1//git//bin//git.exe' }; + +const sshAgentCmdInput = core.getInput('ssh-agent-cmd'); +const sshAddCmdInput = core.getInput('ssh-add-cmd'); +const gitCmdInput = core.getInput('git-cmd'); + +module.exports = { + homePath: defaults.homePath, + sshAgentCmd: sshAgentCmdInput !== '' ? sshAgentCmdInput : defaults.sshAgentCmdDefault, + sshAddCmd: sshAddCmdInput !== '' ? sshAddCmdInput : defaults.sshAddCmdDefault, + gitCmd: gitCmdInput !== '' ? gitCmdInput : defaults.gitCmdDefault, +}; From 72c0bfd31ab22a2e11716951e3f107a9647dc97e Mon Sep 17 00:00:00 2001 From: Jano Paetzold Date: Wed, 8 Jan 2025 18:58:23 +0100 Subject: [PATCH 76/77] Improve documentation on why we use os.userInfo() (use correct syscall name) Co-authored-by: Matthias Pigulla --- paths.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/paths.js b/paths.js index bd4cd30..f440579 100644 --- a/paths.js +++ b/paths.js @@ -2,8 +2,9 @@ const os = require('os'); const core = require('@actions/core'); const defaults = (process.env['OS'] != 'Windows_NT') ? { - // Use getent() system call, since this is what ssh does; makes a difference in Docker-based - // Action runs, where $HOME is different from the pwent + // We use os.userInfo() rather than os.homedir(), since it uses the getpwuid() system call to get the user's home directory (see https://nodejs.org/api/os.html#osuserinfooptions). + // This mimics the way openssh derives the home directory for locating config files (see https://github.com/openssh/openssh-portable/blob/826483d51a9fee60703298bbf839d9ce37943474/ssh.c#L710); + // Makes a difference in Docker-based Action runs, when $HOME is different from what getpwuid() returns (which is based on the entry in /etc/passwd) homePath: os.userInfo().homedir, sshAgentCmdDefault: 'ssh-agent', sshAddCmdDefault: 'ssh-add', From a6f90b1f127823b31d4d4a8d96047790581349bd Mon Sep 17 00:00:00 2001 From: Jano Paetzold Date: Mon, 17 Mar 2025 15:05:58 +0100 Subject: [PATCH 77/77] Release v0.9.1 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c733d3..dc99093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v0.9.1 [2024-03-17] + ### Fixed * Fix path used to execute ssh-agent in cleanup.js to respect custom paths set by input (#235)