Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 98 additions & 6 deletions .github/workflows/pr-core-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ name: Core Tests

on:
pull_request:
workflow_dispatch:
inputs:
pull_request:
description: Pull request number containing the commit
required: true
type: string
commit_sha:
description: Exact reviewed 40-character PR head commit SHA
required: true
type: string

permissions:
contents: read

jobs:
leia-tests:
if: github.event_name == 'pull_request'
runs-on: ${{ matrix.os }}
env:
TERM: xterm
Expand Down Expand Up @@ -125,15 +136,96 @@ jobs:
- name: Run Leia Tests
if: >-
github.event.pull_request.head.repo.full_name == github.repository ||
!contains(fromJSON('["init-github", "plugins"]'), matrix.leia-test)
!contains(fromJSON('["l337", "lando-v4"]'), matrix.leia-test)
uses: lando/run-leia-action@v2
env:
GITHUB_PAT: >-
${{ github.event.pull_request.head.repo.full_name == github.repository &&
secrets.KYBER_AMPLIFICATION_MATRIX || github.token }}
GITHUB_KEY_NAME: "${{ github.sha }}${{ matrix.os }}"
with:
leia-test: "./examples/${{ matrix.leia-test }}/README.md"
cleanup-header: "Destroy tests"
shell: ${{ matrix.shell }}
stdin: true

