diff --git a/.github/.jira_sync_config.yaml b/.github/.jira_sync_config.yaml
new file mode 100644
index 00000000..f897418d
--- /dev/null
+++ b/.github/.jira_sync_config.yaml
@@ -0,0 +1,16 @@
+# See https://github.com/canonical/gh-jira-sync-bot for config
+settings:
+ jira_project_key: "ISD"
+
+ status_mapping:
+ opened: Untriaged
+ closed: done
+ not_planned: rejected
+
+ add_gh_comment: true
+
+ epic_key: ISD-3981
+
+ label_mapping:
+ bug: Bug
+ enhancement: Story
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..a300d58f
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,21 @@
+Applicable spec:
+
+### Overview
+
+
+
+### Rationale
+
+
+
+### Module Changes
+
+
+
+### Checklist
+
+- [ ] The [contributing guide](https://github.com/canonical/is-charms-contributing-guide) was applied
+- [ ] The documentation on README.md is updated.
+- [ ] The PR is tagged with appropriate label (`urgent`, `trivial`, `complex`)
+
+
diff --git a/.github/workflows/checkin.yml b/.github/workflows/checkin.yml
index fe74feb7..88f01383 100644
--- a/.github/workflows/checkin.yml
+++ b/.github/workflows/checkin.yml
@@ -8,11 +8,19 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 20
+ - name: Verify that action.yml files are in sync
+ run: |
+ npm run update-detached-action.yml &&
+ if ! git diff --exit-code \*action.yml
+ then
+ echo '::error::action.yml files are not in sync, maybe run `npm run update-detached-action.yml`?'
+ exit 1
+ fi
- name: Install dependencies
run: npm ci
- name: Run tests
@@ -24,7 +32,8 @@ jobs:
- name: Verify that the project is built
run: |
if [[ -n $(git status -s) ]]; then
- echo "ERROR: generated dist/ differs from the current sources"
+ echo "ERROR: generated lib/ differs from the current sources"
+ git status -s
git diff
exit 1
fi
diff --git a/.github/workflows/manual-detached-test.yml b/.github/workflows/manual-detached-test.yml
index a623483f..f3efbf90 100644
--- a/.github/workflows/manual-detached-test.yml
+++ b/.github/workflows/manual-detached-test.yml
@@ -5,11 +5,10 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: ./
+ - uses: actions/checkout@v4
+ - uses: ./detached
with:
- limit-access-to-actor: true
- detached: true
+ connect-timeout-seconds: 60
- run: |
echo "A busy loop"
for value in $(seq 10)
diff --git a/.github/workflows/manual-test.yml b/.github/workflows/manual-test.yml
index bf1b27bd..a68f773f 100644
--- a/.github/workflows/manual-test.yml
+++ b/.github/workflows/manual-test.yml
@@ -1,43 +1,73 @@
-name: Manual test matrix
-on: workflow_dispatch
+name: Manual test
+on:
+ workflow_dispatch:
+ inputs:
+ runs-on:
+ type: choice
+ description: 'The runner pool to run the job on'
+ required: true
+ default: ubuntu-24.04
+ options:
+ - ubuntu-24.04
+ - ubuntu-22.04
+ - macos-15-large
+ - macos-15
+ - macos-14-large
+ - macos-14
+ - macos-13
+ - macos-13-xlarge
+ - windows-2025
+ - windows-2022
+ - windows-2019
+ - windows-11-arm
+ container-runs-on:
+ type: choice
+ description: 'The Docker container to run the job on (this overrides the `runs-on` input)'
+ required: false
+ default: '(none)'
+ options:
+ - '(none)'
+ - fedora:latest
+ - archlinux:latest
+ - ubuntu:latest
+ limit-access-to-actor:
+ type: choice
+ description: 'Whether to limit access to the actor only'
+ required: true
+ default: 'auto'
+ options:
+ - auto
+ - 'true'
+ - 'false'
jobs:
test:
- strategy:
- fail-fast: false
- matrix:
- runs-on:
- - ubuntu-20.04
- - ubuntu-22.04
- - macos-11.0
- - macos-12.0
- - windows-2019
- - windows-2022
- limit-access-to-actor:
- - true
- - false
- runs-on: ${{ matrix.runs-on }}
+ if: ${{ inputs.container-runs-on == '(none)' }}
+ runs-on: ${{ inputs.runs-on }}
steps:
- - uses: actions/checkout@v3
+ - uses: msys2/setup-msys2@v2
+ # The public preview of GitHub-hosted Windows/ARM64 runners lacks
+ # a working MSYS2 installation, so we need to set it up ourselves.
+ if: ${{ inputs.runs-on == 'windows-11-arm' }}
+ with:
+ msystem: 'CLANGARM64'
+ # We cannot use `C:\` because `msys2/setup-msys2` erroneously
+ # believes that an MSYS2 exists at `C:\msys64`, but it doesn't,
+ # which is the entire reason why we need to set it up in this
+ # here step... However, by using `C:\.\` we can fool that
+ # overzealous check.
+ location: C:\.\
+ - uses: actions/checkout@v4
- uses: ./
with:
- limit-access-to-actor: ${{ matrix.limit-access-to-actor }}
+ limit-access-to-actor: ${{ inputs.limit-access-to-actor }}
test-container:
- strategy:
- fail-fast: false
- matrix:
- container-runs-on:
- - fedora:latest
- - archlinux:latest
- - ubuntu:latest
- limit-access-to-actor:
- - true
- - false
+ if: ${{ inputs.container-runs-on != '(none)' }}
runs-on: ubuntu-latest
container:
- image: ${{ matrix.container-runs-on }}
+ image: ${{ inputs.container-runs-on }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./
with:
- limit-access-to-actor: ${{ matrix.limit-access-to-actor }}
+ limit-access-to-actor: ${{ inputs.limit-access-to-actor }}
diff --git a/.github/workflows/tmate.yaml b/.github/workflows/tmate.yaml
new file mode 100644
index 00000000..8091f6db
--- /dev/null
+++ b/.github/workflows/tmate.yaml
@@ -0,0 +1,13 @@
+name: Canonical action-tmate test
+
+on:
+ push:
+
+jobs:
+ build:
+ runs-on: [self-hosted, linux, X64, jammy, large]
+ steps:
+ - name: Setup tmate session
+ uses: canonical/action-tmate@main
+ with:
+ detached: true
diff --git a/.github/workflows/update-manual-test.js b/.github/workflows/update-manual-test.js
new file mode 100644
index 00000000..5f173370
--- /dev/null
+++ b/.github/workflows/update-manual-test.js
@@ -0,0 +1,62 @@
+#!/usr/bin/env node
+
+// Update the `runs-on` options of the `manual-test.yml` workflow file with the
+// latest available images from the GitHub Actions runner images README file.
+
+(async () => {
+ const fs = require('fs')
+
+ const readme = await (await fetch("https://github.com/actions/runner-images/raw/HEAD/README.md")).text()
+
+ // This will be the first `ubuntu` one.
+ let defaultOption = ''
+
+ const choices = readme
+ // Get the "Available Images" section
+ .split(/\n## Available Images\n/)[1]
+ .split(/##\s*[^#]/)[0]
+ // Split by lines
+ .split('\n')
+ .map(line => {
+ // The relevant lines are table rows; The first column is the image name,
+ // the second one contains a relatively free-form list of the `runs-on`
+ // options that we are interested in. Those `runs-on` options are
+ // surrounded by backticks.
+ const match = line.match(/^\|\s*([^|]+)\s*\|([^|]*)`([^`|]+)`\s*\|/)
+ if (!match) return false // Skip e.g. the table header and empty lines
+ let runsOn = match[3] // default to the last `runs-on` option
+ const alternatives = match[2]
+ .split(/`([^`]*)`/) // split by backticks
+ .filter((_, i) => (i % 2)) // keep only the text between backticks
+ .sort((a, b) => a.length - b.length) // order by length
+ if (alternatives.length > 0 && alternatives[0].length < runsOn.length) runsOn = alternatives[0]
+ if (!defaultOption && match[3].startsWith('ubuntu-')) defaultOption = runsOn
+ return runsOn
+ })
+ .filter(runsOn => runsOn)
+
+ // The Windows/ARM64 runners are in public preview (and for the time being,
+ // not listed in the `runner-images` README file), so we need to add this
+ // manually.
+ if (!choices.includes('windows-11-arm')) choices.push('windows-11-arm')
+
+ // Now edit the `manual-test` workflow definition
+ const ymlPath = `${__dirname}/manual-test.yml`
+ const yml = fs.readFileSync(ymlPath, 'utf8')
+
+ // We want to replace the `runs-on` options and the `default` value. This
+ // would be easy if there was a built-in YAML parser and renderer in Node.js,
+ // but there is none. Therefore, we use a regular expression to find certain
+ // "needles" near the beginning of the file: first `workflow_dispatch:`,
+ // after that `runs-on:` and then `default:` and `options:`. Then we replace
+ // the `default` value and the `options` values with the new ones.
+ const [, beforeDefault, beforeOptions, optionsIndent, afterOptions] =
+ yml.match(/^([^]*?workflow_dispatch:[^]*?runs-on:[^]*?default:)(?:.*)([^]*?options:)(\n +- )(?:.*)(?:\3.*)*([^]*)/) || []
+ if (!beforeDefault) throw new Error(`The 'manual-test.yml' file does not match the expected format!`)
+ const newYML =
+ `${beforeDefault} ${defaultOption}${[beforeOptions, ...choices].join(optionsIndent)}${afterOptions}`
+ fs.writeFileSync(ymlPath, newYML)
+})().catch(e => {
+ console.error(e)
+ process.exitCode = 1
+})
\ No newline at end of file
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 00000000..e9fadb72
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @canonical/platform-engineering
diff --git a/README.md b/README.md
index af43bc6e..7b4039c5 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@ This is a forked version of [action-tmate](https://github.com/mxschmitt/action-t
be used with [GitHub Runner Operator](https://github.com/canonical/github-runner-operator/) to
provide automatic SSH debug access within the Canonical VPN.
+You must have your SSH Key [registered on GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account) to be able to connect.
+
[](https://github.com/canonical/action-tmate/actions)
[](https://github.com/marketplace/actions/debugging-with-tmate)
@@ -31,9 +33,9 @@ jobs:
build:
runs-on: self-hosted
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup tmate session
- uses: canonical/action-tmate@master
+ uses: canonical/action-tmate@main
```
To get the connection string, just open the `Checks` tab in your Pull Request and scroll to the bottom. There you can connect either directly per SSH or via a web based terminal.
@@ -69,7 +71,7 @@ jobs:
steps:
# Enable tmate debugging of manually-triggered workflows if the input option was provided
- name: Setup tmate session
- uses: canonical/action-tmate@mxschmitt
+ uses: canonical/action-tmate@main
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
```
* (any, kinda silly)
-// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
-// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
-// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
-// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
-// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
-function replaceTildes (comp, options) {
- return comp.trim().split(/\s+/).map(function (comp) {
- return replaceTilde(comp, options)
- }).join(' ')
-}
-
-function replaceTilde (comp, options) {
- var r = options.loose ? safeRe[t.TILDELOOSE] : safeRe[t.TILDE]
- return comp.replace(r, function (_, M, m, p, pr) {
- debug('tilde', comp, _, M, m, p, pr)
- var ret
-
- if (isX(M)) {
- ret = ''
- } else if (isX(m)) {
- ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'
- } else if (isX(p)) {
- // ~1.2 == >=1.2.0 <1.3.0
- ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'
- } else if (pr) {
- debug('replaceTilde pr', pr)
- ret = '>=' + M + '.' + m + '.' + p + '-' + pr +
- ' <' + M + '.' + (+m + 1) + '.0'
- } else {
- // ~1.2.3 == >=1.2.3 <1.3.0
- ret = '>=' + M + '.' + m + '.' + p +
- ' <' + M + '.' + (+m + 1) + '.0'
- }
+ if (kind === "error") {
+ hook = function (method, options) {
+ return Promise.resolve()
+ .then(method.bind(null, options))
+ .catch(function (error) {
+ return orig(error, options);
+ });
+ };
+ }
- debug('tilde return', ret)
- return ret
- })
+ state.registry[name].push({
+ hook: hook,
+ orig: orig,
+ });
}
-// ^ --> * (any, kinda silly)
-// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
-// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
-// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
-// ^1.2.3 --> >=1.2.3 <2.0.0
-// ^1.2.0 --> >=1.2.0 <2.0.0
-function replaceCarets (comp, options) {
- return comp.trim().split(/\s+/).map(function (comp) {
- return replaceCaret(comp, options)
- }).join(' ')
-}
-function replaceCaret (comp, options) {
- debug('caret', comp, options)
- var r = options.loose ? safeRe[t.CARETLOOSE] : safeRe[t.CARET]
- return comp.replace(r, function (_, M, m, p, pr) {
- debug('caret', comp, _, M, m, p, pr)
- var ret
-
- if (isX(M)) {
- ret = ''
- } else if (isX(m)) {
- ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'
- } else if (isX(p)) {
- if (M === '0') {
- ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'
- } else {
- ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'
- }
- } else if (pr) {
- debug('replaceCaret pr', pr)
- if (M === '0') {
- if (m === '0') {
- ret = '>=' + M + '.' + m + '.' + p + '-' + pr +
- ' <' + M + '.' + m + '.' + (+p + 1)
- } else {
- ret = '>=' + M + '.' + m + '.' + p + '-' + pr +
- ' <' + M + '.' + (+m + 1) + '.0'
- }
- } else {
- ret = '>=' + M + '.' + m + '.' + p + '-' + pr +
- ' <' + (+M + 1) + '.0.0'
- }
- } else {
- debug('no pr')
- if (M === '0') {
- if (m === '0') {
- ret = '>=' + M + '.' + m + '.' + p +
- ' <' + M + '.' + m + '.' + (+p + 1)
- } else {
- ret = '>=' + M + '.' + m + '.' + p +
- ' <' + M + '.' + (+m + 1) + '.0'
- }
- } else {
- ret = '>=' + M + '.' + m + '.' + p +
- ' <' + (+M + 1) + '.0.0'
- }
- }
+/***/ }),
- debug('caret return', ret)
- return ret
- })
-}
+/***/ 4670:
+/***/ ((module) => {
-function replaceXRanges (comp, options) {
- debug('replaceXRanges', comp, options)
- return comp.split(/\s+/).map(function (comp) {
- return replaceXRange(comp, options)
- }).join(' ')
-}
+module.exports = register;
-function replaceXRange (comp, options) {
- comp = comp.trim()
- var r = options.loose ? safeRe[t.XRANGELOOSE] : safeRe[t.XRANGE]
- return comp.replace(r, function (ret, gtlt, M, m, p, pr) {
- debug('xRange', comp, ret, gtlt, M, m, p, pr)
- var xM = isX(M)
- var xm = xM || isX(m)
- var xp = xm || isX(p)
- var anyX = xp
-
- if (gtlt === '=' && anyX) {
- gtlt = ''
- }
+function register(state, name, method, options) {
+ if (typeof method !== "function") {
+ throw new Error("method for before hook must be a function");
+ }
- // if we're including prereleases in the match, then we need
- // to fix this to -0, the lowest possible prerelease value
- pr = options.includePrerelease ? '-0' : ''
+ if (!options) {
+ options = {};
+ }
- if (xM) {
- if (gtlt === '>' || gtlt === '<') {
- // nothing is allowed
- ret = '<0.0.0-0'
- } else {
- // nothing is forbidden
- ret = '*'
- }
- } else if (gtlt && anyX) {
- // we know patch is an x, because we have any x at all.
- // replace X with 0
- if (xm) {
- m = 0
- }
- p = 0
-
- if (gtlt === '>') {
- // >1 => >=2.0.0
- // >1.2 => >=1.3.0
- // >1.2.3 => >= 1.2.4
- gtlt = '>='
- if (xm) {
- M = +M + 1
- m = 0
- p = 0
- } else {
- m = +m + 1
- p = 0
- }
- } else if (gtlt === '<=') {
- // <=0.7.x is actually <0.8.0, since any 0.7.x should
- // pass. Similarly, <=7.x is actually <8.0.0, etc.
- gtlt = '<'
- if (xm) {
- M = +M + 1
- } else {
- m = +m + 1
- }
- }
+ if (Array.isArray(name)) {
+ return name.reverse().reduce(function (callback, name) {
+ return register.bind(null, state, name, callback, options);
+ }, method)();
+ }
- ret = gtlt + M + '.' + m + '.' + p + pr
- } else if (xm) {
- ret = '>=' + M + '.0.0' + pr + ' <' + (+M + 1) + '.0.0' + pr
- } else if (xp) {
- ret = '>=' + M + '.' + m + '.0' + pr +
- ' <' + M + '.' + (+m + 1) + '.0' + pr
+ return Promise.resolve().then(function () {
+ if (!state.registry[name]) {
+ return method(options);
}
- debug('xRange return', ret)
-
- return ret
- })
+ return state.registry[name].reduce(function (method, registered) {
+ return registered.hook.bind(null, method, options);
+ }, method)();
+ });
}
-// Because * is AND-ed with everything else in the comparator,
-// and '' means "any version", just remove the *s entirely.
-function replaceStars (comp, options) {
- debug('replaceStars', comp, options)
- // Looseness is ignored here. star is always as loose as it gets!
- return comp.trim().replace(safeRe[t.STAR], '')
-}
-// This function is passed to string.replace(re[t.HYPHENRANGE])
-// M, m, patch, prerelease, build
-// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
-// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do
-// 1.2 - 3.4 => >=1.2.0 <3.5.0
-function hyphenReplace ($0,
- from, fM, fm, fp, fpr, fb,
- to, tM, tm, tp, tpr, tb) {
- if (isX(fM)) {
- from = ''
- } else if (isX(fm)) {
- from = '>=' + fM + '.0.0'
- } else if (isX(fp)) {
- from = '>=' + fM + '.' + fm + '.0'
- } else {
- from = '>=' + from
- }
+/***/ }),
- if (isX(tM)) {
- to = ''
- } else if (isX(tm)) {
- to = '<' + (+tM + 1) + '.0.0'
- } else if (isX(tp)) {
- to = '<' + tM + '.' + (+tm + 1) + '.0'
- } else if (tpr) {
- to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr
- } else {
- to = '<=' + to
- }
+/***/ 6819:
+/***/ ((module) => {
- return (from + ' ' + to).trim()
-}
+module.exports = removeHook;
-// if ANY of the sets match ALL of its comparators, then pass
-Range.prototype.test = function (version) {
- if (!version) {
- return false
+function removeHook(state, name, method) {
+ if (!state.registry[name]) {
+ return;
}
- if (typeof version === 'string') {
- try {
- version = new SemVer(version, this.options)
- } catch (er) {
- return false
- }
- }
+ var index = state.registry[name]
+ .map(function (registered) {
+ return registered.orig;
+ })
+ .indexOf(method);
- for (var i = 0; i < this.set.length; i++) {
- if (testSet(this.set[i], version, this.options)) {
- return true
- }
+ if (index === -1) {
+ return;
}
- return false
+
+ state.registry[name].splice(index, 1);
}
-function testSet (set, version, options) {
- for (var i = 0; i < set.length; i++) {
- if (!set[i].test(version)) {
- return false
- }
- }
- if (version.prerelease.length && !options.includePrerelease) {
- // Find the set of versions that are allowed to have prereleases
- // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
- // That should allow `1.2.3-pr.2` to pass.
- // However, `1.2.4-alpha.notready` should NOT be allowed,
- // even though it's within the range set by the comparators.
- for (i = 0; i < set.length; i++) {
- debug(set[i].semver)
- if (set[i].semver === ANY) {
- continue
- }
+/***/ }),
- if (set[i].semver.prerelease.length > 0) {
- var allowed = set[i].semver
- if (allowed.major === version.major &&
- allowed.minor === version.minor &&
- allowed.patch === version.patch) {
- return true
- }
- }
- }
+/***/ 8932:
+/***/ ((__unused_webpack_module, exports) => {
- // Version has a -pre, but it's not one of the ones we like.
- return false
- }
+"use strict";
- return true
-}
-exports.satisfies = satisfies
-function satisfies (version, range, options) {
- try {
- range = new Range(range, options)
- } catch (er) {
- return false
- }
- return range.test(version)
-}
+Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.maxSatisfying = maxSatisfying
-function maxSatisfying (versions, range, options) {
- var max = null
- var maxSV = null
- try {
- var rangeObj = new Range(range, options)
- } catch (er) {
- return null
- }
- versions.forEach(function (v) {
- if (rangeObj.test(v)) {
- // satisfies(v, range, options)
- if (!max || maxSV.compare(v) === -1) {
- // compare(max, v, true)
- max = v
- maxSV = new SemVer(max, options)
- }
- }
- })
- return max
-}
+class Deprecation extends Error {
+ constructor(message) {
+ super(message); // Maintains proper stack trace (only available on V8)
-exports.minSatisfying = minSatisfying
-function minSatisfying (versions, range, options) {
- var min = null
- var minSV = null
- try {
- var rangeObj = new Range(range, options)
- } catch (er) {
- return null
- }
- versions.forEach(function (v) {
- if (rangeObj.test(v)) {
- // satisfies(v, range, options)
- if (!min || minSV.compare(v) === 1) {
- // compare(min, v, true)
- min = v
- minSV = new SemVer(min, options)
- }
- }
- })
- return min
-}
+ /* istanbul ignore next */
-exports.minVersion = minVersion
-function minVersion (range, loose) {
- range = new Range(range, loose)
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor);
+ }
- var minver = new SemVer('0.0.0')
- if (range.test(minver)) {
- return minver
+ this.name = 'Deprecation';
}
- minver = new SemVer('0.0.0-0')
- if (range.test(minver)) {
- return minver
- }
+}
- minver = null
- for (var i = 0; i < range.set.length; ++i) {
- var comparators = range.set[i]
-
- comparators.forEach(function (comparator) {
- // Clone to avoid manipulating the comparator's semver object.
- var compver = new SemVer(comparator.semver.version)
- switch (comparator.operator) {
- case '>':
- if (compver.prerelease.length === 0) {
- compver.patch++
- } else {
- compver.prerelease.push(0)
- }
- compver.raw = compver.format()
- /* fallthrough */
- case '':
- case '>=':
- if (!minver || gt(minver, compver)) {
- minver = compver
- }
- break
- case '<':
- case '<=':
- /* Ignore maximum versions */
- break
- /* istanbul ignore next */
- default:
- throw new Error('Unexpected operation: ' + comparator.operator)
- }
- })
- }
+exports.Deprecation = Deprecation;
- if (minver && range.test(minver)) {
- return minver
- }
- return null
-}
+/***/ }),
-exports.validRange = validRange
-function validRange (range, options) {
- try {
- // Return '*' instead of '' so that truthiness works.
- // This will throw if it's invalid anyway
- return new Range(range, options).range || '*'
- } catch (er) {
- return null
- }
-}
+/***/ 3287:
+/***/ ((__unused_webpack_module, exports) => {
-// Determine if version is less than all the versions possible in the range
-exports.ltr = ltr
-function ltr (version, range, options) {
- return outside(version, range, '<', options)
-}
+"use strict";
-// Determine if version is greater than all the versions possible in the range.
-exports.gtr = gtr
-function gtr (version, range, options) {
- return outside(version, range, '>', options)
-}
-exports.outside = outside
-function outside (version, range, hilo, options) {
- version = new SemVer(version, options)
- range = new Range(range, options)
-
- var gtfn, ltefn, ltfn, comp, ecomp
- switch (hilo) {
- case '>':
- gtfn = gt
- ltefn = lte
- ltfn = lt
- comp = '>'
- ecomp = '>='
- break
- case '<':
- gtfn = lt
- ltefn = gte
- ltfn = gt
- comp = '<'
- ecomp = '<='
- break
- default:
- throw new TypeError('Must provide a hilo val of "<" or ">"')
- }
+Object.defineProperty(exports, "__esModule", ({ value: true }));
- // If it satisifes the range it is not outside
- if (satisfies(version, range, options)) {
- return false
- }
+/*!
+ * is-plain-object
+ *
+ * Copyright (c) 2014-2017, Jon Schlinkert.
+ * Released under the MIT License.
+ */
- // From now on, variable terms are as if we're in "gtr" mode.
- // but note that everything is flipped for the "ltr" function.
+function isObject(o) {
+ return Object.prototype.toString.call(o) === '[object Object]';
+}
- for (var i = 0; i < range.set.length; ++i) {
- var comparators = range.set[i]
+function isPlainObject(o) {
+ var ctor,prot;
- var high = null
- var low = null
+ if (isObject(o) === false) return false;
- comparators.forEach(function (comparator) {
- if (comparator.semver === ANY) {
- comparator = new Comparator('>=0.0.0')
- }
- high = high || comparator
- low = low || comparator
- if (gtfn(comparator.semver, high.semver, options)) {
- high = comparator
- } else if (ltfn(comparator.semver, low.semver, options)) {
- low = comparator
- }
- })
+ // If has modified constructor
+ ctor = o.constructor;
+ if (ctor === undefined) return true;
- // If the edge version comparator has a operator then our version
- // isn't outside it
- if (high.operator === comp || high.operator === ecomp) {
- return false
- }
+ // If has modified prototype
+ prot = ctor.prototype;
+ if (isObject(prot) === false) return false;
- // If the lowest version comparator has an operator and our version
- // is less than it then it isn't higher than the range
- if ((!low.operator || low.operator === comp) &&
- ltefn(version, low.semver)) {
- return false
- } else if (low.operator === ecomp && ltfn(version, low.semver)) {
- return false
- }
+ // If constructor does not have an Object-specific method
+ if (prot.hasOwnProperty('isPrototypeOf') === false) {
+ return false;
}
- return true
-}
-exports.prerelease = prerelease
-function prerelease (version, options) {
- var parsed = parse(version, options)
- return (parsed && parsed.prerelease.length) ? parsed.prerelease : null
+ // Most likely a plain Object
+ return true;
}
-exports.intersects = intersects
-function intersects (r1, r2, options) {
- r1 = new Range(r1, options)
- r2 = new Range(r2, options)
- return r1.intersects(r2)
-}
+exports.isPlainObject = isPlainObject;
-exports.coerce = coerce
-function coerce (version, options) {
- if (version instanceof SemVer) {
- return version
- }
- if (typeof version === 'number') {
- version = String(version)
- }
+/***/ }),
- if (typeof version !== 'string') {
- return null
- }
+/***/ 1223:
+/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
- options = options || {}
+var wrappy = __nccwpck_require__(2940)
+module.exports = wrappy(once)
+module.exports.strict = wrappy(onceStrict)
- var match = null
- if (!options.rtl) {
- match = version.match(safeRe[t.COERCE])
- } else {
- // Find the right-most coercible string that does not share
- // a terminus with a more left-ward coercible string.
- // Eg, '1.2.3.4' wants to coerce '2.3.4', not '3.4' or '4'
- //
- // Walk through the string checking with a /g regexp
- // Manually set the index so as to pick up overlapping matches.
- // Stop when we get a match that ends at the string end, since no
- // coercible string can be more right-ward without the same terminus.
- var next
- while ((next = safeRe[t.COERCERTL].exec(version)) &&
- (!match || match.index + match[0].length !== version.length)
- ) {
- if (!match ||
- next.index + next[0].length !== match.index + match[0].length) {
- match = next
- }
- safeRe[t.COERCERTL].lastIndex = next.index + next[1].length + next[2].length
- }
- // leave it in a clean state
- safeRe[t.COERCERTL].lastIndex = -1
- }
+once.proto = once(function () {
+ Object.defineProperty(Function.prototype, 'once', {
+ value: function () {
+ return once(this)
+ },
+ configurable: true
+ })
+
+ Object.defineProperty(Function.prototype, 'onceStrict', {
+ value: function () {
+ return onceStrict(this)
+ },
+ configurable: true
+ })
+})
- if (match === null) {
- return null
+function once (fn) {
+ var f = function () {
+ if (f.called) return f.value
+ f.called = true
+ return f.value = fn.apply(this, arguments)
}
+ f.called = false
+ return f
+}
- return parse(match[2] +
- '.' + (match[3] || '0') +
- '.' + (match[4] || '0'), options)
+function onceStrict (fn) {
+ var f = function () {
+ if (f.called)
+ throw new Error(f.onceError)
+ f.called = true
+ return f.value = fn.apply(this, arguments)
+ }
+ var name = fn.name || 'Function wrapped with `once`'
+ f.onceError = name + " shouldn't be called more than once"
+ f.called = false
+ return f
}
@@ -17066,14 +13245,6 @@ module.exports = require("assert");
/***/ }),
-/***/ 2081:
-/***/ ((module) => {
-
-"use strict";
-module.exports = require("child_process");
-
-/***/ }),
-
/***/ 6113:
/***/ ((module) => {
@@ -17279,28 +13450,26 @@ var __webpack_exports__ = {};
// ESM COMPAT FLAG
__nccwpck_require__.r(__webpack_exports__);
-// EXTERNAL MODULE: external "os"
-var external_os_ = __nccwpck_require__(2037);
-var external_os_default = /*#__PURE__*/__nccwpck_require__.n(external_os_);
-// EXTERNAL MODULE: external "fs"
-var external_fs_ = __nccwpck_require__(7147);
-var external_fs_default = /*#__PURE__*/__nccwpck_require__.n(external_fs_);
-// EXTERNAL MODULE: external "path"
-var external_path_ = __nccwpck_require__(1017);
-var external_path_default = /*#__PURE__*/__nccwpck_require__.n(external_path_);
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js
var core = __nccwpck_require__(2186);
// EXTERNAL MODULE: ./node_modules/@actions/github/lib/github.js
var github = __nccwpck_require__(5438);
-// EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js
-var tool_cache = __nccwpck_require__(7784);
// EXTERNAL MODULE: ./node_modules/@octokit/rest/dist-node/index.js
var dist_node = __nccwpck_require__(5375);
+// EXTERNAL MODULE: external "fs"
+var external_fs_ = __nccwpck_require__(7147);
+var external_fs_default = /*#__PURE__*/__nccwpck_require__.n(external_fs_);
+// EXTERNAL MODULE: external "os"
+var external_os_ = __nccwpck_require__(2037);
+var external_os_default = /*#__PURE__*/__nccwpck_require__.n(external_os_);
+// EXTERNAL MODULE: external "path"
+var external_path_ = __nccwpck_require__(1017);
+var external_path_default = /*#__PURE__*/__nccwpck_require__.n(external_path_);
;// CONCATENATED MODULE: external "process"
const external_process_namespaceObject = require("process");
var external_process_default = /*#__PURE__*/__nccwpck_require__.n(external_process_namespaceObject);
-// EXTERNAL MODULE: external "child_process"
-var external_child_process_ = __nccwpck_require__(2081);
+;// CONCATENATED MODULE: external "child_process"
+const external_child_process_namespaceObject = require("child_process");
;// CONCATENATED MODULE: ./src/helpers.js
// @ts-check
@@ -17326,14 +13495,14 @@ const execShellCommand = (cmd, options) => {
core.debug(`Executing shell command: [${cmd}]`)
return new Promise((resolve, reject) => {
const proc = (external_process_default()).platform !== "win32" ?
- (0,external_child_process_.spawn)(cmd, [], {
+ (0,external_child_process_namespaceObject.spawn)(cmd, [], {
shell: true,
env: {
...(external_process_default()).env,
HOMEBREW_GITHUB_API_TOKEN: core.getInput('github-token') || undefined
}
}) :
- (0,external_child_process_.spawn)("C:\\msys64\\usr\\bin\\bash.exe", ["-lc", cmd], {
+ (0,external_child_process_namespaceObject.spawn)(`${core.getInput("msys2-location") || "C:\\msys64"}\\usr\\bin\\bash.exe`, ["-lc", cmd], {
env: {
...(external_process_default()).env,
"MSYS2_PATH_TYPE": "inherit", /* Inherit previous path */
@@ -17367,9 +13536,10 @@ const execShellCommand = (cmd, options) => {
* @return {string|undefined} {undefined} or throws an error if input doesn't match regex
*/
const getValidatedEnvVars = (key, re) => {
- const value = (external_process_default()).env[key] || ""
+ const envVarKey = key.toUpperCase().replace(/-/gi, "_")
+ const value = (external_process_default()).env[envVarKey] || ""
if (value !== undefined && !re.test(value)) {
- throw new Error(`Invalid value for '${key}': '${value}'`);
+ throw new Error(`Invalid value for '${key}(${envVarKey})': '${value}'`);
}
return value;
}
@@ -17380,7 +13550,7 @@ const getValidatedEnvVars = (key, re) => {
*/
const getLinuxDistro = async () => {
try {
- const osRelease = await external_fs_default().promises.readFile("/etc/os-release")
+ const osRelease = await fs.promises.readFile("/etc/os-release")
const match = osRelease.toString().match(/^ID=(.*)$/m)
return match ? match[1] : "(unknown)"
} catch (e) {
@@ -17400,17 +13570,20 @@ const getLinuxDistro = async () => {
-
-const TMATE_LINUX_VERSION = "2.4.0"
-
// Map os.arch() values to the architectures in tmate release binary filenames.
// Possible os.arch() values documented here:
// https://nodejs.org/api/os.html#os_os_arch
// Available tmate binaries listed here:
-// https://github.com/tmate-io/tmate/releases/
+// https://packages.ubuntu.com/jammy/tmate
+// For different Ubuntu releases, change the release (i.e. jammy) to the
+// appropriate release.
const TMATE_ARCH_MAP = {
arm64: 'arm64v8',
+ armhf: 'armhf',
x64: 'amd64',
+ ppc64: 'ppc64',
+ riscv64: 'riscv64',
+ s390x: 's390x'
};
/** @param {number} ms */
@@ -17441,12 +13614,17 @@ async function run() {
&& '0' !== await execShellCommand(`${tmate} display -p '#{tmate_num_clients}'`, { quiet: true })
}
})()
- for (let seconds = 10 * 60; seconds > 0; ) {
- console.log(`${
- await hasAnyoneConnectedYet()
+
+ let connectTimeoutSeconds = parseInt(core.getInput("connect-timeout-seconds"))
+ if (Number.isNaN(connectTimeoutSeconds) || connectTimeoutSeconds <= 0) {
+ connectTimeoutSeconds = 10 * 60
+ }
+
+ for (let seconds = connectTimeoutSeconds; seconds > 0;) {
+ console.log(`${await hasAnyoneConnectedYet()
? 'Waiting for session to end'
: `Waiting for client to connect (at most ${seconds} more second(s))`
- }\n${message}`)
+ }\n${message}`)
if (continueFileExists()) {
core.info("Exiting debugging session because the continue file was created")
@@ -17468,43 +13646,31 @@ async function run() {
let tmateExecutable = "tmate"
if (core.getInput("install-dependencies") !== "false") {
core.debug("Installing dependencies")
- if ((external_process_default()).platform === "darwin") {
- await execShellCommand('brew install tmate');
- } else if ((external_process_default()).platform === "win32") {
- await execShellCommand('pacman -S --noconfirm tmate');
- } else {
- const optionalSudoPrefix = useSudoPrefix() ? "sudo " : "";
- const distro = await getLinuxDistro();
- core.debug("linux distro: [" + distro + "]");
- if (distro === "alpine") {
- // for set -e workaround, we need to install bash because alpine doesn't have it
- await execShellCommand(optionalSudoPrefix + 'apk add openssh-client xz bash');
- } else if (distro === "arch") {
- // partial upgrades are not supported so also upgrade everything
- await execShellCommand(optionalSudoPrefix + 'pacman -Syu --noconfirm xz openssh');
- } else if (distro === "fedora") {
- await execShellCommand(optionalSudoPrefix + 'dnf install -y xz openssh');
- } else {
- await execShellCommand(optionalSudoPrefix + 'apt-get update');
- await execShellCommand(optionalSudoPrefix + 'apt-get install -y openssh-client xz-utils');
- }
+ const optionalSudoPrefix = useSudoPrefix() ? "sudo " : "";
+ await execShellCommand(optionalSudoPrefix + 'apt-get update');
+ await execShellCommand(optionalSudoPrefix + 'apt-get install -y openssh-client xz-utils');
+ await execShellCommand(optionalSudoPrefix + 'apt-get install -y tmate');
+
+ const tmateArch = TMATE_ARCH_MAP[external_os_default().arch()];
+ if (!tmateArch) {
+ throw new Error(`Unsupported architecture: ${external_os_default().arch()}`)
+ }
+ // We change from downloading tmate from source built tar from GitHub to the
+ // Ubuntu packages tmate binary. Hence we've removed support for non Ubuntu/Linux
+ // platforms/distributions.
+ // This decision is to support different architectures.
+ tmateExecutable = external_path_default().join("/usr/bin/", "tmate")
- const tmateArch = TMATE_ARCH_MAP[external_os_default().arch()];
- if (!tmateArch) {
- throw new Error(`Unsupported architecture: ${external_os_default().arch()}`)
- }
- const tmateReleaseTar = await tool_cache.downloadTool(`https://github.com/tmate-io/tmate/releases/download/${TMATE_LINUX_VERSION}/tmate-${TMATE_LINUX_VERSION}-static-linux-${tmateArch}.tar.xz`);
- const tmateDir = external_path_default().join(external_os_default().tmpdir(), "tmate")
- tmateExecutable = external_path_default().join(tmateDir, "tmate")
-
- if (external_fs_default().existsSync(tmateExecutable))
- external_fs_default().unlinkSync(tmateExecutable)
- external_fs_default().mkdirSync(tmateDir, { recursive: true })
- await execShellCommand(`tar x -C ${tmateDir} -f ${tmateReleaseTar} --strip-components=1`)
- external_fs_default().unlinkSync(tmateReleaseTar)
+ // Optionally start the proxy service.
+ try {
+ await execShellCommand(optionalSudoPrefix + 'systemctl enable tmate-proxy --now');
+ } catch (error) {
+ core.info(`tmate-proxy not enabled`);
+ core.debug(`tmate-proxy error: ${error.message || error}`);
+ if (error.stderr) core.debug(`stderr: ${error.stderr}`);
}
- core.debug("Installed dependencies successfully");
}
+ core.debug("Installed dependencies successfully");
if ((external_process_default()).platform === "win32") {
tmateExecutable = 'CHERE_INVOKING=1 tmate'
@@ -17524,7 +13690,7 @@ async function run() {
if (limitAccessToActor === "true" || limitAccessToActor === "auto") {
const { actor, apiUrl } = github.context
const auth = core.getInput('github-token')
- const octokit = new dist_node.Octokit({ auth, baseUrl: apiUrl, request: { fetch }});
+ const octokit = new dist_node.Octokit({ auth, baseUrl: apiUrl, request: { fetch } });
const keys = await octokit.users.listPublicKeysForUser({
username: actor
@@ -17536,7 +13702,7 @@ async function run() {
const sshPath = external_path_default().join(external_os_default().homedir(), ".ssh")
await external_fs_default().promises.mkdir(sshPath, { recursive: true })
const authorizedKeysPath = external_path_default().join(sshPath, "authorized_keys")
- await external_fs_default().promises.writeFile(authorizedKeysPath, keys.data.map(e => e.key).join('\n'))
+ await external_fs_default().promises.appendFile(authorizedKeysPath, keys.data.map(e => e.key).join('\n'))
newSessionExtra = `-a "${authorizedKeysPath}"`
tmateSSHDashI = "ssh -i "
}
@@ -17552,10 +13718,10 @@ async function run() {
// values that are not, strictly speaking, valid, but should be good
// enough for detecting obvious errors, which is all we want here.
const options = {
- "TMATE_SERVER_HOST": /^[a-z\d\-]+(\.[a-z\d\-]+)*$/i,
- "TMATE_SERVER_PORT": /^\d{1,5}$/,
- "TMATE_SERVER_RSA_FINGERPRINT": /./,
- "TMATE_SERVER_ED25519_FINGERPRINT": /./,
+ "tmate-server-host": /^[a-z\d\-]+(\.[a-z\d\-]+)*$/i,
+ "tmate-server-port": /^\d{1,5}$/,
+ "tmate-server-rsa-fingerprint": /./,
+ "tmate-server-ed25519-fingerprint": /./,
}
let host = "";
@@ -17564,10 +13730,10 @@ async function run() {
const value = getValidatedEnvVars(key, option);
if (value !== undefined) {
setDefaultCommand = `${setDefaultCommand} set-option -g ${key} "${value}" \\;`;
- if (key === "TMATE_SERVER_HOST") {
+ if (key === "tmate-server-host") {
host = value;
}
- if (key === "TMATE_SERVER_PORT") {
+ if (key === "tmate-server-port") {
port = value;
}
}
@@ -17580,8 +13746,8 @@ async function run() {
core.debug("Fetching connection strings")
const tmateSSH = await execShellCommand(`${tmate} display -p '#{tmate_ssh}'`);
- const [ , ,tokenHost] = tmateSSH.split(" ");
- const [token, ] = tokenHost.split("@")
+ const [, , tokenHost] = tmateSSH.split(" ");
+ const [token,] = tokenHost.split("@")
const tmateWeb = await execShellCommand(`${tmate} display -p '#{tmate_web}'`);
/*
@@ -17610,6 +13776,15 @@ async function run() {
}
core.saveState('message', message)
core.saveState('tmate', tmate)
+
+ // Set the SSH command as an output so other jobs can use it
+ core.setOutput('ssh-command', tmateSSH)
+ // Extract and set the raw SSH address (without the "ssh" prefix)
+ core.setOutput('ssh-address', tmateSSH.replace(/^ssh /, ''))
+ if (tmateWeb) {
+ core.setOutput('web-url', tmateWeb)
+ }
+
console.log(message)
return
}
@@ -17622,7 +13797,7 @@ async function run() {
if (tmateWeb) {
core.info(`Web shell: ${tmateWeb}`);
}
- core.info(`SSH: ssh -p ${port} ${token}@${host}`);
+ core.info(`SSH: ${tmateSSH}`);
if (tmateSSHDashI) {
core.info(`or: ${tmateSSH.replace(/^ssh/, tmateSSHDashI)}`)
}
@@ -17646,12 +13821,12 @@ async function run() {
}
function didTmateQuit() {
- const tmateSocketPath = (external_process_default()).platform === "win32" ? "C:/msys64/tmp/tmate.sock" : "/tmp/tmate.sock"
+ const tmateSocketPath = (external_process_default()).platform === "win32" ? `${core.getInput("msys2-location") || "C:\\msys64"}/tmp/tmate.sock` : "/tmp/tmate.sock"
return !external_fs_default().existsSync(tmateSocketPath)
}
function continueFileExists() {
- const continuePath = (external_process_default()).platform === "win32" ? "C:/msys64/continue" : "/continue"
+ const continuePath = (external_process_default()).platform === "win32" ? `${core.getInput("msys2-location") || "C:\\msys64"}/continue` : "/continue"
return external_fs_default().existsSync(continuePath) || external_fs_default().existsSync(external_path_default().join((external_process_default()).env.GITHUB_WORKSPACE, "continue"))
}
diff --git a/package-lock.json b/package-lock.json
index 02aa63e9..063debbe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3220,12 +3220,12 @@
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -3434,9 +3434,9 @@
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
+ "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
@@ -3647,9 +3647,9 @@
}
},
"node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -5751,12 +5751,12 @@
"dev": true
},
"node_modules/micromatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
- "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
- "braces": "^3.0.2",
+ "braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
@@ -9079,12 +9079,12 @@
}
},
"braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"requires": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
}
},
"browserslist": {
@@ -9224,9 +9224,9 @@
}
},
"cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
+ "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
@@ -9376,9 +9376,9 @@
}
},
"fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
@@ -10936,12 +10936,12 @@
"dev": true
},
"micromatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
- "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"requires": {
- "braces": "^3.0.2",
+ "braces": "^3.0.3",
"picomatch": "^2.3.1"
}
},
diff --git a/package.json b/package.json
index 7aa60e75..8fe1d52d 100644
--- a/package.json
+++ b/package.json
@@ -7,11 +7,12 @@
"scripts": {
"start": "node src/index.js",
"build": "ncc build src/main.js -o lib",
+ "update-detached-action.yml": "sed '/^runs:$/{N;N;N;s/lib\\//..\\/&/g;};/^ detached:/{N;N;N;s/\\(default: .\\)false/\\1true/g;}' action.yml >detached/action.yml",
"test": "GITHUB_EVENT_PATH= jest"
},
"repository": {
"type": "git",
- "url": "https://github.com/mxschmitt/action-tmate.git"
+ "url": "https://github.com/canonical/action-tmate.git"
},
"keywords": [
"actions",
diff --git a/src/helpers.js b/src/helpers.js
index 9318170d..4490a65d 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -29,7 +29,7 @@ export const execShellCommand = (cmd, options) => {
HOMEBREW_GITHUB_API_TOKEN: core.getInput('github-token') || undefined
}
}) :
- spawn("C:\\msys64\\usr\\bin\\bash.exe", ["-lc", cmd], {
+ spawn(`${core.getInput("msys2-location") || "C:\\msys64"}\\usr\\bin\\bash.exe`, ["-lc", cmd], {
env: {
...process.env,
"MSYS2_PATH_TYPE": "inherit", /* Inherit previous path */
@@ -63,9 +63,10 @@ export const execShellCommand = (cmd, options) => {
* @return {string|undefined} {undefined} or throws an error if input doesn't match regex
*/
export const getValidatedEnvVars = (key, re) => {
- const value = process.env[key] || ""
+ const envVarKey = key.toUpperCase().replace(/-/gi, "_")
+ const value = process.env[envVarKey] || ""
if (value !== undefined && !re.test(value)) {
- throw new Error(`Invalid value for '${key}': '${value}'`);
+ throw new Error(`Invalid value for '${key}(${envVarKey})': '${value}'`);
}
return value;
}
diff --git a/src/index.js b/src/index.js
index 76f672c0..6397bfb6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,25 +1,28 @@
// @ts-check
-import os from "os"
-import fs from "fs"
-import path from "path"
import * as core from "@actions/core"
import * as github from "@actions/github"
-import * as tc from "@actions/tool-cache"
import { Octokit } from "@octokit/rest"
+import fs from "fs"
+import os from "os"
+import path from "path"
import process from "process"
-import { execShellCommand, getValidatedEnvVars, getLinuxDistro, useSudoPrefix } from "./helpers"
-
-const TMATE_LINUX_VERSION = "2.4.0"
+import { execShellCommand, getValidatedEnvVars, useSudoPrefix } from "./helpers"
// Map os.arch() values to the architectures in tmate release binary filenames.
// Possible os.arch() values documented here:
// https://nodejs.org/api/os.html#os_os_arch
// Available tmate binaries listed here:
-// https://github.com/tmate-io/tmate/releases/
+// https://packages.ubuntu.com/jammy/tmate
+// For different Ubuntu releases, change the release (i.e. jammy) to the
+// appropriate release.
const TMATE_ARCH_MAP = {
arm64: 'arm64v8',
+ armhf: 'armhf',
x64: 'amd64',
+ ppc64: 'ppc64',
+ riscv64: 'riscv64',
+ s390x: 's390x'
};
/** @param {number} ms */
@@ -50,12 +53,17 @@ export async function run() {
&& '0' !== await execShellCommand(`${tmate} display -p '#{tmate_num_clients}'`, { quiet: true })
}
})()
- for (let seconds = 10 * 60; seconds > 0; ) {
- console.log(`${
- await hasAnyoneConnectedYet()
+
+ let connectTimeoutSeconds = parseInt(core.getInput("connect-timeout-seconds"))
+ if (Number.isNaN(connectTimeoutSeconds) || connectTimeoutSeconds <= 0) {
+ connectTimeoutSeconds = 10 * 60
+ }
+
+ for (let seconds = connectTimeoutSeconds; seconds > 0;) {
+ console.log(`${await hasAnyoneConnectedYet()
? 'Waiting for session to end'
: `Waiting for client to connect (at most ${seconds} more second(s))`
- }\n${message}`)
+ }\n${message}`)
if (continueFileExists()) {
core.info("Exiting debugging session because the continue file was created")
@@ -77,43 +85,31 @@ export async function run() {
let tmateExecutable = "tmate"
if (core.getInput("install-dependencies") !== "false") {
core.debug("Installing dependencies")
- if (process.platform === "darwin") {
- await execShellCommand('brew install tmate');
- } else if (process.platform === "win32") {
- await execShellCommand('pacman -S --noconfirm tmate');
- } else {
- const optionalSudoPrefix = useSudoPrefix() ? "sudo " : "";
- const distro = await getLinuxDistro();
- core.debug("linux distro: [" + distro + "]");
- if (distro === "alpine") {
- // for set -e workaround, we need to install bash because alpine doesn't have it
- await execShellCommand(optionalSudoPrefix + 'apk add openssh-client xz bash');
- } else if (distro === "arch") {
- // partial upgrades are not supported so also upgrade everything
- await execShellCommand(optionalSudoPrefix + 'pacman -Syu --noconfirm xz openssh');
- } else if (distro === "fedora") {
- await execShellCommand(optionalSudoPrefix + 'dnf install -y xz openssh');
- } else {
- await execShellCommand(optionalSudoPrefix + 'apt-get update');
- await execShellCommand(optionalSudoPrefix + 'apt-get install -y openssh-client xz-utils');
- }
+ const optionalSudoPrefix = useSudoPrefix() ? "sudo " : "";
+ await execShellCommand(optionalSudoPrefix + 'apt-get update');
+ await execShellCommand(optionalSudoPrefix + 'apt-get install -y openssh-client xz-utils');
+ await execShellCommand(optionalSudoPrefix + 'apt-get install -y tmate');
- const tmateArch = TMATE_ARCH_MAP[os.arch()];
- if (!tmateArch) {
- throw new Error(`Unsupported architecture: ${os.arch()}`)
- }
- const tmateReleaseTar = await tc.downloadTool(`https://github.com/tmate-io/tmate/releases/download/${TMATE_LINUX_VERSION}/tmate-${TMATE_LINUX_VERSION}-static-linux-${tmateArch}.tar.xz`);
- const tmateDir = path.join(os.tmpdir(), "tmate")
- tmateExecutable = path.join(tmateDir, "tmate")
-
- if (fs.existsSync(tmateExecutable))
- fs.unlinkSync(tmateExecutable)
- fs.mkdirSync(tmateDir, { recursive: true })
- await execShellCommand(`tar x -C ${tmateDir} -f ${tmateReleaseTar} --strip-components=1`)
- fs.unlinkSync(tmateReleaseTar)
+ const tmateArch = TMATE_ARCH_MAP[os.arch()];
+ if (!tmateArch) {
+ throw new Error(`Unsupported architecture: ${os.arch()}`)
+ }
+ // We change from downloading tmate from source built tar from GitHub to the
+ // Ubuntu packages tmate binary. Hence we've removed support for non Ubuntu/Linux
+ // platforms/distributions.
+ // This decision is to support different architectures.
+ tmateExecutable = path.join("/usr/bin/", "tmate")
+
+ // Optionally start the proxy service.
+ try {
+ await execShellCommand(optionalSudoPrefix + 'systemctl enable tmate-proxy --now');
+ } catch (error) {
+ core.info(`tmate-proxy not enabled`);
+ core.debug(`tmate-proxy error: ${error.message || error}`);
+ if (error.stderr) core.debug(`stderr: ${error.stderr}`);
}
- core.debug("Installed dependencies successfully");
}
+ core.debug("Installed dependencies successfully");
if (process.platform === "win32") {
tmateExecutable = 'CHERE_INVOKING=1 tmate'
@@ -133,7 +129,7 @@ export async function run() {
if (limitAccessToActor === "true" || limitAccessToActor === "auto") {
const { actor, apiUrl } = github.context
const auth = core.getInput('github-token')
- const octokit = new Octokit({ auth, baseUrl: apiUrl, request: { fetch }});
+ const octokit = new Octokit({ auth, baseUrl: apiUrl, request: { fetch } });
const keys = await octokit.users.listPublicKeysForUser({
username: actor
@@ -145,7 +141,7 @@ export async function run() {
const sshPath = path.join(os.homedir(), ".ssh")
await fs.promises.mkdir(sshPath, { recursive: true })
const authorizedKeysPath = path.join(sshPath, "authorized_keys")
- await fs.promises.writeFile(authorizedKeysPath, keys.data.map(e => e.key).join('\n'))
+ await fs.promises.appendFile(authorizedKeysPath, keys.data.map(e => e.key).join('\n'))
newSessionExtra = `-a "${authorizedKeysPath}"`
tmateSSHDashI = "ssh -i "
}
@@ -161,10 +157,10 @@ export async function run() {
// values that are not, strictly speaking, valid, but should be good
// enough for detecting obvious errors, which is all we want here.
const options = {
- "TMATE_SERVER_HOST": /^[a-z\d\-]+(\.[a-z\d\-]+)*$/i,
- "TMATE_SERVER_PORT": /^\d{1,5}$/,
- "TMATE_SERVER_RSA_FINGERPRINT": /./,
- "TMATE_SERVER_ED25519_FINGERPRINT": /./,
+ "tmate-server-host": /^[a-z\d\-]+(\.[a-z\d\-]+)*$/i,
+ "tmate-server-port": /^\d{1,5}$/,
+ "tmate-server-rsa-fingerprint": /./,
+ "tmate-server-ed25519-fingerprint": /./,
}
let host = "";
@@ -173,10 +169,10 @@ export async function run() {
const value = getValidatedEnvVars(key, option);
if (value !== undefined) {
setDefaultCommand = `${setDefaultCommand} set-option -g ${key} "${value}" \\;`;
- if (key === "TMATE_SERVER_HOST") {
+ if (key === "tmate-server-host") {
host = value;
}
- if (key === "TMATE_SERVER_PORT") {
+ if (key === "tmate-server-port") {
port = value;
}
}
@@ -189,8 +185,8 @@ export async function run() {
core.debug("Fetching connection strings")
const tmateSSH = await execShellCommand(`${tmate} display -p '#{tmate_ssh}'`);
- const [ , ,tokenHost] = tmateSSH.split(" ");
- const [token, ] = tokenHost.split("@")
+ const [, , tokenHost] = tmateSSH.split(" ");
+ const [token,] = tokenHost.split("@")
const tmateWeb = await execShellCommand(`${tmate} display -p '#{tmate_web}'`);
/*
@@ -219,6 +215,15 @@ export async function run() {
}
core.saveState('message', message)
core.saveState('tmate', tmate)
+
+ // Set the SSH command as an output so other jobs can use it
+ core.setOutput('ssh-command', tmateSSH)
+ // Extract and set the raw SSH address (without the "ssh" prefix)
+ core.setOutput('ssh-address', tmateSSH.replace(/^ssh /, ''))
+ if (tmateWeb) {
+ core.setOutput('web-url', tmateWeb)
+ }
+
console.log(message)
return
}
@@ -231,7 +236,7 @@ export async function run() {
if (tmateWeb) {
core.info(`Web shell: ${tmateWeb}`);
}
- core.info(`SSH: ssh -p ${port} ${token}@${host}`);
+ core.info(`SSH: ${tmateSSH}`);
if (tmateSSHDashI) {
core.info(`or: ${tmateSSH.replace(/^ssh/, tmateSSHDashI)}`)
}
@@ -255,11 +260,11 @@ export async function run() {
}
function didTmateQuit() {
- const tmateSocketPath = process.platform === "win32" ? "C:/msys64/tmp/tmate.sock" : "/tmp/tmate.sock"
+ const tmateSocketPath = process.platform === "win32" ? `${core.getInput("msys2-location") || "C:\\msys64"}/tmp/tmate.sock` : "/tmp/tmate.sock"
return !fs.existsSync(tmateSocketPath)
}
function continueFileExists() {
- const continuePath = process.platform === "win32" ? "C:/msys64/continue" : "/continue"
+ const continuePath = process.platform === "win32" ? `${core.getInput("msys2-location") || "C:\\msys64"}/continue` : "/continue"
return fs.existsSync(continuePath) || fs.existsSync(path.join(process.env.GITHUB_WORKSPACE, "continue"))
}