privileged-leia-tests:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
leia-test:
- l337
- lando-v4
env:
TERM: xterm
steps:
- name: Resolve and validate pull request commit
id: resolve
env:
COMMIT_SHA: ${{ inputs.commit_sha }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ inputs.pull_request }}
run: |
if [[ ! "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "pull_request must be a pull request number"
exit 1
fi
if [[ ! "$COMMIT_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "commit_sha must be an exact 40-character commit SHA"
exit 1
fi
PR_HEAD_SHA=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER" --jq .head.sha)
PR_HEAD_REPO=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER" --jq .head.repo.full_name)
if [[ "$COMMIT_SHA" != "$PR_HEAD_SHA" ]]; then
echo "commit_sha does not match the current head of pull request #$PR_NUMBER"
exit 1
fi
echo "repository=$PR_HEAD_REPO" >> "$GITHUB_OUTPUT"
- name: Checkout code
uses: actions/checkout@v7
with:
repository: ${{ steps.resolve.outputs.repository }}
ref: ${{ inputs.commit_sha }}
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.DEPLOY_KEY }}
known_hosts: unnecessary
if_key_exists: replace
- name: Install node 20
uses: actions/setup-node@v7
with:
node-version: "20"
registry-url: https://registry.npmjs.org
cache: npm
- name: Bundle Deps
uses: lando/prepare-release-action@v3
with:
lando-plugin: true
version: dev
sync: false
- name: Install pkg dependencies
run: npm clean-install --prefer-offline --frozen-lockfile --production
- name: Package into node binary
uses: lando/pkg-action@v6
id: pkg-action
with:
entrypoint: bin/lando
filename: lando
node-version: "20"
options: --options dns-result-order=ipv4first
upload: false
pkg: "@yao-pkg/pkg@5.16.1"
- name: Install full deps
run: npm clean-install --prefer-offline --frozen-lockfile
- name: Setup lando ${{ steps.pkg-action.outputs.file }}
uses: lando/setup-lando@v3
with:
lando-version: ${{ steps.pkg-action.outputs.file }}
telemetry: false
config: |
setup.skipCommonPlugins=true
- name: Run Leia Tests
uses: lando/run-leia-action@v2
with:
leia-test: "./examples/${{ matrix.leia-test }}/README.md"
cleanup-header: "Destroy tests"
shell: bash
stdin: true
5 changes: 1 addition & 4 deletions examples/init-github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Run the following commands to get up and running with this example.
# Should clone code down from GitHub
mkdir -p github && cd github
rm -rf ~/.lando/scripts
lando init --source github --recipe none --github-auth="$GITHUB_PAT" --github-repo="git@github.com:lando/lando.git" --github-key-name="$GITHUB_KEY_NAME" --yes
lando init --source github --recipe none --github-auth=false --github-repo="https://github.com/lando/lando.git" --yes
```

## Verification commands
Expand All @@ -30,9 +30,6 @@ cd github && cat .lando.yml | grep something | grep happening-here
## Destroy tests

```bash
# Should remove key
docker run --rm -v "$(pwd)":/data -w /data badouralix/curl-jq:alpine sh -c "/data/remove-key.sh $GITHUB_PAT $GITHUB_KEY_NAME"

# Should remove initialized code
rm -rf github
```
23 changes: 0 additions & 23 deletions examples/init-github/remove-key.sh

This file was deleted.

19 changes: 17 additions & 2 deletions examples/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory

```bash
# Should start successfully
docker run -d --name lando-plugin-registry -p 4873:4873 \
--volume "$PWD/verdaccio.yml:/verdaccio/conf/config.yaml" \
verdaccio/verdaccio:6
until curl --silent --fail http://localhost:4873/-/ping; do sleep 1; done
REGISTRY_TOKEN=$(curl --silent --fail \
--request PUT \
--header "content-type: application/json" \
--data '{"name":"lando","password":"lando-test","email":"lando@example.com","type":"user"}' \
http://localhost:4873/-/user/org.couchdb.user:lando \
| node -pe "JSON.parse(require('fs').readFileSync(0)).token")
npm publish ./registry-test-plugin \
--access public \
--registry http://localhost:4873 \
--//localhost:4873/:_authToken="$REGISTRY_TOKEN"
lando poweroff
lando start
```
Expand Down Expand Up @@ -58,8 +72,8 @@ lando config | grep -q "plugins/@lando/php"
lando plugin-remove "@lando/php"
lando config | grep -qv "plugins/@lando/php"

# Should execute `lando plugin-login`
lando plugin-login --registry "https://npm.pkg.github.com" --password "$GITHUB_PAT" --username "rtfm-47" --scope "lando::registry=https://npm.pkg.github.com"
# Should execute `lando plugin-login` against an isolated registry
lando plugin-login --registry "http://localhost:4873" --password "lando-test" --username "lando" --scope "lando::registry=http://localhost:4873"

# Should be able to add and remove a private plugin via a registry string.
lando config | grep -qv "plugins/@lando/lando-plugin-test"
Expand All @@ -75,4 +89,5 @@ lando config | grep -qv "plugins/@lando/lando-plugin-test"
# Should destroy successfully
lando destroy -y
lando poweroff
docker rm --force lando-plugin-registry
```
3 changes: 3 additions & 0 deletions examples/plugins/registry-test-plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = () => ({'registry-plugin-test': true});
10 changes: 10 additions & 0 deletions examples/plugins/registry-test-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@lando/lando-plugin-test",
"version": "1.0.0",
"description": "A disposable plugin package used by the plugins Leia test",
"main": "index.js",
"keywords": [
"lando-plugin"
],
"license": "MIT"
}
27 changes: 27 additions & 0 deletions examples/plugins/verdaccio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
storage: /verdaccio/storage

auth:
htpasswd:
file: /verdaccio/conf/htpasswd
max_users: 1000

uplinks:
npmjs:
url: https://registry.npmjs.org/

packages:
'@lando/*':
access: $authenticated
publish: $authenticated
unpublish: $authenticated
proxy: npmjs
'**':
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: npmjs

log:
type: stdout
format: pretty
level: http
46 changes: 32 additions & 14 deletions sources/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const _ = require('lodash');
const fs = require('fs');
const getOctokit = require('../utils/get-octokit');
const isDisabled = require('../utils/is-disabled');
const os = require('os');
const path = require('path');

Expand Down Expand Up @@ -67,7 +68,10 @@ const setCaches = (options, lando) => {
const showTokenList = (source, tokens = []) => !_.isEmpty(tokens) && source === 'github';

// Helper to determine whether to show token password entry or not
const showTokenEntry = (source, answer, tkez = []) => ((_.isEmpty(tkez) || answer === 'more')) && source === 'github';
const showTokenEntry = (source, answer, tkez = []) => {
const explicitlyDisabled = answer !== undefined && isDisabled(answer);
return !explicitlyDisabled && (_.isEmpty(tkez) || answer === 'more') && source === 'github';
};

// Helper to get list of github projects
const getRepos = answers => {
Expand Down Expand Up @@ -96,6 +100,9 @@ const getAutoCompleteRepos = (answers, Promise, input = null) => {
}
};

// Public HTTPS repositories do not need an SSH key uploaded to a GitHub account.
const requiresSshKey = repo => !_.startsWith(repo, 'https://') && !_.startsWith(repo, 'http://');

module.exports = {
sources: [{
name: 'github',
Expand Down Expand Up @@ -142,19 +149,30 @@ module.exports = {
default: 'Landokey',
},
}),
build: (options, lando) => ([
{name: 'wait-for-user', cmd: `/helpers/wait-for-user.sh www-data ${lando.config.uid}`},
{name: 'generate-key', cmd: `/helpers/generate-key.sh ${gitHubLandoKey} ${gitHubLandoKeyComment}`},
{name: 'post-key', func: (options, lando) => {
return postKey(
path.join(lando.config.userConfRoot, 'keys'),
options['github-auth'],
options['github-key-name'],
build: (options, lando) => {
const steps = [
{name: 'wait-for-user', cmd: `/helpers/wait-for-user.sh www-data ${lando.config.uid}`},
];

if (requiresSshKey(options['github-repo'])) {
steps.push(
{name: 'generate-key', cmd: `/helpers/generate-key.sh ${gitHubLandoKey} ${gitHubLandoKeyComment}`},
{name: 'post-key', func: (options, lando) => {
return postKey(
path.join(lando.config.userConfRoot, 'keys'),
options['github-auth'],
options['github-key-name'],
);
}},
{name: 'reload-keys', cmd: '/helpers/load-keys.sh --silent', user: 'root'},
);
}},
{name: 'reload-keys', cmd: '/helpers/load-keys.sh --silent', user: 'root'},
{name: 'clone-repo', cmd: `/helpers/get-remote-url.sh ${options['github-repo']}`, remove: true},
{name: 'set-caches', func: (options, lando) => setCaches(options, lando)},
]),
}

steps.push({name: 'clone-repo', cmd: `/helpers/get-remote-url.sh ${options['github-repo']}`, remove: true});
if (!isDisabled(options['github-auth'])) {
steps.push({name: 'set-caches', func: (options, lando) => setCaches(options, lando)});
}
return steps;
},
}],
};
54 changes: 54 additions & 0 deletions test/github-source.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Tests for the GitHub init source.
* @file github-source.spec.js
*/

'use strict';

const chai = require('chai');
chai.should();

const source = require('../sources/github').sources[0];
const lando = {
cache: {get: () => []},
config: {uid: 1000, userConfRoot: '/tmp/lando'},
};

describe('github source', () => {
it('should not prompt for a token when GitHub authentication is explicitly disabled', () => {
const authPrompt = source.options(lando)['github-auth-token'].interactive;
authPrompt.when({'source': 'github', 'github-auth': false}).should.equal(false);
});

it('should clone public HTTPS repositories without GitHub credentials or SSH key mutation', () => {
const steps = source.build({
'github-auth': false,
'github-repo': 'https://github.com/lando/lando.git',
}, lando);
steps.map(step => step.name).should.deep.equal(['wait-for-user', 'clone-repo']);
});

it('should preserve SSH key setup and authenticated-user caching for SSH repositories', () => {
const steps = source.build({
'github-auth': 'token',
'github-key-name': 'Landokey',
'github-repo': 'git@github.com:lando/lando.git',
}, lando);
steps.map(step => step.name).should.deep.equal([
'wait-for-user',
'generate-key',
'post-key',
'reload-keys',
'clone-repo',
'set-caches',
]);
});

it('should cache credentials without uploading an SSH key for HTTPS repositories', () => {
const steps = source.build({
'github-auth': 'token',
'github-repo': 'https://github.com/lando/lando.git',
}, lando);
steps.map(step => step.name).should.deep.equal(['wait-for-user', 'clone-repo', 'set-caches']);
});
});
Loading