diff --git a/.gitattributes b/.gitattributes index cf0c33326..24fbe2cbe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,3 +9,5 @@ *.ini text eol=lf *.php text eol=lf *.vcl text eol=lf +lash text eol=lf +landorc text eol=lf diff --git a/.github/workflows/build-util-images.yml b/.github/workflows/build-util-images.yml index 76e6b3ed3..0035f26bb 100644 --- a/.github/workflows/build-util-images.yml +++ b/.github/workflows/build-util-images.yml @@ -9,7 +9,7 @@ on: jobs: buildx: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 env: TERM: xterm strategy: diff --git a/.github/workflows/pr-core-tests.yml b/.github/workflows/pr-core-tests.yml index cd18a7bcd..75aa76e70 100644 --- a/.github/workflows/pr-core-tests.yml +++ b/.github/workflows/pr-core-tests.yml @@ -12,33 +12,57 @@ jobs: fail-fast: false matrix: lando-version: - - 3-slim + # - 3-slim # uncomment to test against bleeding edge cli - # - 3-dev-slim + - 3-dev-slim leia-test: - - examples/badname - - examples/base - - examples/events - - examples/experimental - - examples/init-github - - examples/init-remote - - examples/keys - - examples/l337 - - examples/lando-v4 - - examples/landofile-custom - - examples/long-name - - examples/no-services - - examples/orchestrator - - examples/plugin-commands - - examples/services - - examples/sql-helpers - - examples/tooling + - badname + - build + - cache + - certs + - config + - debug + - envfile + # - environment + - events + - exec + - experimental + - healthcheck + - host + # - hostnames + - info + - init-github + - init-remote + - keys + - l337 + - lando-v4 + - landofile + - landofile-custom + - list + - logs + - long-name + - networking + - no-services + - orchestrator + - plugins + - proxy + - rebuild + - recipes + - release-channel + - restart + - scanner + - security + - sql-helpers + - ssh + - tooling + - update + - version node-version: - "18" os: # - macos-13 # - macos-14 - - ubuntu-22.04 + - ubuntu-24.04 # - windows-2022 shell: - bash @@ -85,7 +109,7 @@ jobs: GITHUB_PAT: ${{ secrets.KYBER_AMPLIFICATION_MATRIX }} GITHUB_KEY_NAME: "${{ github.sha }}${{ matrix.os }}" with: - leia-test: "./${{ matrix.leia-test }}/README.md" + leia-test: "./examples/${{ matrix.leia-test }}/README.md" cleanup-header: "Destroy tests" shell: ${{ matrix.shell }} stdin: true diff --git a/.github/workflows/pr-docs-tests.yml b/.github/workflows/pr-docs-tests.yml index e752ddd38..3d6980f3d 100644 --- a/.github/workflows/pr-docs-tests.yml +++ b/.github/workflows/pr-docs-tests.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: - - ubuntu-22.04 + - ubuntu-24.04 node-version: - "18" steps: diff --git a/.github/workflows/pr-linter.yml b/.github/workflows/pr-linter.yml index a6176dd91..a64dc098a 100644 --- a/.github/workflows/pr-linter.yml +++ b/.github/workflows/pr-linter.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: - - ubuntu-22.04 + - ubuntu-24.04 node-version: - "18" steps: diff --git a/.github/workflows/pr-plugin-tests.yml b/.github/workflows/pr-plugin-tests.yml deleted file mode 100644 index 57e8ee7a6..000000000 --- a/.github/workflows/pr-plugin-tests.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Plugin Tests - -on: - pull_request: - -jobs: - leia-tests: - runs-on: ${{ matrix.os }} - env: - TERM: xterm - strategy: - fail-fast: false - matrix: - lando-version: - - 3-slim - # uncomment to test against bleeding edge cli - # - 3-dev-slim - leia-test: - - examples/healthcheck - - examples/networking - - examples/proxy - - examples/scanner - node-version: - - "18" - os: - # - macos-13 - # - macos-14 - - ubuntu-22.04 - # - windows-2022 - shell: - - bash - - # TODO: includes for also running windows tests on cmd/powershell? - - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Install node ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - registry-url: https://registry.npmjs.org - cache: npm - - name: Install dependencies - run: npm clean-install --prefer-offline --frozen-lockfile - # bundle deps is needed so local plugin installation succeeds - - name: Bundle Deps - uses: lando/prepare-release-action@v3 - with: - lando-plugin: true - version: dev - sync: false - # note that we need a custom auto-setup command because dogfooding core can impact the - # subsequent lando setup - - name: Setup lando ${{ matrix.lando-version }} - uses: lando/setup-lando@v3 - with: - auto-setup: lando plugin-add @lando/core@file:${{ github.workspace }} && lando setup - lando-version: ${{ matrix.lando-version }} - telemetry: false - config: | - setup.skipCommonPlugins=true - - name: Run Leia Tests - uses: lando/run-leia-action@v2 - with: - leia-test: "./${{ matrix.leia-test }}/README.md" - cleanup-header: "Destroy tests" - shell: ${{ matrix.shell }} - stdin: true diff --git a/.github/workflows/pr-setup-linux-tests.yml b/.github/workflows/pr-setup-linux-tests.yml index eb5acccdb..975410668 100644 --- a/.github/workflows/pr-setup-linux-tests.yml +++ b/.github/workflows/pr-setup-linux-tests.yml @@ -12,15 +12,15 @@ jobs: fail-fast: false matrix: lando-version: - - 3-slim + # - 3-slim # uncomment to test against bleeding edge cli - # - 3-dev-slim + - 3-dev-slim leia-test: - - examples/setup-linux + - setup-linux node-version: - "18" os: - - ubuntu-22.04 + - ubuntu-24.04 steps: - name: Checkout code @@ -33,7 +33,6 @@ jobs: cache: npm - name: Install dependencies run: npm clean-install --prefer-offline --frozen-lockfile - # bundle deps is needed so local plugin installation succeeds - name: Bundle Deps uses: lando/prepare-release-action@v3 with: @@ -49,7 +48,7 @@ jobs: - name: Run Leia Tests uses: lando/run-leia-action@v2 with: - leia-test: "./${{ matrix.leia-test }}/README.md" + leia-test: "./examples/${{ matrix.leia-test }}/README.md" cleanup-header: "Destroy tests" shell: bash stdin: true diff --git a/.github/workflows/pr-setup-macos-tests.yml b/.github/workflows/pr-setup-macos-tests.yml index 4929a41f8..487afda5f 100644 --- a/.github/workflows/pr-setup-macos-tests.yml +++ b/.github/workflows/pr-setup-macos-tests.yml @@ -12,11 +12,11 @@ jobs: fail-fast: false matrix: lando-version: - - 3-slim + # - 3-slim # uncomment to test against bleeding edge cli - # - 3-dev-slim + - 3-dev-slim leia-test: - - examples/setup-macos + - setup-macos node-version: - "18" os: @@ -34,7 +34,6 @@ jobs: cache: npm - name: Install dependencies run: npm clean-install --prefer-offline --frozen-lockfile - # bundle deps is needed so local plugin installation succeeds - name: Bundle Deps uses: lando/prepare-release-action@v3 with: @@ -50,7 +49,7 @@ jobs: - name: Run Leia Tests uses: lando/run-leia-action@v2 with: - leia-test: "./${{ matrix.leia-test }}/README.md" + leia-test: "./examples/${{ matrix.leia-test }}/README.md" cleanup-header: "Destroy tests" shell: bash stdin: true diff --git a/.github/workflows/pr-setup-windows-tests.yml b/.github/workflows/pr-setup-windows-tests.yml index b4a85875a..6ff039ac8 100644 --- a/.github/workflows/pr-setup-windows-tests.yml +++ b/.github/workflows/pr-setup-windows-tests.yml @@ -12,20 +12,15 @@ jobs: fail-fast: false matrix: lando-version: - - 3-slim + # - 3-slim # uncomment to test against bleeding edge cli - # - 3-dev-slim + - 3-dev-slim leia-test: - - examples/setup-windows + - setup-windows node-version: - "18" os: - windows-2022 - shell: - - bash - - cmd - - powershell - steps: - name: Checkout code uses: actions/checkout@v3 @@ -37,7 +32,6 @@ jobs: cache: npm - name: Install dependencies run: npm clean-install --prefer-offline --frozen-lockfile - # bundle deps is needed so local plugin installation succeeds - name: Bundle Deps uses: lando/prepare-release-action@v3 with: @@ -50,46 +44,11 @@ jobs: auto-setup: false lando-version: ${{ matrix.lando-version }} telemetry: false - - name: Dogfood core plugin - shell: powershell - run: lando plugin-add "@lando/core@file:${{ github.workspace }}" - - # @TODO: we need to reimplement this with leia tests like the other os setup tests but ran out of time - # becuse this one is a bit more involved since it tries three different shells - - name: Lando Setup - BASH - if: matrix.shell == 'bash' - shell: bash - run: lando setup -y - - name: Lando Setup - CMD - if: matrix.shell == 'cmd' - shell: cmd - run: lando setup -y - - name: Lando Setup - POWERSHELL - if: matrix.shell == 'powershell' - shell: powershell - run: lando setup -y - - # @TODO: for some reason the below refused to load anything but bash so we are just going to invoke leia - # directly for now but eventually we need to find out why this is the case - # - name: Run Leia Tests - # uses: lando/run-leia-action@v2 - # env: - # CORE_PLUGIN_PATH: ${{ github.workspace }} - # with: - # leia-test: "./${{ matrix.leia-test }}/README.md" - # cleanup-header: "Destroy tests" - # shell: powershell - # stdin: true - # debug: true - # - name: Run Leia Tests - # shell: bash - # run: | - # npx leia --version - # npx leia "./${{ matrix.leia-test }}/README.md" \ - # --setup-header=Start,Setup,This is the dawning \ - # --test-header=Test,Validat,Verif \ - # --cleanup-header=Destroy tests \ - # --shell=${{ matrix.shell }} \ - # --retry=1 \ - # --stdin \ - # --debug + - name: Run Leia Tests + uses: lando/run-leia-action@v2 + with: + leia-test: "./examples/${{ matrix.leia-test }}/README.md" + cleanup-header: "Destroy tests" + shell: powershell + stdin: true + debug: true diff --git a/.github/workflows/pr-unit-tests.yml b/.github/workflows/pr-unit-tests.yml index 0175c224a..491cbb301 100644 --- a/.github/workflows/pr-unit-tests.yml +++ b/.github/workflows/pr-unit-tests.yml @@ -12,7 +12,7 @@ jobs: os: - macos-13 - macos-14 - - ubuntu-22.04 + - ubuntu-24.04 - windows-2022 node-version: - "18" diff --git a/.github/workflows/pr-update-tests.yml b/.github/workflows/pr-update-tests.yml deleted file mode 100644 index be9e3a7f5..000000000 --- a/.github/workflows/pr-update-tests.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Update Tests - -on: - pull_request: - -jobs: - leia-tests: - runs-on: ${{ matrix.os }} - env: - TERM: xterm - strategy: - fail-fast: false - matrix: - lando-version: - - 3-slim - # uncomment to test against bleeding edge cli - # - 3-dev-slim - leia-test: - - examples/update - node-version: - - "18" - os: - # - macos-13 - # - macos-14 - - ubuntu-22.04 - # - windows-2022 - shell: - - bash - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Install node ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - registry-url: https://registry.npmjs.org - cache: npm - - name: Install dependencies - run: npm clean-install --prefer-offline --frozen-lockfile - - # bundle deps is needed so local plugin installation succeeds - - name: Bundle Deps - uses: lando/prepare-release-action@v3 - with: - lando-plugin: true - version: dev - sync: false - # note that we need a custom auto-setup command because dogfooding core can impact the - # subsequent lando setup - - name: Setup lando ${{ matrix.lando-version }} - uses: lando/setup-lando@v3 - with: - auto-setup: lando plugin-add @lando/core@file:${{ github.workspace }} && lando setup - lando-version: ${{ matrix.lando-version }} - telemetry: false - config: | - setup.skipCommonPlugins=true - - name: Run Leia Tests - uses: lando/run-leia-action@v2 - with: - leia-test: "./${{ matrix.leia-test }}/README.md" - cleanup-header: "Destroy tests" - shell: ${{ matrix.shell }} - stdin: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e037a5222..c15664df4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: - - ubuntu-22.04 + - ubuntu-24.04 node-version: - "18" diff --git a/.gitignore b/.gitignore index 0be923ceb..f9a8a0143 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ # Logs *.log logs +!examples/logs # NPM files node_modules @@ -28,6 +29,7 @@ lando.env # Build dirs build dist +!examples/build # coverage reporting .nyc_output @@ -44,6 +46,7 @@ dist cache temp config.*.timestamp-*-*.* +!examples/cache # YARN yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 77528c7eb..78a3f5d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ ## {{ UNRELEASED_VERSION }} - [{{ UNRELEASED_DATE }}]({{ UNRELEASED_LINK }}) +### New Features + +* Added new [`lando exec`](https://docs.lando.dev/cli/exec.html) command +* Added cross platform `host.lando.internal` for container-to-host access +* Added limited auto setup to app start-y events +* Added Install Certificate Authority `setup` task +* Added limit `app` support in `task` functions +* Added `usage`, `examples` and `positionals` support to `task` and `tooling` objects +* Added `LANDO_SERVICE_CERT` and `LANDO_SERVICE_KEY` envvars +* Improved CA and cert generation +* Improved tooling, events, etc to honor `&` for proper backgrounding with `--detach` +* Improved `v3` plugin script automoving (finally!) +* Updated default Docker Compose version to `2.27.1` +* Updated default Docker Desktop for macOS version to `4.32.0` +* Updated default Docker Desktop for Windows version to `4.32.0` +* Updated default Docker Engine version to `27.0.3` +* Updated tested Docker Desktop range to `<=4.32` + +### Bug Fixes + +* Fixed bug causing weird array merging in dynamic override tasks +* Fixed bug causing debug output showing in some errors +* Fixed bug causing compose cache to not properly empty on `destroy` +* Fixed bug causing `docker-engine.run` to double emit errors +* Fixed bug causing `undefined` to show on some user password prompts +* Fixed bug causing Docker Desktop `version` information to not load correctly in some circumstances +* Fixed various `dc2` renderer bugs + +### DEPRECATIONS + +* **DEPRECATED** `lando ssh` in favor of new `lando exec` +* Defunct `lando share` command is now a hidden command + +### Internal + +* Changed `.ps1` scripts to use `-Option` style option convention +* Changed `LANDO_HOST_IP` to now use new `host.lando.internal` +* Moved Lando Development Certificate Authority creation to `setup` framework +* Moved Landonet 2 upgrade to `setup` framework +* Removed `_casetup` builder in favor of native CA generation +* Removed `ip` `npm` package + ## v3.21.2 - [June 20, 2024](https://github.com/lando/core/releases/tag/v3.21.2) ### Bug Fixes diff --git a/app.js b/app.js index 9c06bd838..3852e7f08 100644 --- a/app.js +++ b/app.js @@ -15,23 +15,25 @@ const getKeys = (keys = true) => { module.exports = async (app, lando) => { // Compose cache key app.composeCache = `${app.name}.compose.cache`; - // Tooling cache key - app.toolingCache = `${app.name}.tooling.cache`; + // recipe cache key + app.recipeCache = `${app.name}.recipe.cache`; // Build step locl files app.preLockfile = `${app.name}.build.lock`; app.postLockfile = `${app.name}.post-build.lock`; // add compose cache updated app.updateComposeCache = () => { lando.cache.set(app.composeCache, { - name: app.name, - project: app.project, + allServices: app.allServices, compose: app.compose, containers: app.containers, - root: app.root, info: app.info, + name: app.name, overrides: { tooling: app._coreToolingOverrides, }, + primary: app._defaultService, + project: app.project, + root: app.root, }, {persist: true}); }; @@ -48,16 +50,19 @@ module.exports = async (app, lando) => { // add compose cache updated app.v4.updateComposeCache = () => { lando.cache.set(app.v4.composeCache, { - name: app.name, - project: app.project, + allServices: app.allServices, compose: app.compose, containers: app.containers, - root: app.root, info: app.info, + executors: require('./utils/get-executors')(_.get(app, 'v4.services', {})), + name: app.name, mounts: require('./utils/get-mounts')(_.get(app, 'v4.services', {})), + root: app.root, overrides: { tooling: app._coreToolingOverrides, }, + primary: app._defaultService, + project: app.project, }, {persist: true}); }; @@ -93,9 +98,6 @@ module.exports = async (app, lando) => { // run v4 build steps app.events.on('post-init', async () => await require('./hooks/app-run-v4-build-steps')(app, lando)); - // Add localhost info to our containers if they are up - app.events.on('post-init', async () => await require('./hooks/app-find-localhosts')(app, lando)); - // refresh all out v3 certs app.events.on('post-init', async () => await require('./hooks/app-refresh-v3-certs')(app, lando)); @@ -119,11 +121,11 @@ module.exports = async (app, lando) => { // @TODO: i feel like there has to be a better way to do this than this mega loop right? app.events.on('post-init', 9999, async () => await require('./hooks/app-set-bind-address')(app, lando)); - // override the ssh tooling command with a good default - app.events.on('ready', 1, async () => await require('./hooks/app-override-ssh-defaults')(app, lando)); + // Add localhost info to our containers if they are up + app.events.on('post-init-engine', async () => await require('./hooks/app-find-localhosts')(app, lando)); - // Discover portforward true info - app.events.on('ready', async () => await require('./hooks/app-set-portforwards')(app, lando)); + // override default tooling commands if needed + app.events.on('ready', 1, async () => await require('./hooks/app-override-tooling-defaults')(app, lando)); // set tooling compose cache app.events.on('ready', async () => await require('./hooks/app-set-compose-cache')(app, lando)); @@ -131,6 +133,9 @@ module.exports = async (app, lando) => { // v4 parts of the app are ready app.events.on('ready', 6, async () => await require('./hooks/app-v4-ready')(app, lando)); + // Discover portforward true info + app.events.on('ready-engine', async () => await require('./hooks/app-set-portforwards')(app, lando)); + // Save a compose cache every time the app is ready, we have to duplicate this for v4 because we modify the // composeData after the v3 app.ready event app.events.on('ready-v4', async () => await require('./hooks/app-set-v4-compose-cache')(app, lando)); @@ -152,6 +157,9 @@ module.exports = async (app, lando) => { // Check for updates if the update cache is empty app.events.on('pre-start', 1, async () => await require('./hooks/app-check-for-updates')(app, lando)); + // Generate certs for v3 SSL services as needed + app.events.on('pre-start', 2, async () => await require('./hooks/app-generate-v3-certs')(app, lando)); + // If the app already is installed but we can't determine the builtAgainst, then set it to something bogus app.events.on('pre-start', async () => await require('./hooks/app-update-built-against-pre')(app, lando)); @@ -168,7 +176,7 @@ module.exports = async (app, lando) => { app.events.on('post-start', async () => await require('./hooks/app-check-docker-compat')(app, lando)); // throw service not start errors - app.events.on('post-start', 9999, async () => await require('./hooks/app-check-v4-service-running')(app, lando)); + app.events.on('post-start', 1, async () => await require('./hooks/app-check-v4-service-running')(app, lando)); // Reset app info on a stop, this helps prevent wrong/duplicate information being reported on a restart app.events.on('post-stop', async () => require('./utils/get-app-info-defaults')(app)); @@ -186,7 +194,10 @@ module.exports = async (app, lando) => { app.events.on('post-uninstall', async () => await require('./hooks/app-purge-compose-cache')(app, lando)); // remove tooling cache - app.events.on('post-uninstall', async () => await require('./hooks/app-purge-tooling-cache')(app, lando)); + app.events.on('post-uninstall', async () => await require('./hooks/app-purge-recipe-cache')(app, lando)); + + // remove compose cache on destroy + app.events.on('post-destroy', 9999, async () => await require('./hooks/app-purge-compose-cache')(app, lando)); // process events if (!_.isEmpty(_.get(app, 'config.events', []))) { diff --git a/builders/_casetup.js b/builders/_casetup.js deleted file mode 100644 index b71cf668a..000000000 --- a/builders/_casetup.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -// Modules -const _ = require('lodash'); -const path = require('path'); - -/* - * Build CA service - */ -module.exports = { - name: '_casetup', - parent: '_landoutil', - config: { - version: 'custom', - type: 'ca', - name: 'ca', - }, - builder: (parent, config) => class LandoCa extends parent { - constructor(userConfRoot, env = {}, labels = {}) { - // Get some shitz - // @TODO: better use of config above - const certsPath = path.join(userConfRoot, 'certs'); - const setupCaScript = path.join(userConfRoot, 'scripts', 'setup-ca.sh'); - // Basic CA service - const caService = { - services: { - ca: { - command: ['tail', '-f', '/dev/null'], - image: 'devwithlando/util:4', - environment: { - LANDO_SERVICE_TYPE: 'ca', - }, - volumes: [ - `${setupCaScript}:/setup-ca.sh`, - `${certsPath}:/certs`, - ], - }, - }, - }; - super('ca', _.merge({}, config, {env, labels, userConfRoot}), caService); - }; - }, -}; diff --git a/builders/_lando.js b/builders/_lando.js index 848172712..8fc8fe780 100644 --- a/builders/_lando.js +++ b/builders/_lando.js @@ -36,7 +36,7 @@ module.exports = { patchesSupported = false, pinPairs = {}, ports = [], - project = '', + project = '_lando_', overrides = {}, refreshCerts = false, remoteFiles = {}, @@ -77,6 +77,20 @@ module.exports = { const addCertsScript = path.join(scriptsDir, 'add-cert.sh'); const refreshCertsScript = path.join(scriptsDir, 'refresh-certs.sh'); + // Handle Environment + const environment = { + LANDO_SERVICE_NAME: name, + LANDO_SERVICE_TYPE: type, + }; + + // Handle labels + const labels = { + 'io.lando.http-ports': _.uniq(['80', '443'].concat(moreHttpPorts)).join(','), + 'io.lando.https-ports': _.uniq(['443'].concat([sport])).join(','), + }; + // Set a reasonable log size + const logging = {driver: 'json-file', options: {'max-file': '3', 'max-size': '10m'}}; + // Handle volumes const volumes = [ `${userConfRoot}:/lando:cached`, @@ -87,8 +101,17 @@ module.exports = { // Handle ssl if (ssl) { - volumes.push(`${addCertsScript}:/scripts/000-add-cert`); + // also expose the sport if (sslExpose) ports.push(sport); + + // certs + const certname = `${id}.${project}.crt`; + const keyname = `${id}.${project}.key`; + environment.LANDO_SERVICE_CERT = `/lando/certs/${certname}`; + environment.LANDO_SERVICE_KEY = `/lando/certs/${keyname}`; + volumes.push(`${addCertsScript}:/scripts/000-add-cert`); + volumes.push(`${path.join(userConfRoot, 'certs', certname)}:/certs/cert.crt`); + volumes.push(`${path.join(userConfRoot, 'certs', keyname)}:/certs/cert.key`); } // Add in some more dirz if it makes sense @@ -114,16 +137,6 @@ module.exports = { } }); - // Handle Environment - const environment = {LANDO_SERVICE_NAME: name, LANDO_SERVICE_TYPE: type}; - // Handle http/https ports - const labels = { - 'io.lando.http-ports': _.uniq(['80', '443'].concat(moreHttpPorts)).join(','), - 'io.lando.https-ports': _.uniq(['443'].concat([sport])).join(','), - }; - // Set a reasonable log size - const logging = {driver: 'json-file', options: {'max-file': '3', 'max-size': '10m'}}; - // Add named volumes and other thingz into our primary service const namedVols = {}; _.set(namedVols, data, {}); @@ -132,6 +145,7 @@ module.exports = { services: _.set({}, name, { entrypoint, environment, + extra_hosts: ['host.lando.internal:host-gateway'], labels, logging, ports, diff --git a/builders/lando-v4.js b/builders/lando-v4.js index 74bc2a233..0e2034a05 100644 --- a/builders/lando-v4.js +++ b/builders/lando-v4.js @@ -1,9 +1,12 @@ 'use strict'; const fs = require('fs'); +const isObject = require('lodash/isPlainObject'); const merge = require('lodash/merge'); const path = require('path'); const uniq = require('lodash/uniq'); +const write = require('../utils/write-file'); +const toPosixPath = require('../utils/to-posix-path'); const states = {APP: 'UNBUILT'}; const groups = { @@ -48,6 +51,24 @@ module.exports = { destination: '/app', exclude: [], }, + 'certs': true, + 'environment': {}, + 'healthcheck': false, + 'hostnames': [], + 'labels': {}, + 'packages': { + 'git': true, + 'ssh-agent': true, + 'sudo': true, + }, + 'overrides': {}, + 'ports': [], + 'security': { + 'ca': [], + 'certificate-authority': [], + 'cas': [], + 'certificate-authorities': [], + }, }, }, router: () => ({}), @@ -62,165 +83,396 @@ module.exports = { binds: [], } - #appBuildOpts = { + #run = { environment: [], + labels: {}, mounts: [], } + #installers = { + 'certs': require('../packages/certs/certs'), + 'git': require('../packages/git/git'), + 'proxy': require('../packages/proxy/proxy'), + 'security': require('../packages/security/security'), + 'sudo': require('../packages/sudo/sudo'), + 'user': require('../packages/user/user'), + + // @TODO: this is a temp implementation until we have an ssh-agent container + 'ssh-agent': require('../packages/ssh-agent/ssh-agent'), + }; + + #addRunEnvironment(data) { + // if data is an object we need to put into array format + if (isObject(data)) data = Object.entries(data).map(([key, value]) => `${key}=${value}`); + // if data is an array then lets concat and uniq to #env + if (Array.isArray(data)) this.#run.environment = uniq([...this.#run.environment, ...data]); + } + + #addRunLabels(data = {}) { + // if data is an array we need to put into object format + if (Array.isArray(data)) data = Object.fromEntries(data).map(datum => datum.split('=')); + // if data is an object then we can merge to #labels + if (isObject(data)) this.#run.labels = merge(this.#run.labels, data); + } + + #addRunVolumes(data = []) { + // if data is an array then lets concat and uniq to #mounts + if (Array.isArray(data)) this.#run.mounts = uniq([...this.#run.mounts, ...data]); + } + + #setupBoot() { + this.addContext(`${path.join(__dirname, '..', 'scripts', 'lash')}:/bin/lash`); + this.addLSF(path.join(__dirname, '..', 'scripts', 'boot.sh')); + this.addLSF(path.join(__dirname, '..', 'scripts', 'exec.sh')); + this.addLSF(path.join(__dirname, '..', 'scripts', 'run-hooks.sh')); + this.addLSF(path.join(__dirname, '..', 'scripts', 'start.sh')); + this.addLSF(path.join(__dirname, '..', 'scripts', 'landorc')); + this.addLSF(path.join(__dirname, '..', 'scripts', 'utils.sh')); + this.addLSF(path.join(__dirname, '..', 'scripts', 'environment.sh'), 'environment'); + this.addLSF(path.join(__dirname, '..', 'scripts', 'install-updates.sh')); + this.addLSF(path.join(__dirname, '..', 'scripts', 'install-bash.sh')); + this.addSteps({group: 'boot', instructions: ` + ENV DEBUG=1 + ENV LANDO_DEBUG=1 + ENV PATH=$PATH:/etc/lando/bin + RUN mkdir -p /etc/lando /etc/lando/env.d /etc/lando/build/image + RUN chmod 777 /etc/lando + RUN ln -sf /etc/lando/environment /etc/profile.d/lando.sh + RUN /etc/lando/boot.sh + SHELL ["/bin/bash", "-c"] + `}); + } + + #setupHooks() { + for (const hook of Object.keys(this._data.groups).filter(group => parseInt(group.weight) <= 100)) { + this.addSteps({group: hook, instructions: ` + RUN mkdir -p /etc/lando/build/image/${hook}.d + RUN /etc/lando/run-hooks.sh image ${hook} + `}); + } + } + constructor(id, options, app, lando) { - // before we call super we need to separate things - const {config, ...upstream} = merge({}, defaults, options); - // @TODO: certs? + // @TODO: overrides for this.run()? // @TODO: better appmount logix? + // @TODO: allow additonal users to be installed in config.users? + // @TODO: change lando literal to "lando product" + // @TODO: debug/lando_debug should be set with env? + // @TODO: command as a full script? - // ger user info - const {gid, uid, username} = lando.config; + // get stuff from config + const {caCert, caDomain, gid, uid, username} = lando.config; + // before we call super we need to separate things + const {config, ...upstream} = merge({}, defaults, options); + // consolidate user info with any incoming stuff + const user = merge({}, {gid, uid, name: username}, require('../utils/parse-v4-user')(config.user)); // add some upstream stuff and legacy stuff upstream.appMount = config['app-mount'].destination; - upstream.legacy = merge({}, upstream.legacy ?? {}, {meUser: username}); // this will change but for right now i just need the image stuff to passthrough - upstream.config = {image: config.image}; + upstream.config = {image: config.image, ports: config.ports}; + // make sure we also pass the user + upstream.user = user.name; // add a user build group groups.user = { description: 'Catch all group for things that should be run as the user', weight: 2000, - user: username, + user: user.name, }; // get this - super(id, merge({}, {groups}, {states}, upstream)); + super(id, merge({}, {groups}, {states}, upstream), app, lando); - // helpful - this.project = app.project; - this.router = options.router; - this.isInteractive = lando.config.isInteractive; - - // healthcheck stuff + // foundational this + this.canExec = true; this.canHealthcheck = true; - this.healthcheck = config.healthcheck ?? false; - - // userstuff - this.gid = gid; - this.uid = uid; - this.username = username; - this.homevol = `${this.project}-${username}-home`; + this.generateCert = lando.generateCert.bind(lando); + this.isInteractive = lando.config.isInteractive; + this.network = lando.config.networkBridge; + this.project = app.project; + this.router = upstream.router; + + // more this + this.certs = config.certs; + this.command = config.command; + this.healthcheck = config.healthcheck; + this.hostnames = uniq([...config.hostnames, `${this.id}.${this.project}.internal`]); + this.packages = config.packages; + this.security = config.security; + this.security.cas.push(caCert, path.join(path.dirname(caCert), `${caDomain}.pem`)); + this.user = user; + + // computed this + this.homevol = `${this.project}-${this.user.name}-home`; this.datavol = `${this.project}-${this.id}-data`; + // boot stuff + this.#setupBoot(); + // hook system + this.#setupHooks(); + + // set up some core package config + this.packages.certs = this.certs; + this.packages.security = this.security; + this.packages.user = this.user; + + // if the proxy is on then set the package + if (lando.config?.proxy === 'ON') { + this.packages.proxy = { + volume: `${lando.config.proxyName}_proxy_config`, + domains: require('../packages/proxy/get-proxy-hostnames')(app?.config?.proxy?.[id] ?? []), + }; + } + // build script // @TODO: handle array content? + // @TODO: halfbaked this.buildScript = config?.build?.app ?? false; - // set some other stuff + // volumes if (config['app-mount']) this.setAppMount(config['app-mount']); + // info things + this.info = {hostnames: this.hostnames}; + // auth stuff - this.setSSHAgent(); + // @TODO: make this into a package? this.setNPMRC(lando.config.pluginConfigFile); - // @NOTE: uh? - this.addSteps({group: 'boot', instructions: ` - RUN rm /bin/sh && ln -s /bin/bash /bin/sh - `}); + // top level considerations + this.addComposeData({ + networks: {[this.network]: {external: true}}, + volumes: {[this.homevol]: {}}, + }); - // @NOTE: setup dat user - this.addSteps({group: 'setup-user', instructions: ` - RUN sed -i '/UID_MIN/c\UID_MIN ${this.uid}' /etc/login.defs - RUN sed -i '/UID_MAX/c\UID_MAX ${parseInt(this.uid) + 10}' /etc/login.defs - RUN sed -i '/GID_MIN/c\GID_MIN ${parseInt(this.gid) + 10}' /etc/login.defs - RUN sed -i '/GID_MAX/c\GID_MAX 600100000' /etc/login.defs - RUN getent group ${this.gid} > /dev/null || groupadd -g ${this.gid} ${this.username} - RUN useradd -l -u ${this.uid} -m -g ${this.gid} ${this.username} - RUN usermod -aG sudo ${this.username} - RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - `}); + // environment + const environment = { + DEBUG: lando.debuggy ? '1' : '', + LANDO: 'ON', + LANDO_DEBUG: lando.debuggy ? '1' : '', + LANDO_HOST_IP: 'host.lando.internal', + LANDO_HOST_GID: require('../utils/get-gid')(), + LANDO_HOST_OS: process.platform, + LANDO_HOST_UID: require('../utils/get-uid')(), + LANDO_HOST_USER: require('../utils/get-username')(), + LANDO_LEIA: lando.config.leia === false ? '0' : '1', + LANDO_PROJECT: this.project, + LANDO_PROJECT_MOUNT: this.appMount, + LANDO_SERVICE_API: 4, + LANDO_SERVICE_NAME: this.id, + LANDO_SERVICE_TYPE: this.type, + // user overrides + ...config.environment, + }; - // add a home folder persistent mount - this.addComposeData({volumes: {[this.homevol]: {}}}); - // add the usual DC stuff - this.addServiceData({user: config.user ?? this.username, volumes: [`${this.homevol}:/home/${this.username}`]}); - // add build vols - this.addAppBuildVolume(`${this.homevol}:/home/${this.username}`); + // labels + const labels = merge({}, app.labels, { + 'dev.lando.container': 'TRUE', + 'dev.lando.id': lando.config.id, + 'dev.lando.src': app.root, + }, config.labels); + + // add it all 2getha + this.addLandoServiceData({ + environment, + extra_hosts: ['host.lando.internal:host-gateway'], + labels, + logging: {driver: 'json-file', options: {'max-file': '3', 'max-size': '10m'}}, + networks: {[this.network]: {aliases: this.hostnames}}, + user: this.user.name, + volumes: [ + `${this.homevol}:/home/${this.user.name}`, + ], + }); + + // add any overrides on top + this.addLandoServiceData(config.overrides); } - addAppBuildVolume(volumes) { - if (Array.isArray(volumes)) { - this.#appBuildOpts.mounts.push(...volumes); - } else { - this.#appBuildOpts.mounts.push(volumes); + addHookFile(file, {id = undefined, hook = 'boot', stage = 'image', priority = '100'} = {}) { + // if file is actually script content we need to normalize and dump it first + if (!require('valid-path')(toPosixPath(file), {simpleReturn: true})) { + // split the file into lines + file = file.split('\n'); + // trim any empty lines at the top + file = file.slice(file.findIndex(line => line.length > 0)); + // now just try to make it look pretty + const leader = file.find(line => line.length > 0).match(/^\s*/)[0].length ?? 0; + const contents = file.map(line => line.slice(leader)).join('\n'); + + // reset file to a path + file = path.join(this.context, id ? `${priority}-${id}.sh` : `${priority}-${stage}-${hook}.sh`); + write(file, contents, {forcePosixLineEndings: true}); + fs.chmodSync(file, '755'); + } + + // image stage should add directly to the build context + if (stage === 'image') { + this.addContext( + `${file}:/etc/lando/build/image/${hook}.d/${priority}-${path.basename(file)}`, + `${hook}-1000-before`, + ); + + // app context should mount into the app + } else if (stage === 'app') { + const volumes = [`${file}:/etc/lando/build/app/${hook}.d/${path.basename(file)}`]; + this.addLandoServiceData({volumes}); } } - // buildapp - async buildApp() { - // bail if no script - if (!this.buildScript) { - this.debug(`no build detected for ${this.id}, skipping`); - return; - }; + addLashRC(file, {priority = '100'} = {}) { + this.addContext(`${file}:/etc/lando/lash.d/${priority}-${path.basename(file)}`); + } - // get build func - const bengine = LandoServiceV4.getBengine(LandoServiceV4.bengineConfig, { - builder: LandoServiceV4.builder, - debug: this.debug, - orchestrator: LandoServiceV4.orchestrator, - }); + addPackageInstaller(id, func) { + this.#installers[id] = func; + } + + async addPackage(id, data = []) { + // check if we have an package installer + // @TODO: should this throw or just log? + if (this.#installers[id] === undefined || typeof this.#installers[id] !== 'function') { + throw new Error(`Could not find a package installer function for ${id}!`); + } + + // normalize data + if (!Array.isArray(data)) data = [data]; - // generate the build script - const buildScript = require('../utils/generate-build-script')( - this.buildScript, - this.username, - this.gid, - process.platform === 'linux' ? process.env.SSH_AUTH_SOCK : `/run/host-services/ssh-auth.sock`, - this.appMount, - ); - const buildScriptPath = path.join(this.context, 'app-build.sh'); - fs.writeFileSync(buildScriptPath, buildScript); + // run installer + return await this.#installers[id](this, ...data); + } + + addLSF(source, dest, {context = 'context'} = {}) { + if (dest === undefined) dest = path.basename(source); + this.addContext(`${source}:/etc/lando/${dest}`, context); + } + + // wrapper around addServiceData so we can also add in #run stuff + addLandoServiceData(data = {}) { + // pass through our run considerations + this.addLandoRunData(data); + // and then super + this.addServiceData(data); + } + + addLandoRunData(data = {}) { + this.#addRunEnvironment(data.environment); + this.#addRunLabels(data.labels); + this.#addRunVolumes(data.volumes); + } + // buildapp + async buildApp() { try { // set state - this.state = {APP: 'BUILDING'}; - - // stuff - const bs = `/etc/lando/build/app.sh`; - const command = `chmod +x ${bs} && sh ${bs}`; - - // add build vols - this.addAppBuildVolume(`${buildScriptPath}:${bs}`); - - // run with the appropriate builder - const success = await bengine.run([command], { - image: this.tag, - attach: true, - interactive: this.isInteractive, - createOptions: { - User: this.username, - WorkingDir: this.appMount, - Entrypoint: ['/bin/sh', '-c'], - Env: uniq(this.#appBuildOpts.environment), - HostConfig: { - Binds: [...uniq(this.#appBuildOpts.mounts)], - }, - }, - }); + this.info = {state: {APP: 'BUILDING'}}; + + // run internal root app build first + await this.runHook(['app', 'internal-root'], {attach: false, user: 'root'}); + + // run user build scripts if we have them + if (this.buildScript && typeof this.buildScript === 'string') { + this.addHookFile(this.buildScript, {stage: 'app', hook: 'user'}); + await this.runHook(['app', 'user']); + }; - // // augment the success info - success.context = {script: fs.readFileSync(buildScriptPath, {encoding: 'utf-8'})}; // state - this.state = {APP: 'BUILT'}; + this.info = {state: {APP: 'BUILT'}}; // log - this.debug('app %o built successfully from %o', `${this.project}-${this.id}`, buildScriptPath); - return success; + this.debug('app %o built successfully', `${this.project}-${this.id}`); + // @TODO: return something? // failure } catch (error) { // augment error error.id = this.id; - error.context = {script: fs.readFileSync(buildScriptPath, {encoding: 'utf-8'}), path: buildScriptPath}; + // log this.debug('app %o build failed with code %o error %o', `${this.project}-${this.id}`, error.code, error); // set the build failure - this.state = {APP: 'BUILD FAILURE'}; + this.info = {state: {APP: 'BUILD FAILURE'}}; + // then throw + throw error; + } + } + + async buildImage() { + // go through all packages and install them + await this.installPackages(); + + // build the image + const image = await super.buildImage(); + + // determine the command and normalize it for wrapper + const command = this.command ?? image?.info?.Config?.Cmd ?? image?.info?.ContainerConfig?.Cmd; + + // if command if null or undefined then throw error + // @TODO: better error? + if (command === undefined || command === null) { + throw new Error(`${this.id} has no command set!`); + } + + // parse command + const parseCommand = command => typeof command === 'string' ? require('string-argv')(command) : command; + // add command wrapper to image + this.addLandoServiceData({command: ['/etc/lando/start.sh', ...parseCommand(command)]}); + + // return + return image; + } + + async installPackages() { + await Promise.all(Object.entries(this.packages).map(async ([id, data]) => { + this.debug('adding package %o with args: %o', id, data); + if (!require('../utils/is-disabled')(data)) { + await this.addPackage(id, data); + } + })); + } + + async runHook(hook, {attach = true, user = this.user.name} = {}) { + return await this.run(['/etc/lando/run-hooks.sh', ...hook], {attach, user, entrypoint: ['/etc/lando/exec.sh']}); + } + + async run(command, { + attach = true, + user = this.user.name, + workingDir = this.appMount, + entrypoint = ['/bin/bash', '-c'], + } = {}) { + const bengine = LandoServiceV4.getBengine(LandoServiceV4.bengineConfig, { + builder: LandoServiceV4.builder, + debug: this.debug, + orchestrator: LandoServiceV4.orchestrator, + }); + + // construct runopts + const runOpts = { + image: this.tag, + attach, + interactive: this.isInteractive, + createOptions: { + User: user, + WorkingDir: workingDir, + Entrypoint: entrypoint, + Env: this.#run.environment, + Labels: this.#run.labels, + HostConfig: { + Binds: this.#run.mounts, + }, + }, + }; + + try { + // run me + const success = await bengine.run(command, runOpts); + // augment the success info + success.context = {command, runOpts}; + // return + return success; + } catch (error) { + // augment error + error.id = this.id; // then throw throw error; } @@ -236,48 +488,18 @@ module.exports = { // write to file const npmauthfile = path.join(this.context, 'npmrc'); - fs.writeFileSync(npmauthfile, contents.join('\n')); + write(npmauthfile, contents.join('\n')); // ensure mount const mounts = [ - `${npmauthfile}:/home/${this.username}/.npmrc`, - `${npmauthfile}:/root/.npmrc`, + `${npmauthfile}:/home/${this.user.name}/.npmrc:ro`, + `${npmauthfile}:/root/.npmrc:ro`, ]; - this.addServiceData({volumes: mounts}); - this.addAppBuildVolume(mounts); + this.addLandoServiceData({volumes: mounts}); this.npmrc = contents.join('\n'); this.npmrcFile = npmauthfile; } - // sets ssh agent and prepares for socating - // DD ssh-agent is a bit strange and we wont use it in v4 plugin but its easiest for demoing purposes - // if you have issues with it its best to do the below - // 0. Close Docker Desktop - // 1. killall ssh-agent - // 2. Start Docker Desktop - // 3. Open a terminal (after Docker Desktop starts) - // 4. ssh-add (use the existing SSH agent, don't start a new one) - // 5. docker run --rm --mount type=bind,src=/run/host-services/ssh-auth.sock,target=/run/host-services/ssh-auth.sock -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" --entrypoint /usr/bin/ssh-add alpine/git -l - setSSHAgent() { - const socket = process.platform === 'linux' ? process.env.SSH_AUTH_SOCK : `/run/host-services/ssh-auth.sock`; - const socater = `/run/ssh-${this.username}.sock`; - - // only add if we have a socket - if (socket) { - this.addComposeData({services: {[this.id]: { - environment: { - SSH_AUTH_SOCK: socater, - }, - volumes: [ - `${socket}:${socket}`, - ], - }}}); - - this.#appBuildOpts.environment.push(`SSH_AUTH_SOCK=${socater}`); - this.addAppBuildVolume(`${socket}:${socket}`); - } - } - // @TODO: more powerful syntax eg go as many levels as you want and maybe ! syntax? setAppMount(config) { // reset the destination @@ -286,9 +508,10 @@ module.exports = { // its easy if we dont have any excludes if (config.exclude.length === 0) { this.#appMount.binds = [`${this.appRoot}:${config.destination}`]; - this.addAppBuildVolume(`${this.appRoot}:${config.destination}`); // if we have excludes then we need to compute somethings + // @TODO: this is busted and needs to be redone when we have a deeper "mounting" + // system } else { // named volumes for excludes this.#appMount.volumes = config.exclude.map(vol => `app-mount-${vol}`); @@ -305,9 +528,9 @@ module.exports = { // and again for appBuild stuff b w/ full mount name binds.map(path => { if (config.exclude.includes(path)) { - this.addAppBuildVolume(`${this.project}_app-mount-${path}:${this.#appMount.destination}/${path}`); + // this.addAppBuildVolume(`${this.project}_app-mount-${path}:${this.#appMount.destination}/${path}`); } else { - this.addAppBuildVolume(`${this.appRoot}/${path}:${this.#appMount.destination}/${path}`); + // this.addAppBuildVolume(`${this.appRoot}/${path}:${this.#appMount.destination}/${path}`); } }); } @@ -318,11 +541,11 @@ module.exports = { } // set bindz - this.addServiceData({volumes: this.#appMount.binds}); + this.addLandoServiceData({volumes: this.#appMount.binds}); // set infp this.appMount = config.destination; - this.info.appMount = this.appMount; + this.info = {appMount: this.appMount}; } }, }; diff --git a/components/docker-engine.js b/components/docker-engine.js index 7da63058d..eecacf372 100644 --- a/components/docker-engine.js +++ b/components/docker-engine.js @@ -5,7 +5,6 @@ const fs = require('fs-extra'); const path = require('path'); const merge = require('lodash/merge'); const slugify = require('slugify'); -const stringArgv = require('string-argv').default; const Dockerode = require('dockerode'); const {EventEmitter} = require('events'); @@ -16,6 +15,9 @@ const makeError = require('../utils/make-error'); const makeSuccess = require('../utils/make-success'); const mergePromise = require('../utils/merge-promise'); +const read = require('../utils/read-file'); +const write = require('../utils/write-file'); + class DockerEngine extends Dockerode { static name = 'docker-engine'; static cspace = 'docker-engine'; @@ -137,6 +139,13 @@ class DockerEngine extends Dockerode { fs.copySync(dockerfile, path.join(context, 'Dockerfile')); debug('copied Imagefile from %o to %o', dockerfile, path.join(context, 'Dockerfile')); + // on windows we want to ensure the build context has linux line endings + if (process.platform === 'win32') { + for (const file of require('glob').sync(path.join(context, '**/*'), {nodir: true})) { + write(file, read(file), {forcePosixLineEndings: true}); + } + } + // call the parent // @TODO: consider other opts? https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageBuild args? debug('building image %o from %o writh build-args %o', tag, context, buildArgs); @@ -272,7 +281,8 @@ class DockerEngine extends Dockerode { // ensure context dir exists fs.mkdirSync(context, {recursive: true}); - // move other sources into the build context + // move other sources into the build contex + // we read/write so we can make sure we are removing windows line endings for (const source of sources) { fs.copySync(source.source, path.join(context, source.destination)); debug('copied %o into build context %o', source.source, path.join(context, source.destination)); @@ -282,6 +292,13 @@ class DockerEngine extends Dockerode { fs.copySync(dockerfile, path.join(context, 'Dockerfile')); debug('copied Imagefile from %o to %o', dockerfile, path.join(context, 'Dockerfile')); + // on windows we want to ensure the build context has linux line endings + if (process.platform === 'win32') { + for (const file of require('glob').sync(path.join(context, '**/*'), {nodir: true})) { + write(file, read(file), {forcePosixLineEndings: true}); + } + } + // debug debug('buildxing image %o from %o with build-args', tag, context, buildArgs); @@ -525,11 +542,11 @@ class DockerEngine extends Dockerode { // handle resolve/reject runner.on('done', data => { closer(stream, isRaw); - resolve(makeSuccess(merge({}, data, {command: 'dockerode run', all: allo, stdout: stdouto, stderr: stderro}, {args: command}))); + resolve(makeSuccess(merge({}, data, {command: copts.Entrypoint, all: allo, stdout: stdouto, stderr: stderro}, {args: command}))); }); runner.on('error', error => { closer(stream, isRaw); - reject(makeError(merge({}, args, {command: 'dockerode run', all: allo, stdout: stdouto, stderr: stderro}, {args: command}, {error}))); + reject(makeError(merge({}, args, {command: copts.Entrypoint, all: allo, stdout: stdouto, stderr: stderro}, {args: command}, {error}))); }); }); }; @@ -538,7 +555,7 @@ class DockerEngine extends Dockerode { const callbackHandler = (error, data) => { // emit error first if (error) runner.emit('error', error); - if (data.StatusCode !== 0) runner.emit('error', data); + else if (data.StatusCode !== 0) runner.emit('error', data); // fire done no matter what? runner.emit('done', data); runner.emit('finished', data); @@ -548,8 +565,7 @@ class DockerEngine extends Dockerode { // error if no command if (!command) throw new Error('you must pass a command into engine.run'); // arrayify commands that are strings - if (typeof command === 'string') command = stringArgv(command); - + if (typeof command === 'string') command = require('string-argv')(command); // some good default createOpts const defaultCreateOptions = { AttachStdin: interactive, @@ -568,7 +584,7 @@ class DockerEngine extends Dockerode { // call the parent with clever stuff const runner = super.run(image, command, stream, copts, {}, callbackHandler); // log - this.debug('running command %o on image %o with create opts %o', command, image, copts); + this.debug('running command %o on image %o with create opts %o', [copts.Entrypoint, command].flat(), image, copts); // make this a hybrid async func and return return mergePromise(runner, awaitHandler); } diff --git a/components/l337-v4.js b/components/l337-v4.js index f38259ce2..e7d094171 100644 --- a/components/l337-v4.js +++ b/components/l337-v4.js @@ -6,16 +6,23 @@ const isObject = require('lodash/isPlainObject'); const os = require('os'); const merge = require('lodash/merge'); const path = require('path'); +const read = require('../utils/read-file'); +const write = require('../utils/write-file'); const {generateDockerFileFromArray} = require('dockerfile-generator/lib/dockerGenerator'); const {nanoid} = require('nanoid'); const {EventEmitter} = require('events'); +// set more appropirate lando limit +EventEmitter.setMaxListeners(64); + // @TODO: should these be methods as well? static or otherwise? const getMountMatches = require('../utils/get-mount-matches'); const hasInstructions = require('../utils/has-instructions'); +const toPosixPath = require('../utils/to-posix-path'); class L337ServiceV4 extends EventEmitter { + #app #data static debug = require('debug')('@lando/l337-service-v4'); @@ -35,7 +42,6 @@ class L337ServiceV4 extends EventEmitter { #init() { return { - compose: [], groups: { context: { description: 'A group for adding and copying sources to the image', @@ -51,26 +57,40 @@ class L337ServiceV4 extends EventEmitter { image: undefined, imageInstructions: undefined, imageFileContext: undefined, - sources: [], - states: { - IMAGE: 'UNBUILT', + info: { + api: 4, + state: { + IMAGE: 'UNBUILT', + }, }, + sources: [], stages: { default: 'image', image: 'Instructions to help generate an image', }, + states: { + IMAGE: 'UNBUILT', + }, steps: [], }; } - set state(states) { - this.#data.states = merge(this.#data.states, states); - this.info.state = this.#data.states; - this.emit('state', this.#data.states); + set info(data) { + // reset state info + if (data === undefined) data = {state: this.#data.states, tag: undefined}; + // merge + this.#data.info = merge(this.#data.info, data); + // if we have app.info then merge into that + if (this.#app.info.find(service => service.service === this.id)) { + merge(this.#app.info.find(service => service.service === this.id) ?? {}, data); + } + this.emit('state', this.#data.info); + this.#app.v4.updateComposeCache(); + this.debug('updated app info to %o', this.#data.info); } - get state() { - return this.#data.states; + get info() { + return this.#data.info; } get _data() { @@ -93,12 +113,8 @@ class L337ServiceV4 extends EventEmitter { states = {}, tag = nanoid(), type = 'l337', - legacy = { - meUser = 'www-data', - moreHttpPorts = [], - sport = '443', - } = {}, - } = {}) { + user = undefined, + } = {}, app, lando) { // instantiate ee immedately super(); @@ -124,29 +140,32 @@ class L337ServiceV4 extends EventEmitter { fs.mkdirSync(this.context, {recursive: true}); // initialize our private data + this.#app = app; this.#data = merge(this.#init(), {groups}, {stages}, {states}); // rework info based on whatever is passed in - this.info = merge({}, { - api: 4, - primary, - service: id, - state: this.#data.states, - type, - }, info); + this.info = merge({}, {state: states}, {primary, service: id, type}, info); + + // do some special undocumented things to "ports" + const {ports, http, https} = require('../utils/parse-v4-ports')(config.ports); // add in the l337 spec config - this.addServiceData(config); + this.addServiceData({ + ...config, + extra_hosts: ['host.lando.internal:host-gateway'], + ports, + }); + this.addServiceData({ports}); // handle legacy and deprecated settings in lando-v4 and above services this.addComposeData({services: {[this.id]: {labels: { - 'io.lando.http-ports': ['80', '443'].concat(legacy.moreHttpPorts).join(','), - 'io.lando.https-ports': ['443'].concat([legacy.sport]).join(','), + 'dev.lando.http-ports': http.join(','), + 'dev.lando.https-ports': https.join(','), }, }}}); - // handle legacy "meUser" setting - this.info.user = legacy.meUser; + // set user into info + this.info = {user: user ?? config.user ?? 'root'}; // if we do not have an appmount yet and we have volumes information then try to infer it if (this.config && this.config.volumes && this.config.volumes.length > 0) { @@ -155,7 +174,7 @@ class L337ServiceV4 extends EventEmitter { // if we have one then set it if (appMounts.length > 0) { this.appMount = appMounts.pop(); - this.info.appMount = this.appMount; + this.info = {appMount: this.appMount}; } // debug this.debug('%o autoset appmount to %o, did not select %o', this.id, this.appMount, appMounts); @@ -214,7 +233,16 @@ class L337ServiceV4 extends EventEmitter { // just pushes the compose data directly into our thing addComposeData(data = {}) { - this.#data.compose.push(data); + // should we try to consolidate this + this.#app.add({ + id: `${this.id}-${nanoid()}`, + info: this.info, + data: [data], + }); + + // update app with new stuff + this.#app.compose = require('../utils/dump-compose-data')(this.#app.composeData, this.#app._dir); + this.#app.v4.updateComposeCache(); this.debug('%o added top level compose data %o', this.id, data); } @@ -256,18 +284,24 @@ class L337ServiceV4 extends EventEmitter { if (context && context.length > 0) { this.#data.sources.push(context.map(file => { // file is a string with src par - if (typeof file === 'string' && file.split(':').length === 1) file = {src: file, dest: file}; + if (typeof file === 'string' && toPosixPath(file).split(':').length === 1) file = {src: file, dest: file}; // file is a string with src and dest parts - if (typeof file === 'string' && file.split(':').length === 2) file = {src: file.split(':')[0], dest: file.split(':')[1]}; // eslint-disable-line max-len + if (typeof file === 'string' && toPosixPath(file).split(':').length === 2) { + const parts = file.split(':'); + const dest = parts.pop(); + const src = parts.join(':'); + file = {src, dest}; + } // file is an object with src key if (isObject(file) && file.src) file.source = file.src; // file is an object with dest key if (isObject(file) && file.dest) file.destination = file.dest; // if source is actually a url then lets address that try { - file.url = new URL(file.source).href; + file.url = new URL(toPosixPath(file.source)).href; delete file.source; } catch {} + // at this point we need to make sure a desintation is set if (!file.destination && file.source) file.destination = file.source; if (!file.destination && file.url) file.destination = new URL(file.url).pathname; @@ -287,7 +321,7 @@ class L337ServiceV4 extends EventEmitter { if (file.owner) file.instructions.push(`--chown=${file.owner}`); if (file.permissions) file.instructions.push(`--chmod=${file.permissions}`); file.instructions.push(file.url || file.destination); - file.instructions.push(path.resolve('/', file.destination)); + file.instructions.push(process.platform === 'win32' ? file.destination : path.resolve('/', file.destination)); file.instructions = file.instructions.join(' '); } @@ -355,20 +389,23 @@ class L337ServiceV4 extends EventEmitter { if (compose.volumes && Array.isArray(compose.volumes)) { compose.volumes = compose.volumes.map(volume => { // if volume is a one part string then just return so we dont have to handle it downstream - if (typeof volume === 'string' && volume.split(':').length === 1) return volume; + if (typeof volume === 'string' && toPosixPath(volume).split(':').length === 1) return volume; // if volumes is a string with two colon-separated parts then do stuff - if (typeof volume === 'string' && volume.split(':').length === 2) { - volume = {source: volume.split(':')[0], target: volume.split(':')[1]}; + if (typeof volume === 'string' && toPosixPath(volume).split(':').length === 2) { + const parts = volume.split(':'); + const target = parts.pop(); + const source = parts.join(':'); + volume = {source, target}; } // if volumes is a string with three colon-separated parts then do stuff - if (typeof volume === 'string' && volume.split(':').length === 3) { - volume = { - source: volume.split(':')[0], - target: volume.split(':')[1], - read_only: volume.split(':')[2] === 'ro', - }; + if (typeof volume === 'string' && toPosixPath(volume).split(':').length === 3) { + const parts = volume.split(':'); + const mode = parts.pop(); + const target = parts.pop(); + const source = parts.join(':'); + volume = {source, target, read_only: mode === 'ro'}; } // if source is not an absolute path that exists relateive to appRoot then set as bind @@ -386,7 +423,7 @@ class L337ServiceV4 extends EventEmitter { } // add the data - this.#data.compose.push({services: {[this.id]: compose}}); + this.addComposeData({services: {[this.id]: compose}}); } addSteps(steps) { @@ -498,36 +535,47 @@ class L337ServiceV4 extends EventEmitter { const {imagefile, ...context} = this.generateBuildContext(); try { - // set state - this.state = {IMAGE: 'BUILDING'}; - // run with the appropriate builder - const success = this.buildkit ? await bengine.buildx(imagefile, context) : await bengine.build(imagefile, context); // eslint-disable-line max-len - // augment the success info - success.context = {imagefile, ...context}; + const success = {imagefile, ...context}; + + // only build if image is not already built + if (this?.info?.state?.IMAGE !== 'BUILT') { + // set state + this.info = {state: {IMAGE: 'BUILDING'}}; + // run with the appropriate builder + const result = this.buildkit + ? await bengine.buildx(imagefile, context) : await bengine.build(imagefile, context); + // augment the success info + Object.assign(success, result); + } + + // get the inspect data so we can do other things + success.info = await bengine.getImage(context.tag).inspect(); + // add the final compose data with the updated image tag on success // @NOTE: ideally its sufficient for this to happen ONLY here but in v3 its not this.addComposeData({services: {[context.id]: {image: context.tag}}}); - - // state - this.state = {IMAGE: 'BUILT'}; // set the image stuff into the info - this.info.image = imagefile; - this.info.tag = context.tag; - + this.info = {image: imagefile, state: {IMAGE: 'BUILT'}, tag: context.tag}; this.debug('image %o built successfully from %o', context.id, imagefile); return success; // failure } catch (error) { + // augment error error.context = {imagefile, ...context}; + error.logfile = path.join(context.context ?? os.tmpdir(), `error-${nanoid()}.log`); this.debug('image %o build failed with code %o error %o', context.id, error.code, error); - this.addComposeData({services: {[context.id]: {command: 'sleep infinity'}}}); - // set the build failure - this.state = {IMAGE: 'BUILD FAILURE'}; - // and remove a bunch of stuff - this.info.image = undefined; - this.info.tag = undefined; + // inject helpful failing stuff to compose + this.addComposeData({services: {[context.id]: { + command: require('../utils/get-v4-image-build-error-command')(error), + image: 'busybox', + user: 'root', + volumes: [`${error.logfile}:/tmp/error.log`], + }}}); + + // set the image stuff into the info + this.info = {error: error.short, image: undefined, state: {IMAGE: 'BUILD FAILURE'}, tag: undefined}; this.tag = undefined; // then throw @@ -564,18 +612,19 @@ class L337ServiceV4 extends EventEmitter { // attempt to normalize newling usage mostly for aesthetic considerations steps[group] = steps[group] - .map(instructions => instructions.split('\n').filter(instruction => instruction && instruction !== '')) + .map(instructions => instructions.split('\n') + .filter(instruction => instruction && instruction !== '')) .flat(Number.POSITIVE_INFINITY); // prefix user and comment data and some helpful envvars steps[group].unshift(`USER ${user}`); - steps[group].unshift(`ENV LANDO_IMAGE_GROUP ${group}`); - steps[group].unshift(`ENV LANDO_IMAGE_USER ${user}`); + steps[group].unshift(`ENV LANDO_IMAGE_GROUP=${group}`); + steps[group].unshift(`ENV LANDO_IMAGE_USER=${user}`); steps[group].unshift(`# group: ${group}`); // and add a newline for readability steps[group].push(''); // and then finally put it all together - steps[group] = steps[group].join('\n'); + steps[group] = steps[group].map(line => line.trimStart()).join('\n'); } // we should have raw instructions data now @@ -595,7 +644,7 @@ class L337ServiceV4 extends EventEmitter { // throw new Error('NO NO NO') // write the imagefile - fs.writeFileSync(this.imagefile, content); + write(this.imagefile, content); // return the build context return { @@ -610,14 +659,6 @@ class L337ServiceV4 extends EventEmitter { }; } - generateOrchestorFiles() { - return { - id: this.id, - info: this.info, - data: this.#data.compose.map(element => merge({}, element)), - }; - } - getSteps(stage) { // if we have a stage then filter by that if (stage) return this.#data.steps.filter(step => step.stage === stage); @@ -675,7 +716,7 @@ class L337ServiceV4 extends EventEmitter { const content = image; image = path.join(require('os').tmpdir(), nanoid(), 'Imagefile'); fs.mkdirSync(path.dirname(image), {recursive: true}); - fs.writeFileSync(image, content); + write(image, content); this.#data.imageFileContext = this.appRoot; } @@ -690,7 +731,7 @@ class L337ServiceV4 extends EventEmitter { // and then generate the image instructions and set info this.#data.imageInstructions = fs.existsSync(image) - ? fs.readFileSync(image, 'utf8') : generateDockerFileFromArray([{from: {baseImage: image}}]); + ? read(image) : generateDockerFileFromArray([{from: {baseImage: image}}]); this.info.image = image; // finally lets reset the relevant build key if applicable @@ -698,8 +739,11 @@ class L337ServiceV4 extends EventEmitter { this.addComposeData({services: {[this.id]: { build: merge({}, buildArgs, {dockerfile: path.basename(image), context: path.dirname(image)}), }}}); + // or the image one if its that one - } else this.addComposeData({services: {[this.id]: {image}}}); + } else { + this.addComposeData({services: {[this.id]: {image}}}); + } // log this.debug('%o set base image to %o with instructions %o', this.id, this.#data.image, this.#data.imageInstructions); diff --git a/config.yml b/config.yml index 21043c82e..8a78c1f5e 100644 --- a/config.yml +++ b/config.yml @@ -8,21 +8,21 @@ dockerSupportedVersions: compose: satisfies: "1.x.x || 2.x.x" recommendUpdate: "<=2.24.6" - tested: "<=2.27.0" + tested: "<=2.27.1" link: linux: https://docs.docker.com/compose/install/#install-compose-on-linux-systems darwin: https://docs.docker.com/desktop/install/mac-install/ win32: https://docs.docker.com/desktop/install/windows-install/ desktop: satisfies: ">=4.0.0 <5" - tested: "<=4.30.0" + tested: "<=4.32.0" recommendUpdate: "<=4.28.0" link: darwin: https://docs.docker.com/desktop/install/mac-install/ win32: https://docs.docker.com/desktop/install/windows-install/ engine: - satisfies: ">=18 <27" - tested: "<=26.1.1" + satisfies: ">=18 <28" + tested: "<=27.0.3" link: linux: https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script diff --git a/docs/env.md b/docs/env.md index f2ea6433f..952544c54 100644 --- a/docs/env.md +++ b/docs/env.md @@ -31,7 +31,7 @@ LANDO_CONFIG_DIR=/Users/pirog/.lando LANDO_DOMAIN=lndo.site LANDO_HOST_HOME=/Users/pirog LANDO_HOST_OS=darwin -LANDO_HOST_IP=host.docker.internal +LANDO_HOST_IP=host.lando.internal LANDO_MOUNT=/app LANDO_APP_NAME=lamp LANDO_APP_ROOT=/Users/pirog/work/lando/examples/lamp diff --git a/docs/events.md b/docs/events.md index 912a1990a..e91255303 100644 --- a/docs/events.md +++ b/docs/events.md @@ -40,11 +40,11 @@ While the above lists are great starting point, they may be out of date. You can ```bash # Discover hookable events for the `lando start` command -lando start -vvv | grep "Emitting" +lando start --debug | grep "emitting" # Discover hookable events for the `lando test` command # NOTE: This assumed you've defined a `test` command in tooling -lando test -vvv | grep "Emitting" +lando test --debug | grep "emitting" ``` Specifically, you need to hook into an event where the service you are running the command against exists and is running. @@ -115,3 +115,13 @@ events: - appserver: composer compile-templates ``` +### Backgrounding + +You can also background a command using `&` + +```yaml +events: + post-some-command: + - node: npm run worker-process & +``` + diff --git a/docs/networking.md b/docs/networking.md index 00b150aff..af3943ef7 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -7,14 +7,14 @@ description: Lando improves the core networking provided by Docker and Docker Co Lando sets up and manages its own internal Docker network. This provides a common pattern, predictable hostnames and a more reliable experience for local development networking, generally. -Specifically, every Lando service, even those added via the `compose` top level config, should be able to communicate with every other service regardless of whether that service is part of your app or not. Also note that because of our [automatic certificate and CA setup](./security.md), you should be able to access all of these services over `https` without needing, for example the `-k` option in `curl`. +Specifically, every Lando service, even those added via the `compose` top level config, should be able to communicate with every other service regardless of whether that service is part of your app or not. + +Also note that because of our [automatic certificate and CA setup](./security.md), you should be able to access all of these services over `https` without needing, for example the `-k` option in `curl`. ::: warning Cross app service communication requires all apps to be running! If you want a service in App A to talk to a service in App B then you need to make sure you've started up both apps! ::: -[[toc]] - ## Automatic Hostnames By default, every service will get and be accessible at a hostname of the form `..internal`. For example, if you have an app called `labouche` and a service called `redis`, it should be accessible from any other container using `redis.labouche.internal`. @@ -25,7 +25,7 @@ You can get information about which hostnames and urls map to what services usin **Note that this automatic networking only happens INSIDE of the Docker daemon and not on your host.** -## Testing +### Testing You can verify that networking is set up correctly by spinning up two `lamp` recipes called `lamp1` and `lamp2` and running a few `curl` commands. @@ -45,61 +45,16 @@ cd /path/to/lamp2 lando ssh -s database -c "mysql -uroot -h database.lamp1.internal" ``` -## Port considerations - -::: warning This behavior changed in `3.0.29` -::: +## Accessing the host -Prior to Lando `3.0.29`, when trying to communicate between two services using `proxy` addresses a la `thing.lndo.site` you had to explicitly use the internal proxy port and protocol if they differed from the proxy. +As of Lando `3.22` you can now access your host from inside every Lando service using `host.lando.internal` -Consider the below example for a clearer picture of this behavior. - -```yaml -proxy: - appserver: - - my-project.lndo.site:8000 - another_thing: - - thing.my-project.lndo.site +```sh +lando exec my-service -- ping host.lando.internal -c 3 ``` -```bash -# Access the services externally (eg on your host) using the proxy -curl https://lamp2.lndo.site -curl thing.my-project.lndo.site - -# Access the services internally (eg from inside a container) using the proxy alias -# Below will fail -lando ssh -s appserver -c "curl my-project.lndo.site" -# Below will succeed -lando ssh -s appserver -c "curl thing.my-project.lndo.site" -# Below will succeed -lando ssh -s appserver -c "curl my-project.lndo.site:8000" -``` +You can also use the environment variable `LANDO_HOST_IP`. -As of Lando `3.0.29` internal requests to proxy addresses are now routed out to the proxy and back into Lando. This means that the behavior is now the same regardless of whether the request originates on your host or from inside a container. - -However, please note that _**this could be a breaking change**_ if your app was hardcoding the needed port. In most of these cases you can now simply omit the port since the proxy will know what port to use for a given service. - -Expressed in terms of the above example you should now expect this: - -```yaml -proxy: - appserver: - - my-project.lndo.site:8000 - another_thing: - - thing.my-project.lndo.site -``` - -```bash -# Access the services externally (eg on your host) using the proxy -curl https://lamp2.lndo.site -curl thing.my-project.lndo.site - -# Access the services internally (eg from inside a container) using the proxy alias -# Below will succeed -lando ssh -s appserver -c "curl my-project.lndo.site" -# Below will succeed -lando ssh -s appserver -c "curl thing.my-project.lndo.site" -# Below will fail -lando ssh -s appserver -c "curl my-project.lndo.site:8000" +```sh +lando exec my-service -- ping "\$LANDO_HOST_IP" -c 3 ``` diff --git a/docs/security.md b/docs/security.md index 14bc5afd0..5806a0c75 100644 --- a/docs/security.md +++ b/docs/security.md @@ -13,7 +13,7 @@ The things we do by default and how you can modify them to your needs are shown ## Exposure -As of `3.0.0-rrc.5`, Lando will bind all exposed services to `127.0.0.1` for security reasons. This means your services are *only* available to your machine. You can alter this behavior in one of two ways. +Lando will bind all exposed services to `127.0.0.1` for security reasons. This means your services are *only* available to your machine. You can alter this behavior in one of two ways. ### 1. Changing the bind address @@ -49,52 +49,70 @@ Note that there are security implications to both of the above and it is not rec ## Certificates -Lando uses its own Certificate Authority to sign the certs for each service and to ensure that these certs are trusted on our [internal Lando network](./networking.md). They should live inside every service at `/certs`. +Lando uses its own Certificate Authority to sign the certs for each service and to ensure that these certs are trusted on our [internal Lando network](./networking.md). -```bash +Where they live in each service will differ depending on the service API version: + +::: code-group + +```sh [API 3] /certs |-- cert.crt -|-- cert.csr -|-- cert.ext |-- cert.key |-- cert.pem ``` -However, for reasons detailed in [this blog post](https://httptoolkit.com/blog/debugging-https-without-global-root-ca-certs/), we do not trust this CA on your system automatically. Instead, we require you to opt-in manually as a security precaution. +```sh [API 4] +/etc/lando/certs +|-- cert.crt +|-- cert.key +|-- cert.pem +``` + +::: + +You can also inspect the `LANDO_SERVICE_CERT` and `LANDO_SERVICE_KEY` envvars to get the cert locations. -**This means that by default you will receive browser warnings** when accessing `https` proxy routes. +```sh +lando exec web -- env | grep LANDO_SERVICE_CERT +lando exec web -- env | grep LANDO_SERVICE_KEY +``` + +Note that in API 3 services you will need to [enable SSL](./services/lando.html#ssl) to get certs. API 4 services will generate certs by default. ## Trusting the CA -While Lando will automatically trust this CA internally, it is up to you to trust it on your host machine. Doing so will alleviate browser warnings regarding certs we issue. +We will also automatically trust the generated Lando Development CA during setup. + +If you missed this or cancelled it for whatever reason you can run [`lando setup --skip-common-plugins`](https://docs.lando.dev/cli/setup.html) and Lando will try to install it again. + +Note that Lando will only add trust to the system store so you may need to trust the CA in additional places. To do so please continue reading. ::: warning You may need to destroy the proxy container and rebuild your app! If you've tried to trust the certificate but are still seeing browser warnings you may need to remove the proxy with `docker rm -f landoproxyhyperion5000gandalfedition_proxy_1` and then `lando rebuild` your app. ::: -The default Lando CA should be located at `~/.lando/certs/lndo.site.pem`. If you don't see the cert there, try starting up an app as this will generate the CA if its not already there. Note that if you change the Lando `domain` in the [global config](./global.md), you will have differently named certs and you will likely need to trust these new certs and rebuild your apps for them to propagate correctly. - -Also note that in accordance with the [restrictions](https://en.wikipedia.org/wiki/Wildcard_certificate#Limitations) on wildcard certs, changing the `domain` may result in unexpected behavior depending on how you set it. For example, setting `domain` to a top level domain such as `test` will not work while `local.test` will. +The default Lando CA should be located at `~/.lando/certs/LandoCA.crt`. If you don't see the cert there, try starting up an app as this will generate the CA if its not already there. -Also note that we produce a duplicate `crt` file that you can use for systems that have stricter rules around how the certs are named. This means that by default, you will also end up with a file called `~/.lando/certs/lndo.site.crt` in addition to `~/.lando/certs/lndo.site.pem`. +Note that if you change the [global config](./global.md), you may have differently named certs and you will likely need to trust these new certs and rebuild your apps for them to propagate correctly. Running `lando config --path caCert` can help in these situations. -That all said, once you've located the correct cert, you can add or remove it with the relevant commands below. +That all said, once you've located the correct cert, you can add or remove it manually with the relevant commands below. -### macOS (see Firefox instructions below) +### macOS -```bash +```zsh # Add the Lando CA -sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ~/.lando/certs/lndo.site.pem +security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain-db ~/.lando/certs/LandoCA.crt # Remove Lando CA -sudo security delete-certificate -c "Lando Local CA" +security delete-certificate -c "Lando Development CA" ``` ### Windows -```bash +```powershell # Add the Lando CA -certutil -addstore -f "ROOT" C:\Users\ME\.lando\certs\lndo.site.pem +certutil -f -user -addstore "ROOT" C:\Users\ME\.lando\certs\LandoCA.crt # Remove Lando CA certutil -delstore "ROOT" serial-number-hex @@ -102,52 +120,48 @@ certutil -delstore "ROOT" serial-number-hex Note that if you want to trust the cert and are using Lando within a Linux environment on WSL2, you'll need to use the path to the cert used by that Linux environment. Ex: -```bash -certutil -addstore -f "ROOT" \\wsl.localhost\LINUX-DISTRIBUTION\home\LINUX-USER\.lando\certs\lndo.site.pem +```sh +certutil -addstore -f "ROOT" \\wsl.localhost\LINUX-DISTRIBUTION\home\LINUX-USER\.lando\certs\LandoCA.crt ``` ### Debian -```bash +```sh # Add the Lando CA -sudo cp -r ~/.lando/certs/lndo.site.pem /usr/local/share/ca-certificates/lndo.site.pem -sudo cp -r ~/.lando/certs/lndo.site.crt /usr/local/share/ca-certificates/lndo.site.crt +sudo cp -r ~/.lando/certs/LandoCA.crt /usr/local/share/ca-certificates/LandoCA.crt sudo update-ca-certificates # Remove Lando CA -sudo rm -f /usr/local/share/ca-certificates/lndo.site.pem -sudo rm -f /usr/local/share/ca-certificates/lndo.site.crt +sudo rm -f /usr/local/share/ca-certificates/LandoCA.crt sudo update-ca-certificates --fresh ``` -### Ubuntu or MacOS with Firefox - -Import the `~/.lando/certs/lndo.site.pem` CA certificate in Firefox by going to `about:preferences#privacy` > `View Certificates` > `Authorities` > `Import`, enabling **Trust this CA to identify websites.**. - -### Ubuntu with Chrome - -On the Authorities tab at chrome://settings/certificates, import `~/.lando/certs/lndo.site.pem or /usr/local/share/ca-certificates/lndo.site.crt` - ### Arch -```bash +```sh # Add the Lando CA -sudo trust anchor ~/.lando/certs/lndo.site.pem -sudo trust anchor ~/.lando/certs/lndo.site.crt +sudo trust anchor ~/.lando/certs/LandoCA.crt # Remove Lando CA -sudo trust anchor --remove ~/.lando/certs/lndo.site.pem -sudo trust anchor --remove ~/.lando/certs/lndo.site.crt +sudo trust anchor --remove ~/.lando/certs/LandoCA.crt ``` -::: warning Firefox maintains its own certificate store! -Firefox users may still see browser warnings after performing the steps above. Firefox maintains its own certificate store and does not, by default, use the operating system's certificate store. To allow Firefox to use the operating system's certificate store, the **security.enterprise_roots.enabled** setting must be set to **true**. +### Firefox + +Firefox users may still see browser warnings after performing the steps above. Firefox maintains its own certificate store and does not, by default, use the operating system's certificate store. + +To allow Firefox to use the operating system's certificate store, the **security.enterprise_roots.enabled** setting must be set to **true**. * In Firefox, type `about:config` in the address bar * If prompted, accept any warnings * Search for `security.enterprise_roots.enabled` * Set the value to `true` -::: + +Or you can [manually import a trusted CA](https://support.mozilla.org/en-US/kb/setting-certificate-authorities-firefox) to the Firefox store. + +### Chrome + +Check out [Step 2](https://support.google.com/chrome/a/answer/6342302?hl=en). ## SSH Keys diff --git a/docs/tooling.md b/docs/tooling.md index 9a9fcd681..65d82ae8f 100644 --- a/docs/tooling.md +++ b/docs/tooling.md @@ -35,8 +35,11 @@ tooling: dir: cwd | absolute path to elsewhere cmd: mycommand user: you + usage: + positionals: options: env: + examples: [] ``` ::: tip Tooling routes are cached! @@ -252,6 +255,63 @@ lando word lando word --word=fox ``` +### Arguments + +Like options based tooling you can also specify some positional arguments in the tooling command. + +```yaml +tooling: + my-command: + service: web + cmd: /app/args.sh + positionals: + arg1: + describe: This is the first arg + type: string + choices: + - thing + - stuff + arg2: + describe: This is the second arg + type: string +``` + +Unlike options this is mostly useful to provide better `--help` and command usage information. + +### Usage & Examples + +If you implement options or positionals in your tooling command it's nice to add additional usage information to `--help` which you can do with `usage` and `examples`. + +```yaml +tooling: + my-command: + cmd: /app/args.sh + service: node + usage: $0 everything [thing|stuff] [arg2] [--word=] + examples: + - $0 everything thing + - $0 everything stuff morestuff + - $0 everything thing whatver --word yes + options: + word: + passthrough: true + alias: + - w + describe: Print what the word is + positionals: + arg1: + describe: Uses arg1 + type: string + choices: + - thing + - stuff + arg2: + describe: Uses arg2 + type: string +``` + +You can use `$0` as a token value to replace the binary name which is usually just `lando`. + ## Overriding You can override tooling provided by Lando recipes or upstream Landofiles by redefining the tooling command in your Landofile. diff --git a/examples/badname/README.md b/examples/badname/README.md index ea2cc4fb5..b31dc0e2e 100644 --- a/examples/badname/README.md +++ b/examples/badname/README.md @@ -1,12 +1,10 @@ -Badname Example -=============== +# Badname Example This example exists primarily to test the following: * [Issue #1767](https://github.com/lando/lando/issues/1767) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -27,8 +24,7 @@ lando ssh -s defaults -c "curl http://localhost | grep ROOTDIR" lando ssh -s defaults-v4 -c "curl http://localhost | grep ROOTDIR" ``` -Destroy tests -------------- +## Destroy tests Run the following commands to trash this app like nothing ever happened. diff --git a/examples/base/.lando.yml b/examples/base/.lando.yml deleted file mode 100644 index 2d670887a..000000000 --- a/examples/base/.lando.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: lando-base -env_file: - - environment/moar.env -compose: - - docker-compose/moar.yml -tooling: - php: - service: log -pluginDirs: - - plugins -plugins: - "@lando/base-test-plugin-2": ./test-plugin-2 - "@lando/core": "../.." -services: - web3: - api: 4 - type: l337 - image: nginx - ports: - - '80' - volumes: - - ./:/usr/share/nginx/html - diff --git a/examples/base/README.md b/examples/base/README.md deleted file mode 100644 index 39f606783..000000000 --- a/examples/base/README.md +++ /dev/null @@ -1,231 +0,0 @@ -Basics Example -============== - -This example exists primarily to test the following documentation: - -**Basics** - -* [Landofiles](http://docs.lando.dev/config/lando.html) -* [Environment](http://docs.lando.dev/config/env.html) - -**CLI** - -* [CLI Usage](http://docs.lando.dev/basics/usage.html) -* [`lando config`](http://docs.lando.dev/basics/config.html) -* [`lando destroy`](http://docs.lando.dev/basics/destroy.html) -* [`lando info`](http://docs.lando.dev/basics/info.html) -* [`lando list`](http://docs.lando.dev/basics/list.html) -* [`lando logs`](http://docs.lando.dev/basics/logs.html) -* [`lando poweroff`](http://docs.lando.dev/basics/poweroff.html) -* [`lando info`](http://docs.lando.dev/basics/info.html) -* [`lando rebuild`](http://docs.lando.dev/basics/rebuild.html) -* [`lando restart`](http://docs.lando.dev/basics/restart.html) -* [`lando ssh`](http://docs.lando.dev/basics/ssh.html) -* [`lando start`](http://docs.lando.dev/basics/start.html) -* [`lando stop`](http://docs.lando.dev/basics/stop.html) -* [`lando version`](http://docs.lando.dev/basics/version.html) - -See the [Landofiles](http://docs.lando.dev/config/lando.html) in this directory for the exact magicks. - -Start up tests --------------- - -```bash -# Should start successfully -lando poweroff -lando start -``` - -Verification commands ---------------------- - -Run the following commands to verify things work as expected - -```bash -# Should merge in all Landofiles correctly -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_log_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web2_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web3_1 - -# Should merge in all Landofiles correctly even if we are down a directory -cd docker-compose -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_log_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web2_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web3_1 -cd .. - -# Should load environment files from all Landofiles -lando ssh -s web -c "env" | grep "MILEY=CYRUS" -lando ssh -s web -c "env" | grep "TAYLOR=SWIFT" -lando ssh -s web -c "env" | grep "LOCAL=LANDO" -lando ssh -s web3 -c "env" | grep "MILEY=CYRUS" -lando ssh -s web3 -c "env" | grep "TAYLOR=SWIFT" -lando ssh -s web3 -c "env" | grep "LOCAL=LANDO" - -# Should load environment files from all Landofiles if we are down a directory -cd environment -lando ssh -s web -c "env" | grep "MILEY=CYRUS" -lando ssh -s web -c "env" | grep "TAYLOR=SWIFT" -lando ssh -s web -c "env" | grep "LOCAL=LANDO" -lando ssh -s web3 -c "env" | grep "MILEY=CYRUS" -lando ssh -s web3 -c "env" | grep "TAYLOR=SWIFT" -lando ssh -s web3 -c "env" | grep "LOCAL=LANDO" -cd .. - -# Should return lando help -lando config --help | grep verbose -lando config --lando | grep verbose - -# Should return the version -lando version | grep "v3." - -# Should run debug messages on stderr -lando info -v | grep INFO || echo $? | grep 1 -lando info -vv | grep VERBOSE || echo $? | grep 1 -lando info -vvv | grep DEBUG || echo $? | grep 1 -lando info -vvvv | grep SILLY || echo $? | grep 1 - -# Should run all log levels on stderr -lando info -v 2>&1 | grep lando | grep + | grep ms -lando info -vv 2>&1 | grep lando | grep + | grep ms -lando info -vvv 2>&1 | grep lando | grep + | grep ms -lando info -vvvv 2>&1 | grep lando | grep + | grep ms -lando info --debug 2>&1 | grep lando | grep + | grep ms - -# Should run lando config without error -lando config - -# Should only show specified field in lando config -lando config --path mode | grep cli -lando config -p mode | grep cli -lando config --field mode | grep cli -lando config --field mode | grep recipes || echo $? | grep 1 - -# Should output JSON in lando config without error -lando config --format json - -# Should run lando info without error -lando info - -# Should return docker inspect data -lando info -d | grep NetworkSettings -lando info --deep | grep NetworkSettings - -# Should output JSON in lando info without error -lando info --format json - -# Should return a specified path when given with lando info -lando info --path "[0]" | grep service | wc -l | grep 1 - -# Should list this apps containers -lando list | grep landobase_log_1 -lando list | grep landobase_web_1 -lando list | grep landobase_web2_1 -lando list | grep landobase_web3_1 - -# Should output JSON in lando list without error -lando list --format json - -# Should return a specified path when given with lando list -lando list --path "landobase" | grep landobase - -# Should return logs without error -lando logs - -# Should return only logs for the specified service -lando logs -s web2 | grep log_1 || echo $? | grep 1 -lando logs --service web2 | grep log_1 || echo $? | grep 1 -lando logs -s web3 | grep log_1 || echo $? | grep 1 -lando logs --service web3 | grep log_1 || echo $? | grep 1 - -# Should run a command as the LANDO_WEBROOT_USER by default -lando ssh -s web2 -c "id | grep \$LANDO_WEBROOT_USER" -lando ssh -s web3 -c "id | grep \$LANDO_WEBROOT_USER" - -# Should run a command as the user specific -lando ssh -s web2 -u root -c "id | grep root" -lando ssh -s web3 -u root -c "id | grep root" - -# Should run commands from /app for v3 services -lando ssh -s web2 -u root -c "pwd" | grep /app - -# Should run commands from appMount for v4 services -lando ssh -s web3 -u root -c "pwd" | grep /usr/share/nginx/html - -# Should stop the apps containers -lando stop -docker ps --filter label=com.docker.compose.project=landobase -q | wc -l | grep 0 - -# Should stop ALL running lando containers -lando start -docker ps --filter label=io.lando.container=TRUE -q | wc -l | grep 4 -lando poweroff -docker ps --filter label=io.lando.container=TRUE -q | wc -l | grep 0 - -# Should rebuild the services without errors -lando rebuild -y -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_log_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web2_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web3_1 - -# Should only rebuild the specified services -lando rebuild -y --service web2 -lando rebuild -y -s web2 -docker ps --latest | grep landobase_web2_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_log_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web2_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web3_1 -lando rebuild -y --service web3 -lando rebuild -y -s web3 -docker ps --latest | grep landobase_web3_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_log_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web2_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web3_1 - -# Should restart the services without errors -lando restart -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_log_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web2_1 -docker ps --filter label=com.docker.compose.project=landobase | grep landobase_web3_1 - -# Should have non-numeric keys in LANDO_INFO -lando php info.php - -# Should clear the lando tasks cache -lando version -lando --clear -ls -lsa ~/.lando/cache | grep _.tasks.cache || echo $? | grep 1 - -# Should set the release channel as stable by default -lando config | grep "channel" | grep "stable" - -# Should set the release channel based on the user option -lando --channel edge -lando config | grep "channel" | grep "edge" -lando --channel stable -lando config | grep "channel" | grep "stable" - -# Should not allow bogus release channels -lando --channel orange || echo $? | grep 1 - -# Should load plugins from pluginDirs -lando stuff | grep "I WORKED" - -# Should load plugins specified in landofile -lando stuff2 | grep "I WORKED" -``` - -Destroy tests -------------- - -```bash -# Should destroy successfully -lando destroy -y -lando poweroff -``` diff --git a/examples/base/info.php b/examples/base/info.php deleted file mode 100644 index 032eeee39..000000000 --- a/examples/base/info.php +++ /dev/null @@ -1,12 +0,0 @@ - $value) { - if (is_numeric($key)) { - throw new Exception('Numeric key detected! TROUBLE TROUBLE TROUBLE!'); - } - } - diff --git a/examples/services/.lando.yml b/examples/build/.lando.yml similarity index 96% rename from examples/services/.lando.yml rename to examples/build/.lando.yml index 7ca74a436..6916f11c4 100644 --- a/examples/services/.lando.yml +++ b/examples/build/.lando.yml @@ -1,4 +1,4 @@ -name: lando-services +name: lando-build events: pre-start: - cat /build_as_root_internal.txt | grep root @@ -14,7 +14,7 @@ events: - cat /var/www/run_internal.txt | grep www-data - cat /run_as_root.txt | grep root - cat /var/www/run.txt | grep www-data - - /bin/sh -c 'echo "$LANDO_APP_PROJECT" | grep landoservices' + - /bin/sh -c 'echo "$LANDO_APP_PROJECT" | grep landobuild' # uncomment below to test out https://github.com/lando/core/issues/70 # this is commented out by default because of https://github.com/actions/runner/issues/241 # - bash /app/post-start.bash diff --git a/examples/services/README.md b/examples/build/README.md similarity index 53% rename from examples/services/README.md rename to examples/build/README.md index 91f74247d..dd8911c55 100644 --- a/examples/services/README.md +++ b/examples/build/README.md @@ -1,16 +1,14 @@ -Services Example -================ +# Build Example This example exists primarily to test the following documentation: -* [Build Steps](http://docs.devwithlando.io/config/services.html#build-steps) -* [Overrides](http://docs.devwithlando.io/config/services.html#overrides) -* [Using Dockerfiles](http://docs.devwithlando.io/config/services.html#using-dockerfiles) +* [Build Steps](https://docs.lando.dev/config/services.html#build-steps) +* [Overrides](https://docs.lando.dev/config/services.html#overrides) +* [Using Dockerfiles](https://docs.lando.dev/config/services.html#using-dockerfiles) -See the [Landofiles](http://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. -Start up tests --------------- +## Start up tests ```bash # Should start successfully and verify build steps run at more or less the right times @@ -18,20 +16,19 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected ```bash # Should have mounted overridden nginx volume -lando ssh -s nginx -c "cat /var/www/test.txt | grep MOUNTED" +lando exec nginx -- cat /var/www/test.txt | grep MOUNTED # Should have injected overridden envvar into nginx -lando ssh -s nginx -c "env | grep THING=STUFF" +lando exec nginx -- env | grep THING=STUFF # Should have built appserver from a custom docker image -lando ssh -s appserver -c "env | grep CUSTOM=PIROG" +lando exec appserver -- env | grep CUSTOM=PIROG # Should be able to rebuild without pulling local image lando rebuild -y @@ -39,27 +36,26 @@ lando rebuild -y # Should rerun build steps even if containers are manually removed and stuff lando destroy -y lando start -y -docker rm -f landoservices_nginx_1 -docker rm -f landoservices_appserver_1 +docker rm -f landobuild_nginx_1 +docker rm -f landobuild_appserver_1 lando start -y -lando ssh -s appserver -c "vim --version" -lando ssh -s appserver -c "cat /var/www/build.txt" -lando ssh -s appserver -c "cat /run_as_root.txt" -lando ssh -s appserver -c "cat /var/www/run.txt" +lando exec appserver -- vim --version +lando exec appserver -- cat /var/www/build.txt +lando exec appserver -- cat /run_as_root.txt +lando exec appserver -- cat /var/www/run.txt # Should be able to set the timezone in a Lando service. # This tests the 'How do I set the timezone in a Lando service?' guide. # https://docs.lando.dev/guides/how-do-i-set-the-timezone-of-a-lando-service.html -lando ssh -s nginx -c "date" | grep -E "EST|EDT" +lando exec nginx -- date | grep -E "EST|EDT" # Should be able to handle running things from different directories when building from local dockerfile # https://github.com/lando/lando/issues/2102 cd php -lando ssh -s appserver -c "true" +lando exec appserver -- pwd | grep /app/php ``` -Destroy tests -------------- +## Destroy tests ```bash # Should destroy successfully diff --git a/examples/services/build_as_root.bash b/examples/build/build_as_root.bash similarity index 100% rename from examples/services/build_as_root.bash rename to examples/build/build_as_root.bash diff --git a/examples/services/php/Dockerfile b/examples/build/php/Dockerfile similarity index 100% rename from examples/services/php/Dockerfile rename to examples/build/php/Dockerfile diff --git a/examples/services/post-start.bash b/examples/build/post-start.bash similarity index 100% rename from examples/services/post-start.bash rename to examples/build/post-start.bash diff --git a/examples/services/test.txt b/examples/build/test.txt similarity index 100% rename from examples/services/test.txt rename to examples/build/test.txt diff --git a/examples/services/www/index.html b/examples/build/www/index.html similarity index 100% rename from examples/services/www/index.html rename to examples/build/www/index.html diff --git a/examples/base/.gitignore b/examples/cache/.gitignore similarity index 100% rename from examples/base/.gitignore rename to examples/cache/.gitignore diff --git a/examples/cache/.lando.yml b/examples/cache/.lando.yml new file mode 100644 index 000000000..68b5a856e --- /dev/null +++ b/examples/cache/.lando.yml @@ -0,0 +1,33 @@ +name: lando-cache +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/cache/README.md b/examples/cache/README.md new file mode 100644 index 000000000..9e2ee5e37 --- /dev/null +++ b/examples/cache/README.md @@ -0,0 +1,52 @@ +# Cache Example + +This example exists primarily to test the task caches + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should have both a global and app task cache after a start +cat ~/.lando/cache/_.tasks.cache +cat ~/.lando/cache/lando-cache.compose.cache + +# Should not have either after a lando --clear +lando --clear +cat ~/.lando/cache/_.tasks.cache || echo $? | grep 1 +cat ~/.lando/cache/lando-cache.compose.cache || echo $? | grep 1 + +# Should regenerate the caches on lando +lando --clear +lando || true +cat ~/.lando/cache/_.tasks.cache +cat ~/.lando/cache/lando-cache.compose.cache + +# Should regenerate the caches on any --help before the help is displayed +lando --clear +lando exec --help | grep service | grep choices | grep web | grep web2 | grep web3 | grep web4 +cat ~/.lando/cache/_.tasks.cache +cat ~/.lando/cache/lando-cache.compose.cache + +# Should remove the compose cache after a destroy +lando destroy -y +cat ~/.lando/cache/lando-cache.compose.cache || echo $? | grep 1 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/cache/compose.yml b/examples/cache/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/cache/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/base/index.html b/examples/cache/index.html similarity index 100% rename from examples/base/index.html rename to examples/cache/index.html diff --git a/examples/certs/.lando.yml b/examples/certs/.lando.yml new file mode 100644 index 000000000..5a7ac75c2 --- /dev/null +++ b/examples/certs/.lando.yml @@ -0,0 +1,87 @@ +name: lando-certs +proxy: + web: + - web.lndo.site:8080 + web2: + - web2.lndo.site:8080 + +services: + web: + api: 3 + type: lando + ssl: true + sslExpose: true + sport: "8443" + services: + image: bitnami/nginx + command: /opt/bitnami/scripts/nginx/entrypoint.sh /opt/bitnami/scripts/nginx/run.sh + ports: + - "8080" + - "8443" + user: root + volumes: + - ./default-ssl.conf:/opt/bitnami/nginx/conf/server_blocks/my_server_block.conf + - ./:/usr/share/nginx/html + web2: + api: 4 + image: + imagefile: nginxinc/nginx-unprivileged:1.26.1 + context: + - ./default-ssl-2.conf:/etc/nginx/conf.d/default.conf + user: nginx + ports: + - 8080/http + - 8443/https + web3: + api: 4 + certs: /certs/cert.crt + hostnames: + - vibes.rising + image: + imagefile: nginxinc/nginx-unprivileged:1.26.1 + context: + - ./default-ssl.conf:/etc/nginx/conf.d/default.conf + user: nginx + ports: + - 8080/http + - 8443/https + web4: + api: 4 + certs: + cert: /frank/cert.crt + key: /bob/key.key + image: + imagefile: nginxinc/nginx-unprivileged:1.26.1 + context: + - ./default-ssl-3.conf:/etc/nginx/conf.d/default.conf + user: nginx + ports: + - 8080/http + - 8443/https + web5: + api: 4 + certs: false + image: + imagefile: nginxinc/nginx-unprivileged:1.26.1 + context: + - ./default.conf:/etc/nginx/conf.d/default.conf + user: nginx + ports: + - 8080/http +tooling: + certinfo: + cmd: openssl x509 -in "$LANDO_SERVICE_CERT" -noout -text + service: :service + options: + service: + default: web + alias: + - s + describe: Runs on a different service + +plugins: + "@lando/core": "../.." + "@lando/healthcheck": "../../plugins/healthcheck" + "@lando/networking": "../../plugins/networking" + "@lando/proxy": "../../plugins/proxy" + "@lando/scanner": "../../plugins/scanner" diff --git a/examples/certs/README.md b/examples/certs/README.md new file mode 100644 index 000000000..2420be37f --- /dev/null +++ b/examples/certs/README.md @@ -0,0 +1,98 @@ +# Certificates Example + +This example exists primarily to test the following documentation: + +* [Lando 3 Certs](https://docs.lando.dev/core/v3/services/lando.html#ssl) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should set the environment variables correctly +lando exec web -- env | grep LANDO_SERVICE_CERT | grep /lando/certs/web.landocerts.crt +lando exec web -- env | grep LANDO_SERVICE_KEY | grep /lando/certs/web.landocerts.key +lando exec web2 -- env | grep LANDO_SERVICE_CERT | grep /etc/lando/certs/cert.crt +lando exec web2 -- env | grep LANDO_SERVICE_KEY | grep /etc/lando/certs/cert.key +lando exec web3 -- env | grep LANDO_SERVICE_CERT | grep /certs/cert.crt +lando exec web3 -- env | grep LANDO_SERVICE_KEY | grep /certs/cert.key +lando exec web4 -- env | grep LANDO_SERVICE_CERT | grep /frank/cert.crt +lando exec web4 -- env | grep LANDO_SERVICE_KEY | grep /bob/key.key +lando exec web5 -- env | grep LANDO_SERVICE_CERT || echo $? | grep 1 +lando exec web5 -- env | grep LANDO_SERVICE_KEY || echo $? | grep 1 + +# Should have certs and ancillary files in the correct places +lando exec web -- bash -c "cat \"\$LANDO_SERVICE_CERT\"" +lando exec web -- bash -c "cat \"\$LANDO_SERVICE_KEY\"" +lando exec web -- cat /certs/cert.crt +lando exec web -- cat /certs/cert.key +lando exec web -- cat /certs/cert.pem +lando exec web -- cat /certs/server.crt +lando exec web -- cat /certs/server.key +lando exec web2 -- cat "\$LANDO_SERVICE_CERT" +lando exec web2 -- cat "\$LANDO_SERVICE_KEY" +lando exec web3 -- cat "\$LANDO_SERVICE_CERT" +lando exec web3 -- cat "\$LANDO_SERVICE_KEY" +lando exec web4 -- cat "\$LANDO_SERVICE_KEY" +lando exec web4 -- cat "\$LANDO_SERVICE_CERT" + +# Should also have certs in the default locations +lando exec web -- cat /certs/cert.crt +lando exec web -- cat /certs/cert.key +lando exec web -- cat /certs/cert.pem +lando exec web -- cat /certs/server.crt +lando exec web -- cat /certs/server.key +lando exec web2 -- cat /etc/lando/certs/cert.crt +lando exec web2 -- cat /etc/lando/certs/cert.key +lando exec web3 -- cat /etc/lando/certs/cert.crt +lando exec web3 -- cat /etc/lando/certs/cert.key +lando exec web4 -- cat /etc/lando/certs/cert.crt +lando exec web4 -- cat /etc/lando/certs/cert.key + +# Should not generate certs if certs is disable-y +lando exec web5 -- ls -lsa /etc/lando/certs || echo $? | grep 1 + +# Should have the correct cert issuer +lando certinfo | grep Issuer | grep "Lando Development CA" +lando certinfo --service web2 | grep Issuer | grep "Lando Development CA" +lando certinfo --service web3 | grep Issuer | grep "Lando Development CA" +lando certinfo --service web4 | grep Issuer | grep "Lando Development CA" + +# Should have the correct cert SANS +lando certinfo | grep DNS | grep -w localhost +lando certinfo | grep DNS | grep -w web.landocerts.internal +lando certinfo | grep DNS | grep -w web +lando certinfo | grep "IP Address" | grep 127.0.0.1 +lando certinfo --service web2 | grep DNS | grep -w localhost +lando certinfo --service web2 | grep DNS | grep -w web2.lndo.site +lando certinfo --service web2 | grep DNS | grep -w web2.landocerts.internal +lando certinfo --service web2 | grep DNS | grep -w web2 +lando certinfo --service web2 | grep "IP Address" | grep 127.0.0.1 +lando certinfo --service web3 | grep DNS | grep -w vibes.rising +lando certinfo --service web3 | grep DNS | grep -w localhost +lando certinfo --service web3 | grep DNS | grep -w web3.landocerts.internal +lando certinfo --service web3 | grep DNS | grep -w web3 +lando certinfo --service web3 | grep "IP Address" | grep 127.0.0.1 +lando certinfo --service web4 | grep DNS | grep -w localhost +lando certinfo --service web4 | grep DNS | grep -w web4.landocerts.internal +lando certinfo --service web4 | grep DNS | grep -w web4 +lando certinfo --service web4 | grep "IP Address" | grep 127.0.0.1 +``` + +## Destroy tests + +```bash +# Should destroy and poweroff +lando destroy -y +lando poweroff +``` diff --git a/examples/certs/default-ssl-2.conf b/examples/certs/default-ssl-2.conf new file mode 100644 index 000000000..77861be20 --- /dev/null +++ b/examples/certs/default-ssl-2.conf @@ -0,0 +1,28 @@ +server { + listen 0.0.0.0:8443 ssl; + listen 0.0.0.0:8080; + server_name localhost; + + ssl_certificate /etc/lando/certs/cert.crt; + ssl_certificate_key /etc/lando/certs/cert.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/examples/certs/default-ssl-3.conf b/examples/certs/default-ssl-3.conf new file mode 100644 index 000000000..3b19f0516 --- /dev/null +++ b/examples/certs/default-ssl-3.conf @@ -0,0 +1,28 @@ +server { + listen 0.0.0.0:8443 ssl; + listen 0.0.0.0:8080; + server_name localhost; + + ssl_certificate /frank/cert.crt; + ssl_certificate_key /bob/key.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/examples/certs/default-ssl.conf b/examples/certs/default-ssl.conf new file mode 100644 index 000000000..0a75c2b8c --- /dev/null +++ b/examples/certs/default-ssl.conf @@ -0,0 +1,28 @@ +server { + listen 0.0.0.0:8443 ssl; + listen 0.0.0.0:8080; + server_name localhost; + + ssl_certificate /certs/cert.crt; + ssl_certificate_key /certs/cert.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/examples/certs/default.conf b/examples/certs/default.conf new file mode 100644 index 000000000..e47a27e38 --- /dev/null +++ b/examples/certs/default.conf @@ -0,0 +1,18 @@ +server { + listen 0.0.0.0:8080; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/examples/certs/index.html b/examples/certs/index.html new file mode 100644 index 000000000..8552fc6ac --- /dev/null +++ b/examples/certs/index.html @@ -0,0 +1 @@ +HELLO THERE diff --git a/examples/config/.gitignore b/examples/config/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/config/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/config/.lando.yml b/examples/config/.lando.yml new file mode 100644 index 000000000..6388177a4 --- /dev/null +++ b/examples/config/.lando.yml @@ -0,0 +1,33 @@ +name: lando-config +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/config/README.md b/examples/config/README.md new file mode 100644 index 000000000..3f86fd8fa --- /dev/null +++ b/examples/config/README.md @@ -0,0 +1,52 @@ +# Config Example + +This example exists primarily to test the following documentation: + +* [`lando config`](https://docs.lando.dev/cli/config.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should run lando config without error in app context +lando config + +# Should run lando config without error in global context +cd .. +lando config + +# Should return lando help +lando config --help | grep "lando config --format table --path env" +lando config --lando | grep "lando config --format table --path env" + +# Should only show specified path in lando config +lando config --path mode | grep cli +lando config -p mode | grep cli +lando config --field mode | grep cli +lando config --field mode | grep recipes || echo $? | grep 1 + +# Should output in json format +lando config --format json | grep "^{\"" + +# Should output in table format +lando config --format table | grep landoFileConfig.name | grep lando-config +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/config/compose.yml b/examples/config/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/config/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/config/index.html b/examples/config/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/config/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/debug/.gitignore b/examples/debug/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/debug/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/debug/.lando.yml b/examples/debug/.lando.yml new file mode 100644 index 000000000..90f2d24d9 --- /dev/null +++ b/examples/debug/.lando.yml @@ -0,0 +1,33 @@ +name: lando-debug +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/debug/README.md b/examples/debug/README.md new file mode 100644 index 000000000..7e794be7d --- /dev/null +++ b/examples/debug/README.md @@ -0,0 +1,42 @@ +# CLI Debug Example + +This example exists primarily to test the following documentation: + +* [CLI Options](https://docs.lando.dev/cli/) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should run debug messages on stderr +lando info -v | grep INFO || echo $? | grep 1 +lando info -vv | grep VERBOSE || echo $? | grep 1 +lando info -vvv | grep DEBUG || echo $? | grep 1 +lando info -vvvv | grep SILLY || echo $? | grep 1 + +# Should run all log levels on stderr +lando info -v 2>&1 | grep lando | grep + | grep ms +lando info -vv 2>&1 | grep lando | grep + | grep ms +lando info -vvv 2>&1 | grep lando | grep + | grep ms +lando info -vvvv 2>&1 | grep lando | grep + | grep ms +lando info --debug 2>&1 | grep lando | grep + | grep ms +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/debug/compose.yml b/examples/debug/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/debug/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/debug/index.html b/examples/debug/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/debug/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/envfile/.gitignore b/examples/envfile/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/envfile/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/base/.lando.dist.yml b/examples/envfile/.lando.dist.yml similarity index 67% rename from examples/base/.lando.dist.yml rename to examples/envfile/.lando.dist.yml index 025ea5184..3f515225c 100644 --- a/examples/base/.lando.dist.yml +++ b/examples/envfile/.lando.dist.yml @@ -1,4 +1,4 @@ -name: lando-base-dist +name: lando-envfile-dist env_file: - defaults.env compose: diff --git a/examples/base/.lando.local.yml b/examples/envfile/.lando.local.yml similarity index 64% rename from examples/base/.lando.local.yml rename to examples/envfile/.lando.local.yml index 9152f6b4d..73eda4293 100644 --- a/examples/base/.lando.local.yml +++ b/examples/envfile/.lando.local.yml @@ -1,3 +1,3 @@ -name: lando-base +name: lando-envfile env_file: - environment/local.env diff --git a/examples/envfile/.lando.yml b/examples/envfile/.lando.yml new file mode 100644 index 000000000..f73b8e1e1 --- /dev/null +++ b/examples/envfile/.lando.yml @@ -0,0 +1,30 @@ +name: lando-envfile +env_file: + - environment/moar.env +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html diff --git a/examples/envfile/README.md b/examples/envfile/README.md new file mode 100644 index 000000000..f32aba1ec --- /dev/null +++ b/examples/envfile/README.md @@ -0,0 +1,67 @@ +# Envfile Example + +This example exists primarily to test the following documentation: + +* [Environment Files](https://docs.lando.dev/config/env.html#environment-files) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should load environment files from all Landofiles in the correct order +lando exec web -- env | grep "MILEY=CYRUS" +lando exec web -- env | grep "TAYLOR=SWIFT" +lando exec web -- env| grep "LOCAL=LANDO" +lando exec web -- env| grep "COUNT=1" +lando exec web2 -- env | grep "MILEY=CYRUS" +lando exec web2 -- env | grep "TAYLOR=SWIFT" +lando exec web2 -- env| grep "LOCAL=LANDO" +lando exec web2 -- env| grep "COUNT=1" +lando exec web3 -- env | grep "MILEY=CYRUS" +lando exec web3 -- env | grep "TAYLOR=SWIFT" +lando exec web3 -- env| grep "LOCAL=LANDO" +lando exec web3 -- env| grep "COUNT=1" +lando exec web4 -- env | grep "MILEY=CYRUS" +lando exec web4 -- env | grep "TAYLOR=SWIFT" +lando exec web4 -- env| grep "LOCAL=LANDO" +lando exec web4 -- env| grep "COUNT=1" + +# Should load environment files from all Landofiles if we are down a directory +cd environment +lando exec web -- env | grep "MILEY=CYRUS" +lando exec web -- env | grep "TAYLOR=SWIFT" +lando exec web -- env| grep "LOCAL=LANDO" +lando exec web -- env| grep "COUNT=1" +lando exec web2 -- env | grep "MILEY=CYRUS" +lando exec web2 -- env | grep "TAYLOR=SWIFT" +lando exec web2 -- env| grep "LOCAL=LANDO" +lando exec web2 -- env| grep "COUNT=1" +lando exec web3 -- env | grep "MILEY=CYRUS" +lando exec web3 -- env | grep "TAYLOR=SWIFT" +lando exec web3 -- env| grep "LOCAL=LANDO" +lando exec web3 -- env| grep "COUNT=1" +lando exec web4 -- env | grep "MILEY=CYRUS" +lando exec web4 -- env | grep "TAYLOR=SWIFT" +lando exec web4 -- env| grep "LOCAL=LANDO" +lando exec web4 -- env| grep "COUNT=1" +cd .. +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/envfile/compose.yml b/examples/envfile/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/envfile/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/base/defaults.env b/examples/envfile/defaults.env similarity index 60% rename from examples/base/defaults.env rename to examples/envfile/defaults.env index ce3700dd0..52f2b1f50 100644 --- a/examples/base/defaults.env +++ b/examples/envfile/defaults.env @@ -1 +1,2 @@ MILEY=CYRUS +COUNT=0 diff --git a/examples/base/environment/local.env b/examples/envfile/environment/local.env similarity index 60% rename from examples/base/environment/local.env rename to examples/envfile/environment/local.env index fc4c14c41..be53fa2b1 100644 --- a/examples/base/environment/local.env +++ b/examples/envfile/environment/local.env @@ -1 +1,2 @@ LOCAL=LANDO +COUNT=1 diff --git a/examples/base/environment/moar.env b/examples/envfile/environment/moar.env similarity index 100% rename from examples/base/environment/moar.env rename to examples/envfile/environment/moar.env diff --git a/examples/envfile/index.html b/examples/envfile/index.html new file mode 100644 index 000000000..dd3354c63 --- /dev/null +++ b/examples/envfile/index.html @@ -0,0 +1 @@ +HELLO THERE! diff --git a/examples/events/.lando.yml b/examples/events/.lando.yml index 9487343fa..d472e5a5e 100644 --- a/examples/events/.lando.yml +++ b/examples/events/.lando.yml @@ -8,47 +8,84 @@ services: image: | FROM nginx:1.22.1 ENV SERVICE l337 + RUN apt update -y && apt install procps -y # if you are running these tests locally on linux then you **might** need to update the below userid RUN usermod -o -u 1001 www-data volumes: - ./:/app + web2: + api: 4 + image: | + FROM nginxinc/nginx-unprivileged:1.26.1 + USER root + RUN apt update -y && apt install procps -y + user: nginx + environment: + SERVICE: web2 + ports: + - 8080/http + events: pre-start: - - mkdir -p /app/test && echo "$(hostname -s)" > /app/test/appserver-pre-start.txt + - id && mkdir -p /app/test && echo "$(hostname -s)" > /app/test/appserver-pre-start.txt - web: id && mkdir -p /app/test && echo "$(hostname -s)" > /app/test/web-pre-start.txt + - web2: id && mkdir -p /app/test && echo "$(hostname -s)" > /app/test/web2-pre-start.txt - l337: id && ls -lsa /app && mkdir -p /app/test && echo "$(hostname -s)" > /app/test/l337-pre-start.txt + - id -un > /app/test/appserver-user.txt + - web: id -un > /app/test/web-user.txt + - web2: id -un > /app/test/web2-user.txt + - l337: id -un > /app/test/l337-user.txt + - web2: env > /app/test/web2-event-env.txt post-start: - web: id && mkdir -p /app/test && echo "$(hostname -s)" > /app/test/web-post-start.txt + - web2: id && mkdir -p /app/test && echo "$(hostname -s)" > /app/test/web2-post-start.txt - l337: id && mkdir -p /app/test && echo "$(hostname -s)" > /app/test/l337-post-start.txt post-thing: - web: mkdir -p /app/test && echo "$(hostname -s)" > /app/test/web-post-thing.txt + - web2: mkdir -p /app/test && echo "$(hostname -s)" > /app/test/web2-post-thing.txt - env | grep "SERVICE=web" post-stuff: - l337: mkdir -p /app/test && echo "$(hostname -s)" > /app/test/l337-post-stuff.txt + - web2: mkdir -p /app/test && echo "$(hostname -s)" > /app/test/web2-post-stuff.txt - env | grep "SERVICE=l337" pre-rebuild: - web: mkdir -p /app/test && echo "rebuilding" > /app/test/web-pre-rebuild.txt + - web2: mkdir -p /app/test && echo "rebuilding" > /app/test/web2-pre-rebuild.txt - l337: mkdir -p /app/test && echo "rebuilding" > /app/test/l337-pre-rebuild.txt post-rebuild: - web: echo "ET TU, BRUT?" + - web2: echo "ET TU, BRUT?" - l337: echo "ET TU, BRUT?" post-dynamic: - web: env | grep "SERVICE=web" - l337: env | grep "SERVICE=l337" + - web2: env | grep LANDO | sort - web: echo "thing" + - web2: echo "thing2" - echo "$SERVICE" - echo "stuff" + post-backgrounder: + - appserver: tail -f /dev/null & + - alpine: sleep infinity & + - web2: sleep infinity & + - l337: sleep infinity & + post-env: + - env > /app/test/web2-tooling-event-env.txt post-multi-pass: - env | grep "SERVICE=appserver" - web: env | grep "SERVICE=web" + - web2: env | grep "SERVICE=web2" - l337: env | grep "SERVICE=l337" post-what-service: - echo "$SERVICE" pre-destroy: - web: mkdir -p /app/test && touch /app/test/destroy.txt + - web2: mkdir -p /app/test && touch /app/test/destroy-web2.txt - l337: mkdir -p /app/test && touch /app/test/destroy-l337.txt tooling: + env: + service: web2 thing: service: web cmd: echo "THINGS" @@ -59,6 +96,9 @@ tooling: cmd: - web: echo "KORBEN" - l337: echo "DALLAS" + backgrounder: + cmd: echo "backgrounding" + service: web dynamic: cmd: env service: :host diff --git a/examples/events/README.md b/examples/events/README.md index 86fc7b263..df2c5f61a 100644 --- a/examples/events/README.md +++ b/examples/events/README.md @@ -1,14 +1,12 @@ -Events Example -============== +# Events Example This example exists primarily to test the following documentation: -* [Events](http://docs.devwithlando.io/config/events.html) +* [Events](https://docs.devwithlando.io/config/events.html) -See the [Landofiles](http://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. +See the [Landofiles](https://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. -Start up tests --------------- +## Start up tests ```bash # Should start successfully @@ -16,13 +14,12 @@ rm -rf test lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected ```bash -# Should run events on the appserver container by default +# Should run events on the primary service by default lando ssh -s appserver -c "cat /app/test/appserver-pre-start.txt | grep \$(hostname -s)" # Should run events on the specified service @@ -30,18 +27,23 @@ lando ssh -s web -c "cat /app/test/web-pre-start.txt | grep \$(hostname -s)" lando ssh -s web -c "cat /app/test/web-post-start.txt | grep \$(hostname -s)" lando ssh -s l337 -c "cat /app/test/l337-pre-start.txt | grep \$(hostname -s)" lando ssh -s l337 -c "cat /app/test/l337-post-start.txt | grep \$(hostname -s)" +lando exec web2 -- "cat /app/test/web2-pre-start.txt | grep \$(hostname -s)" +lando exec web2 -- "cat /app/test/web2-post-start.txt | grep \$(hostname -s)" # Should run tooling command events using the tooling command service as the default lando thing lando ssh -s web -c "cat /app/test/web-post-thing.txt | grep \$(hostname -s)" +lando exec web2 -- "cat /app/test/web2-post-thing.txt | grep \$(hostname -s)" lando stuff lando ssh -s l337 -c "cat /app/test/l337-post-stuff.txt | grep \$(hostname -s)" +lando exec web2 -- "cat /app/test/web2-post-stuff.txt | grep \$(hostname -s)" # Should run dynamic tooling command events using argv if set or option default otherwise lando dynamic lando dynamic --host l337 lando what-service | grep l337 | wc -l | grep 2 lando what-service --service web | grep web | wc -l | grep 2 +lando what-service --service web2 | grep web | wc -l | grep 2 # Should use the app default service as the default in multi-service tooling lando multi-pass @@ -50,10 +52,28 @@ lando multi-pass lando rebuild -y | grep "ET TU, BRUT" lando ssh -s web -c "cat /app/test/web-pre-rebuild.txt | grep rebuilding" lando ssh -s l337 -c "cat /app/test/l337-pre-rebuild.txt | grep rebuilding" +lando exec web2 -- cat /app/test/web2-pre-rebuild.txt | grep rebuilding + +# Should run events as the correct user +lando ssh -s appserver -c "cat /app/test/appserver-user.txt" | grep www-data +lando ssh -s web -c "cat /app/test/web-user.txt" | grep www-data +lando ssh -s l337 -c "cat /app/test/l337-user.txt" | grep root +lando exec web2 -- cat /app/test/web2-user.txt | grep nginx + +# Should load the correct environment for lando 4 service events +lando env +lando exec web2 -- cat /app/test/web2-event-env.txt | grep LANDO_ENVIRONMENT | grep loaded +lando exec web2 -- cat /app/test/web2-tooling-event-env.txt | grep LANDO_ENVIRONMENT | grep loaded + +# Should be able to background events with line ending ampersands +lando backgrounder +lando exec appserver -- ps a | grep "tail -f /dev/null" +lando exec --user root alpine -- ps a | grep "sleep infinity" +lando exec l337 -- ps -e -o cmd | grep "sleep infinity" +lando exec web2 -- ps -e -o cmd | grep "sleep infinity" ``` -Destroy tests -------------- +## Destroy tests ```bash # Should destroy successfully @@ -63,4 +83,5 @@ lando poweroff # Should trigger pre-destroy event stat test/destroy.txt stat test/destroy-l337.txt +stat test/destroy-web2.txt ``` diff --git a/examples/events/compose.yml b/examples/events/compose.yml index 6d330823a..af7cf73e1 100644 --- a/examples/events/compose.yml +++ b/examples/events/compose.yml @@ -1,4 +1,3 @@ -version: '3.6' services: appserver: image: php:7.1-fpm-alpine @@ -8,3 +7,6 @@ services: image: nginx environment: SERVICE: 'web' + alpine: + image: alpine + command: tail -f /dev/null diff --git a/examples/exec/.gitignore b/examples/exec/.gitignore new file mode 100644 index 000000000..56bcb429f --- /dev/null +++ b/examples/exec/.gitignore @@ -0,0 +1,2 @@ +.lando +.test diff --git a/examples/exec/.lando.yml b/examples/exec/.lando.yml new file mode 100644 index 000000000..08b618b4e --- /dev/null +++ b/examples/exec/.lando.yml @@ -0,0 +1,44 @@ +name: lando-exec +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + build_as_root: + - apt update -y && apt install procps -y + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: | + FROM nginx + RUN apt update -y && apt install procps -y + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: | + FROM nginxinc/nginx-unprivileged:1.26.1 + USER root + RUN apt update -y && apt install procps -y + USER nginx + user: nginx + environment: + MESSAGE: hellothere + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/exec/README.md b/examples/exec/README.md new file mode 100644 index 000000000..76542d221 --- /dev/null +++ b/examples/exec/README.md @@ -0,0 +1,101 @@ +# Exec Example + +This example exists primarily to test the following documentation: + +* [`lando exec`](https://docs.lando.dev/cli/exec.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should set the correct envvars +lando exec web -- env | grep LANDO_WEBROOT_USER | grep www-data +lando exec web -- env | grep LANDO_WEBROOT_GROUP | grep www-data +lando exec web2 -- env | grep LANDO_WEBROOT_USER | grep www-data +lando exec web2 -- env | grep LANDO_WEBROOT_GROUP | grep www-data +lando exec web3 -- env | grep LANDO_WEBROOT_USER | grep www-data +lando exec web3 -- env | grep LANDO_WEBROOT_GROUP | grep www-data +lando exec web4 -- env | grep LANDO_USER | grep nginx + +# Should run a command as the default user +lando exec web -- whoami | grep www-data +lando exec web2 -- whoami | grep www-data +lando exec web3 -- whoami | grep root +lando exec web4 -- whoami | grep nginx +lando exec web4 -- "whoami | grep \$LANDO_USER" + +# Should run a command as the --user +lando exec web -u root -- whoami | grep root +lando exec web2 -u root -- whoami | grep root +lando exec web3 -u root -- whoami | grep root +lando exec web4 -u root -- whoami | grep root + +# Should run commands from appMount for +lando exec web -u root -- pwd | grep /app +lando exec web2 -u root -- pwd | grep /app +lando exec web3 -u root -- pwd | grep /usr/share/nginx/html +lando exec web4 -u root -- pwd | grep /usr/share/nginx/html + +# Should track appMounted commands +cd folder +lando exec web2 -u root -- pwd | grep /app/folder +lando exec web3 -u root -- pwd | grep /usr/share/nginx/html/folder +lando exec web4 -u root -- pwd | grep /usr/share/nginx/html/folder + +# Should load the correct lando environment +lando exec web -u root -- env | grep LANDO=ON +lando exec web2 -u root -- env | grep LANDO=ON +lando exec web2 -u root -- env | grep LANDO=ON +lando exec web4 -u root -- env | grep LANDO_ENVIRONMENT=loaded + +# Should honor --debug on v4 +lando exec web4 -- env | grep "LANDO_DEBUG=--debug" || echo $? || echo 1 +lando exec web4 --debug -- env | grep LANDO_DEBUG=--debug + +# Should be able to background commands with line ending ampersands +lando exec --user root alpine -- "sleep infinity &" +lando exec web2 -- "sleep infinity &" +lando exec web3 -- "sleep infinity &" +lando exec web4 -- "sleep infinity &" +lando exec --user root alpine -- ps a | grep "sleep infinity" +lando exec web2 -- ps -e -o cmd | grep "sleep infinity" +lando exec web3 -- ps -e -o cmd | grep "sleep infinity" +lando exec web4 -- ps -e -o cmd | grep "sleep infinity" +lando restart +lando exec --user root alpine -- sh -c "sleep infinity &" +lando exec web2 -- /bin/sh -c "sleep infinity &" +lando exec web3 -- bash -c "sleep infinity &" +lando exec --user root alpine -- ps a | grep "sleep infinity" +lando exec web2 -- ps -e -o cmd | grep "sleep infinity" +lando exec web3 -- ps -e -o cmd | grep "sleep infinity" + +# Should run complex commands on v4 +lando exec web4 -- "echo -n hello && echo there" | grep hellothere +lando exec web4 -- "nope || echo hellothere" | grep hellothere +lando exec web4 -- "echo -n hello; echo there;" | grep hellothere +lando exec web4 -- "echo -n hello; echo there;" | grep hellothere +lando exec web4 -- "echo \"\$MESSAGE\"" | grep hellothere +lando exec web4 -- "mkdir -p /usr/share/nginx/html/test && echo hellothere > /usr/share/nginx/html/test/msg1 && cat /usr/share/nginx/html/test/msg1" | grep hellothere +lando exec web4 -- "mkdir -p /usr/share/nginx/html/test && echo -n hello >> /usr/share/nginx/html/test/msg2 && echo there >> /usr/share/nginx/html/test/msg2 && cat /usr/share/nginx/html/test/msg2" | grep hellothere +lando exec web4 -- "cat < /usr/share/nginx/html/test/msg2" | grep hellothere +lando exec web4 -- "echo hellothere &> /dev/null" | grep hellothere || echo $? || echo 1 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/exec/compose.yml b/examples/exec/compose.yml new file mode 100644 index 000000000..5c38b52d1 --- /dev/null +++ b/examples/exec/compose.yml @@ -0,0 +1,6 @@ +services: + web: + image: nginx + alpine: + image: alpine + command: tail -f /dev/null diff --git a/examples/exec/folder/.gitkeep b/examples/exec/folder/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/examples/exec/index.html b/examples/exec/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/exec/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/exec/test/message b/examples/exec/test/message new file mode 100644 index 000000000..8ef3d7721 --- /dev/null +++ b/examples/exec/test/message @@ -0,0 +1 @@ +hellothere diff --git a/examples/exec/test/msg2 b/examples/exec/test/msg2 new file mode 100644 index 000000000..e3c8c1d1d --- /dev/null +++ b/examples/exec/test/msg2 @@ -0,0 +1,2 @@ +hellothere +hellothere diff --git a/examples/experimental/README.md b/examples/experimental/README.md index 9f84c5de2..4ab121f70 100644 --- a/examples/experimental/README.md +++ b/examples/experimental/README.md @@ -4,16 +4,14 @@ This example exists primarily to test the following documentation: * [Experimental](https://docs.lando.dev/core/v3/experimental.html) -Start up tests --------------- +## Start up tests ```bash # Should start successfully lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -31,8 +29,7 @@ lando --experimental lando config --path experimental | grep false ``` -Destroy tests -------------- +## Destroy tests ```bash # Should destroy successfully diff --git a/examples/healthcheck/.lando.yml b/examples/healthcheck/.lando.yml index c6e3483b8..ae6f35526 100755 --- a/examples/healthcheck/.lando.yml +++ b/examples/healthcheck/.lando.yml @@ -37,16 +37,17 @@ services: context: - ./healthcheck.sh:/usr/local/bin/healthcheck imagefile: | - FROM nginx:1.22.1 - RUN apt update -u + FROM nginxinc/nginx-unprivileged:1.26.1 + USER root + RUN apt update -y RUN apt install git ssh socat sudo -y RUN ssh-keyscan github.com >> /etc/ssh/ssh_known_hosts steps: - instructions: RUN chmod +x /usr/local/bin/healthcheck - user: root + user: nginx healthcheck: command: healthcheck - retry: 100 + retry: 10 delay: 1000 database1: api: 3 diff --git a/examples/healthcheck/README.md b/examples/healthcheck/README.md index bc243a0d2..a7c3bbb17 100755 --- a/examples/healthcheck/README.md +++ b/examples/healthcheck/README.md @@ -1,12 +1,10 @@ -Healthcheck Example -=================== +# Healthcheck Example This example exists primarily to test the following documentation: * [Healthcheck](https://docs.lando.dev/core/v3/healthcheck.html) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -48,8 +45,7 @@ lando info -s database2 | grep healthy: | grep true lando info -s disablebase | grep healthy: | grep unknown ``` -Destroy tests -------------- +## Destroy tests Run the following commands to trash this app like nothing ever happened. diff --git a/examples/host/.gitignore b/examples/host/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/host/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/host/.lando.yml b/examples/host/.lando.yml new file mode 100644 index 000000000..0eafdabc0 --- /dev/null +++ b/examples/host/.lando.yml @@ -0,0 +1,24 @@ +name: lando-host +services: + pinger: + api: 3 + type: lando + meUser: root + services: + image: alpine + command: sleep infinity + pinger2: + api: 4 + type: l337 + image: alpine + user: root + command: sleep infinity + pinger3: + api: 4 + type: lando + image: alpine + user: root + command: sleep infinity + +plugins: + "@lando/core": "../.." diff --git a/examples/host/README.md b/examples/host/README.md new file mode 100644 index 000000000..010e18169 --- /dev/null +++ b/examples/host/README.md @@ -0,0 +1,39 @@ +# Host Example + +This example exists primarily to test the following documentation: + +* [Networking](https://docs.lando.dev/core/v3/networking.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should have the correct envvars set +lando exec pinger -- env | grep LANDO_HOST_IP | grep host.lando.internal +lando exec pinger2 -- env | grep LANDO_HOST_IP | grep host.lando.internal +lando exec pinger3 -- env | grep LANDO_HOST_IP | grep host.lando.internal + +# Should be able to ping the host at host.lando.internal +lando exec pinger -- ping host.lando.internal -c 3 +lando exec pinger2 -- ping host.lando.internal -c 3 +lando exec pinger3 -- ping host.lando.internal -c 3 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/host/index.html b/examples/host/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/host/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/info/.gitignore b/examples/info/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/info/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/info/.lando.yml b/examples/info/.lando.yml new file mode 100644 index 000000000..f71f8c536 --- /dev/null +++ b/examples/info/.lando.yml @@ -0,0 +1,34 @@ +name: lando-info +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + user: nginx + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/info/README.md b/examples/info/README.md new file mode 100644 index 000000000..48232bcc2 --- /dev/null +++ b/examples/info/README.md @@ -0,0 +1,174 @@ +# Info Example + +This example exists primarily to test the following documentation: + +* [`lando info`](https://docs.lando.dev/cli/info.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should run lando info without error +lando info + +# Should return expanded data with --deep +lando info -d | grep NetworkSettings +lando info --deep | grep NetworkSettings + +# Should return filtered data +lando info --filter service=web4 --path api | grep 4 +lando info --filter api=4 --filter primary=true --path service | grep web3 +lando info --deep --filter Path=/docker-entrypoint.sh --filter Config.User=nginx --path Config.User | grep nginx +lando info --filter api=4 --path "[0].service" | grep web3 + +# Should output JSON with --format json +lando info --format json | grep "^\[{\"" +lando info --deep --format json | grep "^\[{\"" + +# Should output tabular data with --format table +lando info --format table | grep Key | grep Value +lando info --deep --format table | grep NetworkSettings.Networks.lando_bridge_network.Aliases | grep web4.landoinfo.internal + +# Should return a specified --path when given with lando info +lando info --path "[0]" | grep service | wc -l | grep 1 +lando info --deep --path "[0].Config.User" | grep nginx + +# Should return --path without preceding index if array has size 1 +lando info --path service --service web4 | grep web4 +lando info --deep --service web4 --path Config.User | grep nginx + +# Should return info for --services only +lando info --service web4 | grep service | wc -l | grep 1 +lando info --service web4 --service web3 | grep service | wc -l | grep 2 +lando info -d --service web4 | grep NetworkSettings: | wc -l | grep 1 +lando info -d --service web4 --service web3 | grep NetworkSettings: | wc -l | grep 2 + +# Should have the correct default info before start or after destroy +lando destroy -y +lando info --service web --path urls | grep "\[\]" +lando info --service web --path type | grep docker-compose +lando info --service web --path healthy | grep unknown +lando info --service web --path hostnames | grep web.landoinfo.internal +lando info --service web2 --path urls | grep "\[\]" +lando info --service web2 --path type | grep lando +lando info --service web2 --path healthy | grep unknown +lando info --service web2 --path version | grep custom +lando info --service web2 --path meUser | grep www-data +lando info --service web2 --path hasCerts | grep "false" +lando info --service web2 --path api | grep 3 +lando info --service web2 --path hostnames | grep web2.landoinfo.internal +lando info --service web3 --path urls | grep "\[\]" +lando info --service web3 --path type | grep l337 +lando info --service web3 --path healthy | grep unknown +lando info --service web3 --path api | grep 4 +lando info --service web3 --path state.IMAGE | grep UNBUILT +lando info --service web3 --path primary | grep "true" +lando info --service web3 --path image | grep nginx +lando info --service web3 --path user | grep root +lando info --service web3 --path appMount | grep /usr/share/nginx/html +lando info --service web3 --path hostnames | grep web3.landoinfo.internal +lando info --service web4 --path urls | grep "\[\]" +lando info --service web4 --path type | grep lando +lando info --service web4 --path healthy | grep unknown +lando info --service web4 --path api | grep 4 +lando info --service web4 --path state.IMAGE | grep UNBUILT +lando info --service web4 --path state.APP | grep UNBUILT +lando info --service web4 --path primary | grep "false" +lando info --service web4 --path image | grep nginxinc/nginx-unprivileged:1.26.1 +lando info --service web4 --path user | grep nginx +lando info --service web4 --path appMount | grep /usr/share/nginx/html +lando info --service web4 --path hostnames | grep web4.landoinfo.internal + +# Should have the correct info after a start and/or rebuild +lando start +lando info --service web --path urls | grep "\[\]" +lando info --service web --path type | grep docker-compose +lando info --service web --path healthy | grep unknown +lando info --service web --path hostnames | grep web.landoinfo.internal +lando info --service web2 --path urls | grep "http://localhost" +lando info --service web2 --path type | grep lando +lando info --service web2 --path healthy | grep unknown +lando info --service web2 --path version | grep custom +lando info --service web2 --path meUser | grep www-data +lando info --service web2 --path hasCerts | grep "false" +lando info --service web2 --path api | grep 3 +lando info --service web2 --path hostnames | grep web2.landoinfo.internal +lando info --service web3 --path urls | grep "http://localhost" +lando info --service web3 --path type | grep l337 +lando info --service web3 --path healthy | grep unknown +lando info --service web3 --path api | grep 4 +lando info --service web3 --path state.IMAGE | grep BUILT +lando info --service web3 --path tag | grep "lando/lando-info-.*-web3:latest" +lando info --service web3 --path primary | grep "true" +lando info --service web3 --path image | grep nginx +lando info --service web3 --path user | grep root +lando info --service web3 --path appMount | grep /usr/share/nginx/html +lando info --service web3 --path hostnames | grep web3.landoinfo.internal +lando info --service web4 --path urls | grep "http://localhost" +lando info --service web4 --path type | grep lando +lando info --service web4 --path healthy | grep unknown +lando info --service web4 --path api | grep 4 +lando info --service web4 --path state.IMAGE | grep BUILT +lando info --service web4 --path state.APP | grep BUILT +lando info --service web4 --path tag | grep "lando/lando-info-.*-web4:latest" +lando info --service web4 --path primary | grep "false" +lando info --service web4 --path image | grep nginxinc/nginx-unprivileged:1.26.1 +lando info --service web4 --path user | grep nginx +lando info --service web4 --path appMount | grep /usr/share/nginx/html +lando info --service web4 --path hostnames | grep web4.landoinfo.internal +lando rebuild -y +lando info --service web --path urls | grep "\[\]" +lando info --service web --path type | grep docker-compose +lando info --service web --path healthy | grep unknown +lando info --service web --path hostnames | grep web.landoinfo.internal +lando info --service web2 --path urls | grep "http://localhost" +lando info --service web2 --path type | grep lando +lando info --service web2 --path healthy | grep unknown +lando info --service web2 --path version | grep custom +lando info --service web2 --path meUser | grep www-data +lando info --service web2 --path hasCerts | grep "false" +lando info --service web2 --path api | grep 3 +lando info --service web2 --path hostnames | grep web2.landoinfo.internal +lando info --service web3 --path urls | grep "http://localhost" +lando info --service web3 --path type | grep l337 +lando info --service web3 --path healthy | grep unknown +lando info --service web3 --path api | grep 4 +lando info --service web3 --path state.IMAGE | grep BUILT +lando info --service web3 --path tag | grep "lando/lando-info-.*-web3:latest" +lando info --service web3 --path primary | grep "true" +lando info --service web3 --path image | grep nginx +lando info --service web3 --path user | grep root +lando info --service web3 --path appMount | grep /usr/share/nginx/html +lando info --service web3 --path hostnames | grep web3.landoinfo.internal +lando info --service web4 --path urls | grep "http://localhost" +lando info --service web4 --path type | grep lando +lando info --service web4 --path healthy | grep unknown +lando info --service web4 --path api | grep 4 +lando info --service web4 --path state.IMAGE | grep BUILT +lando info --service web4 --path state.APP | grep BUILT +lando info --service web4 --path tag | grep "lando/lando-info-.*-web4:latest" +lando info --service web4 --path primary | grep "false" +lando info --service web4 --path image | grep nginxinc/nginx-unprivileged:1.26.1 +lando info --service web4 --path user | grep nginx +lando info --service web4 --path appMount | grep /usr/share/nginx/html +lando info --service web4 --path hostnames | grep web4.landoinfo.internal +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/info/compose.yml b/examples/info/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/info/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/info/index.html b/examples/info/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/info/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/init-github/README.md b/examples/init-github/README.md index bfdeba3b7..8ef52335f 100644 --- a/examples/init-github/README.md +++ b/examples/init-github/README.md @@ -1,12 +1,10 @@ -Lando Init GitHub Source Example -================================ +# Lando Init GitHub Source Example This example exists primarily to test the following documentation: * [Lando Init with GitHub Source](https://docs.lando.dev/cli/init.html#github) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -16,8 +14,7 @@ mkdir -p github && cd github 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 ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -26,8 +23,7 @@ Run the following commands to verify things work as expected cd github && cat README.md ``` -Destroy tests -------------- +## Destroy tests ```bash # Should remove key diff --git a/examples/init-remote/README.md b/examples/init-remote/README.md index c92162b0d..012b3bdb6 100644 --- a/examples/init-remote/README.md +++ b/examples/init-remote/README.md @@ -1,12 +1,10 @@ -Lando Init Remote Source Example -================================ +# Lando Init Remote Source Example This example exists primarily to test the following documentation: * [Lando Init with Remote Source](https://docs.lando.dev/cli/init.html#remote-git-repo-or-archive) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -20,8 +18,7 @@ mkdir -p tar && cd tar lando init --source remote --recipe none --remote-url="https://github.com/lando/lando/archive/refs/tags/v3.20.2.tar.gz" --remote-options="--strip-components=1" --yes ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -33,8 +30,7 @@ cd git && cat README.md cd tar && cat .lando.yml ``` -Destroy tests -------------- +## Destroy tests ```bash # Should remove initialized code diff --git a/examples/keys/README.md b/examples/keys/README.md index 1c4f5da0e..291f62452 100644 --- a/examples/keys/README.md +++ b/examples/keys/README.md @@ -1,14 +1,12 @@ -Keys Example -============ +# Keys Example This example exists primarily to test the following documentation: * [SSH Keys](https://docs.devwithlando.io/config/ssh.html) -See the [Landofiles](http://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. +See the [Landofiles](https://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. -Start up tests --------------- +## Start up tests ```bash # Should start successfully @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -47,8 +44,7 @@ lando ssh -s thesekeys -c "cat /etc/ssh/ssh_config" | grep "/user/.ssh/mykey2" lando ssh -s thesekeys -c "cat /etc/ssh/ssh_config" | grep "/user/.ssh/mykey3" || echo "$?" | grep 1 ``` -Destroy tests -------------- +## Destroy tests ```bash # Remove generated test keys and files diff --git a/examples/l337/.lando.yml b/examples/l337/.lando.yml index 1822cc7fc..5aa82e844 100644 --- a/examples/l337/.lando.yml +++ b/examples/l337/.lando.yml @@ -48,10 +48,10 @@ services: type: l337 primary: true image: | - FROM nginx:1.22.1 + FROM nginxinc/nginx-unprivileged:1.26.1 COPY ./nginx.conf /etc/nginx/conf.d/default.conf ENV SERVICE web - meUser: nginx + user: nginx networks: my-network: volumes: @@ -59,9 +59,7 @@ services: - "./file1:/file-ro:ro" - "./:/site" ports: - - 8888 - moreHttpPorts: - - 8888 + - 8888/http # image features image-1: diff --git a/examples/l337/README.md b/examples/l337/README.md index 17a136925..feae568b0 100644 --- a/examples/l337/README.md +++ b/examples/l337/README.md @@ -1,12 +1,10 @@ -L337 Example -============ +# L337 Example This example exists primarily to test the v3 runtime implementation of following documentation: * [Lando 3 l337 service](https://docs.lando.dev/core/v3/services/l337.html) -Start up tests --------------- +## Start up tests ```bash # should start successfully @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -32,8 +29,8 @@ lando info --service db | grep healthy: | grep unknown lando info --service db | grep state: | grep IMAGE: | grep UNBUILT lando info --service db | grep -z image: | grep core/examples/l337/Dockerfile lando info --service db | grep primary: | grep false -lando info --service db | grep user: | grep www-data -cat $(lando info --service db --path "[0].image" --format json | tr -d '"') | grep "ENV SERVICE=db" +lando info --service db | grep user: | grep root +cat $(lando info --service db --path "image" --format json | tr -d '"') | grep "ENV SERVICE=db" lando info --service web | grep api: | grep 4 lando info --service web | grep type: | grep l337 lando info --service web | grep state: | grep IMAGE: | grep UNBUILT @@ -41,7 +38,7 @@ lando info --service web | grep -z image: | grep /Imagefile lando info --service web | grep primary: | grep true lando info --service web | grep appMount: | grep /site lando info --service web | grep user: | grep nginx -cat $(lando info --service web --path "[0].image" --format json | tr -d '"') | grep ENV | grep SERVICE | grep web +cat $(lando info --service web --path "image" --format json | tr -d '"') | grep ENV | grep SERVICE | grep web lando info --service image-1 | grep image: | grep nginx:1.21.6 lando info --service image-2 | grep -z image: | grep core/examples/l337/images/nginx/Dockerfile lando info --service image-3 | grep -z image: | grep /Imagefile @@ -84,11 +81,12 @@ lando info --service image-6 | grep tag: | grep "lando/l337\-" | grep "\-image-6 lando ssh --command "env" | grep SERVICE | grep web lando env | grep SERVICE | grep web -# should allow legacy meUser to work like it does for v3 +# should use the user as the default exec user lando whoami | grep nginx -# should allow legacy moreHttpPorts to work like it does for v3 -docker inspect l337_web_1 | grep io.lando.http-ports | grep "80,443,8888" +# should set http/https metadata as needed +docker inspect l337_web_1 | grep dev.lando.http-ports | grep "8888" +docker inspect l337_web_1 | grep dev.lando.https-ports | grep '"",' # should automatically set appMount if appRoot is volume mounted lando pwd | grep /site @@ -223,8 +221,7 @@ lando ssh --service steps-1 --command "cat /stuff" | sed -n '2p' | grep middle lando ssh --service steps-1 --command "cat /stuff" | sed -n '3p' | grep last ``` -Destroy tests -------------- +## Destroy tests ```bash # should destroy successfully diff --git a/examples/lando-101/README.md b/examples/lando-101/README.md index f69e2c758..828264677 100644 --- a/examples/lando-101/README.md +++ b/examples/lando-101/README.md @@ -1,10 +1,8 @@ -Lando 101 -========= +# Lando 101 This example tests the [Lando 101](https://docs.lando.dev/guides/lando-101/lando-overview.html) course. -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example @@ -20,8 +18,7 @@ echo "" > index.php lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -65,8 +62,7 @@ lando phpcs --version |grep squiz lando destroy -y ``` -Destroy tests -------------- +## Destroy tests Run the following commands to trash this app like nothing ever happened. diff --git a/examples/lando-v4/.lando.yml b/examples/lando-v4/.lando.yml index 341e95bc2..db90b78a0 100644 --- a/examples/lando-v4/.lando.yml +++ b/examples/lando-v4/.lando.yml @@ -4,11 +4,12 @@ services: api: 4 image: imagefile: | - FROM nginx:1.22.1 - RUN apt update -u - RUN apt install git ssh socat sudo -y + FROM nginxinc/nginx-unprivileged:1.26.1 + USER root + RUN apt update -y + RUN apt install ssh -y RUN ssh-keyscan github.com >> /etc/ssh/ssh_known_hosts - user: root + user: nginx build: app: | env @@ -24,17 +25,24 @@ services: web-2: api: 4 - user: root - image: nginx:1.22.1 + user: nginx + image: nginxinc/nginx-unprivileged:1.26.1 web-3: api: 4 - user: root - image: nginx:1.22.1 + user: nginx + image: nginxinc/nginx-unprivileged:1.26.1 build: app: | set -e echo "stuff" + + # persistent-storage + # mounts + # app-mount + # build + # files + # appmount # support appMount but prefer app-mount # project-mount: /project how does this work? diff --git a/examples/lando-v4/README.md b/examples/lando-v4/README.md index cfcf5aa9e..d85d4b44e 100644 --- a/examples/lando-v4/README.md +++ b/examples/lando-v4/README.md @@ -1,12 +1,10 @@ -Lando V4 Service Example -======================== +# Lando V4 Service Example This example exists primarily to test the v3 runtime implementation of following documentation: * [Lando 4 service](https://docs.lando.dev/core/v4/landofile/services.html#lando-service) -Start up tests --------------- +## Start up tests ```bash # should start successfully @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -29,8 +26,7 @@ true true ``` -Destroy tests -------------- +## Destroy tests ```bash # should destroy successfully diff --git a/examples/landofile-custom/README.md b/examples/landofile-custom/README.md index e964caa79..b58d253f7 100644 --- a/examples/landofile-custom/README.md +++ b/examples/landofile-custom/README.md @@ -1,12 +1,10 @@ -Custom Landofile Name Example -============================= +# Custom Landofile Name Example This example exists primarily to test the issue: * [Issue #1919](https://github.com/lando/lando/issues/1919) -Start up tests --------------- +## Start up tests ```bash # Should set up the config @@ -17,8 +15,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -32,8 +29,7 @@ lando npm2 --version lando yarn2 --version ``` -Destroy tests -------------- +## Destroy tests ```bash # Should destroy successfully diff --git a/examples/landofile/.gitignore b/examples/landofile/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/landofile/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/landofile/.lando.dist.yml b/examples/landofile/.lando.dist.yml new file mode 100644 index 000000000..20a64ac94 --- /dev/null +++ b/examples/landofile/.lando.dist.yml @@ -0,0 +1,4 @@ +name: lando-landofile-dist +compose: + - compose.yml + diff --git a/examples/landofile/.lando.local.yml b/examples/landofile/.lando.local.yml new file mode 100644 index 000000000..d23ea5590 --- /dev/null +++ b/examples/landofile/.lando.local.yml @@ -0,0 +1,10 @@ +name: lando-landofile +services: + web3: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html diff --git a/examples/landofile/.lando.yml b/examples/landofile/.lando.yml new file mode 100644 index 000000000..f38fb1647 --- /dev/null +++ b/examples/landofile/.lando.yml @@ -0,0 +1,3 @@ +name: lando-landofile +compose: + - docker-compose/moar.yml diff --git a/examples/landofile/README.md b/examples/landofile/README.md new file mode 100644 index 000000000..15f1bc0cf --- /dev/null +++ b/examples/landofile/README.md @@ -0,0 +1,43 @@ +# Landofile Example + +This example exists primarily to test the following documentation: + +* [Landofiles](https://docs.lando.dev/config/lando.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should merge in all Landofiles correctly +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_log_1 +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_web_1 +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_web2_1 +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_web3_1 + +# Should merge in all Landofiles correctly even if we are down a directory +cd docker-compose +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_log_1 +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_web_1 +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_web2_1 +docker ps --filter label=com.docker.compose.project=landolandofile | grep landolandofile_web3_1 +cd .. +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/base/compose.yml b/examples/landofile/compose.yml similarity index 84% rename from examples/base/compose.yml rename to examples/landofile/compose.yml index 56eb2380e..1587fb178 100644 --- a/examples/base/compose.yml +++ b/examples/landofile/compose.yml @@ -1,4 +1,3 @@ -version: '3.6' services: web: image: nginx diff --git a/examples/base/docker-compose/moar.yml b/examples/landofile/docker-compose/moar.yml similarity index 75% rename from examples/base/docker-compose/moar.yml rename to examples/landofile/docker-compose/moar.yml index bb104fe94..184769146 100644 --- a/examples/base/docker-compose/moar.yml +++ b/examples/landofile/docker-compose/moar.yml @@ -1,4 +1,3 @@ -version: '3.2' services: log: image: php:7.1-fpm-alpine diff --git a/examples/landofile/index.html b/examples/landofile/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/landofile/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/list/.gitignore b/examples/list/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/list/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/list/.lando.yml b/examples/list/.lando.yml new file mode 100644 index 000000000..b3873645a --- /dev/null +++ b/examples/list/.lando.yml @@ -0,0 +1,33 @@ +name: lando-list +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/list/README.md b/examples/list/README.md new file mode 100644 index 000000000..cd8596740 --- /dev/null +++ b/examples/list/README.md @@ -0,0 +1,69 @@ +# List Example + +This example exists primarily to test the following documentation: + +* [`lando list`](https://docs.lando.dev/cli/list.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should run lando list without error from app context +lando list + +# Should run lando list witout error from outside app context +cd .. +lando list + +# Should list this apps containers +lando list --app landolist | grep landolist_web_1 +lando list --app landolist | grep landolist_web2_1 +lando list --app landolist | grep landolist_web3_1 +lando list --app landolist | grep landolist_web4_1 + +# Should list no containers if we spin down the app +lando stop +lando list | grep "\[\]" + +# Should list even stopped containers with --all +lando list --all | grep landolist_web_1 +lando list --all | grep landolist_web2_1 +lando list --all | grep landolist_web3_1 +lando list --all | grep landolist_web4_1 + +# Should output JSON with --format json +lando list --all --format json | grep "^\[{\"" + +# Should output tabular data with --format table +lando list --all --format table | grep Key | grep Value + +# Should return a specified path when given with --path +lando list --all --path "[0].service" | grep web4 + +# Should return --path without preceding index if array has size 1 +lando start +lando list --filter app=landolist --filter service=web4 --path service | grep web4 + +# Should allow data to be filtered +docker stop landolist_web4_1 +lando list --all --filter running=false --filter app=landolist --path service | grep web4 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/list/compose.yml b/examples/list/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/list/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/list/index.html b/examples/list/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/list/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/logs/.gitignore b/examples/logs/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/logs/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/logs/.lando.yml b/examples/logs/.lando.yml new file mode 100644 index 000000000..22a7d8df6 --- /dev/null +++ b/examples/logs/.lando.yml @@ -0,0 +1,33 @@ +name: lando-logs +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/logs/README.md b/examples/logs/README.md new file mode 100644 index 000000000..95d5e4b63 --- /dev/null +++ b/examples/logs/README.md @@ -0,0 +1,39 @@ +# Logs Example + +This example exists primarily to test the following documentation: + +* [`lando logs`](https://docs.lando.dev/cli/logs.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should return logs without error +lando logs + +# Should return only logs for the specified service +lando logs -s web2 | grep web2_1 || echo $? | grep 1 +lando logs --service web2 | grep web2_1 || echo $? | grep 1 +lando logs -s web3 | grep web3_1 || echo $? | grep 1 +lando logs --service web3 | grep web3_1 || echo $? | grep 1 +lando logs --service web4 | grep web4_1 || echo $? | grep 1 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/logs/compose.yml b/examples/logs/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/logs/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/logs/index.html b/examples/logs/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/logs/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/long-name/README.md b/examples/long-name/README.md index 2d06de050..3a68dd1fd 100644 --- a/examples/long-name/README.md +++ b/examples/long-name/README.md @@ -1,12 +1,10 @@ -Long Name Example -================= +# Long Name Example This example exists primarily to test the following documentation: * [Issue #3179](https://github.com/lando/lando/issues/3179) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -27,8 +24,7 @@ lando ssh -s defaults -c "curl http://localhost:80" | grep ROOTDIR lando ssh -s l337 -c "curl http://localhost:80" | grep ROOTDIR ``` -Destroy tests -------------- +## Destroy tests Run the following commands to trash this app like nothing ever happened. diff --git a/examples/networking/.lando.lemp.yml b/examples/networking/.lando.lemp.yml index 7918188f6..8a69cb1eb 100644 --- a/examples/networking/.lando.lemp.yml +++ b/examples/networking/.lando.lemp.yml @@ -1,34 +1,22 @@ name: lando-lemp proxy: appserver_nginx: - - lando-lemp.lndo.site - placeholder: - - placeholder.lando-lemp.lndo.site + - lando-lemp.lndo.site:8080 services: - placeholder: - api: 3 - type: lando - ssl: true - healthcheck: curl -I http://localhost - services: - image: php:8.2-apache - command: docker-php-entrypoint apache2-foreground - volumes: - - ./apache.conf:/etc/apache2/sites-enabled/000-default.conf - ports: - - 80 - - 443 appserver_nginx: api: 4 - type: l337 + type: lando + user: nginx + healthcheck: curl -I https://lando-lemp.lndo.site image: - imagefile: nginx:1.22.1 + imagefile: nginxinc/nginx-unprivileged:1.26.1 context: - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - - 80 - volumes: - - ./:/var/www/html + - 8080/http + - 8443/https + app-mount: + destination: /var/www/html links: - appserver depends_on: diff --git a/examples/networking/README.md b/examples/networking/README.md index 9a85eb05a..cc863724a 100644 --- a/examples/networking/README.md +++ b/examples/networking/README.md @@ -3,9 +3,9 @@ Networking Example This example exists primarily to test the following documentation: -* [Networking](http://docs.devwithlando.io/config/networking.html) +* [Networking](https://docs.devwithlando.io/config/networking.html) -See the [Landofiles](http://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. +See the [Landofiles](https://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. Start up tests -------------- @@ -21,7 +21,6 @@ cd lamp && lando start # Should init and start a lemp app rm -rf lemp && mkdir -p lemp cp -rf index.php lemp/index.php -cp -rf apache.conf lemp/apache.conf cp -rf nginx.conf lemp/nginx.conf cp -rf .lando.lemp.yml lemp/.lando.yml cd lemp && lando start @@ -33,55 +32,40 @@ Verification commands Run the following commands to verify things work as expected ```bash -# Should have the correct entries in /certs/cert.ext -cd lamp -lando ssh -s appserver -c "cat /certs/cert.ext" | grep DNS.1 | grep -w appserver.landolamp.internal -lando ssh -s appserver -c "cat /certs/cert.ext" | grep DNS.2 | grep -w appserver -lando ssh -s appserver -c "cat /certs/cert.ext" | grep DNS.3 | grep -w localhost -lando ssh -s appserver -c "cat /certs/cert.ext" | grep lando-lamp.lndo.site -cd .. && cd lemp -lando ssh -s placeholder -c "cat /certs/cert.ext" | grep DNS.1 | grep -w placeholder.landolemp.internal -lando ssh -s placeholder -c "cat /certs/cert.ext" | grep DNS.2 | grep -w placeholder -lando ssh -s placeholder -c "cat /certs/cert.ext" | grep DNS.3 | grep -w localhost -lando ssh -s placeholder -c "cat /certs/cert.ext" | grep placeholder.lando-lemp.lndo.site - # Should have the correct internal hostname info cd lamp lando info -s appserver | grep hostnames: | grep appserver.landolamp.internal cd .. && cd lemp -lando info -s placeholder | grep hostnames: | grep placeholder.landolemp.internal +lando info -s appserver | grep hostnames: | grep appserver.landolemp.internal +lando info -s appserver_nginx | grep hostnames: | grep appserver_nginx.landolemp.internal # Should be able to self connect from lamp cd lamp -lando ssh -s appserver -c "curl http://localhost" -lando ssh -s appserver -c "curl https://localhost" +lando exec appserver -- curl http://localhost +lando exec appserver -- curl https://localhost # Should be able to self connect from lemp cd lemp -lando ssh -s placeholder -c "curl http://localhost" -lando ssh -s placeholder -c "curl https://localhost" +lando exec appserver_nginx -- curl http://localhost:8080 +lando exec appserver_nginx -- curl https://localhost:8443 # Should be able to curl lemp from lamp at proxy addresses and internal hostnames cd lamp -lando ssh -s appserver -c "curl http://lando-lemp.lndo.site" -lando ssh -s appserver -c "curl http://appserver_nginx.landolemp.internal" -# lando ssh -s appserver -c "curl https://lando-lemp.lndo.site" -# lando ssh -s appserver -c "curl https://appserver_nginx.landolemp.internal" -lando ssh -s appserver -c "curl https://placeholder.lando-lemp.lndo.site" -lando ssh -s appserver -c "curl https://placeholder.landolemp.internal" +lando exec appserver -- curl http://lando-lemp.lndo.site +lando exec appserver -- curl http://appserver_nginx.landolemp.internal:8080 +lando exec appserver -- curl https://lando-lemp.lndo.site +lando exec appserver -- curl https://appserver_nginx.landolemp.internal:8443 # Should be able to curl lamp from lemp at proxy addresses and internal hostname cd lemp -lando ssh -s appserver -c "curl http://lando-lamp.lndo.site" -lando ssh -s appserver -c "curl http://appserver.landolamp.internal" -# lando ssh -s appserver -c "curl https://lando-lamp.lndo.site" -# lando ssh -s appserver -c "curl https://appserver.landolamp.internal" -lando ssh -s placeholder -c "curl https://lando-lamp.lndo.site" -lando ssh -s placeholder -c "curl https://appserver.landolamp.internal" +lando exec appserver_nginx -- curl http://lando-lamp.lndo.site +lando exec appserver_nginx -- curl http://appserver.landolamp.internal +lando exec appserver_nginx -- curl https://lando-lamp.lndo.site +lando exec appserver_nginx -- curl https://appserver.landolamp.internal # Should even be able to connect to a database in a different app cd lamp -lando ssh -s database -c "mysql -uroot -h database.landolemp.internal -e 'quit'" +lando exec database -- mysql -uroot -h database.landolemp.internal -e "quit" ``` Destroy tests diff --git a/examples/networking/nginx.conf b/examples/networking/nginx.conf index 0f856378a..bdfa4cd84 100644 --- a/examples/networking/nginx.conf +++ b/examples/networking/nginx.conf @@ -1,16 +1,30 @@ server { - index index.php index.html; - server_name appserver; - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - root /var/www/html; - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass appserver:9000; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } + listen 8443 ssl; + listen 8080; + server_name localhost; + + ssl_certificate /etc/lando/certs/cert.crt; + ssl_certificate_key /etc/lando/certs/cert.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + index index.php index.html; + + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + root /var/www/html; + + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass appserver:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } } diff --git a/examples/no-services/README.md b/examples/no-services/README.md index c7844cd36..b446cba12 100644 --- a/examples/no-services/README.md +++ b/examples/no-services/README.md @@ -1,10 +1,8 @@ -No Services Example -=================== +# No Services Example This example exists primarily to test what happens if you Lando start an app with no services. -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -14,8 +12,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -31,8 +28,7 @@ lando list lando logs ``` -Destroy tests -------------- +## Destroy tests Run the following commands to trash this app like nothing ever happened. diff --git a/examples/orchestrator/README.md b/examples/orchestrator/README.md index d3afb2c38..d9b8a53a1 100755 --- a/examples/orchestrator/README.md +++ b/examples/orchestrator/README.md @@ -1,12 +1,10 @@ -Orchestrator Example -==================== +# Orchestrator Example This example exists primarily to test the following documentation: * [Orchestrator](https://docs.lando.dev/core/v3/orchestrator.html) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -34,8 +31,8 @@ LANDO_ORCHESTRATOR_VERSION="2.19.1" lando config --path orchestratorVersion --fo LANDO_ORCHESTRATOR_VERSION="2.19.1" lando start LANDO_ORCHESTRATOR_VERSION="2.19.1" lando start -vvv 2>&1 | grep ".lando/bin/docker-compose-v2.19.1" -# Should use a system fallback if avialable if version is bogus -LANDO_ORCHESTRATOR_VERSION="UHNO" lando start -vvv 2>&1 | grep "/usr/local/bin/docker-compose" +# Should use a system fallback or automatically download the default compose when version is bogus +LANDO_ORCHESTRATOR_VERSION="UHNO" lando start -vvv 2>&1 | grep -E "/usr/local/bin/docker-compose|.lando/bin/docker-compose" # Should use the orchestratorBin if set LANDO_ORCHESTRATOR_BIN="/usr/local/bin/docker-compose" lando config --path orchestratorBin | grep "$LANDO_ORCHESTRATOR_BIN" @@ -44,12 +41,11 @@ LANDO_ORCHESTRATOR_BIN="/usr/local/bin/docker-compose" lando start -vvv 2>&1 | g # Should set orchestratorBin with composeBin if orchestratorBin is not set LANDO_COMPOSE_BIN="/usr/local/bin/docker-compose" lando config --path orchestratorBin | grep "$LANDO_COMPOSE_BIN" -# Shoud prefer orchestratorBin to composeBin +# Should prefer orchestratorBin to composeBin LANDO_COMPOSE_BIN="/usr/local/bin/bogus" LANDO_ORCHESTRATOR_BIN="/usr/local/bin/docker-compose" lando config --path orchestratorBin | grep "$LANDO_ORCHESTRATOR_BIN" ``` -Destroy tests -------------- +## Destroy tests Run the following commands to trash this app like nothing ever happened. diff --git a/examples/plugins/.gitignore b/examples/plugins/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/plugins/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/plugins/.lando.yml b/examples/plugins/.lando.yml new file mode 100644 index 000000000..b63a36759 --- /dev/null +++ b/examples/plugins/.lando.yml @@ -0,0 +1,36 @@ +name: lando-base +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +pluginDirs: + - plugins +plugins: + "@lando/base-test-plugin-2": ./test-plugin-2 + "@lando/core": "../.." diff --git a/examples/plugin-commands/README.md b/examples/plugins/README.md similarity index 77% rename from examples/plugin-commands/README.md rename to examples/plugins/README.md index 529888192..50a493ef3 100644 --- a/examples/plugin-commands/README.md +++ b/examples/plugins/README.md @@ -1,27 +1,30 @@ -Plugin Login Test -================ +# Plugins Example This example exists primarily to test the following documentation: -* [plugin-login](https://docs.lando.dev/cli/plugin-login.html) +* [Plugins](https://docs.lando.dev/core/v3/plugins.html) -Start up tests --------------- +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. -Run the following commands to get up and running with this example. +## Start up tests ```bash -# Should poweroff +# Should start successfully lando poweroff - +lando start ``` -Verification commands ---------------------- +## Verification commands -Run the following commands to validate things are rolling as they should. +Run the following commands to verify things work as expected ```bash +# Should load plugins from pluginDirs +lando stuff | grep "I WORKED" + +# Should load plugins specified in landofile +lando stuff2 | grep "I WORKED" + # Should be able to add a public plugin via a registry string. lando config | grep -qv "plugins/@lando/php" lando plugin-add "@lando/php" @@ -65,3 +68,11 @@ lando config | grep -q "plugins/@lando/lando-plugin-test" lando plugin-remove "@lando/lando-plugin-test" lando config | grep -qv "plugins/@lando/lando-plugin-test" ``` + +# Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/plugins/compose.yml b/examples/plugins/compose.yml new file mode 100644 index 000000000..1587fb178 --- /dev/null +++ b/examples/plugins/compose.yml @@ -0,0 +1,7 @@ +services: + web: + image: nginx + web2: + image: nginx + ports: + - '80' diff --git a/examples/plugins/index.html b/examples/plugins/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/plugins/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/base/plugins/test-plugin/app.js b/examples/plugins/plugins/test-plugin/app.js similarity index 100% rename from examples/base/plugins/test-plugin/app.js rename to examples/plugins/plugins/test-plugin/app.js diff --git a/examples/base/plugins/test-plugin/index.js b/examples/plugins/plugins/test-plugin/index.js similarity index 100% rename from examples/base/plugins/test-plugin/index.js rename to examples/plugins/plugins/test-plugin/index.js diff --git a/examples/base/plugins/test-plugin/package.json b/examples/plugins/plugins/test-plugin/package.json similarity index 100% rename from examples/base/plugins/test-plugin/package.json rename to examples/plugins/plugins/test-plugin/package.json diff --git a/examples/base/plugins/test-plugin/plugin.yaml b/examples/plugins/plugins/test-plugin/plugin.yaml similarity index 100% rename from examples/base/plugins/test-plugin/plugin.yaml rename to examples/plugins/plugins/test-plugin/plugin.yaml diff --git a/examples/base/plugins/test-plugin/tasks/stuff.js b/examples/plugins/plugins/test-plugin/tasks/stuff.js similarity index 100% rename from examples/base/plugins/test-plugin/tasks/stuff.js rename to examples/plugins/plugins/test-plugin/tasks/stuff.js diff --git a/examples/base/test-plugin-2/app.js b/examples/plugins/test-plugin-2/app.js similarity index 100% rename from examples/base/test-plugin-2/app.js rename to examples/plugins/test-plugin-2/app.js diff --git a/examples/base/test-plugin-2/index.js b/examples/plugins/test-plugin-2/index.js similarity index 100% rename from examples/base/test-plugin-2/index.js rename to examples/plugins/test-plugin-2/index.js diff --git a/examples/base/test-plugin-2/package.json b/examples/plugins/test-plugin-2/package.json similarity index 100% rename from examples/base/test-plugin-2/package.json rename to examples/plugins/test-plugin-2/package.json diff --git a/examples/base/test-plugin-2/plugin.yaml b/examples/plugins/test-plugin-2/plugin.yaml similarity index 100% rename from examples/base/test-plugin-2/plugin.yaml rename to examples/plugins/test-plugin-2/plugin.yaml diff --git a/examples/base/test-plugin-2/tasks/stuff.js b/examples/plugins/test-plugin-2/tasks/stuff.js similarity index 100% rename from examples/base/test-plugin-2/tasks/stuff.js rename to examples/plugins/test-plugin-2/tasks/stuff.js diff --git a/examples/proxy/.lando.yml b/examples/proxy/.lando.yml index 6510498fe..286d19226 100644 --- a/examples/proxy/.lando.yml +++ b/examples/proxy/.lando.yml @@ -37,6 +37,9 @@ proxy: - name: test key: headers.customresponseheaders.X-Lando-Test value: on + web4: + - hostname: lando4.lndo.site + port: 8080 php: - hostname: object-format.lndo.site @@ -91,6 +94,19 @@ services: ports: - "8080" user: root + web4: + api: 4 + type: lando + image: + imagefile: nginxinc/nginx-unprivileged:1.26.1 + context: + - ./default-ssl.conf:/etc/nginx/conf.d/default.conf + certs: /certs/cert.crt + ports: + - 8080/http + - 8443/https + app-mount: + destination: /usr/share/nginx/html plugins: "@lando/core": "../.." diff --git a/examples/proxy/README.md b/examples/proxy/README.md index e19098310..0846522fd 100644 --- a/examples/proxy/README.md +++ b/examples/proxy/README.md @@ -1,14 +1,12 @@ -Proxy Example -============= +# Proxy Example This example exists primarily to test the following documentation: -* [Proxy](http://docs.devwithlando.io/config/proxy.html) +* [Proxy](https://docs.devwithlando.io/config/proxy.html) -See the [Landofiles](http://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. +See the [Landofiles](https://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. -Start up tests --------------- +## Start up tests ```bash # Should start successfully @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -34,10 +31,14 @@ curl -s -o /dev/null -I -w "%{http_code}" http://lando-proxy.lndo.site | grep 20 curl -s -o /dev/null -I -w "%{http_code}" http://sub.lando-proxy.lndo.site | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://another-way-to-eighty.lndo.site | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://l337.lndo.site | grep 200 +curl -s -o /dev/null -I -w "%{http_code}" http://lando4.lndo.site | grep 200 # Should also work over https if ssl is true and we have certs curl -s -o /dev/null -Ik -w "%{http_code}" https://web3.lndo.site | grep 200 +curl -s -o /dev/null -Ik -w "%{http_code}" https://lando4.lndo.site | grep 200 lando info -s web3 | grep hasCerts | grep true +lando exec web4 -- cat \$LANDO_SERVICE_CERT +lando exec web4 -- env | grep LANDO_SERVICE_CERT | grep /certs/cert.crt # Should route to a different port if specified curl -s -o /dev/null -I -w "%{http_code}" http://another-way-to-eighty.lndo.site | grep 200 @@ -45,8 +46,10 @@ curl -s -o /dev/null -I -w "%{http_code}" http://web3.lndo.site | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://lets.combine.really.lndo.site/everything/for-real | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://lets.combine.things.lndo.site/everything/for-real | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://l337.lndo.site | grep 200 -lando ssh -s php -u root -c "curl -s -o /dev/null -I -w \"%{http_code}\" http://web3:8080" | grep 200 -lando ssh -s php -u root -c "curl -s -o /dev/null -I -w \"%{http_code}\" http://l337:8888" | grep 200 +curl -s -o /dev/null -I -w "%{http_code}" http://lando4.lndo.site | grep 200 +lando exec php --user root -- curl -s -o /dev/null -I -w "%{http_code}" http://web3:8080 | grep 200 +lando exec php --user root -- curl -s -o /dev/null -I -w "%{http_code}" http://l337:8888 | grep 200 +lando exec php --user root -- curl -s -o /dev/null -I -w "%{http_code}" https://web4:8443 | grep 200 # Should handle wildcard entries curl -s -o /dev/null -I -w "%{http_code}" http://thiscouldbeanything-lando-proxy.lndo.site | grep 200 @@ -58,6 +61,7 @@ curl -s -o /dev/null -I -w "%{http_code}" http://web5.lndo.site | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://object-format.lndo.site | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://object-format.lndo.site/test | grep 200 curl -s -o /dev/null -I -w "%{http_code}" http://headers.l337.lndo.site | grep 200 +curl -s -o /dev/null -I -w "%{http_code}" http://lando4.lndo.site | grep 200 # Should handle sites in subdirectories curl -s -o /dev/null -I -w "%{http_code}" http://lando-proxy.lndo.site/api | grep 200 @@ -84,10 +88,13 @@ docker exec landoproxyhyperion5000gandalfedition_proxy_1 cat /proxy_config/defau # Should generate proxy cert files and move them into the right location as needed docker exec landoproxy_web3_1 cat /proxy_config/web3.landoproxy.yaml| grep certFile | grep "/lando/certs/web3.landoproxy.crt" docker exec landoproxy_web3_1 cat /proxy_config/web3.landoproxy.yaml| grep keyFile | grep "/lando/certs/web3.landoproxy.key" +lando exec web4 -- cat \$LANDO_SERVICE_CERT +lando exec web4 -- env | grep LANDO_SERVICE_CERT | grep /certs/cert.crt +lando exec web4 -- cat \$LANDO_SERVICE_KEY +lando exec web4 -- env | grep LANDO_SERVICE_KEY | grep /certs/cert.key ``` -Destroy tests -------------- +## Destroy tests ```bash # Should destroy successfully diff --git a/examples/proxy/default-ssl.conf b/examples/proxy/default-ssl.conf new file mode 100644 index 000000000..9f00cbd12 --- /dev/null +++ b/examples/proxy/default-ssl.conf @@ -0,0 +1,28 @@ +server { + listen 8443 ssl; + listen 8080; + server_name localhost; + + ssl_certificate /certs/cert.crt; + ssl_certificate_key /certs/cert.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/examples/rebuild/.gitignore b/examples/rebuild/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/rebuild/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/rebuild/.lando.yml b/examples/rebuild/.lando.yml new file mode 100644 index 000000000..a023aeb5d --- /dev/null +++ b/examples/rebuild/.lando.yml @@ -0,0 +1,33 @@ +name: lando-rebuild +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/rebuild/README.md b/examples/rebuild/README.md new file mode 100644 index 000000000..229785ac4 --- /dev/null +++ b/examples/rebuild/README.md @@ -0,0 +1,52 @@ +# Rebuild Example + +This example exists primarily to test the following documentation: + +* [`lando rebuild`](https://docs.lando.dev/cli/rebuild.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should rebuild the services without errors +lando rebuild -y +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web2_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web3_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web4_1 + +# Should only rebuild the specified services +lando rebuild -y --service web2 +lando rebuild -y -s web2 +docker ps --latest | grep landorebuild_web2_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web2_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web3_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web4_1 +lando rebuild -y --service web3 +lando rebuild -y -s web3 +docker ps --latest | grep landorebuild_web3_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web2_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web3_1 +docker ps --filter label=com.docker.compose.project=landorebuild | grep landorebuild_web4_1 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/rebuild/compose.yml b/examples/rebuild/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/rebuild/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/rebuild/index.html b/examples/rebuild/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/rebuild/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/recipes/.gitignore b/examples/recipes/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/recipes/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/recipes/.lando.yml b/examples/recipes/.lando.yml new file mode 100644 index 000000000..e84870e6e --- /dev/null +++ b/examples/recipes/.lando.yml @@ -0,0 +1,5 @@ +name: lando-recipes +recipe: test +plugins: + "@lando/recipe-test": ./plugin-recipe-test + "@lando/core": "../.." diff --git a/examples/recipes/README.md b/examples/recipes/README.md new file mode 100644 index 000000000..73b8a7875 --- /dev/null +++ b/examples/recipes/README.md @@ -0,0 +1,48 @@ +# Recipes Example + +This example exists primarily to test the following documentation: + +* [Recipes](https://docs.lando.dev/config/recipes.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should load in correct recipe services +lando info --service web --path service | grep web +lando info --service web2 --path service | grep web2 + +# Should load in correct recipe tasks +lando recipe | grep "I WORKED\!" + +# Should load in correct recipe tooling +lando env | grep LANDO_SERVICE_NAME | grep web + +# Should add recipe services +skip + +# Should add recipe init +skip + +# Should add recipe source +skip +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/recipes/plugin-recipe-test/app.js b/examples/recipes/plugin-recipe-test/app.js new file mode 100644 index 000000000..529aa95af --- /dev/null +++ b/examples/recipes/plugin-recipe-test/app.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = () => {}; diff --git a/examples/recipes/plugin-recipe-test/builders/test.js b/examples/recipes/plugin-recipe-test/builders/test.js new file mode 100644 index 000000000..4d31afee2 --- /dev/null +++ b/examples/recipes/plugin-recipe-test/builders/test.js @@ -0,0 +1,39 @@ +'use strict'; + +// Modules +const _ = require('lodash'); + +/* + * test recipe + */ +module.exports = { + name: 'test', + parent: '_recipe', + config: { + proxy: {}, + services: {}, + tooling: {env: { + service: 'web', + }}, + }, + builder: (parent, config) => class LandoDrupal7 extends parent { + constructor(id, options = {}) { + options.services = _.merge({}, options.services, { + web: { + api: 4, + type: 'lando', + image: 'nginxinc/nginx-unprivileged:1.26.1', + ports: ['8080/http'], + }, + web2: { + api: 4, + type: 'lando', + image: 'nginxinc/nginx-unprivileged:1.26.1', + ports: ['8080/http'], + }, + }); + + super(id, _.merge({}, config, options)); + }; + }, +}; diff --git a/examples/recipes/plugin-recipe-test/index.js b/examples/recipes/plugin-recipe-test/index.js new file mode 100644 index 000000000..529aa95af --- /dev/null +++ b/examples/recipes/plugin-recipe-test/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = () => {}; diff --git a/examples/recipes/plugin-recipe-test/package.json b/examples/recipes/plugin-recipe-test/package.json new file mode 100644 index 000000000..d980d2558 --- /dev/null +++ b/examples/recipes/plugin-recipe-test/package.json @@ -0,0 +1,5 @@ +{ + "name": "@lando/recipe-test", + "version": "1.0.2", + "description": "test recipe stuff" +} diff --git a/examples/recipes/plugin-recipe-test/plugin.yaml b/examples/recipes/plugin-recipe-test/plugin.yaml new file mode 100644 index 000000000..4a247896d --- /dev/null +++ b/examples/recipes/plugin-recipe-test/plugin.yaml @@ -0,0 +1 @@ +name: "@lando/recipe-test" diff --git a/examples/recipes/plugin-recipe-test/tasks/recipe.js b/examples/recipes/plugin-recipe-test/tasks/recipe.js new file mode 100644 index 000000000..eeb5d1a25 --- /dev/null +++ b/examples/recipes/plugin-recipe-test/tasks/recipe.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = lando => ({ + command: 'recipe', + describe: 'Tests recipe tooling stuff', + run: () => { + console.log('I WORKED!'); + }, +}); diff --git a/examples/release-channel/.gitignore b/examples/release-channel/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/release-channel/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/release-channel/.lando.yml b/examples/release-channel/.lando.yml new file mode 100644 index 000000000..9cde0ccd1 --- /dev/null +++ b/examples/release-channel/.lando.yml @@ -0,0 +1,33 @@ +name: lando-release-channel +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/release-channel/README.md b/examples/release-channel/README.md new file mode 100644 index 000000000..785f5620f --- /dev/null +++ b/examples/release-channel/README.md @@ -0,0 +1,41 @@ +# Release Channel Example + +This example exists primarily to test the following documentation: + +* [Release Channels](https://docs.lando.dev/core/v3/releases.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should set the release channel as stable by default +lando config | grep "channel" | grep "stable" + +# Should set the release channel based on the user option +lando --channel edge +lando config | grep "channel" | grep "edge" +lando --channel stable +lando config | grep "channel" | grep "stable" + +# Should not allow bogus release channels +lando --channel orange || echo $? | grep 1 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/release-channel/compose.yml b/examples/release-channel/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/release-channel/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/release-channel/index.html b/examples/release-channel/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/release-channel/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/restart/.gitignore b/examples/restart/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/restart/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/restart/.lando.yml b/examples/restart/.lando.yml new file mode 100644 index 000000000..0fc886854 --- /dev/null +++ b/examples/restart/.lando.yml @@ -0,0 +1,33 @@ +name: lando-restart +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/restart/README.md b/examples/restart/README.md new file mode 100644 index 000000000..eb7c5c2a7 --- /dev/null +++ b/examples/restart/README.md @@ -0,0 +1,48 @@ +# Restart Example + +This example exists primarily to test the following documentation: + +* [`lando start`](https://docs.lando.dev/cli/start.html) +* [`lando stop`](https://docs.lando.dev/cli/stop.html) +* [`lando restart`](https://docs.lando.dev/cli/restart.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should stop the apps containers +lando stop +docker ps --filter label=com.docker.compose.project=landorestart -q | wc -l | grep 0 + +# Should stop ALL running lando containers +lando start +docker ps --filter label=io.lando.container=TRUE -q | wc -l | grep 4 +lando poweroff +docker ps --filter label=io.lando.container=TRUE -q | wc -l | grep 0 + +# Should restart the services without errors +lando restart +docker ps --filter label=com.docker.compose.project=landorestart | grep landorestart_web_1 +docker ps --filter label=com.docker.compose.project=landorestart | grep landorestart_web2_1 +docker ps --filter label=com.docker.compose.project=landorestart | grep landorestart_web3_1 +docker ps --filter label=com.docker.compose.project=landorestart | grep landorestart_web4_1 +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/restart/compose.yml b/examples/restart/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/restart/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/restart/index.html b/examples/restart/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/restart/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/scanner/.lando.yml b/examples/scanner/.lando.yml index 9d084d055..a3d631fa6 100755 --- a/examples/scanner/.lando.yml +++ b/examples/scanner/.lando.yml @@ -101,11 +101,22 @@ services: context: - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - - 8888 - moreHttpPorts: - - '8888' + - 8888/http volumes: - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: + imagefile: nginxinc/nginx-unprivileged:1.26.1 + context: + - ./default-ssl.conf:/etc/nginx/conf.d/default.conf + certs: /certs/cert.crt + ports: + - 8080/http + - 8443/https + app-mount: + destination: /usr/share/nginx/html plugins: "@lando/core": "../.." diff --git a/examples/scanner/README.md b/examples/scanner/README.md index ca0239f2b..cdb8d19f0 100755 --- a/examples/scanner/README.md +++ b/examples/scanner/README.md @@ -1,12 +1,10 @@ -Scanner Example -=============== +# Scanner Example This example exists primarily to test the following documentation: * [Scanning](https://docs.lando.dev/core/v3/scanner.html) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -27,11 +24,15 @@ docker inspect landoscanner_scanme_1 | grep io.lando.http-ports | grep "80,443" # Should add an extra port to io.lando.http-ports if specified docker inspect landoscanner_moreports_1 | grep io.lando.http-ports | grep "80,443,8888" -docker inspect landoscanner_l337_1 | grep io.lando.http-ports | grep "80,443,8888" +docker inspect landoscanner_l337_1 | grep dev.lando.http-ports | grep "8888" + +# Should set correct labels in Lando 4 services +docker inspect landoscanner_web4_1 | grep io.lando.http-ports | grep "80,443" +docker inspect landoscanner_web4_1 | grep dev.lando.http-ports | grep "8080" +docker inspect landoscanner_web4_1 | grep dev.lando.https-ports | grep "8443" ``` -Destroy tests -------------- +## Destroy tests Run the following commands to trash this app like nothing ever happened. diff --git a/examples/scanner/default-ssl.conf b/examples/scanner/default-ssl.conf new file mode 100755 index 000000000..9f00cbd12 --- /dev/null +++ b/examples/scanner/default-ssl.conf @@ -0,0 +1,28 @@ +server { + listen 8443 ssl; + listen 8080; + server_name localhost; + + ssl_certificate /certs/cert.crt; + ssl_certificate_key /certs/cert.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/examples/security/.lando.yml b/examples/security/.lando.yml new file mode 100644 index 000000000..a8b278b4c --- /dev/null +++ b/examples/security/.lando.yml @@ -0,0 +1,48 @@ +name: lando-security +proxy: + web: + - web.lndo.site:8080 + +services: + web: + api: 4 + security: + ca: + - SoloCA.crt + certs: + cert: /frank/cert.crt + key: /bob/key.key + image: + imagefile: nginxinc/nginx-unprivileged:1.26.1 + context: + - ./default-ssl-3.conf:/etc/nginx/conf.d/default.conf + user: nginx + ports: + - 8080/http + - 8443/https + fedora: + api: 4 + security: + ca: SoloCA.crt + image: | + FROM fedora:40 + RUN dnf update -y && dnf install openssl -y + command: sleep infinity + +tooling: + certinfo: + cmd: openssl x509 -in "$LANDO_SERVICE_CERT" -noout -text + service: :service + options: + service: + default: web + alias: + - s + describe: Runs on a different service + +plugins: + "@lando/core": "../.." + "@lando/healthcheck": "../../plugins/healthcheck" + "@lando/networking": "../../plugins/networking" + "@lando/proxy": "../../plugins/proxy" + "@lando/scanner": "../../plugins/scanner" diff --git a/examples/security/README.md b/examples/security/README.md new file mode 100644 index 000000000..c47acf060 --- /dev/null +++ b/examples/security/README.md @@ -0,0 +1,62 @@ +# Security Example + +This example exists primarily to test the following documentation: + +* [Security](https://docs.lando.dev/core/v3/security.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should have the correct default cert issuer +lando certinfo | grep Issuer | grep "Lando Development CA" +lando certinfo --service fedora | grep Issuer | grep "Lando Development CA" + +# Should set the environment variables correctly +lando exec web -- env | grep LANDO_CA_DIR | grep /etc/ssl/certs/ +lando exec web -- env | grep LANDO_CA_BUNDLE | grep /etc/ssl/certs/ca-certificates.crt +lando exec fedora -- env | grep LANDO_CA_DIR | grep /etc/pki/ca-trust/source/anchors +lando exec fedora -- env | grep LANDO_CA_BUNDLE | grep /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem +lando exec web -- "cat \$LANDO_CA_BUNDLE" +lando exec fedora -- "cat \$LANDO_CA_BUNDLE" | grep "Lando Development CA" +lando exec fedora -- "cat \$LANDO_CA_BUNDLE" | grep "Solo Development CA" + +# Should have installed the CAs +lando exec web -- "ls -lsa \$LANDO_CA_DIR" | grep LandoCA.pem +lando exec fedora -- "ls -lsa \$LANDO_CA_DIR" | grep LandoCA.crt + +# Should use additional CAs if specified +lando exec web -- "ls -lsa \$LANDO_CA_DIR" | grep SoloCA.crt +lando exec fedora -- "ls -lsa \$LANDO_CA_DIR" | grep SoloCA.crt + +# Should trust CA signed web traffic on host and in container +curl https://web.lndo.site +lando exec web -- curl https://localhost:8443 + +# Should have the correct cert issuer if LANDO_CA_CERT and LANDO_CA_KEY are set differently +LANDO_CA_CERT="$(pwd)/SoloCA.crt" LANDO_CA_KEY="$(pwd)/SoloCA.key" lando config --path caCert | grep "SoloCA.crt" +LANDO_CA_CERT="$(pwd)/SoloCA.crt" LANDO_CA_KEY="$(pwd)/SoloCA.key" lando config --path caKey | grep "SoloCA.key" +LANDO_CA_CERT="$(pwd)/SoloCA.crt" LANDO_CA_KEY="$(pwd)/SoloCA.key" lando rebuild -y +lando certinfo | grep Issuer | grep "Solo Development CA" +curl https://web.lndo.site +lando exec web -- curl https://localhost:8443 +``` + +## Destroy tests + +```bash +# Should destroy and poweroff +lando destroy -y +lando poweroff +``` diff --git a/examples/security/SoloCA.crt b/examples/security/SoloCA.crt new file mode 100644 index 000000000..7be9c0175 --- /dev/null +++ b/examples/security/SoloCA.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFZDCCA0wCCQDKgPmqD5wcHzANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoM +BlNvbG9yZzERMA8GA1UECwwIQ29yZWxsaWExHDAaBgNVBAMME1NvbG8gRGV2ZWxv +cG1lbnQgQ0EwHhcNMjQwNzI0MTIyMzExWhcNMzQwNzIyMTIyMzExWjB0MQswCQYD +VQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzAN +BgNVBAoMBlNvbG9yZzERMA8GA1UECwwIQ29yZWxsaWExHDAaBgNVBAMME1NvbG8g +RGV2ZWxvcG1lbnQgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCV +5j3/rBOGfBbmdnS/gnkF9IubvlngTTDcEHrZcDsvf48DyhhEw9bhP6CDiaHYeUAe +LCwChZujehDOdkJJINMh894HlgVqMDoT6m3CCwAIdHbGKUVHHyGKh0iJmKPby/eP +fv6pcfN5RBAbuzp+CheD8d+Ja6WjDRqCgO8vCTm9fIlFRMbyYcva7rrGZSY5aslD +D26jJl5/DlvLBAhmlpXoQLH2sLc6ywV58/S1tfinDyeOXOdji8nOTRPcpCT6xY+8 +d8sFWL+TMzEiG24CBmVs6mmBRxGVkTaIsQgDB2nnNBVRaX6eu2Zw8wZQGYONZshg +gPqAHrIRPVkXfuxMygb0WrURKuFys6GyVGZ/iv0aj6UYK44AT0ygjfx3XUx7kgmV +Nc7vX5xFJYfyrM2W3Vg85N1qcSlIISie/tEA9ovgBSYkGAVA+ooOFVzB1zcpvBud +QxhauYFv/sshojrRgnM14I4xGxwqU+P2eZbdAZoyaAMjB6cdzBHTwhhHGUdEIS5x +R8U/sgLRcFZwqWH2ggAxa8uAEcBB137rSqYJmJZ51HXgG3pCM/9H44BfDS8P+6dg +cYKZqyqDMm5Vso6PUwwzL5BoYN+aPcBSpB3X1n3X/E4h7tzLnMeo2ri/4xPc/Var +P0mxKd11EKRjg6FlnxgVKFiqNBj3jAWXKPRlwCDIEwIDAQABMA0GCSqGSIb3DQEB +CwUAA4ICAQAooTHp89bhm/9Y+7jSUl6lyLdOBQm+dgKg4jUAyXtXFNi0niFj0coa +zwEXx00YB28ucsfSM5fATWnoM7oMO6fTZtXx9vlTCnJ5DcW/Yg8XgtLYiYDlwcQn +1mb0FUwYofEYut3dXFMHKYuxc6qp1dQsoHhZP0WnJnSKTpBGm2AnXMHx1bVJ08CY +5T56Wq+5P70+LNRPdBNT9A1UL1ey35NgW8wtT5nV6T/PkS3Isu0s4dMAm8xhYT9k +UwXkaWtBCDdFkpVLW2W2QvhM9yKNBbIz9oxpCg4Tj0hQWQpDck1yxxasCQ/wiiyI +/A3H+sY/W0L/vDKX8v4fbSc3fGqOOCJbAOxQA2tzUK26PI1vaIScXkyzmOmqw1ij +G/JfHf1ZsHpopCbhIvdBA/wp3iSyN6fN4KnC+3SUqtAXKXaEHHs7oSYbpfYTobRk +nUnNP7Hp9MCkToGHBDAK/J47Uz6UlYp3KR8YlR8mlItdvEmIEvf/A+cmy/ya9uz+ +Owp7Rl6tbmkLtu7s7OC3kaACsYoSXUHh/pMzhjV+mwQozOlp6kZfDv3aAAu0/6zg +AtcHpcb2kU2gzxz4DfucBMBsLkoqfP7ULS4hYLkJQ+A+zv9nCEYtfztFAcR05Hpu +fz9hmLfYUMztFjBqhK0oR7XGt+g9Jj/dTXcB4RMx+w44QNq/eKGo5A== +-----END CERTIFICATE----- diff --git a/examples/security/SoloCA.key b/examples/security/SoloCA.key new file mode 100644 index 000000000..183d55b6d --- /dev/null +++ b/examples/security/SoloCA.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCV5j3/rBOGfBbm +dnS/gnkF9IubvlngTTDcEHrZcDsvf48DyhhEw9bhP6CDiaHYeUAeLCwChZujehDO +dkJJINMh894HlgVqMDoT6m3CCwAIdHbGKUVHHyGKh0iJmKPby/ePfv6pcfN5RBAb +uzp+CheD8d+Ja6WjDRqCgO8vCTm9fIlFRMbyYcva7rrGZSY5aslDD26jJl5/DlvL +BAhmlpXoQLH2sLc6ywV58/S1tfinDyeOXOdji8nOTRPcpCT6xY+8d8sFWL+TMzEi +G24CBmVs6mmBRxGVkTaIsQgDB2nnNBVRaX6eu2Zw8wZQGYONZshggPqAHrIRPVkX +fuxMygb0WrURKuFys6GyVGZ/iv0aj6UYK44AT0ygjfx3XUx7kgmVNc7vX5xFJYfy +rM2W3Vg85N1qcSlIISie/tEA9ovgBSYkGAVA+ooOFVzB1zcpvBudQxhauYFv/ssh +ojrRgnM14I4xGxwqU+P2eZbdAZoyaAMjB6cdzBHTwhhHGUdEIS5xR8U/sgLRcFZw +qWH2ggAxa8uAEcBB137rSqYJmJZ51HXgG3pCM/9H44BfDS8P+6dgcYKZqyqDMm5V +so6PUwwzL5BoYN+aPcBSpB3X1n3X/E4h7tzLnMeo2ri/4xPc/VarP0mxKd11EKRj +g6FlnxgVKFiqNBj3jAWXKPRlwCDIEwIDAQABAoICABfHOjaAeWhv7sIIMGTgsYn8 +TNkbzO4D0KhYBOTRJNJYDbuwJ0FhP4jjqvaysnXAZidjImgUAahhCKF3qPQovNU5 +9hKF/b3mgJAANAD/9bVhpCWzDkZF7fAnnZ4WFIgdRtwAbS074j9uSI/dl12/BBPu +HmOSK+g9f+MLyOVRNVOVmcDfNB/m24uTKxWlnfaltd9pZ0eCIqNNB8qgjSSY5pa9 +DH3xcl9lLS03Qa5Be3wkr0Wp/xqPZifPmkL51tPg2vgumIn0lg8no7ehWkX/9b7d +QHc9atCrBFeSnY69clM2s9sCPQ+48nsgUfQK2A9qKocEbrg8JksNzEAp5hoYQhnq +0v1vCl3IwToLQ6fW1VWdeJIrn9u6O6pNOeDEp2wfX518ukmyqww6nssoW85cw2D3 +F14VAJYnmAGO1lDZsfiSq9gDdzvDCRUdvsnOwn7fb73RnhqHqp5zPGNXksMzIIYR +Cg3DEbXxSg4HcsK9WCrZKFBH02VzPkk8wcje2dz+kHjqD/LavU0TtReZTa+pEwIe +Ox29PRpXoBljQlLahh3KXgsz/2LKbW9R0Mhy9XWN3oU8eW1/D0SxkhAHI6s26SMv +S1EJc/DA/H4tweazvLdrZowWBCzLMyXGe0PWzIl2dkzxA0tksaq6pVYcw8YSXAgh +DC10r6j+eD7SXAs/8MSBAoIBAQDFWmSooi9yS2O+yl994ddMADSWUc6QpLH2+RFp +gs9cmgE5VwomWN/HTNp41BHU64+ApZmzunKpSmNh074kvdeZKRp2OciZSjF7k9OT +FAVUgn6rR7zd4IFJhQRBjPe7wUMqRtRB5KmAXhZi0G6gDObhgQ97uylM7/dJgJea +XiLKx7m6Asi59L3JXJBCI8eVR6Kt90zG8RNeTGTUwQK6a9jBaJ+BpDDpTJBAqSV3 +hp95Rfnto9vyoirKr5mswpg4JMQmvrFvwQBlI7SbAFIhHNvkBS0oWqIv1ZmQJlb/ +JynzCf9XGBa93fcVu+cpaFap7wvN5ecwNuHAuGKzg9mOaGdDAoIBAQDCcdCbqjZ4 +0REb0p4RtQJh7ZckgHhqdDTlg0zFyKsnpwMVycmFlPsLz5aACrCndU19n2hrg6NT +3NNUsZ8DxeZseH1ixhJgigcubED3sbqqzxD3dbKyOwgsfJ61VjdnDxliQXt0RJQo +Ik8ox/QKzQT3myiGBQp+1+BWCxY3CYpTzdvWblLaG3oqUiYuLhqqEdFlJicRzoCa +oIYuSuv05WBZL/vlhLyi2XurHLWzQmGnBTod24pkAFLeXnYO9vOYFpA31x+574/5 +LzR8wG3U0IHBdWRchEJ1tqP8PT/D5y1Ne1/7GY8xdpeVDuHBTip8JrWzYr5EE8k1 +KG7OBL/oJAbxAoIBAQCJpch7TdOl9is10VTVKgXOPn5vMdPPUu/FgGbCnrgesFOW +OL0djfNWwKXIjLF7Pmkyo04W6z46EWZLvzHp0ndjniWUvCzLrdHhjXOOK/KjxPiw +YjK61nGWY65aQgYv8FX2ULyO0PvgSr92YEYoX5dRRYEVHa9quBxUKdqTkoDVyoQh +1vtFqAwPO/5qAyabWgF/MPNd9ps5tDLHqW9LsxjVnTFTbL+omPwr/U3ilgT4wvPU +6eroym7qO5wFwRwGXK5rD6oWdhjecg7v5UNjUQuVeH7MnJpunp6iyfr3r8s0do6f +om+KMhy6DfrnCJ0ZnV8wVt/u4viGQJSm/JlrGCqdAoIBAHLuNSCdhl75LESmxDmx +JPxfI/Q2X3aEw3NZnXpWdxwT8pXhVNU5Tv4XMFz0dKA2jJwRKfZKs7JxFxS7fEMN +qXo56dsFOn2HeGEvKWN+0Nf/Vob+MaZ5kAZDjseec1beLOHP1LnPg0cJqIJxVcVA +k4wLUPOObTq2POp+2R2k7PdF+YgQY7Z5gUcckWbAZ5BYwc0otPUoewlqkoUwUbHK +Fp4A58ItKBaVuCxW5utS9Ed1pnlZd75OFq1LZjrIKwmdZJcs95q+h/oAteR7FTAy +IlAIJE8u+d18HAeO6G7R6QwgPYY9AE97SnOXfUb1/dSuSL4EQnQYwdhC0uPBGPGM +wfECggEBAK2dtIm8Gm/FOKcTXMRx41FKirfDH7ad88yXYCJ1nea4ENPTx1QMn1CA +glonDiKIPOBsPCzFijYW4oH4VbV4H8x7WJsykvX3RjNfzENGtd3Vf8IT+/B6G9kR +8opqLrwXODpHL6SYBhoHqPrLM+kiqh5G+d9yzvUCjz1w7IOaKU+sIfe4QgQ7aPWL +WHGmp9khKQ8c5V4J4P+nY00osyVW3VrsH0GW6upx0HroEHM1NV9n0lEO9skOkiyo +ATN9clUbFs3qxdZ+ZN/Py2fXXC49hI6JpRLAeduS9AlNtwyTbiUt+0h8mVl56ZjJ +XzimfJWh+v2tiFWzRyxIXHpj7A+uXns= +-----END PRIVATE KEY----- diff --git a/examples/security/default-ssl-3.conf b/examples/security/default-ssl-3.conf new file mode 100644 index 000000000..3b19f0516 --- /dev/null +++ b/examples/security/default-ssl-3.conf @@ -0,0 +1,28 @@ +server { + listen 0.0.0.0:8443 ssl; + listen 0.0.0.0:8080; + server_name localhost; + + ssl_certificate /frank/cert.crt; + ssl_certificate_key /bob/key.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/examples/setup-linux/README.md b/examples/setup-linux/README.md index 1b72c6a1d..a9aba8152 100644 --- a/examples/setup-linux/README.md +++ b/examples/setup-linux/README.md @@ -1,12 +1,10 @@ -Setup Linux Tests -================= +# Setup Linux Tests This example exists primarily to test the following documentation: * [lando setup](https://docs.lando.dev/cli/setup.html) -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -22,6 +20,22 @@ dpkg -l | grep docker-desktop || echo $? | grep 1 # Should be able to run lando setup lando setup -y +# Should have installed Docker Engine +docker version +docker info + +# Should have installed Docker Compose +find ~/.lando/bin -type f -name 'docker-compose-v2*' -exec {} version \; + +# Should have created the Lando Development CA +stat ~/.lando/certs/LandoCA.crt + +# Should have installed the Lando Development CA +openssl x509 -in /etc/ssl/certs/LandoCA.pem -text -noout | grep -A 1 "Issuer:" | grep "Lando Development CA" + +# Should have created the Landonet +docker network ls | grep lando_bridge_network + # Should be able to start a basic app lando start ``` diff --git a/examples/setup-macos/README.md b/examples/setup-macos/README.md index d12e9af7d..0f59287c2 100644 --- a/examples/setup-macos/README.md +++ b/examples/setup-macos/README.md @@ -1,12 +1,10 @@ -Setup macOS Tests -================= +# Setup macOS Tests This example exists primarily to test the following documentation: * [lando setup](https://docs.lando.dev/cli/setup.html) -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. @@ -14,10 +12,22 @@ Run the following commands to validate things are rolling as they should. # Should dogfood the core plugin we are testing against lando plugin-add "@lando/core@file:../.." -# Should be able to uninstall docker engine succesfully +# Should be able to uninstall docker desktop succesfully brew uninstall --force --ignore-dependencies docker-desktop brew list --versions docker-desktop || echo $? | grep 1 # Should be able to run lando setup -lando setup -y -``` +lando setup -y --skip-networking + +# Should have installed Docker Desktop +stat /Applications/Docker.app +docker --version + +# Should have installed Docker Compose +find ~/.lando/bin -type f -name 'docker-compose-v2*' -exec {} version \; + +# Should have created the Lando Development CA +stat ~/.lando/certs/LandoCA.crt + +# Should have installed the Lando Development CA +security find-certificate -a -c "Lando Development CA" -p ~/Library/Keychains/login.keychain-db diff --git a/examples/setup-windows/README.md b/examples/setup-windows/README.md index d80173a6d..fa4ad58ff 100644 --- a/examples/setup-windows/README.md +++ b/examples/setup-windows/README.md @@ -1,16 +1,30 @@ -Setup Windows Test -================== +# Setup Windows Test This example exists primarily to test the following documentation: * [lando setup](https://docs.lando.dev/cli/setup.html) -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. ```bash +# Should dogfood the core plugin we are testing against +lando plugin-add "@lando/core@file:../.." + # Should be able to run lando setup -lando setup -y +lando setup -y --skip-networking + +# Should have installed Docker Desktop +Test-Path "$Env:ProgramFiles\Docker\Docker\Docker Desktop.exe" +& "$Env:ProgramFiles\Docker\Docker\resources\bin\docker.exe" --version + +# Should have installed Docker Compose +Get-ChildItem -Path "$HOME/.lando/bin" -Filter "docker-compose-v2*" -Recurse | ForEach-Object { & $_.FullName version } + +# Should have created the Lando Development CA +Test-Path "$HOME/.lando/certs/LandoCA.crt" + +# Should have installed the Lando Development CA +certutil -store Root | findstr /C:"Lando Development CA" ``` diff --git a/examples/sql-helpers/README.md b/examples/sql-helpers/README.md index ea004c0a7..4a819d4ac 100644 --- a/examples/sql-helpers/README.md +++ b/examples/sql-helpers/README.md @@ -1,10 +1,8 @@ -SQL Import/Export Example -============ +# SQL Import/Export Example This example exists primarily to test the `sql-import.sh` and `sql-export.sh` helper scripts. -Start up tests --------------- +## Start up tests ```bash # Should init and start a lando app @@ -15,8 +13,7 @@ cp -rf testdata2.sql sqlhelpers/testdata2.sql cd sqlhelpers && lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -109,8 +106,7 @@ lando ssh -s postgres16 -c "/helpers/sql-import.sh testdata2.sql" lando ssh -s postgres16 -c "psql -U postgres -d lando_test -c 'select * from lando_test'" | grep -v "lando_original" | grep "lando_updated" ``` -Destroy tests -------------- +## Destroy tests ```bash # Should destroy sqlhelpers successfully diff --git a/examples/ssh/.gitignore b/examples/ssh/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/ssh/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/ssh/.lando.yml b/examples/ssh/.lando.yml new file mode 100644 index 000000000..38cbad750 --- /dev/null +++ b/examples/ssh/.lando.yml @@ -0,0 +1,42 @@ +name: lando-ssh +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + build_as_root: + - apt-get update -y && apt-get install procps -y + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: | + FROM nginx + RUN apt-get update -y && apt-get install procps -y + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: | + FROM nginxinc/nginx-unprivileged:1.26.1 + USER root + RUN apt-get update -y && apt-get install procps -y + USER nginx + user: nginx + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." diff --git a/examples/ssh/README.md b/examples/ssh/README.md new file mode 100644 index 000000000..0187d5376 --- /dev/null +++ b/examples/ssh/README.md @@ -0,0 +1,69 @@ +# SSH Example + +This example exists primarily to test the following documentation: + +* [`lando ssh`](https://docs.lando.dev/cli/ssh.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should run a command as the default user +lando ssh -s web -c "id | grep \$LANDO_WEBROOT_USER" +lando ssh -s web2 -c "id | grep \$LANDO_WEBROOT_USER" +lando ssh -s web3 -c "id" | grep root +lando ssh -s web4 -c "whoami | grep \$LANDO_USER" + +# Should run a command as the --user +lando ssh -s web -u root -c "id | grep root" +lando ssh -s web2 -u root -c "id | grep root" +lando ssh -s web3 -u root -c "id | grep root" +lando ssh -s web4 -u root -c "id | grep root" + +# Should run commands from appMount for +lando ssh -s web -u root -c "pwd" | grep /app +lando ssh -s web2 -u root -c "pwd" | grep /app +lando ssh -s web3 -u root -c "pwd" | grep /usr/share/nginx/html +lando ssh -s web4 -u root -c "pwd" | grep /usr/share/nginx/html + +# Should track appMounted commands +cd folder +lando ssh -s web2 -u root -c "pwd" | grep /app/folder +lando ssh -s web3 -u root -c "pwd" | grep /usr/share/nginx/html/folder +lando ssh -s web4 -u root -c "pwd" | grep /usr/share/nginx/html/folder + +# Should load the v3 lando environment +lando ssh -s web -u root -c "env" | grep LANDO=ON +lando ssh -s web2 -u root -c "env" | grep LANDO=ON +lando ssh -s web2 -u root -c "env" | grep LANDO=ON +lando ssh -s web4 -u root -c "env" | grep LANDO=ON + +# Should be able to background commands with line ending ampersands +lando ssh -s alpine -u root -c "sleep infinity &" +lando ssh -s web2 -u root -c "sleep infinity &" +lando ssh -s web3 -u root -c "sleep infinity &" +lando ssh -s web4 -u root -c "sleep infinity &" +lando ssh -s alpine -u root -c "ps a" | grep "sleep infinity" +lando ssh -s web2 -u root -c "ps -e -o cmd" | grep "sleep infinity" +lando ssh -s web3 -u root -c "ps -e -o cmd" | grep "sleep infinity" +lando ssh -s web4 -u root -c "ps -e -o cmd" | grep "sleep infinity" +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/ssh/compose.yml b/examples/ssh/compose.yml new file mode 100644 index 000000000..5c38b52d1 --- /dev/null +++ b/examples/ssh/compose.yml @@ -0,0 +1,6 @@ +services: + web: + image: nginx + alpine: + image: alpine + command: tail -f /dev/null diff --git a/examples/ssh/folder/.gitkeep b/examples/ssh/folder/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/examples/ssh/index.html b/examples/ssh/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/ssh/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/tooling/.lando.yml b/examples/tooling/.lando.yml index cbfc63f08..06d5d5d49 100644 --- a/examples/tooling/.lando.yml +++ b/examples/tooling/.lando.yml @@ -12,7 +12,7 @@ services: meUser: node services: image: node:16 - command: docker-entrypoint.sh sleep infinity + command: docker-entrypoint.sh tail -f /dev/null environment: PRIMARY_SERVICE: yes LANDO_WEBROOT_USER: 'node' @@ -23,8 +23,8 @@ services: api: 4 type: l337 image: node:16 - command: sleep infinity - meUser: node + command: tail -f /dev/null + user: node volumes: - "./:/app" environment: @@ -38,10 +38,18 @@ services: l337-slim: api: 4 type: l337 - command: sleep infinity + command: tail -f /dev/null image: alpine:3.18.3 - meUser: root + user: root working_dir: /tmp + lando4: + api: 4 + type: lando + command: tail -f /dev/null + image: | + FROM debian + RUN apt-get update -y && apt-get install procps -y + user: bob tooling: php: @@ -153,6 +161,8 @@ tooling: dynamic: cmd: env service: :service + env: + MILEY: cyrus options: service: default: web @@ -173,6 +183,9 @@ tooling: cmd: echo "$TAYLOR" env: TAYLOR: swift + l4env: + service: lando4 + cmd: env listfiles: service: web cmd: ls -lsa /app/* @@ -197,6 +210,17 @@ tooling: alias: - s describe: Runs in this service + backgrounder: + cmd: sleep infinity & + service: :service + user: root + options: + service: + default: alpine + alias: + - s + describe: Runs in this service + 'pwd-app [file]': cmd: pwd service: :container @@ -208,6 +232,25 @@ tooling: describe: Runs in this service bad-tool: disabled naughty-tool: false + everything: + cmd: /app/args.sh + service: node + user: root + usage: $0 everything [arg1] [arg2] MORETHINGS + examples: + - $0 everything thing + - $0 everything stuff morestuff + - $0 this is just for testing + positionals: + arg1: + describe: Uses arg1 + type: string + choices: + - thing + - stuff + arg2: + describe: Uses arg2 + type: string plugins: "@lando/core": "../.." diff --git a/examples/tooling/README.md b/examples/tooling/README.md index d2929a3f2..354686e52 100644 --- a/examples/tooling/README.md +++ b/examples/tooling/README.md @@ -1,14 +1,12 @@ -Tooling Example -=============== +# Tooling Example This example exists primarily to test the following documentation: -* [Tooling](http://docs.devwithlando.io/config/tooling.html) +* [Tooling](https://docs.devwithlando.io/config/tooling.html) -See the [Landofiles](http://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. +See the [Landofiles](https://docs.devwithlando.io/config/lando.html) in this directory for the exact magicks. -Start up tests --------------- +## Start up tests ```bash # Should start successfully @@ -16,8 +14,7 @@ lando poweroff lando start ``` -Verification commands ---------------------- +## Verification commands Run the following commands to verify things work as expected @@ -27,20 +24,22 @@ lando php -v lando php -m lando php -r "phpinfo();" -# Should run as meUser by default +# Should run as correct user lando whoami | grep www-data -lando whoami --service l337-php | grep www-data +lando whoami --service l337-php | grep root lando nodeme | grep node lando nodeme --service l337-node | grep node lando stillme | grep node | wc -l | grep 2 +lando whoami --service lando4 | grep bob # Should run as the specified user lando iamroot -lando ssh -s php -c "cat /whoami | grep root" +lando exec php -- cat /whoami | grep root lando iamroot --service l337-php -lando ssh -s l337-php -c "cat /whoami | grep root" +lando exec l337-php -- cat /whoami | grep root lando notme | grep www-data lando notme --service l337-node | grep www-data +lando iamroot --service lando4 | grep root # Should be able to run multiple commands on one service lando test @@ -77,6 +76,7 @@ cat pipe.txt | grep more # Should be able to set envvars lando envvar | grep swift +lando dynamic --service lando4 | grep cyrus # Should be able to use * lando listfiles | grep /app/README.md @@ -96,10 +96,10 @@ lando workdir | grep "/tmp" lando workdir | grep /tmp/folder || echo "$?" | grep 1 # Should use /app as the default appMount for v3 services -lando ssh --service web -c "pwd" | grep /app -lando ssh --service web2 -c "pwd" | grep /app -lando ssh --service php -c "pwd" | grep /app -lando ssh --service node -c "pwd" | grep /app +lando exec web -- pwd | grep /app +lando exec web2 -- pwd | grep /app +lando exec php -- pwd | grep /app +lando exec node -- pwd | grep /app # Should use and track appMount by default lando pwd | grep /app @@ -112,26 +112,55 @@ lando ssh -c "pwd" | grep /app cd folder && lando ssh -c "pwd" | grep /app/folder && cd .. lando pwd --service l337-node | grep /app cd folder && lando pwd --service l337-node | grep /app/folder && cd .. -lando ssh --service l337-node -c "pwd" | grep /app -cd folder && lando ssh --service l337-node -c "pwd" | grep /app/folder && cd .. +lando exec l337-node -- pwd | grep /app +cd folder && lando exec l337-node -- pwd | grep /app/folder && cd .. lando pwd --service l337-php | grep /web cd folder && lando pwd --service l337-php | grep /web/folder && cd .. -lando ssh --service l337-php -c "pwd" | grep /web -cd folder && lando ssh --service l337-php -c "pwd" | grep /web/folder && cd .. +lando exec l337-php -- pwd | grep /web +cd folder && lando exec l337-php -- pwd | grep /web/folder && cd .. lando pwd --service web | grep /app cd folder && lando pwd --service web | grep /app/folder && cd .. -lando ssh --service web -c "pwd" | grep /app -cd folder && lando ssh --service web -c "pwd" | grep /app/folder && cd .. +lando exec web -- pwd | grep /app +cd folder && lando exec web -- pwd | grep /app/folder && cd .. # Should use working_dir if no app mount for v4 services lando pwd --service l337-slim | grep /tmp # Should use first lando 3 service as default if no appserver lando ssh -c "env" | grep PRIMARY_SERVICE | grep yes + +# Should load lando4 environment +lando l4env | grep LANDO_ENVIRONMENT | grep loaded +lando dynamic --service lando4 | grep LANDO_ENVIRONMENT | grep loaded + +# Should honor --debug on v4 +lando l4env -- env | grep "LANDO_DEBUG=--debug" || echo $? || echo 1 +lando l4env --debug -- env | grep LANDO_DEBUG=--debug + +# Should background commands with line ending ampersands +lando backgrounder +lando --service node backgrounder +lando --service l337-slim backgrounder +lando --service lando4 backgrounder +lando exec --user root alpine -- ps a | grep "sleep infinity" +lando exec --user root node -- ps -e -o cmd | grep "sleep infinity" +lando exec --user root l337-slim -- ps a | grep "sleep infinity" +lando exec --user root lando4 -- ps -e -o cmd | grep "sleep infinity" + +# Should allow for positional pasthru in task definition +lando everything --help | grep arg1 | grep "Uses arg1" | grep "choices:" | grep thing | grep stuff +lando everything --help | grep arg2 | grep "Uses arg2" +lando everything thing morething | grep "thing morething" +lando everything stuff morestuff | grep "stuff morestuff" + +# Should allow for usage pasthru in task definition +lando everything --help | grep "lando everything \[arg1\] \[arg2\] MORETHINGS" + +# Should allow for example pasthru in task definition +lando everything --help | grep "lando this is just for testing" ``` -Destroy tests -------------- +## Destroy tests ```bash # Should destroy successfully diff --git a/examples/tooling/args.sh b/examples/tooling/args.sh new file mode 100755 index 000000000..b00e31206 --- /dev/null +++ b/examples/tooling/args.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "$1 $2" diff --git a/examples/tooling/compose.yml b/examples/tooling/compose.yml index 341ef8cb0..e443dd92c 100644 --- a/examples/tooling/compose.yml +++ b/examples/tooling/compose.yml @@ -13,3 +13,6 @@ services: image: php:7.1-fpm-alpine environment: SERVICE: php + alpine: + image: alpine + command: tail -f /dev/null diff --git a/examples/update/README.md b/examples/update/README.md index 8dac56839..d236a9c88 100644 --- a/examples/update/README.md +++ b/examples/update/README.md @@ -1,12 +1,10 @@ -Update Test -=========== +# Update Test This example exists primarily to test the following documentation: * [update](https://docs.lando.dev/cli/update.html) -Start up tests --------------- +## Start up tests Run the following commands to get up and running with this example. @@ -15,8 +13,7 @@ Run the following commands to get up and running with this example. lando poweroff ``` -Verification commands ---------------------- +## Verification commands Run the following commands to validate things are rolling as they should. diff --git a/examples/version/.gitignore b/examples/version/.gitignore new file mode 100644 index 000000000..55f47a3ef --- /dev/null +++ b/examples/version/.gitignore @@ -0,0 +1 @@ +.lando diff --git a/examples/version/.lando.yml b/examples/version/.lando.yml new file mode 100644 index 000000000..4f0e19970 --- /dev/null +++ b/examples/version/.lando.yml @@ -0,0 +1,34 @@ +name: lando-version +compose: + - compose.yml +services: + web2: + api: 3 + type: lando + services: + image: nginx:1.22.1 + command: /docker-entrypoint.sh nginx -g "daemon off;" + ports: + - 80 + volumes: + - ./:/usr/share/nginx/html + web3: + api: 4 + type: l337 + image: nginx + ports: + - '80/http' + volumes: + - ./:/usr/share/nginx/html + web4: + api: 4 + type: lando + image: nginxinc/nginx-unprivileged:1.26.1 + ports: + - 8080/http + app-mount: + destination: /usr/share/nginx/html + +plugins: + "@lando/core": "../.." + "@lando/base-test-plugin-2": ./test-plugin-2 diff --git a/examples/version/README.md b/examples/version/README.md new file mode 100644 index 000000000..1f570ac2f --- /dev/null +++ b/examples/version/README.md @@ -0,0 +1,58 @@ +# Version Example + +This example exists primarily to test the following documentation: + +* [`lando version`](https://docs.lando.dev/cli/version.html) + +See the [Landofiles](https://docs.lando.dev/config/lando.html) in this directory for the exact magicks. + +## Start up tests + +```bash +# Should start successfully +lando poweroff +lando start +``` + +## Verification commands + +Run the following commands to verify things work as expected + +```bash +# Should run version without error in app context +lando version + +# Should run version without error in global context +cd .. +lando version + +# Should return the lando core version by default +lando version | grep "$(lando version --component core)" + +# Should print all version information with --all +lando version --all +lando version --all | grep @lando/core | grep "$(lando version --component @lando/core)" +lando version --all | grep @lando/healthcheck | grep "$(lando version --component core)" +lando version --all | grep @lando/networking | grep "$(lando version --component core)" +lando version --all | grep @lando/proxy | grep "$(lando version --component core)" +lando version --all | grep @lando/scanner | grep "$(lando version --component core)" +lando version --all | grep @lando/sharing | grep "$(lando version --component core)" +lando version --all | grep @lando/test | grep "$(lando version --component core)" +lando version --all | grep @lando/base-test-plugin-2 | grep v1.0.2 +lando version -a | grep @lando/core | grep v3 + +# Should print specific component information +lando version --component healthcheck | grep "$(lando version --component @lando/core)" +lando version -c healthcheck | grep "$(lando version --component core)" + +# Should print full version information +lando version --full | grep @lando/core | grep "$(lando version -c core)" | grep "$(lando config --path os.platform | tr -d '\n' | sed -e "s/^'//" -e "s/'$//")" | grep "$(lando config --path os.arch | tr -d '\n' | sed -e "s/^'//" -e "s/'$//")" | grep node-v18 | grep cli | grep "$(lando version -c cli)" +``` + +## Destroy tests + +```bash +# Should destroy successfully +lando destroy -y +lando poweroff +``` diff --git a/examples/version/compose.yml b/examples/version/compose.yml new file mode 100644 index 000000000..2ca784f72 --- /dev/null +++ b/examples/version/compose.yml @@ -0,0 +1,3 @@ +services: + web: + image: nginx diff --git a/examples/version/index.html b/examples/version/index.html new file mode 100644 index 000000000..eb938d355 --- /dev/null +++ b/examples/version/index.html @@ -0,0 +1 @@ +BEASTINBLACK diff --git a/examples/version/test-plugin-2/app.js b/examples/version/test-plugin-2/app.js new file mode 100644 index 000000000..60fcdabb0 --- /dev/null +++ b/examples/version/test-plugin-2/app.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = injected => ({'app-plugin-test-app-2': true}); diff --git a/examples/version/test-plugin-2/index.js b/examples/version/test-plugin-2/index.js new file mode 100644 index 000000000..08a206586 --- /dev/null +++ b/examples/version/test-plugin-2/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = injected => ({'app-plugin-test-2': true}); diff --git a/examples/version/test-plugin-2/package.json b/examples/version/test-plugin-2/package.json new file mode 100644 index 000000000..7d62059ca --- /dev/null +++ b/examples/version/test-plugin-2/package.json @@ -0,0 +1,5 @@ +{ + "name": "@lando/base-test-plugin-2", + "version": "1.0.2", + "description": "just another test plugin" +} diff --git a/examples/version/test-plugin-2/plugin.yaml b/examples/version/test-plugin-2/plugin.yaml new file mode 100644 index 000000000..0ea414d75 --- /dev/null +++ b/examples/version/test-plugin-2/plugin.yaml @@ -0,0 +1,5 @@ +name: "@lando/base-test-plugin-2" +registry: + legacy: + tasks: + stuff2: "./tasks/stuff" diff --git a/examples/version/test-plugin-2/tasks/stuff.js b/examples/version/test-plugin-2/tasks/stuff.js new file mode 100644 index 000000000..253150625 --- /dev/null +++ b/examples/version/test-plugin-2/tasks/stuff.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = lando => ({ + command: 'stuff2', + level: 'tasks', + describe: 'Tests another app loaded plugin', + run: () => { + console.log('I WORKED!'); + }, +}); diff --git a/hooks/app-add-recipes.js b/hooks/app-add-recipes.js index 4bb0aaa45..1d700ad8e 100644 --- a/hooks/app-add-recipes.js +++ b/hooks/app-add-recipes.js @@ -9,16 +9,19 @@ module.exports = async (app, lando) => { app.log.warn('%s is not a supported recipe type.', app.config.recipe); } // Log da things - app.log.verbose('building %s recipe named', app.config.recipe); + app.log.verbose('building %s recipe named %s', app.config.recipe, app.project); // Build da things // @NOTE: this also gathers app.info and build steps const Recipe = lando.factory.get(app.config.recipe); const config = require('../utils/parse-recipe-config')(app.config.recipe, app); + // Get recipe config const recipe = new Recipe(config.name, config).config; + // Cache dump our app tooling so we can use it in our entrypoint // @NOTE: we dump pre-merge so that tooling directly in the landofile is not mixed in - lando.cache.set(app.toolingCache, JSON.stringify(recipe.tooling), {persist: true}); + lando.cache.set(app.recipeCache, recipe, {persist: true}); + // Merge stuff together correctly app.config.proxy = _.merge({}, recipe.proxy, _.get(app, 'config.proxy', {})); app.config = lando.utils.merge({services: recipe.services, tooling: recipe.tooling}, app.config); diff --git a/hooks/app-add-tooling.js b/hooks/app-add-tooling.js index 8d1a1e425..1efb80b1c 100644 --- a/hooks/app-add-tooling.js +++ b/hooks/app-add-tooling.js @@ -5,6 +5,7 @@ const _ = require('lodash'); module.exports = async (app, lando) => { if (!_.isEmpty(_.get(app, 'config.tooling', {}))) { app.log.verbose('additional tooling detected'); + // Add the tasks after we init the app _.forEach(require('../utils/get-tooling-tasks')(app.config.tooling, app), task => { app.log.debug('adding app cli task %s', task.name); diff --git a/hooks/app-add-v4-services.js b/hooks/app-add-v4-services.js index 8aed8e86f..a7de5eec7 100644 --- a/hooks/app-add-v4-services.js +++ b/hooks/app-add-v4-services.js @@ -13,7 +13,7 @@ module.exports = async (app, lando) => { // if no service is set as the primary one lets set the first one as primary if (_.find(app.v4.parsedConfig, service => service.primary === true) === undefined) { - if (_.has(app, 'v4.parsedConfig[0.name')) app.v4.parsedConfig[0].primary = true; + if (_.has(app, 'v4.parsedConfig[0].name')) app.v4.parsedConfig[0].primary = true; } // note the primary service in a more convenient place so we dont have to search for it all the time @@ -62,17 +62,7 @@ module.exports = async (app, lando) => { app.info.push(service.info); }); - // emit an event so other plugins can augment the servies with additonal things before we get their data - return app.events.emit('pre-services-generate', app.v4.services).then(services => { - // handle top level volumes and networks here - if (!_.isEmpty(app.config.volumes)) app.v4.addVolumes(app.config.volumes); - if (!_.isEmpty(app.config.networks)) app.v4.addNetworks(app.config.networks); - - // then generate the orchestrator files for each service - _.forEach(app.v4.services, service => { - app.add(service.generateOrchestorFiles()); - // Log da things - app.log.debug('generated v4 %s service %s', service.type, service.name); - }); - }); + // handle top level volumes and networks here + if (!_.isEmpty(app.config.volumes)) app.v4.addVolumes(app.config.volumes); + if (!_.isEmpty(app.config.networks)) app.v4.addNetworks(app.config.networks); }; diff --git a/hooks/app-check-v4-service-running.js b/hooks/app-check-v4-service-running.js index 4908ff16c..655e3b3ed 100644 --- a/hooks/app-check-v4-service-running.js +++ b/hooks/app-check-v4-service-running.js @@ -11,11 +11,17 @@ module.exports = async (app, lando) => { const containers = await lando.engine.list({project: app.project, all: true}) .filter(container => buildV4Services.includes(container.service)) + .filter(container => { + const info = app.info.find(service => service.service === container.service); + return info?.state?.IMAGE === 'BUILT'; + }) .filter(container => !container.running); if (containers.length > 0) { for (const container of containers) { const err = new Error(`Service ${container.service} not running: ${container.status}`); + const info = app.info.find(service => service.service === container.service); + info.error = err.message; app.addMessage(require('../messages/service-not-running-error')(container.service), err); } } diff --git a/hooks/app-find-localhosts.js b/hooks/app-find-localhosts.js index 7f4f5321b..7a4077a4a 100644 --- a/hooks/app-find-localhosts.js +++ b/hooks/app-find-localhosts.js @@ -2,9 +2,19 @@ const _ = require('lodash'); -// Helper to get http ports -const getHttpPorts = data => _.get(data, 'Config.Labels["io.lando.http-ports"]', '80,443').split(','); -const getHttpsPorts = data => _.get(data, 'Config.Labels["io.lando.https-ports"]', '443').split(','); +// Helper to get http/https ports +const getHttpPorts = data => { + return _.uniq([ + ..._.get(data, 'Config.Labels["io.lando.http-ports"]', '80,443').split(','), + ..._.get(data, 'Config.Labels["dev.lando.http-ports"]', '').split(','), + ]); +}; +const getHttpsPorts = data => { + return _.uniq([ + ..._.get(data, 'Config.Labels["io.lando.https-ports"]', '443').split(','), + ..._.get(data, 'Config.Labels["dev.lando.https-ports"]', '').split(','), + ]); +}; module.exports = async (app, lando) => { app.log.verbose('attempting to find open services...'); @@ -17,7 +27,8 @@ module.exports = async (app, lando) => { .map(container => app.engine.scan(container)) // Scan all the http ports .map(data => require('../utils/get-exposed-localhosts')( - data, getHttpPorts(data), + data, + _.uniq([...getHttpPorts(data), ...getHttpsPorts(data)]), getHttpsPorts(data), lando.config.bindAddress, )) diff --git a/hooks/app-generate-v3-certs.js b/hooks/app-generate-v3-certs.js new file mode 100644 index 000000000..dcf503770 --- /dev/null +++ b/hooks/app-generate-v3-certs.js @@ -0,0 +1,29 @@ +'use strict'; + +const _ = require('lodash'); + +const parseUrls = (urls = []) => { + return urls.map(url => { + try { + url = new URL(url); + return url.hostname; + } catch { + return undefined; + } + }) + .filter(hostname => hostname !== undefined); +}; + +module.exports = async (app, lando) => { + const certServices = app.info + .filter(service => service.hasCerts === true) + .map(service => ({ + name: `${service.service}.${app.project}`, + domains: _.uniq([...parseUrls(service.urls), ...service.hostnames, service.service]), + })); + + // and then run them in parallel + await Promise.all(certServices.map(async ({name, domains}) => { + await lando.generateCert(name, {domains}); + })); +}; diff --git a/hooks/app-override-ssh-defaults.js b/hooks/app-override-ssh-defaults.js deleted file mode 100644 index 7f6ebd16f..000000000 --- a/hooks/app-override-ssh-defaults.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -module.exports = async (app, lando) => { - if (_.find(lando.tasks, {command: 'ssh'})) { - const sshTask = _.cloneDeep(_.find(lando.tasks, {command: 'ssh'})); - _.set(sshTask, 'options.service.default', app._defaultService); - app._coreToolingOverrides.push(sshTask); - } -}; diff --git a/hooks/app-override-tooling-defaults.js b/hooks/app-override-tooling-defaults.js new file mode 100644 index 000000000..e2847b6e6 --- /dev/null +++ b/hooks/app-override-tooling-defaults.js @@ -0,0 +1,10 @@ +'use strict'; + +const merge = require('lodash/merge'); + +module.exports = async (app, lando) => { + for (const task of lando.tasks.filter(task => task.override)) { + app.log.debug('overriding task %s with dynamic app options', task.command); + app._coreToolingOverrides = merge({}, app._coreToolingOverrides, {[task.command]: require(task.file)(lando, app)}); + } +}; diff --git a/hooks/app-purge-compose-cache.js b/hooks/app-purge-compose-cache.js index 48fd42c0b..779848d4a 100644 --- a/hooks/app-purge-compose-cache.js +++ b/hooks/app-purge-compose-cache.js @@ -1,3 +1,14 @@ 'use strict'; -module.exports = async (app, lando) => lando.cache.remove(app.composeCache); +module.exports = async (app, lando) => { + // reset v4 service state + for (const service of app?.v4?.services ?? []) { + service.info = undefined; + } + + // reset tooling overrides + app._coreToolingOverrides = {}; + + // wipe the compose cache + lando.cache.remove(app.composeCache); +}; diff --git a/hooks/app-purge-recipe-cache.js b/hooks/app-purge-recipe-cache.js new file mode 100644 index 000000000..aee9f90c4 --- /dev/null +++ b/hooks/app-purge-recipe-cache.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = async (app, lando) => { + app.log.verbose('removing recipe cache...'); + lando.cache.remove(app.recipeCache); +}; diff --git a/hooks/app-purge-tooling-cache.js b/hooks/app-purge-tooling-cache.js deleted file mode 100644 index d72aee045..000000000 --- a/hooks/app-purge-tooling-cache.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports = async (app, lando) => { - app.log.verbose('removing tooling cache...'); - lando.cache.remove(app.toolingCache); -}; diff --git a/hooks/app-purge-v4-build-locks.js b/hooks/app-purge-v4-build-locks.js index aac061c8c..0bc5a068d 100644 --- a/hooks/app-purge-v4-build-locks.js +++ b/hooks/app-purge-v4-build-locks.js @@ -1,7 +1,13 @@ 'use strict'; +const _ = require('lodash'); + module.exports = async (app, lando) => { - lando.cache.remove(app.v4.preLockfile); - lando.cache.remove(app.v4.postLockfile); - app.log.debug('removed v4 build locks'); + return lando.engine.list({project: app.project, all: true}).then(data => { + if (_.isEmpty(data)) { + lando.cache.remove(app.v4.preLockfile); + lando.cache.remove(app.v4.postLockfile); + app.log.debug('removed v4 build locks'); + } + }); }; diff --git a/hooks/app-run-v4-build-app.js b/hooks/app-run-v4-build-app.js new file mode 100644 index 000000000..76f51b48e --- /dev/null +++ b/hooks/app-run-v4-build-app.js @@ -0,0 +1,30 @@ +'use strict'; + +const _ = require('lodash'); + +module.exports = async app => { + // get buildable services + const buildV4Services = _(app.v4.parsedConfig) + .filter(service => _.includes(_.get(app, 'opts.services', app.services), service.name)) + .map(service => service.name) + .value(); + + // filter out any services that dont need to be built + const services = _(app.v4.services) + .filter(service => _.includes(buildV4Services, service.id)) + .filter(service => typeof service.buildApp === 'function') + .filter(service => service?.info?.state?.IMAGE === 'BUILT') + .filter(service => service?.info?.state?.APP !== 'BUILT') + .value(); + + // and then run them in parallel + await Promise.all(services.map(async service => { + try { + await service.buildApp(); + } catch (error) { + // @TODO: improve this? + app.log.debug('app build error %o %o', error.message, error); + app.addMessage(require('../messages/app-build-v4-error')(error), error, true); + } + })); +}; diff --git a/hooks/app-run-v4-build-image.js b/hooks/app-run-v4-build-image.js new file mode 100644 index 000000000..e30f70da7 --- /dev/null +++ b/hooks/app-run-v4-build-image.js @@ -0,0 +1,49 @@ +'use strict'; + +const _ = require('lodash'); + +module.exports = async (app, lando) => { + // get buildable services + const buildV4Services = _(app.v4.parsedConfig) + .filter(service => _.includes(_.get(app, 'opts.services', app.services), service.name)) + .map(service => service.name) + .value(); + + // filter out any services that dont need to be built + const services = _(app.v4.services) + .filter(service => _.includes(buildV4Services, service.id)) + .filter(service => typeof service.buildImage === 'function') + .value(); + + app.log.debug('going to build v4 images', services.map(service => service.id)); + + // now build an array of promises with our services + const tasks = services.map(service => { + const container = [app.project, service.id, '1'].join(lando.config.orchestratorSeparator); + return { + title: `Image for ${container}`, + task: async (ctx, task) => { + try { + await service.buildImage(); + } catch (error) { + ctx.errors.push(error); + app.addMessage(require('../messages/image-build-v4-error')(error), error, true); + throw error; + } + }, + }; + }); + + // and then run them in parallel + await app.runTasks(tasks, { + renderer: 'dc2', + rendererOptions: { + header: 'Building', + states: { + COMPLETED: 'Built', + STARTED: 'Building', + FAILED: 'FAILED', + }, + }, + }); +}; diff --git a/hooks/app-run-v4-build-steps.js b/hooks/app-run-v4-build-steps.js index b597c2e52..5fd3a0088 100644 --- a/hooks/app-run-v4-build-steps.js +++ b/hooks/app-run-v4-build-steps.js @@ -1,144 +1,11 @@ 'use strict'; -const _ = require('lodash'); -const os = require('os'); -const path = require('path'); -const {nanoid} = require('nanoid'); - module.exports = async (app, lando) => { - // get buildable services - const buildV4Services = _(app.v4.parsedConfig) - .filter(service => _.includes(_.get(app, 'opts.services', app.services), service.name)) - .map(service => service.name) - .value(); - // @TODO: build locks and hash for v4? - app.events.on('pre-start', () => { - return lando.engine.list({project: app.project, all: true}).then(data => { - if (_.isEmpty(data)) { - lando.cache.remove(app.v4.preLockfile); - lando.cache.remove(app.v4.postLockfile); - app.log.debug('removed v4 image build locks'); - } - }); - }); - - // run v4 build steps if applicable - app.events.on('pre-start', 100, async () => { - if (!lando.cache.get(app.v4.preLockfile)) { - // filter out any services that dont need to be built - const services = _(app.v4.services) - .filter(service => _.includes(buildV4Services, service.id)) - .value(); - - app.log.debug('going to build v4 images', services.map(service => service.id)); - - // now build an array of promises with our services - const tasks = services.map(service => { - const container = [app.project, service.id, '1'].join(lando.config.orchestratorSeparator); - return { - title: `Image for ${container}`, - task: async (ctx, task) => { - try { - await service.buildImage(); - } catch (error) { - ctx.errors.push(error); - app.addMessage(require('../messages/image-build-v4-error')(error), error, true); - throw error; - } - }, - }; - }); - - // and then run them in parallel - const {errors} = await app.runTasks(tasks, { - renderer: 'dc2', - rendererOptions: { - header: 'Building', - states: { - COMPLETED: 'Built', - STARTED: 'Building', - FAILED: 'FAILED', - }, - }, - }); - - // write build lock if we have no failures - if (_.isEmpty(errors)) lando.cache.set(app.v4.preLockfile, app.configHash, {persist: true}); - - // merge rebuild success results into app.info for downstream usage for api 4 services - _.forEach(services, service => { - service.error = errors.find(error => error?.context?.id === service.id); - const info = _.find(app.info, {service: service.id, api: 4}); - if (info) { - Object.assign(info, { - image: service.info.image, - state: service.state, - tag: service.tag, - error: service.error, - }); - } - }); - } - - // at this point we should have the tags of successfull images and can iterate and app.add as needed - _.forEach(app.info, service => { - if (service.api === 4) { - const data = {image: service.tag}; - - // if image build failed and has an error lets change the data - if (service?.state?.IMAGE === 'BUILD FAILURE' && service.error) { - const dir = service?.error?.context?.context ?? os.tmpdir(); - service.error.logfile = path.join(dir, `error-${nanoid()}.log`); - data.image = 'busybox'; - data.user = 'root'; - data.command = require('../utils/get-v4-image-build-error-command')(service.error); - data.volumes = [`${service.error.logfile}:/tmp/error.log`]; - } - - app.add({ - id: service.service, - info: {}, - data: [{services: {[service.service]: data}}], - }); - } - }); - - // and reset app.compose - app.compose = require('../utils/dump-compose-data')(app.composeData, app._dir); - - // and reset the compose cache as well - app.v4.updateComposeCache(); - }); - - // run app build steps - app.events.on('pre-start', 110, async () => { - // get buildable services - const buildV4Services = _(app.v4.parsedConfig) - .filter(service => _.includes(_.get(app, 'opts.services', app.services), service.name)) - .map(service => service.name) - .value(); - - // filter out any services that dont need to be built - const services = _(app.v4.services) - .filter(service => _.includes(buildV4Services, service.id)) - .filter(service => typeof service.buildApp === 'function') - .filter(service => service?.info?.state?.IMAGE === 'BUILT') - .filter(service => service?.info?.state?.APP !== 'BUILT') - .value(); - - // and then run them in parallel - await Promise.all(services.map(async service => { - try { - await service.buildApp(); - } catch (error) { - // @TODO: improve this? - app.log.debug('app build error %o %o', error.message, error); - app.addMessage(require('../messages/app-build-v4-error')(error), error, true); - } - })); - - // and reset the compose cache as well - app.v4.updateComposeCache(); - }); + app.events.on('pre-start', () => async () => await require('./app-purge-v4-build-locks')(app, lando)); + // IMAGE BUILD + app.events.on('pre-start', 100, async () => await require('./app-run-v4-build-image')(app, lando)); + // APP BUILD + app.events.on('pre-start', 110, async () => await require('./app-run-v4-build-app')(app, lando)); + // @TODO: POST BUILD/EXEC BUILD }; diff --git a/hooks/lando-copy-ca.js b/hooks/lando-copy-ca.js deleted file mode 100644 index 1d55cb489..000000000 --- a/hooks/lando-copy-ca.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); - -module.exports = async (lando, {caCert, caDir, caDomain}) => { - const caNormalizedCert = path.join(caDir, `${caDomain}.crt`); - if (fs.existsSync(caCert) && !fs.existsSync(caNormalizedCert)) { - // @NOTE: we need to use pre node 8.x-isms because pld roles with node 7.9 currently - fs.writeFileSync(caNormalizedCert, fs.readFileSync(caCert)); - } -}; diff --git a/hooks/lando-copy-v3-scripts.js b/hooks/lando-copy-v3-scripts.js new file mode 100644 index 000000000..e378e352c --- /dev/null +++ b/hooks/lando-copy-v3-scripts.js @@ -0,0 +1,15 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +module.exports = async lando => { + return lando.Promise.map(lando.config.plugins, plugin => { + if (fs.existsSync(plugin.scripts)) { + const confDir = path.join(lando.config.userConfRoot, 'scripts'); + const dest = require('../utils/move-config')(plugin.scripts, confDir); + require('../utils/make-executable')(fs.readdirSync(dest), dest); + lando.log.debug('automoved scripts from %s to %s and set to mode 755', plugin.scripts, confDir); + } + }); +}; diff --git a/hooks/lando-setup-build-engine-darwin.js b/hooks/lando-setup-build-engine-darwin.js index c0234cda9..eac24e74c 100644 --- a/hooks/lando-setup-build-engine-darwin.js +++ b/hooks/lando-setup-build-engine-darwin.js @@ -7,6 +7,8 @@ const semver = require('semver'); const {color} = require('listr2'); const buildIds = { + '4.32.0': '157355', + '4.31.0': '153195', '4.30.0': '149282', '4.29.0': '145265', '4.28.0': '139021', @@ -24,6 +26,8 @@ const buildIds = { * Helper to get build engine id */ const getId = version => { + // if false return false + // if version is an integer then assume its already the id if (semver.valid(version) === null && Number.isInteger(parseInt(version))) return version; // otherwise return that corresponding build-id @@ -67,9 +71,14 @@ const downloadDockerDesktop = (url, {debug, task, ctx}) => new Promise((resolve, module.exports = async (lando, options) => { const debug = require('../utils/debug-shim')(lando.log); + // if build engine is set to false allow it to be skipped + // @NOTE: this is mostly for internal stuff + if (options.buildEngine === false) return; + // get stuff from config/opts const build = getId(options.buildEngine); const version = getVersion(options.buildEngine); + // cosmetics const install = version ? `v${version}` : `build ${build}`; @@ -117,7 +126,7 @@ module.exports = async (lando, options) => { ctx.download = await downloadDockerDesktop(getEngineDownloadUrl(build), {ctx, debug, task}); // prompt for password if interactive - if (lando.config.isInteractive) { + if (ctx.password === undefined && lando.config.isInteractive) { ctx.password = await task.prompt({ type: 'password', name: 'password', @@ -139,7 +148,7 @@ module.exports = async (lando, options) => { // add optional args if (options.buildEngineAcceptLicense) command.push('--accept-license'); - if (options.debug || options.verbose > 0) command.push('--debug'); + if (options.debug || options.verbose > 0 || lando.debuggy) command.push('--debug'); // run const result = await require('../utils/run-elevated')(command, {debug, password: ctx.password}); diff --git a/hooks/lando-setup-build-engine-linux.js b/hooks/lando-setup-build-engine-linux.js index 0c13b8fa1..f80198471 100644 --- a/hooks/lando-setup-build-engine-linux.js +++ b/hooks/lando-setup-build-engine-linux.js @@ -25,9 +25,11 @@ const downloadDockerEngine = (url = 'https://get.docker.com', {debug, task, ctx} module.exports = async (lando, options) => { const debug = require('../utils/debug-shim')(lando.log); + // if build engine is set to false allow it to be skipped + // @NOTE: this is mostly for internal stuff + if (options.buildEngine === false) return; + const version = options.buildEngine; - // set out of scope password so we can reuse it - let password = undefined; // darwin install task options.tasks.push({ @@ -61,11 +63,11 @@ module.exports = async (lando, options) => { ctx.download = await downloadDockerEngine('https://get.docker.com', {ctx, debug, task}); // prompt for password if interactive and we dont have it - if (password === undefined && lando.config.isInteractive) { - password = await task.prompt({ + if (ctx.password === undefined && lando.config.isInteractive) { + ctx.password = await task.prompt({ type: 'password', name: 'password', - message: `Enter computer password for ${lando.config.usernam} to add them to docker group`, + message: `Enter computer password for ${lando.config.username} to add them to docker group`, validate: async (input, state) => { const options = {debug, ignoreReturnCode: true, password: input}; const response = await require('../utils/run-elevated')(['echo', 'hello there'], options); @@ -81,7 +83,7 @@ module.exports = async (lando, options) => { const command = [script, '--installer', ctx.download.dest, '--version', version]; // add optional args - if (options.debug || options.verbose > 0) command.push('--debug'); + if (options.debug || options.verbose > 0 || lando.debuggy) command.push('--debug'); // run const result = await require('../utils/run-elevated')(command, {debug, password: ctx.password}); @@ -112,8 +114,8 @@ module.exports = async (lando, options) => { if (require('../utils/is-group-member')('docker')) return {code: 0}; // prompt for password if interactive and we dont have it - if (password === undefined && lando.config.isInteractive) { - password = await task.prompt({ + if (ctx.password === undefined && lando.config.isInteractive) { + ctx.password = await task.prompt({ type: 'password', name: 'password', message: `Enter computer password for ${lando.config.usernam} to install build engine`, @@ -128,7 +130,7 @@ module.exports = async (lando, options) => { try { const command = ['usermod', '-aG', 'docker', lando.config.username]; - const response = await require('../utils/run-elevated')(command, {debug}); + const response = await require('../utils/run-elevated')(command, {debug, password: ctx.password}); task.title = `Added ${lando.config.username} to docker group`; return response; } catch (error) { diff --git a/hooks/lando-setup-build-engine-win32.js b/hooks/lando-setup-build-engine-win32.js index 861eaa81e..f771e028e 100644 --- a/hooks/lando-setup-build-engine-win32.js +++ b/hooks/lando-setup-build-engine-win32.js @@ -3,11 +3,13 @@ const os = require('os'); const path = require('path'); const semver = require('semver'); - const {color} = require('listr2'); const {nanoid} = require('nanoid'); const buildIds = { + '4.32.0': '157355', + '4.31.1': '153621', + '4.31.0': '153195', '4.30.0': '149282', '4.29.0': '145265', '4.28.0': '139021', @@ -75,6 +77,10 @@ module.exports = async (lando, options) => { const debug = require('../utils/debug-shim')(lando.log); debug.enabled = lando.debuggy; + // if build engine is set to false allow it to be skipped + // @NOTE: this is mostly for internal stuff + if (options.buildEngine === false) return; + // get stuff from config/opts const build = getId(options.buildEngine); const version = getVersion(options.buildEngine); @@ -127,9 +133,9 @@ module.exports = async (lando, options) => { // script const script = [path.join(lando.config.userConfRoot, 'scripts', 'install-docker-desktop.ps1')]; // args - const args = ['-installer', ctx.download.dest]; - if (options.buildEngineAcceptLicense) args.push('-acceptlicense'); - if ((options.debug || options.verbose > 0) && lando.config.isInteractive) args.push('-debug'); + const args = ['-Installer', ctx.download.dest]; + if (options.buildEngineAcceptLicense) args.push('-AcceptLicense'); + if ((options.debug || options.verbose > 0 || lando.debuggy) && lando.config.isInteractive) args.push('-Debug'); // run install command task.title = `Installing build engine ${color.dim('(this may take a minute)')}`; diff --git a/hooks/lando-setup-ca.js b/hooks/lando-setup-ca.js deleted file mode 100644 index fb3002b4d..000000000 --- a/hooks/lando-setup-ca.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const fs = require('fs'); - -/* - * Helper to get ca run object - */ -const getCaRunner = (project, files, separator = '_') => ({ - id: [project, 'ca', '1'].join(separator), - compose: files, - project: project, - cmd: '/setup-ca.sh', - opts: { - mode: 'attach', - services: ['ca'], - autoRemove: true, - }, -}); - -module.exports = async (lando, data, {caCert, caDir, caProject}) => { - if (!fs.existsSync(caCert) && data.project !== caProject) { - const LandoCa = lando.factory.get('_casetup'); - const env = _.cloneDeep(lando.config.appEnv); - const labels = _.cloneDeep(lando.config.appLabels); - const caData = new LandoCa(lando.config.userConfRoot, env, labels); - const caFiles = require('../utils/dump-compose-data')(caData, caDir); - lando.log.debug('setting up Lando Local CA at %s', caCert); - return lando.engine.run(getCaRunner(caProject, caFiles, lando.config.orchestratorSeparator)); - } -}; diff --git a/hooks/lando-setup-create-ca.js b/hooks/lando-setup-create-ca.js new file mode 100644 index 000000000..083104a19 --- /dev/null +++ b/hooks/lando-setup-create-ca.js @@ -0,0 +1,67 @@ +'use strict'; + +const fs = require('fs'); + +module.exports = async (lando, options) => { + const debug = require('../utils/debug-shim')(lando.log); + + const {caCert, caKey} = lando.config; + + // create CA + options.tasks.push({ + title: 'Creating Lando Development CA', + id: 'create-ca', + description: '@lando/ca', + comments: { + 'NOT INSTALLED': 'Will create Lando Development Certificate Authority (CA)', + }, + hasRun: async () => { + const forge = require('node-forge'); + const read = require('../utils/read-file'); + + if ([caCert, caKey].some(file => !fs.existsSync(file))) return false; + + // check if the ca is valid and has a matching key + try { + const ca = forge.pki.certificateFromPem(read(caCert)); + const key = forge.pki.privateKeyFromPem(read(caKey)); + + // verify the signature using the public key in the CA certificate + const md = forge.md.sha256.create(); + md.update('taanab', 'utf8'); + const signature = key.sign(md); + + // if they dont match then throw + if (!ca.publicKey.verify(md.digest().bytes(), signature)) { + throw new Error('CA and its private key do not match'); + } + + // @TODO: throw error if CA has expired? + + return true; + } catch (error) { + debug('Something is wrong with the CA %o %o', error.message, error.stack); + [caCert, caKey].some(file => !fs.unlinkSync(file)); + return false; + } + }, + task: async (ctx, task) => { + const write = require('../utils/write-file'); + const {createCA} = require('mkcert'); + + // generate the CA and KEY + const {cert, key} = await createCA({ + organization: 'Lando Development CA', + countryCode: 'US', + state: 'California', + locality: 'Oakland', + validity: 8675, + }); + + // write the cert and key + write(caCert, cert); + write(caKey, key); + task.title = 'Created Lando Development CA'; + }, + }); +}; diff --git a/hooks/lando-setup-install-ca-darwin.js b/hooks/lando-setup-install-ca-darwin.js new file mode 100644 index 000000000..d9abb4980 --- /dev/null +++ b/hooks/lando-setup-install-ca-darwin.js @@ -0,0 +1,59 @@ +'use strict'; + +const path = require('path'); + +module.exports = async (lando, options) => { + const debug = require('../utils/debug-shim')(lando.log); + + const {caCert} = lando.config; + + // skip the installation of the CA if set + if (options.skipInstallCA) return; + + // install ca + options.tasks.push({ + title: `Installing Lando Development CA`, + id: 'install-ca', + dependsOn: ['create-ca'], + description: '@lando/install-ca', + comments: { + 'NOT INSTALLED': 'Will install Lando Development Certificate Authority (CA) to system store', + }, + hasRun: async () => { + try { + const fingerprint = require('../utils/get-fingerprint')(caCert); + debug('computed sha1 fingerprint %o for ca %o', fingerprint, caCert); + return require('../utils/get-system-cas')().includes(fingerprint); + } catch (error) { + debug('error determining fingerprint of %o: %o %o', caCert, error.message, error.stack); + return false; + } + }, + canRun: async () => { + return true; + }, + task: async (ctx, task) => { + try { + task.title = 'Installing Lando Development Certificate Authority (CA)'; + + // assemble + const fingerprint = require('../utils/get-fingerprint')(caCert); + const script = path.join(lando.config.userConfRoot, 'scripts', 'install-system-ca-macos.sh'); + const args = ['--ca', caCert, '--fingerprint', fingerprint]; + + // add optional args + if (options.debug || options.verbose > 0 || lando.debuggy) args.push('--debug'); + if (!lando.config.isInteractive) args.push('--non-interactive'); + + // run + const result = await require('../utils/run-command')(script, args, {debug}); + + // finish up + task.title = 'Installed Lando Development Certificate Authority (CA)'; + return result; + } catch (error) { + throw error; + } + }, + }); +}; diff --git a/hooks/lando-setup-install-ca-linux.js b/hooks/lando-setup-install-ca-linux.js new file mode 100644 index 000000000..4378b0f75 --- /dev/null +++ b/hooks/lando-setup-install-ca-linux.js @@ -0,0 +1,83 @@ +'use strict'; + +const path = require('path'); + +module.exports = async (lando, options) => { + const debug = require('../utils/debug-shim')(lando.log); + + const {caCert} = lando.config; + + // skip the installation of the CA if set + if (options.skipInstallCA) return; + + // install ca + options.tasks.push({ + title: `Installing Lando Development CA`, + id: 'install-ca', + dependsOn: ['create-ca'], + description: '@lando/install-ca', + comments: { + 'NOT INSTALLED': 'Will install Lando Development Certificate Authority (CA) to system store', + }, + hasRun: async () => { + try { + const fingerprint = require('../utils/get-fingerprint')(caCert); + debug('computed sha1 fingerprint %o for ca %o', fingerprint, caCert); + + return require('../utils/get-system-cas')().includes(fingerprint); + } catch (error) { + debug('error determining fingerprint of %o: %o %o', caCert, error.message, error.stack); + return false; + } + }, + canRun: async () => { + // throw error if not online + if (!await require('is-online')()) throw new Error('Cannot detect connection to internet!'); + // throw if user is not an admin + if (!await require('../utils/is-admin-user')()) { + throw new Error([ + `User "${lando.config.username}" does not have permission to trust the CA!`, + 'Contact your system admin for permission and then rerun setup.', + ].join(os.EOL)); + } + + return true; + }, + task: async (ctx, task) => { + try { + task.title = 'Installing Lando Development Certificate Authority (CA)'; + + // prompt for password if interactive and we dont have it + if (ctx.password === undefined && lando.config.isInteractive) { + ctx.password = await task.prompt({ + type: 'password', + name: 'password', + message: `Enter computer password for ${lando.config.username} to install the CA`, + validate: async (input, state) => { + const options = {debug, ignoreReturnCode: true, password: input}; + const response = await require('../utils/run-elevated')(['echo', 'hello there'], options); + if (response.code !== 0) return response.stderr; + return true; + }, + }); + } + + // assemble + const script = path.join(lando.config.userConfRoot, 'scripts', 'install-system-ca-linux.sh'); + const command = [script, '--ca', caCert]; + + // // add optional args + if (options.debug || options.verbose > 0 || lando.debuggy) command.push('--debug'); + + // run + const result = await require('../utils/run-elevated')(command, {debug, password: ctx.password}); + + // finish up + task.title = 'Installed Lando Development Certificate Authority (CA)'; + return result; + } catch (error) { + throw error; + } + }, + }); +}; diff --git a/hooks/lando-setup-install-ca-win32.js b/hooks/lando-setup-install-ca-win32.js new file mode 100644 index 000000000..169286c6d --- /dev/null +++ b/hooks/lando-setup-install-ca-win32.js @@ -0,0 +1,58 @@ +'use strict'; + +const path = require('path'); + +module.exports = async (lando, options) => { + const debug = require('../utils/debug-shim')(lando.log); + + const {caCert} = lando.config; + + // skip the installation of the CA if set + if (options.skipInstallCA) return; + + // install ca + options.tasks.push({ + title: `Installing Lando Development CA`, + id: 'install-ca', + dependsOn: ['create-ca'], + description: '@lando/install-ca', + comments: { + 'NOT INSTALLED': 'Will install Lando Development Certificate Authority (CA) to system store', + }, + hasRun: async () => { + try { + const fingerprint = require('../utils/get-fingerprint')(caCert); + debug('computed sha1 fingerprint %o for ca %o', fingerprint, caCert); + return require('../utils/get-system-cas')().includes(fingerprint); + } catch (error) { + debug('error determining fingerprint of %o: %o %o', caCert, error.message, error.stack); + return false; + } + }, + canRun: async () => { + return true; + }, + task: async (ctx, task) => { + try { + task.title = 'Installing Lando Development Certificate Authority (CA)'; + + // assemble + const script = path.join(lando.config.userConfRoot, 'scripts', 'install-system-ca-win32.ps1'); + const args = ['-CA', caCert]; + + // add optional args + if (options.debug || options.verbose > 0 || lando.debuggy) args.push('-Debug'); + if (!lando.config.isInteractive) args.push('-NonInteractive'); + + // run + const result = await require('../utils/run-powershell-script')(script, args, {debug}); + + // finish up + task.title = 'Installed Lando Development Certificate Authority (CA)'; + return result; + } catch (error) { + throw error; + } + }, + }); +}; diff --git a/hooks/lando-setup-orchestrator.js b/hooks/lando-setup-orchestrator.js index 406b8f30a..080c28fb9 100644 --- a/hooks/lando-setup-orchestrator.js +++ b/hooks/lando-setup-orchestrator.js @@ -6,7 +6,7 @@ const path = require('path'); /* * Helper to get docker compose v2 download url */ -const getComposeDownloadUrl = (version = '2.21.0') => { +const getComposeDownloadUrl = (version = '2.27.1') => { const mv = version.split('.')[0] > 1 ? '2' : '1'; const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64'; const toggle = `${process.platform}-${mv}`; @@ -30,7 +30,7 @@ const getComposeDownloadUrl = (version = '2.21.0') => { /* * Helper to get docker compose v2 download destination */ -const getComposeDownloadDest = (base, version = '2.21.0') => { +const getComposeDownloadDest = (base, version = '2.27.1') => { switch (process.platform) { case 'linux': case 'darwin': @@ -47,6 +47,11 @@ module.exports = async (lando, options) => { // get stuff from config/opts const {orchestratorBin, userConfRoot} = lando.config; const {orchestrator} = options; + + // if orchestrator engine is set to false allow it to be skipped + // @NOTE: this is mostly for internal stuff + if (orchestrator === false) return; + const dest = getComposeDownloadDest(path.join(userConfRoot, 'bin'), orchestrator); const url = getComposeDownloadUrl(orchestrator); diff --git a/index.js b/index.js index 90ae8e6ce..786e55c95 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,6 @@ // Modules const _ = require('lodash'); const fs = require('fs'); -const ip = require('ip'); const path = require('path'); // Default env values @@ -46,11 +45,10 @@ module.exports = async lando => { // certs stuff // @TODO: should this end up elsewhere? + const caName = `${_.capitalize(lando.config.product)}CA`; const caDomain = lando.config.domain; - const caCert = path.join(caDir, `${caDomain}.pem`); - const caKey = path.join(caDir, `${caDomain}.key`); - const caProject = `landocasetupkenobi38ahsoka${lando.config.instance}`; - const certData = {caCert, caDir, caDomain, caKey, caProject}; + const caCert = path.join(caDir, `${caName}.crt`); + const caKey = path.join(caDir, `${caName}.key`); // Ensure some dirs exist before we start _.forEach([binDir, caDir, sshDir], dir => fs.mkdirSync(dir, {recursive: true})); @@ -58,20 +56,18 @@ module.exports = async lando => { // Ensure we munge plugin stuff together appropriately lando.events.once('pre-install-plugins', async options => await require('./hooks/lando-setup-common-plugins')(lando, options)); // eslint-disable-line max-len - // Ensure we setup docker-compose if needed - lando.events.once('pre-setup', async options => await require('./hooks/lando-setup-orchestrator')(lando, options)); // eslint-disable-line max-len + // move v3 scripts directories as needed + lando.events.on('pre-setup', 0, async () => await require('./hooks/lando-copy-v3-scripts')(lando)); // Ensure we setup docker if needed - lando.events.once('pre-setup', async options => { - switch (process.platform) { - case 'darwin': - return await require('./hooks/lando-setup-build-engine-darwin')(lando, options); - case 'linux': - return await require('./hooks/lando-setup-build-engine-linux')(lando, options); - case 'win32': - return await require('./hooks/lando-setup-build-engine-win32')(lando, options); - } - }); + lando.events.once('pre-setup', async options => await require(`./hooks/lando-setup-build-engine-${process.platform}`)(lando, options)); // eslint-disable-line max-len + + // Ensure we create and install ca if needed + lando.events.once('pre-setup', async options => await require('./hooks/lando-setup-create-ca')(lando, options)); + lando.events.once('pre-setup', async options => await require(`./hooks/lando-setup-install-ca-${process.platform}`)(lando, options)); // eslint-disable-line max-len + + // Ensure we setup docker-compose if needed + lando.events.once('pre-setup', async options => await require('./hooks/lando-setup-orchestrator')(lando, options)); // make sure Lando Specification 337 is available to all lando.events.on('post-bootstrap-app', async () => await require('./hooks/lando-add-l337-spec')(lando)); @@ -91,13 +87,8 @@ module.exports = async lando => { // autostart docker if we need to lando.events.once('engine-autostart', async () => await require('./hooks/lando-autostart-engine')(lando)); - // Make sure we have a host-exposed root ca if we don't already - // NOTE: we don't run this on the caProject otherwise infinite loop happens! - lando.events.on('pre-engine-start', 2, async data => await require('./hooks/lando-setup-ca')(lando, data, certData)); - - // Let's also make a copy of caCert with the standarized .crt ending for better linux compat - // See: https://github.com/lando/lando/issues/1550 - lando.events.on('pre-engine-start', 3, async () => await require('./hooks/lando-copy-ca')(lando, certData)); + // move v3 scripts directories as needed + lando.events.on('pre-engine-start', 0, async () => await require('./hooks/lando-copy-v3-scripts')(lando)); // Return some default things return _.merge({}, defaults, uc(), {config: { @@ -108,7 +99,7 @@ module.exports = async lando => { LANDO_DOMAIN: lando.config.domain, LANDO_HOST_HOME: lando.config.home, LANDO_HOST_OS: lando.config.os.platform, - LANDO_HOST_IP: (process.platform === 'linux') ? ip.address() : 'host.docker.internal', + LANDO_HOST_IP: 'host.lando.internal', LANDO_LEIA: _.toInteger(lando.config.leia), LANDO_MOUNT: '/app', }, @@ -119,7 +110,6 @@ module.exports = async lando => { caCert, caDomain, caKey, - caProject, maxKeyWarning: 10, }}); }; diff --git a/lib/app.js b/lib/app.js index 079fac78f..8699a5b28 100644 --- a/lib/app.js +++ b/lib/app.js @@ -63,7 +63,8 @@ module.exports = class App { this._dir = path.join(this._config.userConfRoot, 'compose', this.project); this._lando = lando; this._name = name; - this._coreToolingOverrides = []; + this._coreToolingOverrides = {}; + this.debuggy = lando.debuggy; /** * The apps logger @@ -172,7 +173,6 @@ module.exports = class App { else this.composeData.push(data); } - /* * @TODO, add and log a warning */ @@ -268,7 +268,7 @@ module.exports = class App { * @fires ready * @return {Promise} A Promise. */ - init() { + init({noEngine = false} = {}) { // We should only need to initialize once, if we have just go right to app ready if (this.initialized) return this.events.emit('ready', this); // Get compose data if we have any, otherwise set to [] @@ -298,6 +298,8 @@ module.exports = class App { .then(() => { // Get all the services this.services = require('../utils/get-app-services')(this.composeData); + // add a double property because we have weird differnces between cli getApp and core getApp + this.allServices = this.services; // Merge whatever we have thus far together this.info = require('../utils/get-app-info-defaults')(this); // finally just make a list of containers @@ -317,6 +319,10 @@ module.exports = class App { * @property {App} app The app instance. */ .then(() => this.events.emit('post-init', this)) + + // special event for post-init stuff that requires the engine + // @NOTE: dont ask, just continuing to work around v3-wasnt-intended-to-do-this problems + .then(() => noEngine === true ? undefined : this.events.emit('post-init-engine', this)) // Finish up .then(() => { // Front load our app mounts @@ -365,7 +371,10 @@ module.exports = class App { * @event ready * @property {App} app The app instance. */ - .then(() => this.events.emit('ready', this)); + .then(() => this.events.emit('ready', this)) + + // @NOTE: dont ask, just continuing to work around v3-wasnt-intended-to-do-this problems + .then(() => noEngine === true ? undefined : this.events.emit('ready-engine', this)); }; /** diff --git a/lib/compose.js b/lib/compose.js index 770b2e215..34cc67c18 100644 --- a/lib/compose.js +++ b/lib/compose.js @@ -121,7 +121,30 @@ exports.remove = (compose, project, opts = {}) => { /* * Run docker compose run */ -exports.run = (compose, project, opts = {}) => buildShell('exec', project, compose, opts); +exports.run = (compose, project, opts = {}) => { + // add some deep handling for detaching + // @TODO: should we let and explicit set of opts.detach override this? + // thinking probably not because & is just not going to work the way you want without detach? + // that said we can skip this if detach is already set to true + if (opts.detach !== true) { + if (opts.cmd[0] === '/etc/lando/exec.sh' && opts.cmd[opts.cmd.length - 1] === '&') { + opts.cmd.pop(); + opts.detach = true; + } else if (opts.cmd[0].endsWith('sh') && opts.cmd[1] === '-c' && opts.cmd[2].endsWith('&')) { + opts.cmd[2] = opts.cmd[2].slice(0, -1).trim(); + opts.detach = true; + } else if (opts.cmd[0].endsWith('bash') && opts.cmd[1] === '-c' && opts.cmd[2].endsWith('&')) { + opts.cmd[2] = opts.cmd[2].slice(0, -1).trim(); + opts.detach = true; + } else if (opts.cmd[opts.cmd.length - 1] === '&') { + opts.cmd.pop(); + opts.detach = true; + } + } + + // and return + return buildShell('exec', project, compose, opts); +}; /* * You can do a create, rebuild and start with variants of this diff --git a/lib/daemon.js b/lib/daemon.js index 5076dc45a..856c6a77f 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -57,7 +57,7 @@ module.exports = class LandoDaemon { log = new Log(), context = 'node', compose = require('../utils/get-compose-x')(), - orchestratorVersion = '1.29.2', + orchestratorVersion = '2.27.1', userConfRoot = path.join(os.homedir(), '.lando'), ) { this.cache = cache; @@ -234,11 +234,11 @@ module.exports = class LandoDaemon { case 'win32': const dockerBinPath = require('../utils/get-docker-bin-path')(); const componentsVersionFile = path.resolve(dockerBinPath, '..', 'componentsVersion.json'); + // If we found one, use it but allow for a fallback in case these keys change if (componentsVersionFile) { - // There are two different keys that map to the docker desktip version depending on the version - versions.desktop = _.get(require(componentsVersionFile), 'Informational', '4.0.0'); - versions.desktop = _.get(require(componentsVersionFile), 'Version', '4.0.0'); + const {appVersion, Version, Informational} = require(componentsVersionFile); + versions.desktop = appVersion ?? Version ?? Informational; } return Promise.resolve(versions); } diff --git a/lib/events.js b/lib/events.js index 53559e559..83db106ee 100644 --- a/lib/events.js +++ b/lib/events.js @@ -106,10 +106,8 @@ class AsyncEvents extends EventEmitter { const fns = _.map(evnts, evnt => ({fn: evnt.fn, id: evnt.id})); // Log non engine events so we can keep things quiet - if (!_.includes(name, '-engine-')) { - this.log.debug('emitting event %s', name); - this.log.silly('event %s has %s listeners', name, fns.length); - } + this.log.debug('emitting event %s', name); + this.log.silly('event %s has %s listeners', name, fns.length); // Make listener functions to a promise in series. return Promise.each(fns, listener => { diff --git a/lib/lando.js b/lib/lando.js index 984add575..ae77b413d 100644 --- a/lib/lando.js +++ b/lib/lando.js @@ -129,9 +129,9 @@ const bootstrapTasks = lando => { ) // Loadem and loggem .then(tasks => _.flatten(tasks)) - .each(task => { - lando.tasks.push(require(task)(lando)); - lando.log.debug('autoloaded task %s', path.basename(task, '.js')); + .each(file => { + lando.tasks.push({...require(file)(lando, {}), file}); + lando.log.debug('autoloaded global task %s', path.basename(file, '.js')); }) // Reset the task cache .then(() => { @@ -163,16 +163,6 @@ const bootstrapEngine = lando => { rimrafSync(path.join(lando.config.userConfRoot, 'scripts')); lando.cache.set('VIRTUOFSNUKE1', 'yes', {persist: true}); } - - // Auto move and make executable any scripts - return lando.Promise.map(lando.config.plugins, plugin => { - if (fs.existsSync(plugin.scripts)) { - const confDir = path.join(lando.config.userConfRoot, 'scripts'); - const dest = require('../utils/move-config')(plugin.scripts, confDir); - require('../utils/make-executable')(fs.readdirSync(dest), dest); - lando.log.debug('automoved scripts from %s to %s and set to mode 755', plugin.scripts, confDir); - } - }); }; /* @@ -447,6 +437,44 @@ module.exports = class Lando { .then(() => this); } + async generateCert(name, { + caCert = this.config.caCert, + caKey = this.config.caKey, + domains = [], + organization = 'Lando System', + validity = 365, + } = {}) { + const read = require('../utils/read-file'); + const write = require('../utils/write-file'); + const {createCert} = require('mkcert'); + + // compute + const certPath = path.join(this.config.userConfRoot, 'certs', `${name}.crt`); + const keyPath = path.join(this.config.userConfRoot, 'certs', `${name}.key`); + // push localhost and 127.0.0.1 to domains + domains.push('127.0.0.1', 'localhost'); + this.log.debug('received cert request for %o with names %j using CA %o', name, domains, caCert); + + // generate cert + const {cert, key} = await createCert({ + ca: { + cert: read(caCert), + key: read(caKey), + }, + domains, + organization, + validity, + }); + + // write + // @NOTE: we just regenerate every single time because the logic is easier since things are dyanmic + // and, presumably the cost is low? + write(certPath, cert); + write(keyPath, key); + this.log.debug('generated cert/key pair %o %o', certPath, keyPath); + return {certPath, keyPath}; + } + /** * Gets a fully instantiated App instance. * @@ -546,9 +574,15 @@ module.exports = class Lando { // try to fetch the plugins const {data, errors, results, total} = await this.runTasks(tasks, { - renderer: 'lando', + renderer: 'dc2', rendererOptions: { - level: 0, + header: 'Installing Plugins', + states: { + COMPLETED: 'Installed', + STARTED: 'Installing', + FAILED: 'FAILED', + WAITING: 'Waiting', + }, }, }); @@ -623,9 +657,14 @@ module.exports = class Lando { { concurrent: true, exitOnError: false, - renderer: 'lando', + renderer: 'dc2', rendererOptions: { - level: 0, + header: 'Running Setup Tasks', + states: { + COMPLETED: 'Completed', + STARTED: 'Running', + FAILED: 'FAILED', + }, }, }, ); diff --git a/lib/router.js b/lib/router.js index c4986eabd..e894937c7 100644 --- a/lib/router.js +++ b/lib/router.js @@ -74,6 +74,7 @@ exports.logs = (data, compose) => retryEach(data, datum => compose('logs', datum exports.run = (data, compose, docker, started = true) => Promise.mapSeries(normalizer(data), datum => { // Merge in default cli envars datum.opts.environment = require('../utils/get-cli-env')(datum.opts.environment); + datum.kill = true; // Escape command if it is still a string if (_.isString(datum.cmd)) datum.cmd = require('../utils/shell-escape')(datum.cmd, true); return docker.isRunning(getContainerId(datum)).then(isRunning => { diff --git a/lib/shell.js b/lib/shell.js index 6de682aae..31ab3e64f 100644 --- a/lib/shell.js +++ b/lib/shell.js @@ -155,6 +155,7 @@ module.exports = class Shell { this.log.silly('process %s had output', id, {stdout, stderr}); // Return _.remove(this.running, proc => proc.id === id); + return (code !== 0) ? Promise.reject(new Error(stderr)) : Promise.resolve(stdout); }); }; diff --git a/lib/utils.js b/lib/utils.js index 6bede376f..1a459e7bc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,12 +7,12 @@ const {color} = require('listr2'); // @NOTE: this file exists for backwards compatibility const getHealth = (info, data) => { + // any error should be red + if (info?.error) return color.red(data); // image build failures should be red if (info?.state?.IMAGE === 'BUILD FAILURE') return color.red(data); - // failed healthchecks are yellow if (info?.healthy === false) return color.yellow(data); - // otherwise green will do return color.green(data); }; diff --git a/messages/app-build-v4-error.js b/messages/app-build-v4-error.js index 4184357ab..707cd970a 100644 --- a/messages/app-build-v4-error.js +++ b/messages/app-build-v4-error.js @@ -9,9 +9,9 @@ module.exports = error => ({ title: `Could not build app in "${error.id}!"`, type: 'warn', detail: [ - `App build steps failed in ${bold(error.context.path)}`, + `App build steps failed in ${bold([error.command, error.args].flat().join(' '))}`, `Rerun with ${bold('lando rebuild --debug')} to see the entire build log and look for errors.`, `When you've resolved the build issues you can then:`, ], - command: 'lando rebuild', + command: 'lando rebuild --debug', }); diff --git a/package-lock.json b/package-lock.json index d973440af..ce254f689 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@lando/core", - "version": "3.21.1", + "version": "3.21.2", "license": "GPL-3.0", "dependencies": { "@npmcli/arborist": "^6.2.9", @@ -24,7 +24,6 @@ "enquirer": "^2.4.1", "fs-extra": "^11.1.1", "glob": "^7.1.3", - "ip": "^1.1.8", "is-class": "^0.0.9", "is-interactive": "^1", "is-online": "^9", @@ -34,8 +33,11 @@ "listr2": "^6.6.1", "lodash": "^4.17.21", "log-update": "4.0.0", + "mac-ca": "^3.1.0", + "mkcert": "^3.2.0", "nanoid": "^3", "node-cache": "^4.1.1", + "node-forge": "^1.3.1", "npm-package-arg": "^11.0.1", "npm-profile": "^9.0.0", "object-hash": "^1.1.8", @@ -45,20 +47,23 @@ "shelljs": "^0.8.4", "slugify": "^1.6.5", "string-argv": "0.1.1", + "strip-ansi": "^6.0.1", + "system-ca": "^2.0.0", "through": "^2.3.8", + "valid-path": "^2.1.0", "valid-url": "^1.0.9", + "win-ca": "^3.5.1", "winston": "2.4.5", "wrap-ansi": "7.0.0", "yargs-parser": "^11.1.1" }, "devDependencies": { "@babel/eslint-parser": "^7.16.0", - "@lando/leia": "^1.0.0-beta.1", + "@lando/leia": "^1.0.0-beta.4", "@lando/vitepress-theme-default-plus": "^1.0.2", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-events": "^0.0.1", - "command-line-test": "^1.0.10", "eslint": "^7.32.0", "eslint-config-google": "^0.9.1", "eslint-plugin-vue": "^8.0.3", @@ -1382,9 +1387,9 @@ } }, "node_modules/@lando/leia": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@lando/leia/-/leia-1.0.0-beta.3.tgz", - "integrity": "sha512-JW8so42+UcDHzNg1LHb7wta13NRAEd4ammKNaPSJVd5qZ4tu4aVk8aUi1wmUONamZlLEsB8/oy7eBFzbHjumvw==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@lando/leia/-/leia-1.0.0-beta.4.tgz", + "integrity": "sha512-mJ6ZR8KuB8HmsThV2A0VfRhinJ517TmRR6kw8hux/O0zCwyd7peFK/8ASkkahC2Pni1CwDIApVu1q7d/LX68Fg==", "dev": true, "dependencies": { "@lando/argv": "^1.0.6", @@ -4011,6 +4016,15 @@ "node": ">=8" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -4652,6 +4666,14 @@ "integrity": "sha512-osdAsKGgEG457KlkGCMS4dPi7zJPPh8pZURhAmBcS3jO+prfYlQ6K0XagfGRGT1ztxfV2flNfBnGQz7kzHDlew==", "dev": true }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/common-ancestor-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", @@ -5912,6 +5934,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -6686,11 +6714,6 @@ "node": ">= 0.10" } }, - "node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" - }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -6757,11 +6780,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6778,7 +6805,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -7729,6 +7755,29 @@ "yallist": "^3.0.2" } }, + "node_modules/mac-ca": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mac-ca/-/mac-ca-3.1.0.tgz", + "integrity": "sha512-ts8slRarTfSQhtEYRVRjfLMEOsvFBtZdlnI6jvqAcWAS8dSQwyrcACkFz5GvV4bshe3WAOcPHwN8fuovdqsxOQ==", + "dependencies": { + "node-forge": "^1.3.1", + "undici": "^6.16.1" + } + }, + "node_modules/macos-export-certificate-and-key": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/macos-export-certificate-and-key/-/macos-export-certificate-and-key-1.1.2.tgz", + "integrity": "sha512-kd4ba3kVKZXy46p4tg3X19dmwaXjtz0La5It6Rt6PbtwP+YcQ0F7ab8MjcSHOvz9NSXmAU15qQG53OlBDAPDzQ==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^4.3.0" + } + }, "node_modules/magic-string": { "version": "0.30.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", @@ -8408,6 +8457,21 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, + "node_modules/mkcert": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mkcert/-/mkcert-3.2.0.tgz", + "integrity": "sha512-026Eivq9RoOjOuLJGzbhGwXUAjBxRX11Z7Jbm4/7lqT/Av+XNy9SPrJte6+UpEt7i+W3e/HZYxQqlQcqXZWSzg==", + "dependencies": { + "commander": "^11.0.0", + "node-forge": "^1.3.1" + }, + "bin": { + "mkcert": "dist/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/mkdir-p": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mkdir-p/-/mkdir-p-0.0.7.tgz", @@ -8723,6 +8787,12 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "optional": true + }, "node_modules/node-cache": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.1.tgz", @@ -8754,6 +8824,14 @@ } } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-gyp": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", @@ -9931,6 +10009,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "engines": { + "node": ">=4" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -11357,6 +11443,17 @@ "node": ">=0.10.0" } }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", @@ -11513,6 +11610,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/system-ca": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/system-ca/-/system-ca-2.0.0.tgz", + "integrity": "sha512-eEWsCZHEyXdRPPMO680gLUhb9x8RK7YlXvv+I0zCvmGg9zf9OCchJxDf5NHqGPwAzLDEFpLXL5qv9KEU62N4Nw==", + "optionalDependencies": { + "macos-export-certificate-and-key": "^1.1.1", + "win-export-certificate-and-key": "^2.0.0" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -11958,6 +12064,14 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/undici": { + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.18.2.tgz", + "integrity": "sha512-o/MQLTwRm9IVhOqhZ0NQ9oXax1ygPjw6Vs+Vq/4QRjbOAC3B1GCHy7TYxxbExKlb7bzDRzt9vBWU6BDz0RFfYg==", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -12068,6 +12182,14 @@ "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", "dev": true }, + "node_modules/valid-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/valid-path/-/valid-path-2.1.0.tgz", + "integrity": "sha512-hYanLM6kqE7zLl0oykV//2q3meRgYGOtS2lgChozuWjjghSqR8xwc3199bDGUFPwszk/MhvHhyVys8HdHU9buw==", + "dependencies": { + "is-glob": "^4.0.3" + } + }, "node_modules/valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", @@ -12286,6 +12408,49 @@ "node": ">=8" } }, + "node_modules/win-ca": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/win-ca/-/win-ca-3.5.1.tgz", + "integrity": "sha512-RNy9gpBS6cxWHjfbqwBA7odaHyT+YQNhtdpJZwYCFoxB/Dq22oeOZ9YCXMwjhLytKpo7JJMnKdJ/ve7N12zzfQ==", + "hasInstallScript": true, + "dependencies": { + "is-electron": "^2.2.0", + "make-dir": "^1.3.0", + "node-forge": "^1.2.1", + "split": "^1.0.1" + } + }, + "node_modules/win-ca/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/win-export-certificate-and-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/win-export-certificate-and-key/-/win-export-certificate-and-key-2.0.1.tgz", + "integrity": "sha512-GsPUuIn95CepWgfiaqyIBWlj1uzr0LMfWIHBESSa+f84Zll9SjIX7Jj0+xNs/FlhH5zEkPO6k+SRQX1dfv3zPg==", + "hasInstallScript": true, + "optional": true, + "os": [ + "win32" + ], + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^3.1.0" + } + }, + "node_modules/win-export-certificate-and-key/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "optional": true + }, "node_modules/winston": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.5.tgz", @@ -13468,9 +13633,9 @@ } }, "@lando/leia": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@lando/leia/-/leia-1.0.0-beta.3.tgz", - "integrity": "sha512-JW8so42+UcDHzNg1LHb7wta13NRAEd4ammKNaPSJVd5qZ4tu4aVk8aUi1wmUONamZlLEsB8/oy7eBFzbHjumvw==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@lando/leia/-/leia-1.0.0-beta.4.tgz", + "integrity": "sha512-mJ6ZR8KuB8HmsThV2A0VfRhinJ517TmRR6kw8hux/O0zCwyd7peFK/8ASkkahC2Pni1CwDIApVu1q7d/LX68Fg==", "dev": true, "requires": { "@lando/argv": "^1.0.6", @@ -15422,6 +15587,15 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -15892,6 +16066,11 @@ "integrity": "sha512-osdAsKGgEG457KlkGCMS4dPi7zJPPh8pZURhAmBcS3jO+prfYlQ6K0XagfGRGT1ztxfV2flNfBnGQz7kzHDlew==", "dev": true }, + "commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==" + }, "common-ancestor-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", @@ -16831,6 +17010,12 @@ "flat-cache": "^3.0.4" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -17393,11 +17578,6 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, - "ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" - }, "ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -17443,11 +17623,15 @@ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true }, + "is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -17458,7 +17642,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -18138,6 +18321,25 @@ "yallist": "^3.0.2" } }, + "mac-ca": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mac-ca/-/mac-ca-3.1.0.tgz", + "integrity": "sha512-ts8slRarTfSQhtEYRVRjfLMEOsvFBtZdlnI6jvqAcWAS8dSQwyrcACkFz5GvV4bshe3WAOcPHwN8fuovdqsxOQ==", + "requires": { + "node-forge": "^1.3.1", + "undici": "^6.16.1" + } + }, + "macos-export-certificate-and-key": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/macos-export-certificate-and-key/-/macos-export-certificate-and-key-1.1.2.tgz", + "integrity": "sha512-kd4ba3kVKZXy46p4tg3X19dmwaXjtz0La5It6Rt6PbtwP+YcQ0F7ab8MjcSHOvz9NSXmAU15qQG53OlBDAPDzQ==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "node-addon-api": "^4.3.0" + } + }, "magic-string": { "version": "0.30.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", @@ -18669,6 +18871,15 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, + "mkcert": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mkcert/-/mkcert-3.2.0.tgz", + "integrity": "sha512-026Eivq9RoOjOuLJGzbhGwXUAjBxRX11Z7Jbm4/7lqT/Av+XNy9SPrJte6+UpEt7i+W3e/HZYxQqlQcqXZWSzg==", + "requires": { + "commander": "^11.0.0", + "node-forge": "^1.3.1" + } + }, "mkdir-p": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mkdir-p/-/mkdir-p-0.0.7.tgz", @@ -18903,6 +19114,12 @@ } } }, + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "optional": true + }, "node-cache": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.1.tgz", @@ -18920,6 +19137,11 @@ "whatwg-url": "^5.0.0" } }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, "node-gyp": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", @@ -19809,6 +20031,11 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==" + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -20858,6 +21085,14 @@ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "dev": true }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, "split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", @@ -20970,6 +21205,15 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "system-ca": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/system-ca/-/system-ca-2.0.0.tgz", + "integrity": "sha512-eEWsCZHEyXdRPPMO680gLUhb9x8RK7YlXvv+I0zCvmGg9zf9OCchJxDf5NHqGPwAzLDEFpLXL5qv9KEU62N4Nw==", + "requires": { + "macos-export-certificate-and-key": "^1.1.1", + "win-export-certificate-and-key": "^2.0.0" + } + }, "tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -21335,6 +21579,11 @@ "is-typedarray": "^1.0.0" } }, + "undici": { + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.18.2.tgz", + "integrity": "sha512-o/MQLTwRm9IVhOqhZ0NQ9oXax1ygPjw6Vs+Vq/4QRjbOAC3B1GCHy7TYxxbExKlb7bzDRzt9vBWU6BDz0RFfYg==" + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -21410,6 +21659,14 @@ "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", "dev": true }, + "valid-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/valid-path/-/valid-path-2.1.0.tgz", + "integrity": "sha512-hYanLM6kqE7zLl0oykV//2q3meRgYGOtS2lgChozuWjjghSqR8xwc3199bDGUFPwszk/MhvHhyVys8HdHU9buw==", + "requires": { + "is-glob": "^4.0.3" + } + }, "valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", @@ -21547,6 +21804,45 @@ "string-width": "^4.0.0" } }, + "win-ca": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/win-ca/-/win-ca-3.5.1.tgz", + "integrity": "sha512-RNy9gpBS6cxWHjfbqwBA7odaHyT+YQNhtdpJZwYCFoxB/Dq22oeOZ9YCXMwjhLytKpo7JJMnKdJ/ve7N12zzfQ==", + "requires": { + "is-electron": "^2.2.0", + "make-dir": "^1.3.0", + "node-forge": "^1.2.1", + "split": "^1.0.1" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + } + } + }, + "win-export-certificate-and-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/win-export-certificate-and-key/-/win-export-certificate-and-key-2.0.1.tgz", + "integrity": "sha512-GsPUuIn95CepWgfiaqyIBWlj1uzr0LMfWIHBESSa+f84Zll9SjIX7Jj0+xNs/FlhH5zEkPO6k+SRQX1dfv3zPg==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "node-addon-api": "^3.1.0" + }, + "dependencies": { + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "optional": true + } + } + }, "winston": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.5.tgz", diff --git a/package.json b/package.json index bd3ec89f0..1f537aac0 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "enquirer": "^2.4.1", "fs-extra": "^11.1.1", "glob": "^7.1.3", - "ip": "^1.1.8", "is-class": "^0.0.9", "is-interactive": "^1", "is-online": "^9", @@ -72,8 +71,11 @@ "listr2": "^6.6.1", "lodash": "^4.17.21", "log-update": "4.0.0", + "mac-ca": "^3.1.0", + "mkcert": "^3.2.0", "nanoid": "^3", "node-cache": "^4.1.1", + "node-forge": "^1.3.1", "npm-package-arg": "^11.0.1", "npm-profile": "^9.0.0", "object-hash": "^1.1.8", @@ -83,20 +85,23 @@ "shelljs": "^0.8.4", "slugify": "^1.6.5", "string-argv": "0.1.1", + "strip-ansi": "^6.0.1", + "system-ca": "^2.0.0", "through": "^2.3.8", + "valid-path": "^2.1.0", "valid-url": "^1.0.9", + "win-ca": "^3.5.1", "winston": "2.4.5", "wrap-ansi": "7.0.0", "yargs-parser": "^11.1.1" }, "devDependencies": { "@babel/eslint-parser": "^7.16.0", - "@lando/leia": "^1.0.0-beta.1", + "@lando/leia": "^1.0.0-beta.4", "@lando/vitepress-theme-default-plus": "^1.0.2", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-events": "^0.0.1", - "command-line-test": "^1.0.10", "eslint": "^7.32.0", "eslint-config-google": "^0.9.1", "eslint-plugin-vue": "^8.0.3", diff --git a/packages/certs/certs.js b/packages/certs/certs.js new file mode 100644 index 000000000..a7fd15718 --- /dev/null +++ b/packages/certs/certs.js @@ -0,0 +1,48 @@ +'use strict'; + +const isObject = require('lodash/isPlainObject'); +const path = require('path'); +const uniq = require('lodash/uniq'); + +module.exports = async (service, certs) => { + // if cert is true then just map to the usual + if (certs === true) certs = '/etc/lando/certs/cert.crt'; + + // if cert is a string then compute the key and objectify + if (typeof certs === 'string') certs = {cert: certs}; + + // if cert is an object with no key then compute the key with the cert + if (isObject(certs) && certs?.key === undefined) { + certs.key = path.posix.join(path.dirname(certs.cert), 'cert.key'); + } + + // make sure both cert and key are arrays + if (typeof certs?.cert === 'string') certs.cert = [certs.cert]; + if (typeof certs?.key === 'string') certs.key = [certs.key]; + + // generate certs + const {certPath, keyPath} = await service.generateCert(`${service.id}.${service.project}`, { + domains: [ + ...service.packages?.proxy?.domains ?? [], + ...service.hostnames, + service.id, + ], + }); + + // build the volumes + const volumes = uniq([ + ...certs.cert.map(file => `${certPath}:${file}`), + ...certs.key.map(file => `${keyPath}:${file}`), + `${certPath}:/etc/lando/certs/cert.crt`, + `${keyPath}:/etc/lando/certs/cert.key`, + ]); + + // add things + service.addLandoServiceData({ + volumes, + environment: { + LANDO_SERVICE_CERT: certs.cert[0], + LANDO_SERVICE_KEY: certs.key[0], + }, + }); +}; diff --git a/packages/git/git.js b/packages/git/git.js new file mode 100644 index 000000000..c8931cac0 --- /dev/null +++ b/packages/git/git.js @@ -0,0 +1,13 @@ +'use strict'; + +const path = require('path'); + +module.exports = async service => { + service.addHookFile(path.join(__dirname, 'install-git.sh'), {hook: 'boot'}); + service.addHookFile(` + # temp stuff for demo purposes + if command -v git > /dev/null 2>&1; then + git config --global --add safe.directory ${service.appMount} + fi + `, {stage: 'app', hook: 'internal-root', id: 'git-safe'}); +}; diff --git a/packages/git/install-git.sh b/packages/git/install-git.sh new file mode 100755 index 000000000..4f131feea --- /dev/null +++ b/packages/git/install-git.sh @@ -0,0 +1,31 @@ +#!/bin/lash +set -eo pipefail + +# if git exists then we can skip +if [ -x "$(command -v git)" ]; then + exit 0 +fi + +case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk add git + ;; + apt) + apt install -y git + ;; + dnf) + dnf install -y git + ;; + microdnf) + microdnf install git + ;; + pacman) + pacman -Sy --noconfirm git + ;; + yum) + yum install -y git + ;; + *) + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not install git!" + ;; +esac diff --git a/packages/proxy/get-proxy-hostnames.js b/packages/proxy/get-proxy-hostnames.js new file mode 100644 index 000000000..b94a0e768 --- /dev/null +++ b/packages/proxy/get-proxy-hostnames.js @@ -0,0 +1,18 @@ +'use strict'; + +// @TODO: move this into utils and reuse in app-generate-certs.js? +const parseUrls = (urls = []) => { + return urls.map(url => { + try { + url = new URL(url); + return url.hostname; + } catch { + return undefined; + } + }) + .filter(hostname => hostname !== undefined); +}; + +module.exports = (routes = []) => parseUrls(routes + .map(route => route?.hostname ?? route?.host ?? route) + .map(route => `http://${route}`)); diff --git a/packages/proxy/proxy.js b/packages/proxy/proxy.js new file mode 100644 index 000000000..88247357b --- /dev/null +++ b/packages/proxy/proxy.js @@ -0,0 +1,37 @@ +'use strict'; + +module.exports = async (service, {volume, routes = []}) => { + // add run data + service.addLandoRunData({ + environment: { + LANDO_PROXY_CERT: `/lando/certs/${service.id}.${service.project}.crt`, + LANDO_PROXY_KEY: `/lando/certs/${service.id}.${service.project}.key`, + LANDO_PROXY_CONFIG_FILE: `/proxy_config/${service.id}.${service.project}.yaml`, + }, + volumes: [ + `${volume}:/proxy_config`, + ], + }); + + // add hook file + service.addHookFile(` + # if we have certs then lets add the proxy config + # we do this here instead of in the plugin code because it avoids a race condition + # where the proxy config file exists before the certs + if [ ! -z "\${LANDO_PROXY_CONFIG_FILE+x}" ] \ + && [ ! -z "\${LANDO_PROXY_CERT+x}" ] \ + && [ ! -z "\${LANDO_PROXY_KEY+x}" ]; then + # remove older config if its there + # we need to do this so traefik recognizes new certs and loads them + rm -f "$LANDO_PROXY_CONFIG_FILE" + + # Dump the yaml + tee "$LANDO_PROXY_CONFIG_FILE" > /dev/null <> /etc/lando/env.d/install-ca-certs.sh + echo "export LANDO_CA_BUNDLE=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" >> /etc/lando/env.d/install-ca-certs.sh + update-ca-trust + ;; + *) + cp -r /etc/lando/ca-certificates/. /usr/local/share/ca-certificates/ + echo "export LANDO_CA_DIR=/etc/ssl/certs/" >> /etc/lando/env.d/install-ca-certs.sh + echo "export LANDO_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt" >> /etc/lando/env.d/install-ca-certs.sh + update-ca-certificates + ;; +esac diff --git a/packages/security/security.js b/packages/security/security.js new file mode 100644 index 000000000..1799a32a3 --- /dev/null +++ b/packages/security/security.js @@ -0,0 +1,20 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +module.exports = async (service, security) => { + // right now this is mostly just CA setup, lets munge it all together and normalize and whatever + const cas = [security.ca, security.cas, security['certificate-authority'], security['certificate-authorities']] + .flat(Number.POSITIVE_INFINITY) + .filter(cert => fs.existsSync(cert)) + .map(cert => path.isAbsolute(cert) ? cert : path.resolve(service.appRoot, cert)); + + // add ca-cert install hook if we have some to add + if (cas.length > 0) { + service.addHookFile(path.join(__dirname, 'install-ca-certs.sh'), {hook: 'boot'}); + } + + // inject them + for (const ca of cas) service.addLSF(ca, `ca-certificates/${path.basename(ca)}`); +}; diff --git a/packages/ssh-agent/check-ssh-agent.sh b/packages/ssh-agent/check-ssh-agent.sh new file mode 100755 index 000000000..72afd89ca --- /dev/null +++ b/packages/ssh-agent/check-ssh-agent.sh @@ -0,0 +1,19 @@ +#!/bin/lash + +# Check if SSH_AUTH_SOCK is set +if [ -z "$SSH_AUTH_SOCK" ]; then + debug "SSH_AUTH_SOCK is not set. Please start the SSH agent." + exit 1 +fi + +# Test the connection to the SSH agent +if ssh-add -l > /dev/null 2>&1; then + debug "Connected to SSH agent." +elif [ $? -eq 1 ]; then + # Exit code 1 means the agent is running but has no identities + debug "SSH agent is running, but has no identities." +else + # Any other exit code means we couldn't connect to the agent + debug "Could not connect to SSH agent." + exit 1 +fi diff --git a/packages/ssh-agent/install-socat.sh b/packages/ssh-agent/install-socat.sh new file mode 100755 index 000000000..26ec61e65 --- /dev/null +++ b/packages/ssh-agent/install-socat.sh @@ -0,0 +1,31 @@ +#!/bin/lash +set -eo pipefail + +# if socat exists then we can skip +if [ -x "$(command -v socat)" ]; then + exit 0 +fi + +case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk add socat + ;; + apt) + apt install -y socat + ;; + dnf) + dnf install -y socat + ;; + microdnf) + microdnf install socat + ;; + pacman) + pacman -Sy --noconfirm socat + ;; + yum) + yum install -y socat + ;; + *) + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not install socat!" + ;; +esac diff --git a/packages/ssh-agent/install-ssh-add.sh b/packages/ssh-agent/install-ssh-add.sh new file mode 100755 index 000000000..989c89e5f --- /dev/null +++ b/packages/ssh-agent/install-ssh-add.sh @@ -0,0 +1,31 @@ +#!/bin/lash +set -eo pipefail + +# if ssh-add exists then we can skip +if [ -x "$(command -v ssh-add)" ]; then + exit 0 +fi + +case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk add openssh + ;; + apt) + apt install -y openssh-client + ;; + dnf) + dnf install -y openssh + ;; + microdnf) + microdnf install openssh + ;; + pacman) + pacman -Sy --noconfirm openssh + ;; + yum) + yum install -y openssh-clients + ;; + *) + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not install openssh!" + ;; +esac diff --git a/packages/ssh-agent/ssh-agent.js b/packages/ssh-agent/ssh-agent.js new file mode 100644 index 000000000..394fbcb2b --- /dev/null +++ b/packages/ssh-agent/ssh-agent.js @@ -0,0 +1,46 @@ +'use strict'; + +const path = require('path'); + +// sets ssh agent and prepares for socating +// DD ssh-agent is a bit strange and we wont use it in v4 plugin but its easiest for demoing purposes +// if you have issues with it its best to do the below +// 0. Close Docker Desktop +// 1. killall ssh-agent +// 2. Start Docker Desktop +// 3. Open a terminal (after Docker Desktop starts) +// 4. ssh-add (use the existing SSH agent, don't start a new one) +// 5. docker run --rm --mount type=bind,src=/run/host-services/ssh-auth.sock,target=/run/host-services/ssh-auth.sock -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" --entrypoint /usr/bin/ssh-add alpine/git -l +module.exports = async service => { + const {name, uid, gid} = service.user; + const socket = process.platform === 'linux' ? process.env.SSH_AUTH_SOCK : `/run/host-services/ssh-auth.sock`; + + // if no socket or on windows then just bail + if (!socket || process.platform === 'win32') return; + + // if not root then we need to do some extra stuff + if (name !== 'root' && uid !== 0 || uid !== '0') { + service.addLSF(path.join(__dirname, 'check-ssh-agent.sh'), 'bin/check-ssh-agent'); + service.addHookFile(path.join(__dirname, 'install-socat.sh'), {hook: 'boot'}); + service.addHookFile(path.join(__dirname, 'install-ssh-add.sh'), {hook: 'boot'}); + service.addHookFile(` + #!/bin/lash + # make the socket accessible by group + if [ -S "${socket}" ]; then + chown :${gid} ${socket} + chmod 660 ${socket} + retry check-ssh-agent + fi + `, {stage: 'app', hook: 'internal-root', id: 'socat-docker-socket', priority: '000'}); + } + + // finally add the data + service.addLandoServiceData({ + environment: { + SSH_AUTH_SOCK: socket, + }, + volumes: [ + `${socket}:${socket}`, + ], + }); +}; diff --git a/packages/sudo/install-sudo.sh b/packages/sudo/install-sudo.sh new file mode 100755 index 000000000..3811dfe22 --- /dev/null +++ b/packages/sudo/install-sudo.sh @@ -0,0 +1,31 @@ +#!/bin/lash +set -eo pipefail + +# if sudo exists then we can skip +if [ -x "$(command -v sudo)" ]; then + exit 0 +fi + +case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk add sudo + ;; + apt) + apt install -y sudo + ;; + dnf) + dnf install -y sudo + ;; + microdnf) + microdnf install sudo + ;; + pacman) + pacman -Sy --noconfirm sudo + ;; + yum) + yum install -y sudo + ;; + *) + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not install sudo!" + ;; +esac diff --git a/packages/sudo/sudo.js b/packages/sudo/sudo.js new file mode 100644 index 000000000..a0b1acde0 --- /dev/null +++ b/packages/sudo/sudo.js @@ -0,0 +1,13 @@ +'use strict'; + +const path = require('path'); + +module.exports = async service => { + service.addHookFile(path.join(__dirname, 'install-sudo.sh'), {hook: 'boot'}); + service.addSteps({group: 'setup-user-1-after', instructions: ` + RUN touch /etc/sudoers + RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + RUN getent group sudo > /dev/null || groupadd sudo + RUN usermod -aG sudo ${service.user.name} + `}); +}; diff --git a/packages/user/add-user.sh b/packages/user/add-user.sh new file mode 100755 index 000000000..696460b7c --- /dev/null +++ b/packages/user/add-user.sh @@ -0,0 +1,124 @@ +#!/bin/lash +set -eo pipefail + +# PARSE THE ARGZZ +while (( "$#" )); do + case "$1" in + --gid) + LANDO_GID="$2" + shift 2 + ;; + --gid=*) + LANDO_GID="${1#*=}" + shift + ;; + --uid) + LANDO_UID="$2" + shift 2 + ;; + --uid=*) + LANDO_UID="${1#*=}" + shift + ;; + --name) + LANDO_USER="$2" + shift 2 + ;; + --name=*) + LANDO_USER="${1#*=}" + shift + ;; + --) + shift + break + ;; + -*|--*=) + shift + ;; + *) + shift + ;; + esac +done + +# if we have no LANDOUSER then throw an error +if [ -z "${LANDO_USER+x}" ]; then + abort "You must provide at least a username!" +fi + +# special dispensation +if [ "${LANDO_USER}" == 'root' ] || [ "${LANDO_UID}" == '0' ]; then + debug "Running as root, no need for creation or map!" + exit 0 +fi + +# Set min and max UID/GID values +LANDO_GIDMIN="$LANDO_GID" +LANDO_GIDMAX="600100000" +LANDO_UIDMIN="$LANDO_UID" +LANDO_UIDMAX="$((LANDO_UID + 10))" + +# Debug information +debug "LANDO_GID=${LANDO_GID}" +debug "LANDO_USER=${LANDO_USER}" +debug "LANDO_UID=${LANDO_UID}" +debug "LANDO_GIDMIN=${LANDO_GIDMIN}" +debug "LANDO_GIDMAX=${LANDO_GIDMAX}" +debug "LANDO_UIDMIN=${LANDO_UIDMIN}" +debug "LANDO_UIDMAX=${LANDO_UIDMAX}" + +# Ensure /etc/login.defs exists +if [ ! -e "/etc/login.defs" ]; then + touch "/etc/login.defs" +fi + +# Update UID_MIN and UID_MAX in /etc/login.defs +if [ -n "${LANDO_UID}" ]; then + sed -i '/^UID_MIN/d' /etc/login.defs + sed -i '/^UID_MAX/d' /etc/login.defs + echo "UID_MIN ${LANDO_UIDMIN}" >> /etc/login.defs + echo "UID_MAX ${LANDO_UIDMAX}" >> /etc/login.defs +fi + +# Update GID_MIN and GID_MAX in /etc/login.defs +if [ -n "${LANDO_GID}" ]; then + sed -i '/^GID_MIN/d' /etc/login.defs + sed -i '/^GID_MAX/d' /etc/login.defs + echo "GID_MIN ${LANDO_GIDMIN}" >> /etc/login.defs + echo "GID_MAX ${LANDO_GIDMAX}" >> /etc/login.defs +fi + +# Add group if GID is provided and group does not exist +if [ -n "${LANDO_GID}" ]; then + if ! getent group "$LANDO_GID" > /dev/null; then + groupadd -g "$LANDO_GID" "$LANDO_USER" || groupadd -g "$LANDO_GID" lando + fi +fi + +# Update existing user or create a new user +if id "$LANDO_USER" &>/dev/null; then + # User exists, update UID and GID + ouid="$(id -u "$LANDO_USER")" + ogid="$(id -g "$LANDO_USER")" + debug "user $LANDO_USER already exists with id ${ouid}:${ogid}" + debug "updating ${LANDO_USER} to ${LANDO_UID}:${LANDO_GID}" + if [ -n "${LANDO_UID}" ]; then + usermod -u "$LANDO_UID" "$LANDO_USER" + if [ -n "${LANDO_GID}" ]; then + usermod -g "$LANDO_GID" "$LANDO_USER" + fi + find / \ + \( -path /proc -o -path /sys -o -path /dev \) -prune -o \ + -user "$ouid" -exec chown -h "$LANDO_UID" {} + 2>/dev/null + fi +else + # User does not exist, create new user + debug "creating new user ${LANDO_USER}:${LANDO_UID}:${LANDO_GID}" + if [ -z "${LANDO_GID}" ] && [ -z "${LANDO_UID}" ]; then + useradd -l -m "$LANDO_USER" + elif [ -z "${LANDO_GID}" ] && [ -n "${LANDO_UID}" ]; then + useradd -l -u "$LANDO_UID" -m "$LANDO_USER" + elif [ -n "${LANDO_GID}" ] && [ -n "${LANDO_UID}" ]; then + useradd -l -u "$LANDO_UID" -m -g "$LANDO_GID" "$LANDO_USER" + fi +fi diff --git a/packages/user/install-useradd.sh b/packages/user/install-useradd.sh new file mode 100755 index 000000000..f948497b0 --- /dev/null +++ b/packages/user/install-useradd.sh @@ -0,0 +1,29 @@ +#!/bin/lash +set -eo pipefail + +# make sure we have user add +if [ ! -x "$(command -v useradd)" ]; then + case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk add shadow + ;; + apt) + apt install -y passwd + ;; + dnf) + dnf install -y shadow-utils + ;; + microdnf) + microdnf install shadow-utils + ;; + pacman) + pacman -Sy --noconfirm shadow + ;; + yum) + yum install -y shadow-utils + ;; + *) + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not install useradd!" + ;; + esac +fi diff --git a/packages/user/user.js b/packages/user/user.js new file mode 100644 index 000000000..f242663d1 --- /dev/null +++ b/packages/user/user.js @@ -0,0 +1,18 @@ +'use strict'; + +const path = require('path'); + +module.exports = async (service, user) => { + service.addLSF(path.join(__dirname, 'add-user.sh')); + service.addHookFile(path.join(__dirname, 'install-useradd.sh'), {hook: 'boot', priority: 10}); + service.addSteps({group: 'setup-user', instructions: ` + RUN /etc/lando/add-user.sh ${require('../../utils/parse-v4-pkginstall-opts')(user)}`, + }); + service.addLandoServiceData({ + environment: { + LANDO_USER: user.name, + LANDO_GID: user.gid, + LANDO_UID: user.uid, + }, + }); +}; diff --git a/plugins/healthcheck/hooks/app-add-healthchecks.js b/plugins/healthcheck/hooks/app-add-healthchecks.js index beaa3446b..0f25a70a6 100644 --- a/plugins/healthcheck/hooks/app-add-healthchecks.js +++ b/plugins/healthcheck/hooks/app-add-healthchecks.js @@ -71,6 +71,10 @@ module.exports = async app => { .groupBy('container') .map(checks => checks[0]) .filter(check => !require('../../../utils/is-disabled')(check.command)) + .filter(check => { + const info = app.info.find(data => data.service === check.service); + return info.error === undefined; + }) .value(); // put into checks format diff --git a/plugins/networking/index.js b/plugins/networking/index.js index 1ac940d22..8368ab7f8 100644 --- a/plugins/networking/index.js +++ b/plugins/networking/index.js @@ -43,42 +43,73 @@ const cleanNetworks = lando => lando.engine.getNetworks() }); module.exports = lando => { + const debug = require('../../utils/debug-shim')(lando.log); + // Preemptively make sure we have enough networks and if we don't smartly prune some of them lando.events.on('pre-engine-start', 1, () => cleanNetworks(lando)); - // Assess whether we need to upgrade the lando network or not - lando.events.on('pre-engine-start', 2, () => { - if (lando.versions.networking === 1) { - lando.log.warn('Version %s Landonet detected, attempting upgrade...', lando.versions.networking); - const landonet = lando.engine.getNetwork(lando.config.networkBridge); - // Remove the old network - return landonet.inspect() - .then(data => _.keys(data.Containers)) - .each(id => landonet.disconnect({Container: id, Force: true})) - .then(() => landonet.remove()) - .catch(err => { - lando.log.verbose('Error inspecting lando_bridge_network, probably does not exit yet'); - lando.log.debug(err); - }); - } - }); + // Add network add task + lando.events.once('pre-setup', async options => { + // skip the installation of the network if set + if (options.skipNetworking) return; + + options.tasks.push({ + title: `Creating Landonet`, + id: 'create-landonet', + dependsOn: ['setup-build-engine'], + description: '@lando/landonet', + comments: { + 'NOT INSTALLED': 'Will create Landonet', + }, + hasRun: async () => { + // if docker isnt even installed then this is easy + if (lando.engine.dockerInstalled === false) return false; + + // otherwise attempt to sus things out + try { + const landonet = lando.engine.getNetwork(lando.config.networkBridge); + await lando.engine.daemon.up(); + await landonet.inspect(); + return lando.versions.networking > 1; + } catch (error) { + debug('looks like there isnt a landonet yet %o %o', error.message, error.stack); + return false; + } + }, + task: async (ctx, task) => { + // we reinstantiate instead of using lando.engine.daemon so we can ensure an up-to-date docker bin + const LandoDaemon = require('../../lib/daemon'); + const daemon = new LandoDaemon(lando.cache, lando.events, undefined, lando.log); + + // we need docker up for this + await daemon.up(); + + // if we are v1 then disconnect and remove for upgrade + if (lando.versions.networking === 1) { + const landonet = lando.engine.getNetwork(lando.config.networkBridge); + await landonet.inspect() + .then(data => _.keys(data.Containers)) + .each(id => landonet.disconnect({Container: id, Force: true})) + .then(() => landonet.remove()) + .catch(error => { + debug('error disconnecting from old landonet %o %o', error.message, error.stack); + }); + } - // Make sure we have a lando bridge network - // We do this here so we can take advantage of docker up assurancs in engine.js - // and to make sure it covers all non-app services - lando.events.on('pre-engine-start', 3, () => { - // Let's get a list of network - return lando.engine.getNetworks() - // Try to find our net - .then(networks => _.some(networks, network => network.Name === lando.config.networkBridge)) - // Create if needed and set our network version number - .then(exists => { - if (!exists) { - return lando.engine.createNetwork(lando.config.networkBridge).then(() => { - lando.cache.set('versions', _.merge({}, lando.versions, {networking: 2}), {persist: true}); - lando.versions = lando.cache.get('versions'); - }); - } + // create landonet2 + await lando.engine.getNetworks() + .then(networks => _.some(networks, network => network.Name === lando.config.networkBridge)) + .then(exists => { + if (!exists) { + return lando.engine.createNetwork(lando.config.networkBridge).then(() => { + lando.cache.set('versions', _.merge({}, lando.versions, {networking: 2}), {persist: true}); + lando.versions = lando.cache.get('versions'); + debug('created %o with version info %o', lando.config.networkBridge, lando.versions.networking); + }); + } + }); + task.title = 'Created Landonet'; + }, }); }); diff --git a/plugins/proxy/app.js b/plugins/proxy/app.js index 17299f0ff..b21a53a2c 100644 --- a/plugins/proxy/app.js +++ b/plugins/proxy/app.js @@ -36,6 +36,12 @@ const getAllPorts = (noHttp = false, noHttps = false, config) => { return _.flatten(ports).join(', '); }; +const hasCerts = (app, id) => { + const info = app.info.find(service => service.service === id); + const v4 = _.get(app, 'v4.services', []).find(service => service.id === id); + return info?.hasCerts === true || v4?.certs !== false; +}; + /* * Helper to scanPorts */ @@ -91,9 +97,14 @@ module.exports = (app, lando) => { _.forEach(files, file => lando.yaml.dump(file.path, file.data)); }); - // Start and setup the proxy and services - app.events.on('pre-start', 1, () => { + app.events.on('pre-start', 1, async () => { + // generate proxy cert + await lando.generateCert('proxy._lando_', {domains: [ + `*.${lando.config.proxyDomain}`, + 'proxy._lando_.internal', + ]}); + // Determine what ports we need to discover const protocolStatus = utils.needsProtocolScan(lando.config.proxyCurrentPorts, lando.config.proxyLastPorts); // And then discover! @@ -123,7 +134,7 @@ module.exports = (app, lando) => { } // Get list of services that *should* have certs for SSL - const sslReady = _(_.get(app, 'config.services', {})) + const sslReady = _(_.get(app, 'config.services', [])) .map((data, name) => _.merge({}, data, {name})) .filter(data => data.ssl) .map(data => data.name) @@ -140,11 +151,17 @@ module.exports = (app, lando) => { // Add hasCerts to servedBys _.forEach(servedBy, name => { const service = _.find(app.info, {service: name}); - service.hasCerts = true; + if (service) service.hasCerts = true; }); + // get new v4 ssl ready services + const sslReadyV4 = _(_.get(app, 'v4.services', [])) + .filter(data => data.certs) + .map(data => data.id) + .value(); + // Parse config - return utils.parseConfig(app.config.proxy, _.compact(_.flatten([sslReady, servedBy]))); + return utils.parseConfig(app.config.proxy, _.compact(_.flatten([sslReady, servedBy, sslReadyV4]))); }) // Map to docker compose things @@ -201,7 +218,7 @@ module.exports = (app, lando) => { .flatMap(s => s.urls = _.uniq(s.urls.concat(utils.parse2Info( app.config.proxy[s.service], ports, - _.get(s, 'hasCerts', false), + hasCerts(app, s.service), )))) .value(); } diff --git a/plugins/proxy/builders/_proxy.js b/plugins/proxy/builders/_proxy.js index 4a08d446a..83a623f95 100644 --- a/plugins/proxy/builders/_proxy.js +++ b/plugins/proxy/builders/_proxy.js @@ -63,6 +63,7 @@ module.exports = { version: 'custom', type: 'traefix', name: 'proxy', + project: '_lando_', ssl: true, sslExpose: false, refreshCerts: true, diff --git a/plugins/proxy/scripts/proxy-certs.sh b/plugins/proxy/scripts/proxy-certs.sh index 9c53664de..66ae3b38f 100644 --- a/plugins/proxy/scripts/proxy-certs.sh +++ b/plugins/proxy/scripts/proxy-certs.sh @@ -56,8 +56,8 @@ if [ -f "$LANDO_PROXY_CERT" ] && [ -f "$LANDO_PROXY_KEY" ]; then cat > "$LANDO_PROXY_CONFIG_FILE" < { switch (state) { @@ -13,6 +13,8 @@ const getDefaultColor = state => { return 'green'; case 'FAILED': return 'red'; + case 'WAITING': + return 'yellow'; default: return 'dim'; } @@ -28,12 +30,14 @@ class DC2Renderer extends LandoRenderer { options.icon = {...options.icon, DOCKER_COMPOSE_HEADER: '[+]'}; options.taskCount = options.taskCount || 0; options.showErrorMessage = false; + options.spacer = options.spacer ?? 3; // dc2 state changes options.states = { COMPLETED: {message: 'Done', color: 'green'}, FAILED: {message: 'ERROR', color: 'red'}, STARTED: {message: 'Waiting', color: 'green'}, + WAITING: {message: 'Waiting', color: 'yellow'}, ...options.states, }; @@ -44,6 +48,15 @@ class DC2Renderer extends LandoRenderer { // super super(tasks, options, $renderHook); + + // normalize output data + for (const task of this.tasks) { + task.on(ListrTaskEventType.MESSAGE, message => { + if (message?.error && typeof message.error === 'string') { + message.error = message.error.trim(); + } + }); + } } create(options) { @@ -78,46 +91,43 @@ class DC2Renderer extends LandoRenderer { return render.join(EOL); } - getSpacer(size, max) { - if (!max || max === 0 || !Number.isInteger(max)) return ' '; - return require('lodash/range')(max - size + 3).map(s => '').join(' '); + getMax(tasks = []) { + if (tasks.length === 0) return 0; + + const lengths = tasks + .flatMap(task => task) + .filter(task => task.hasTitle() && typeof task.initialTitle === 'string') + .flatMap(task => ([ + task.initialTitle, + task?.title, + task?.message?.error, + ])) + .filter(data => typeof data === 'string') + .map(data => data.length); + + return Math.max(...lengths); } - renderer(tasks, level, max) { - // figure out the max if there is one - if (Array.isArray(tasks) && tasks.length > 1) { - const lengths = tasks - .flatMap(task => task) - .filter(task => task.hasTitle() && typeof task.initialTitle === 'string') - .map(task => task.initialTitle.length); - max = Math.max(...lengths); - } + getSpacer(data = '', max = 0) { + data = require('strip-ansi')(data); + if (!max || max === 0 || !Number.isInteger(max)) return ' '; + return require('lodash/range')(max - data.trim().length + this.options.spacer).map(s => '').join(' '); + } - // loop through tasks and add our listener stuff - tasks.flatMap(task => { - if (task.hasTitle() && typeof task.initialTitle === 'string') { - // get the spacer - task.spacer = this.getSpacer(task.initialTitle.length, max); - - // update title based on state change - for (const [state, data] of Object.entries(this.options.states)) { - if (task.state === state) { - task.title = `${task.initialTitle}${task.spacer}${color[data.color](data.message)}`; - } - // update error message on state fail - if (task.state === state - && task.state === 'FAILED' - && task?.message?.error - && !task.message.error.endsWith(color[data.color](data.message)) - ) { - task.message.error = `${task.message.error}${task.spacer}${color[data.color](data.message)}`; - } - } - } + renderer(tasks, level, max = 0) { + // get output + const output = super.renderer(tasks, level); + + // hack output to emulate DC style stuff + output.flatMap((line, index) => { + const task = tasks.filter(task => task.enabled)[index]; + const vibe = this.options.states[task.state] ?? this.options.states['STARTED']; + task.spacer = this.getSpacer(task?.message?.error ?? task.title ?? task.initialTitle, this.getMax(tasks)); + task.status = color[vibe.color](vibe.message); + output[index] = `${line}${task.spacer}${task.status}`; }); - // pass up - return super.renderer(tasks, level); + return output; } renderHeader(header, count) { diff --git a/scripts/add-cert.sh b/scripts/add-cert.sh index 51d610491..4f4adfdd1 100755 --- a/scripts/add-cert.sh +++ b/scripts/add-cert.sh @@ -24,105 +24,41 @@ if [ $(id -u) != 0 ]; then fi # Vars and defaults -: ${LANDO_DOMAIN:="lndo.site"} -: ${COMMON_NAME:="${LANDO_APP_COMMON_NAME}"} -: ${LANDO_CA_CERT:="/lando/certs/lndo.site.pem"} -: ${LANDO_CA_KEY:="/lando/certs/lndo.site.key"} -: ${LANDO_EXTRA_NAMES}:=""} -: ${LANDO_PROXY_NAMES}:=""} +: ${LANDO_CA_CERT:="/lando/certs/LandoCA.crt"} +: ${LANDO_CA_KEY:="/lando/certs/LandoCA.key"} : ${CA_DIR:="/usr/share/ca-certificates"} -: ${CA_CERT_FILENAME:="${LANDO_DOMAIN}.pem"} +: ${CA_CERT_FILENAME:="LandoCA.crt"} : ${CA_CERT_CONTAINER:="$CA_DIR/$CA_CERT_FILENAME"} # Make sure our cert directories exists mkdir -p /certs $CA_DIR -# Setup cert SANz -cat > /certs/cert.ext </dev/null; then - lando_info "Certs are not valid! Lets remove them." - rm -f /certs/cert.key - rm -f /certs/cert.csr - rm -f /certs/cert.crt - rm -f /certs/cert.pem -fi - -# Cert add heating up -lando_info "Cert creation kicking off...." -lando_info "" -lando_debug "==================================================" -lando_debug "LANDO_CA_CERT : $LANDO_CA_CERT" -lando_debug "LANDO_CA_KEY : $LANDO_CA_KEY" -lando_debug "CA_DIR : $CA_DIR" -lando_debug "CA_CERT_FILENAME : $CA_CERT_FILENAME" -lando_debug "CA_CERT_CONTAINER : $CA_CERT_CONTAINER" -lando_debug "COMMON_NAME : $COMMON_NAME" -lando_debug "LANDO_PROXY_NAMES : $LANDO_PROXY_NAMES" -lando_debug "LANDO_EXTRA_NAMES : $LANDO_EXTRA_NAMES" -lando_debug "==================================================" -lando_info "" - -lando_info "Generating certs..." -openssl genrsa -out /certs/cert.key 2048 -openssl req -new -key /certs/cert.key -out /certs/cert.csr -subj "/C=US/ST=California/L=San Francisco/O=Lando/OU=Bespin/CN=${COMMON_NAME}" -openssl x509 \ - -req \ - -in /certs/cert.csr \ - -CA $LANDO_CA_CERT \ - -CAkey $LANDO_CA_KEY \ - -CAcreateserial \ - -out /certs/cert.crt \ - -days 825 \ - -sha256 \ - -extfile /certs/cert.ext +# @TODO: create a combined CA if the older DOMANCERT still exists? +# @TODO: just print the entire /lando/certs and /certs dirs? # Pemify cat /certs/cert.crt /certs/cert.key > /certs/cert.pem -# Also copy to our persistent cert volume -cp -f /certs/cert.crt "/lando/certs/${LANDO_SERVICE_NAME}.${LANDO_APP_PROJECT}.crt" -cp -f /certs/cert.key "/lando/certs/${LANDO_SERVICE_NAME}.${LANDO_APP_PROJECT}.key" + # This is a weird hack to handle recent changes to bitnami's apache image without causing # breaking changes cp -f /certs/cert.crt /certs/server.crt cp -f /certs/cert.key /certs/server.key + # Set the cert and key on host to host-uid/gid ownership -chown "$LANDO_HOST_UID:$LANDO_HOST_GID" "/lando/certs/${LANDO_SERVICE_NAME}.${LANDO_APP_PROJECT}.crt" -chown "$LANDO_HOST_UID:$LANDO_HOST_GID" "/lando/certs/${LANDO_SERVICE_NAME}.${LANDO_APP_PROJECT}.key" +chown "$LANDO_HOST_UID:$LANDO_HOST_GID" "$LANDO_SERVICE_CERT" +chown "$LANDO_HOST_UID:$LANDO_HOST_GID" "$LANDO_SERVICE_KEY" # Trust our root CA if [ ! -f "$CA_CERT_CONTAINER" ]; then diff --git a/scripts/boot.sh b/scripts/boot.sh new file mode 100755 index 000000000..7fa25d864 --- /dev/null +++ b/scripts/boot.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -e + +# this is pre-lash so we need to source directly +. /etc/lando/utils.sh + +# run updates +/etc/lando/install-updates.sh + +# see if bash exists +if [ ! -x "$(command -v bash)" ]; then + /etc/lando/install-bash.sh +fi + +# Run /etc/lando/build/image/boot.d scripts +/etc/lando/run-hooks.sh image boot + +log "boot completed" diff --git a/scripts/docker-desktop-start.ps1 b/scripts/docker-desktop-start.ps1 index 98b503919..3125826a9 100644 --- a/scripts/docker-desktop-start.ps1 +++ b/scripts/docker-desktop-start.ps1 @@ -7,63 +7,63 @@ $mutex = New-Object System.Threading.Mutex($false, $mutexName) $lockAcquired = $false try { - $lockAcquired = $mutex.WaitOne(0, $false) - if (-not $lockAcquired) { - Write-Output "Another instance of the script is already starting Docker Desktop." - exit 0 - } + $lockAcquired = $mutex.WaitOne(0, $false) + if (-not $lockAcquired) { + Write-Output "Another instance of the script is already starting Docker Desktop." + exit 0 + } - $AppName = "Docker Desktop" - $app = "shell:AppsFolder\$((Get-StartApps $AppName | Select-Object -First 1).AppId)" - $startMenuPath = [System.Environment]::GetFolderPath("CommonStartMenu") - $shortcutPath = Join-Path $startMenuPath "Docker Desktop.lnk" - $programFiles = [System.Environment]::GetFolderPath("ProgramFiles") - $exePath = Join-Path $programFiles "Docker\Docker\Docker Desktop.exe" + $AppName = "Docker Desktop" + $app = "shell:AppsFolder\$((Get-StartApps $AppName | Select-Object -First 1).AppId)" + $startMenuPath = [System.Environment]::GetFolderPath("CommonStartMenu") + $shortcutPath = Join-Path $startMenuPath "Docker Desktop.lnk" + $programFiles = [System.Environment]::GetFolderPath("ProgramFiles") + $exePath = Join-Path $programFiles "Docker\Docker\Docker Desktop.exe" - if (Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue) { - Write-Output "Docker Desktop is already running." - exit 0 - } + if (Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue) { + Write-Output "Docker Desktop is already running." + exit 0 + } - function Start-App($path) { - Write-Debug "Attempting to start $path" - if (Test-Path $path) { - Start-Process $path - return $true - } - return $false + function Start-App($path) { + Write-Debug "Attempting to start $path" + if (Test-Path $path) { + Start-Process $path + return $true } + return $false + } - # Try to start via the App, Start Menu shortcut, then Program Files - if (!(Start-App $app) -and !(Start-App $shortcutPath) -and !(Start-App $exePath)) { - Write-Output "Docker Desktop could not be started. Please check the installation." - exit 1 - } + # Try to start via the App, Start Menu shortcut, then Program Files + if (!(Start-App $app) -and !(Start-App $shortcutPath) -and !(Start-App $exePath)) { + Write-Output "Docker Desktop could not be started. Please check the installation." + exit 1 + } # Wait for Docker Desktop to start (Lando only waits 25 seconds before giving up) - $timeout = 25 - for ($i = $timeout; $i -gt 0; $i--) { - if (($i % 5) -eq 0) { Write-Debug "Waiting for Docker Desktop to start ($i seconds remaining)" } - Start-Sleep -Seconds 1 + $timeout = 25 + for ($i = $timeout; $i -gt 0; $i--) { + if (($i % 5) -eq 0) { Write-Debug "Waiting for Docker Desktop to start ($i seconds remaining)" } + Start-Sleep -Seconds 1 - if (Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue) { - try { - docker info -f '{{.ServerVersion}}' 2>$null | Out-Null - Write-Host "Docker Desktop is running." - break - } catch { - # Ignore error - } - } + if (Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue) { + try { + docker info -f '{{.ServerVersion}}' 2>$null | Out-Null + Write-Host "Docker Desktop is running." + break + } catch { + # Ignore error + } } + } } catch { - Write-Error "An error occurred: $_" - exit 1 + Write-Error "An error occurred: $_" + exit 1 } finally { - if ($lockAcquired) { - $mutex.ReleaseMutex() - } - $mutex.Dispose() + if ($lockAcquired) { + $mutex.ReleaseMutex() + } + $mutex.Dispose() } diff --git a/scripts/docker-engine-start.sh b/scripts/docker-engine-start.sh old mode 100644 new mode 100755 diff --git a/scripts/docker-engine-stop.sh b/scripts/docker-engine-stop.sh old mode 100644 new mode 100755 diff --git a/scripts/environment.sh b/scripts/environment.sh new file mode 100755 index 000000000..132f5bb37 --- /dev/null +++ b/scripts/environment.sh @@ -0,0 +1,82 @@ +#!/bin/sh +. /etc/lando/utils.sh + +# Path to the os-release file +OS_RELEASE_FILE="/etc/os-release" + +# Check if the os-release file exists +if [ ! -f "$OS_RELEASE_FILE" ]; then + abort "$OS_RELEASE_FILE not found." +fi + +export LANDO_LINUX_DISTRO=$(grep -E '^ID=' "$OS_RELEASE_FILE" | cut -d '=' -f 2 | tr -d '"') +export LANDO_LINUX_DISTRO_LIKE=$(grep -E '^ID_LIKE=' "$OS_RELEASE_FILE" | cut -d '=' -f 2 | tr -d '"') + +# Find correct package manager based on DISTRO +case "$LANDO_LINUX_DISTRO" in + alpine) + export LANDO_LINUX_PACKAGE_MANAGER="apk" + ;; + arch|archarm|manjaro) + export LANDO_LINUX_PACKAGE_MANAGER="pacman" + ;; + centos) + export LANDO_LINUX_PACKAGE_MANAGER="yum" + ;; + debian|pop|ubuntu) + export LANDO_LINUX_PACKAGE_MANAGER="apt" + ;; + fedora) + export LANDO_LINUX_PACKAGE_MANAGER="dnf" + ;; + ol) + export LANDO_LINUX_PACKAGE_MANAGER="microdnf" + ;; + *) + abort "$LANDO_LINUX_DISTRO not supported! Could not locate package manager!" + ;; +esac + +# Use PACKAGE_MANAGER env var if available, argument if not +if ! command -v "$LANDO_LINUX_PACKAGE_MANAGER" > /dev/null 2>&1; then + abort "$LANDO_LINUX_PACKAGE_MANAGER could not be found." +fi + +debug LANDO_LINUX_DISTRO="$LANDO_LINUX_DISTRO" +debug LANDO_LINUX_DISTRO_LIKE="$LANDO_LINUX_DISTRO_LIKE" +debug LANDO_LINUX_PACKAGE_MANAGER="$LANDO_LINUX_PACKAGE_MANAGER" + +# unset some build and legacy stuff just to keep LANDO_* slim +# @NOTE: is it a mistake to remove some of these? +unset BITNAMI_DEBUG +unset LANDO_APP_COMMON_NAME +unset LANDO_APP_NAME +unset LANDO_APP_PROJECT +unset LANDO_APP_ROOT +unset LANDO_APP_ROOT_BIND +unset LANDO_CA_CERT +unset LANDO_CA_KEY +unset LANDO_CONFIG_DIR +unset LANDO_HOST_HOME +unset LANDO_IMAGE_GROUP +unset LANDO_IMAGE_USER +unset LANDO_INFO +unset LANDO_LOAD_KEYS +unset LANDO_MOUNT +unset LANDO_PROXY_NAMES +unset LANDO_PROXY_PASSTHRU +unset LANDO_WEBROOT_GROUP +unset LANDO_WEBROOT_USER + +# envvar so we can test if this loaded +export LANDO_ENVIRONMENT="loaded" + +# Execute sh scripts in /etc/lando/env.d +for script in /etc/lando/env.d/*.sh; do + if [ -e "$script" ]; then + if [ -r "$script" ] && [ -f "$script" ]; then + debug "Sourcing $script" + . "$script" + fi + fi +done diff --git a/scripts/exec.sh b/scripts/exec.sh new file mode 100755 index 000000000..967fa8c55 --- /dev/null +++ b/scripts/exec.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# load in any dynamically set envvars +source /etc/lando/environment + +# List of characters that exec cannot handle directly +SPECIAL_CHARS='&&|\||;|\$|\`|>|>>|<|2>|&>' + +# Join all arguments into a single string +CMD="$*" + +# Check if the command contains any special characters +if echo "$CMD" | grep -qE "$SPECIAL_CHARS"; then + exec sh -c "$CMD" +else + exec "$@" +fi diff --git a/scripts/install-bash.sh b/scripts/install-bash.sh new file mode 100755 index 000000000..c493bfcd4 --- /dev/null +++ b/scripts/install-bash.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +# this is pre-lash so we need to source directly +. /etc/lando/environment + +# if bash exists then we can skip +if [ -x "$(command -v bash)" ]; then + exit 0 +fi + +case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk add bash + ;; + apt) + apt install -y bash + ;; + dnf) + dnf install -y bash + ;; + microdnf) + microdnf install bash + ;; + pacman) + pacman -Sy --noconfirm bash + ;; + yum) + yum install -y bash + ;; + *) + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not install bash!" + ;; +esac diff --git a/scripts/install-docker-desktop.ps1 b/scripts/install-docker-desktop.ps1 index 8e385b5d0..0b8d41d0e 100644 --- a/scripts/install-docker-desktop.ps1 +++ b/scripts/install-docker-desktop.ps1 @@ -27,9 +27,8 @@ Write-Debug "DEBUG: $debug" # validation # @TODO: check if installer exists on fs? -if ([string]::IsNullOrEmpty($installer)) -{ - throw "You must pass in an -installer!" +if ([string]::IsNullOrEmpty($installer)) { + throw "You must pass in an -Installer!" } # Start arg stuff diff --git a/scripts/install-docker-desktop.sh b/scripts/install-docker-desktop.sh old mode 100644 new mode 100755 diff --git a/scripts/install-docker-engine.sh b/scripts/install-docker-engine.sh old mode 100644 new mode 100755 index 2c7178264..8b2195e82 --- a/scripts/install-docker-engine.sh +++ b/scripts/install-docker-engine.sh @@ -57,7 +57,3 @@ sh "$INSTALLER" --version "$VERSION" # add group groupadd docker || true - -# enable system start? -# systemctl enable docker.service || true -# systemctl enable containerd.service || true diff --git a/scripts/install-system-ca-linux.sh b/scripts/install-system-ca-linux.sh new file mode 100755 index 000000000..25afd77c3 --- /dev/null +++ b/scripts/install-system-ca-linux.sh @@ -0,0 +1,138 @@ +#!/bin/bash +set -eo pipefail + +CA="~/.lando/certs/LandoCA.crt" +DEBUG=0 + +debug() { + if [ "${DEBUG}" == 1 ]; then printf '%s\n' "$1" >&2; fi +} + +# PARSE THE ARGZZ +while (( "$#" )); do + case "$1" in + -c|--ca) + CA="$2" + shift 2 + ;; + -c=*|--ca=*) + CA="${1#*=}" + shift + ;; + --debug) + DEBUG=1 + shift + ;; + --) + shift + break + ;; + -*|--*=) + shift + ;; + *) + shift + ;; + esac +done + +# debug +debug "running script with:" +debug "CA: $CA" +debug "CI: ${CI:-}" +debug "DEBUG: $DEBUG" + +# Path to the os-release file +OS_RELEASE_FILE="/etc/os-release" + +# Check if the os-release file exists +if [ ! -f "$OS_RELEASE_FILE" ]; then + echo "$OS_RELEASE_FILE not found." >&2 + exit 1 +fi + +export LANDO_LINUX_DISTRO=$(grep -E '^ID=' "$OS_RELEASE_FILE" | cut -d '=' -f 2 | tr -d '"') +export LANDO_LINUX_DISTRO_LIKE=$(grep -E '^ID_LIKE=' "$OS_RELEASE_FILE" | cut -d '=' -f 2 | tr -d '"') + +# Find correct package manager based on DISTRO +case "$LANDO_LINUX_DISTRO" in + alpine) + export LANDO_LINUX_PACKAGE_MANAGER="apk" + ;; + arch|archarm|manjaro) + export LANDO_LINUX_PACKAGE_MANAGER="pacman" + ;; + centos) + export LANDO_LINUX_PACKAGE_MANAGER="yum" + ;; + debian|pop|ubuntu) + export LANDO_LINUX_PACKAGE_MANAGER="apt" + ;; + fedora) + export LANDO_LINUX_PACKAGE_MANAGER="dnf" + ;; + ol) + export LANDO_LINUX_PACKAGE_MANAGER="microdnf" + ;; + *) + echo "$LANDO_LINUX_DISTRO not supported! Could not locate package manager!" >&2 + exit 1 + ;; +esac + +# Use PACKAGE_MANAGER env var if available, argument if not +if ! command -v "$LANDO_LINUX_PACKAGE_MANAGER" > /dev/null 2>&1; then + echo "$LANDO_LINUX_PACKAGE_MANAGER could not be found." >&2 + exit 1 +fi + +debug LANDO_LINUX_DISTRO="$LANDO_LINUX_DISTRO" +debug LANDO_LINUX_DISTRO_LIKE="$LANDO_LINUX_DISTRO_LIKE" +debug LANDO_LINUX_PACKAGE_MANAGER="$LANDO_LINUX_PACKAGE_MANAGER" + +# make sure we have needed commandz +if [ ! -x "$(command -v update-ca-certificates)" ]; then + case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk add ca-certificates + ;; + apt) + apt install -y ca-certificates + ;; + pacman) + pacman -Sy --noconfirm ca-certificates-utils + ;; + esac +fi + +if [ ! -x "$(command -v update-ca-trust)" ]; then + case $LANDO_LINUX_PACKAGE_MANAGER in + dnf) + dnf install -y ca-certificates + ;; + microdnf) + microdnf install ca-certificates + ;; + yum) + yum install -y ca-certificates + ;; + esac +fi + +# abort if we cannot install the things we need +if [ ! -x "$(command -v update-ca-certificates)" ] && [ ! -x "$(command -v update-ca-trust)" ]; then + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not install ca-certs!" +fi + +# move all cas to the correct place and update trust +case $LANDO_LINUX_PACKAGE_MANAGER in + dnf|microdnf|yum) + cp -r "$CA" /etc/pki/ca-trust/source/anchors/ + update-ca-trust + ;; + *) + cp -r "$CA" /usr/local/share/ca-certificates/ + update-ca-certificates + ;; +esac + diff --git a/scripts/install-system-ca-macos.sh b/scripts/install-system-ca-macos.sh new file mode 100755 index 000000000..b462b8699 --- /dev/null +++ b/scripts/install-system-ca-macos.sh @@ -0,0 +1,88 @@ +#!/bin/bash +set -eo pipefail + +CA="~/.lando/certs/LandoCA.crt" +DEBUG=0 +FINGERPRINT= +KEYCHAIN="$(security login-keychain | sed 's/^ *"//;s/" *$//' | xargs)" +NONINTERACTIVE=0 + +debug() { + if [ "${DEBUG}" == 1 ]; then printf '%s\n' "$1" >&2; fi +} + +# PARSE THE ARGZZ +while (( "$#" )); do + case "$1" in + -c|--ca) + CA="$2" + shift 2 + ;; + -c=*|--ca=*) + CA="${1#*=}" + shift + ;; + --debug) + DEBUG=1 + shift + ;; + -f|--fingerprint) + FINGERPRINT="$2" + shift 2 + ;; + -f=*|--fingerprint=*) + FINGERPRINT="${1#*=}" + shift + ;; + -k|--keychain) + KEYCHAIN="$2" + shift 2 + ;; + -k=*|--keychain=*) + KEYCHAIN="${1#*=}" + shift + ;; + --non-interactive) + NONINTERACTIVE=1 + shift + ;; + --) + shift + break + ;; + -*|--*=) + shift + ;; + *) + shift + ;; + esac +done + +# debug +debug "running script with:" +debug "CA: $CA" +debug "CI: ${CI:-}" +debug "DEBUG: $DEBUG" +debug "FINGERPRINT: $FINGERPRINT" +debug "KEYCHAIN: $KEYCHAIN" +debug "NONINTERACTIVE: $NONINTERACTIVE" + +# force noninteractive in CI +if [[ -n "${CI-}" && "$NONINTERACTIVE" == "0" ]]; then + debug "running in non-interactive mode because CI=${CI} is set." + NONINTERACTIVE=1 +fi + +# suppress GUI prompt in non interactive situations +if [[ "$NONINTERACTIVE" == "1" ]]; then + debug "disabling password popup because in noninteractive mode" + sudo security authorizationdb write com.apple.trust-settings.user allow +fi + +# add CA to default login keychain +security add-trusted-cert \ + -r trustRoot \ + -k "$KEYCHAIN" \ + "$CA" \ + || (security delete-certificate -Z "$FINGERPRINT" -t "$KEYCHAIN" && exit 1) diff --git a/scripts/install-system-ca-win32.ps1 b/scripts/install-system-ca-win32.ps1 new file mode 100644 index 000000000..7c8882fa7 --- /dev/null +++ b/scripts/install-system-ca-win32.ps1 @@ -0,0 +1,63 @@ +#!/ + +# handle params +# @NOTE: we omit DEBUG as a param because its "built in" +[CmdletBinding(PositionalBinding=$false)] +Param( + [string]$ca, + [switch]$noninteractive = $false +) + +# error handling +$ErrorActionPreference = "Stop" + +# handle uncaught errorz +trap { + Write-Error "An unhandled error occurred: $_" + exit 1 +} + +# validation +# @TODO: check if installer exists on fs? +if ([string]::IsNullOrEmpty($ca)) { + throw "You must pass in a -CA!" +} + +# enable debugging if debug is true +$DebugPreference = If ($DebugPreference -eq "Inquire") {"Continue"} Else {"SilentlyContinue"} +$debug = If ($DebugPreference -eq "Continue") {$true} Else {$false} +Write-Debug "running script with:" +Write-Debug "CA: $ca" +Write-Debug "CI: $env:CI" +Write-Debug "DEBUG: $debug" +Write-Debug "NONINTERACTIVE: $noninteractive" + +# if we are in CI then reset non-interactive to true +if ($env:CI) { + $noninteractive = $true + Write-Debug "Running in non-interactive mode because CI=$env:CI is set." +} + +# if non-interactive eg we are probably on CI lets just powershell it out as admin +if ($noninteractive -eq $true) { + # start the process with elevated permissions + $p = Start-Process -FilePath certutil.exe -ArgumentList "-addstore Root `"$ca`"" -Verb RunAs -Wait -PassThru + Write-Debug "Process finished with return code: $($p.ExitCode)" + + # if there is an error then throw here + if ($($p.ExitCode) -ne 0) {throw "CA install failed! Rerun setup with --debug or -vvv for more info!"} + +# otherwise we can add directly +} else { + # read the certificate + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 + $cert.Import($ca) + # add it to the store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "Root", "CurrentUser" + $store.Open("ReadWrite") + $store.Add($cert) + $store.Close() +} + +# Debug +Write-Output "Certificate added to the Trusted Root Certification Authorities store for the current user." diff --git a/scripts/install-updates.sh b/scripts/install-updates.sh new file mode 100755 index 000000000..723119e75 --- /dev/null +++ b/scripts/install-updates.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +set -e + +# this is pre-lash so we need to source directly +. /etc/lando/environment + +case $LANDO_LINUX_PACKAGE_MANAGER in + apk) + apk update + ;; + apt) + apt -y update + ;; + dnf) + dnf -y update + ;; + microdnf) + microdnf update + ;; + pacman) + pacman -Syu --noconfirm + ;; + yum) + yum -y update + ;; + *) + abort "$LANDO_LINUX_PACKAGE_MANAGER not supported! Could not run package updates!" + ;; +esac diff --git a/scripts/landorc b/scripts/landorc new file mode 100755 index 000000000..09a5da42d --- /dev/null +++ b/scripts/landorc @@ -0,0 +1,12 @@ +#!/bin/bash + +source /etc/lando/environment + +# Execute sh scripts in /etc/lando/boot.d +for script in /etc/lando/lash.d/*.sh; do + if [ -e "$script" ]; then + if [ -r "$script" ] && [ -f "$script" ]; then + source "$script" + fi + fi +done diff --git a/scripts/lash b/scripts/lash new file mode 100755 index 000000000..97a7fadb6 --- /dev/null +++ b/scripts/lash @@ -0,0 +1,15 @@ +#!/bin/bash + +# if arg 1 is a file then eval its contents +if [ -f "$1" ]; then + # get the file and shift + file="$1" + shift + # source and eval + . /etc/lando/landorc + eval "$(cat "$file")" +# otherwise pass everything through +else + export BASH_ENV="/etc/lando/landorc" + exec /bin/bash -c "$*" +fi diff --git a/scripts/refresh-certs.sh b/scripts/refresh-certs.sh index 78159ca54..33694cd59 100755 --- a/scripts/refresh-certs.sh +++ b/scripts/refresh-certs.sh @@ -16,11 +16,6 @@ fi # Set the module LANDO_MODULE="refreshcerts" -# Run add certs if we need to -if [ ! -f "/certs/cert.pem" ]; then - /helpers/add-cert.sh -fi - # Check if update-ca-certificates is installed, if not install it and update our trusted certs # # The logic here is not 100% solid. We are assuming if you don't have update-ca-certificates available diff --git a/scripts/run-hooks.sh b/scripts/run-hooks.sh new file mode 100755 index 000000000..d0f1bd5ae --- /dev/null +++ b/scripts/run-hooks.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -eo pipefail + +# this is pre-lash so we need to source directly +. /etc/lando/utils.sh + +# get the stage and hook +STAGE="${1:-image}" +HOOK="${2:-boot}" + +# run hook scripts +if [ -d "/etc/lando/build/${STAGE}/${HOOK}.d" ]; then + debug "$(ls -lsa /etc/lando/build/${STAGE}/${HOOK}.d)" + debug "${tty_magenta}$LANDO_SERVICE_NAME${tty_reset} running /etc/lando/build/${STAGE}/${HOOK}.d scripts" + for script in /etc/lando/build/${STAGE}/${HOOK}.d/*.sh; do + if [ -e "$script" ]; then + if [ -r "$script" ] && [ -f "$script" ]; then + debug "${tty_magenta}$LANDO_SERVICE_NAME${tty_reset} running hook $script" + "$script" + else + debug "${tty_magenta}$LANDO_SERVICE_NAME${tty_reset} skipping hook $script, not readable or not a file" + fi + fi + done + + # Unset the variable after use + unset script + debug "${tty_magenta}$LANDO_SERVICE_NAME${tty_reset} completed $STAGE $HOOK hooks" +fi diff --git a/scripts/setup-ca.sh b/scripts/setup-ca.sh deleted file mode 100755 index 0fb852f19..000000000 --- a/scripts/setup-ca.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/sh - -set -e - -# Set defaults -: ${SILENT:=$1} - -# Echo helper to recognize silence -if [ "$SILENT" = "--silent" ]; then - LANDO_QUIET="yes" -fi - -# Get the lando logger -. /helpers/log.sh - -# Set the module -LANDO_MODULE="setupca" - -# Default CAs -: ${LANDO_CA_CERT:='/certs/lndo.site.pem'} -: ${LANDO_CA_KEY:='/certs/lndo.site.key'} - -# Let's log some helpful things -lando_info "Looks like you do not have a Lando CA yet! Let's set one up!" -lando_info "Trying to setup root CA with..." -lando_info "LANDO_CA_CERT: $LANDO_CA_CERT" -lando_info "LANDO_CA_KEY: $LANDO_CA_KEY" - -# Set get the key ready -if [ ! -f "$LANDO_CA_KEY" ]; then - lando_info "$LANDO_CA_KEY not found... generating one" - openssl genrsa -out $LANDO_CA_KEY 2048 -fi - -# Set up a CA for lando things -if [ ! -f "$LANDO_CA_CERT" ]; then - # Log - lando_info "$LANDO_CA_CERT not found... generating one" - # Check if openssl is installed, it not install it - if ! [ -x "$(command -v openssl)" ]; then - lando_info "Installing needed dependencies..." - apt-get update -y && apt-get install openssl -y || apk add --no-cache openssl - fi - # Generate the cert - openssl req \ - -x509 \ - -new \ - -nodes \ - -key $LANDO_CA_KEY \ - -sha256 \ - -days 8675 \ - -out $LANDO_CA_CERT \ - -subj "/C=US/ST=California/L=San Francisco/O=Lando/OU=Bespin/CN=Lando Local CA" - # Set the cert and key on host to host-uid/gid ownership - chown "$LANDO_HOST_UID:$LANDO_HOST_GID" "$LANDO_CA_KEY" - chown "$LANDO_HOST_UID:$LANDO_HOST_GID" "$LANDO_CA_CERT" - # log - lando_info "CA generated at $LANDO_CA_CERT" -fi diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100755 index 000000000..1e6541ca5 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -eo pipefail + +# load in any dynamically set envvars +source /etc/lando/environment + +# exec +# TODO: lando banner? +debug "Executing start up command: $@" +exec "$@" diff --git a/scripts/utils.sh b/scripts/utils.sh new file mode 100755 index 000000000..d03681a0d --- /dev/null +++ b/scripts/utils.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +# Set lando debug +if [ "$DEBUG" = "1" ]; then + LANDO_DEBUG="--debug" +fi + +if [ -t 1 ]; then + tty_escape() { printf "\033[%sm" "$1"; } +else + tty_escape() { :; } +fi +tty_mkbold() { tty_escape "1;$1"; } +tty_mkdim() { tty_escape "2;$1"; } +tty_blue="$(tty_escape 34)" +tty_bold="$(tty_mkbold 39)" +tty_dim="$(tty_mkdim 39)" +tty_green="$(tty_escape 32)" +tty_magenta="$(tty_escape 35)" +tty_red="$(tty_mkbold 31)" +tty_reset="$(tty_escape 0)" +tty_underline="$(tty_escape "4;39")" +tty_yellow="$(tty_escape 33)" + +chomp() { + printf "%s" "${1%$'\n'}" +} + +shell_join() { + local arg + printf "%s" "${1:-}" + shift + for arg in "$@"; do + printf " " + printf "%s" "${arg// /\ }" + done +} + +# redefine this one +abort() { + printf "${tty_red}ERROR${tty_reset}: %s\n" "$(chomp "$1")" >&2 + exit 1 +} + +abort_multi() { + while read -r line; do + printf "${tty_red}ERROR${tty_reset}: %s\n" "$(chomp "$line")" >&2 + done + exit 1 +} + +debug() { + if [ -n "${LANDO_DEBUG-}" ]; then + printf "${tty_dim}debug${tty_reset} %s\n" "$(shell_join "$@")" >&2 + fi +} + +debug_multi() { + if [ -n "${LANDO_DEBUG-}" ]; then + while read -r line; do + debug "$1 $line" + done + fi +} + +log() { + printf "%s\n" "$(shell_join "$@")" +} + +retry() { + max_attempts=${MAX_ATTEMPTS-10} + initial_delay=${INITIAL_DELAY-1} + factor=${FACTOR-2} + attempt=1 + delay=$initial_delay + + while true; do + "$@" + local status=$? + if [ $status -eq 0 ]; then + return 0 + fi + + if [ $attempt -ge $max_attempts ]; then + debug "attempt $attempt failed and there are no more attempts left!" + return $status + fi + + debug "attempt $attempt failed! retrying in $delay seconds..." + sleep $delay + attempt=$((attempt + 1)) + delay=$((delay * factor)) + done +} + +warn() { + printf "${tty_yellow}warning${tty_reset}: %s\n" "$(chomp "$@")" >&2 +} + +warn_multi() { + while read -r line; do + warn "${line}" + done +} diff --git a/sources/github.js b/sources/github.js index e7aa213bc..6bd217957 100644 --- a/sources/github.js +++ b/sources/github.js @@ -103,7 +103,7 @@ module.exports = { label: 'github', options: lando => ({ 'github-auth': { - describe: 'A GitHub personal access token', + describe: 'Uses a GitHub personal access token', string: true, interactive: { type: 'list', @@ -124,7 +124,7 @@ module.exports = { }, }, 'github-repo': { - describe: 'GitHub git url', + describe: 'Uses the GitHub git url', string: true, interactive: { type: 'autocomplete', diff --git a/sources/remote.js b/sources/remote.js index 004d377c7..371ddce39 100644 --- a/sources/remote.js +++ b/sources/remote.js @@ -10,7 +10,7 @@ module.exports = { label: 'remote git repo or archive', options: lando => ({ 'remote-url': { - describe: 'The URL of your git repo or archive, only works when you set source to remote', + describe: 'Uses the URL of your git repo or archive, only works when you set source to remote', string: true, interactive: { type: 'input', @@ -26,7 +26,7 @@ module.exports = { }, 'remote-options': { default: '', - describe: 'Some options to pass into either the git clone or archive extract command', + describe: 'Passes options into either the git clone or archive extract command', string: true, }, }), diff --git a/tasks/config.js b/tasks/config.js index 00aae0325..809fbc686 100644 --- a/tasks/config.js +++ b/tasks/config.js @@ -6,15 +6,22 @@ module.exports = lando => ({ command: 'config', level: 'tasks', describe: 'Displays the lando configuration', + usage: '$0 config [--format ] [--path ]', + examples: [ + '$0 config --format table --path env', + ], options: _.merge({}, lando.cli.formatOptions(['filter']), { - // @NOTE: This is for backwards compat and needs to be removed field: { - describe: 'Show only a specific field', + describe: 'Shows only a specific field', hidden: true, string: true, }, }), run: options => { + // if options is a table then map it over to the new otable + if (options.format === 'table') options.format = 'otable'; + + // render if (!_.isNil(options.field)) options.path = options.field; console.log(lando.cli.formatData(lando.config, options, {sort: true})); }, diff --git a/tasks/destroy.js b/tasks/destroy.js index 286e36c5c..85479bdc5 100644 --- a/tasks/destroy.js +++ b/tasks/destroy.js @@ -4,6 +4,10 @@ module.exports = lando => { return { command: 'destroy', describe: 'Destroys your app', + usage: '$0 destroy [--yes]', + examples: [ + '$0 destroy --yes', + ], options: { yes: lando.cli.confirm('Are you sure you want to DESTROY?'), }, diff --git a/tasks/exec.js b/tasks/exec.js new file mode 100644 index 000000000..ae17eab3e --- /dev/null +++ b/tasks/exec.js @@ -0,0 +1,154 @@ +'use strict'; + +// Modules +const fs = require('fs'); +const path = require('path'); +const _ = require('lodash'); + +const {color} = require('listr2'); + +// @TODO: when we have a file for recipes/compose we can set choices on service + +module.exports = (lando, config = lando.appConfig) => ({ + command: 'exec', + describe: 'Runs command(s) on a service', + usage: '$0 exec [--user ] -- ', + override: true, + level: 'engine', + examples: [ + '$0 exec appserver -- lash bash', + '$0 exec nginx --user root -- whoami', + `$0 exec my-service -- "env && echo 'hello there!'"`, + `$0 exec worker -- "background-service &"`, + ], + positionals: { + service: { + describe: 'Runs on this service', + type: 'string', + choices: config?.allServices ?? [], + }, + }, + options: { + user: { + describe: 'Runs as a specific user', + alias: ['u'], + }, + }, + run: async options => { + // construct a minapp from various places + const minapp = !_.isEmpty(config) ? config : lando.appConfig; + + // if no app then we need to throw + if (!fs.existsSync(minapp.composeCache)) { + throw new Error('Could not detect a built app. Rebuild or move into the correct location!'); + } + + // Build a minimal app + const AsyncEvents = require('../lib/events'); + const app = lando.cache.get(path.basename(minapp.composeCache)); + + // augment + app.config = minapp; + app.events = new AsyncEvents(lando.log); + + // Load only what we need so we don't pay the appinit penalty + if (!_.isEmpty(_.get(app, 'config.events', []))) { + _.forEach(app.config.events, (cmds, name) => { + app.events.on(name, 9999, async data => await require('../hooks/app-run-events')(app, lando, cmds, data)); + }); + } + + // nice things + const aservices = app.config.allServices ?? []; + const choices = `[${color.green('choices:')} ${aservices.map(service => `"${service}"`).join(', ')}]`; + + // gather our options + options.service = options._[1]; + options.command = options['--']; + + // and validate + try { + // no service + if (!options.service) { + throw new Error('You must specific a service! See usage above.'); + } + + // not a valid service + if (!aservices.includes(options.service)) { + throw new Error(`Service must be one of ${choices}! See usage above.`); + } + + // empty or nonexistent command + if (!options.command || options.command.length === 0) { + throw new Error('You must specify a command! See usage above.'); + } + + // collect, usage throw + } catch (error) { + if (options?._yargs?.showHelp) options._yargs.showHelp(); + console.log(''); + throw error; + } + + // if command is a single thing then lets string argv that + // this is useful to handle wrapping more complex commands a la "cmd && cmd" + if (Array.isArray(options.command) && options.command.length === 1) { + options.command = require('string-argv')(options.command[0]); + } + + // if this service has /etc/lando/exec then prepend + if (app?.executors?.[options.service]) options.command.unshift('/etc/lando/exec.sh'); + + // spoof options we can pass into build tooling runner + const ropts = [ + app, + options.command, + options.service, + options.user ?? null, + { + DEBUG: lando.debuggy ? '1' : '', + LANDO_DEBUG: lando.debuggy ? '1' : '', + }, + ]; + + // ensure all v3 services have their appMount set to /app + // @TODO: do we still need this? + const v3Mounts = _(_.get(app, 'info', [])) + .filter(service => service.api !== 4) + .map(service => ([service.service, service.appMount || '/app'])) + .fromPairs() + .value(); + app.mounts = _.merge({}, v3Mounts, app.mounts); + + // and working dir data if no dir or appMount + ropts.push(app?.config?.services?.[options.service]?.working_dir); + // mix in mount if applicable + ropts.push(app?.mounts[options.service]); + + // emit pre-exec + await app.events.emit('pre-exec', config); + + // get tooling runner + const runner = require('../utils/build-tooling-runner')(...ropts); + + // try to run it + try { + await require('../utils/build-docker-exec')(lando, ['inherit', 'pipe', 'pipe'], runner); + + // error + } catch (error) { + return lando.engine.isRunning(runner.id).then(isRunning => { + if (!isRunning) { + throw new Error(`Looks like your app is stopped! ${color.bold('lando start')} it up to exec your heart out.`); + } else { + error.hide = true; + throw error; + } + }); + + // finally + } finally { + await app.events.emit('post-exec', config); + } + }, +}); diff --git a/tasks/info.js b/tasks/info.js index 460ae67dc..0a77a5018 100644 --- a/tasks/info.js +++ b/tasks/info.js @@ -2,44 +2,79 @@ const _ = require('lodash'); -// Helper to filter services -const filterServices = (service, services = []) => { - return !_.isEmpty(services) ? _.includes(services, service) : true; -}; - module.exports = lando => ({ command: 'info', describe: 'Prints info about your app', + usage: '$0 info [--deep] [--filter ...] [--format ] [--path ] [--service ...]', // eslint-disable-line max-len + examples: [ + '$0 info --deep', + '$0 info --format json --service appserver', + ], options: _.merge({}, lando.cli.formatOptions(), { deep: { - describe: 'Get ALL the info', + describe: 'Gets ALL the info', alias: ['d'], default: false, boolean: true, }, service: { - describe: 'Get info for only the specified services', + describe: 'Gets info for only the specified services', alias: ['s'], array: true, }, }), run: async options => { + // if options is a table then map it over to the new otable + if (options.format === 'table') options.format = 'otable'; + // Try to get our app const app = lando.getApp(options._app.root); - // Get services - app.opts = (!_.isEmpty(options.service)) ? {services: options.service} : {}; - // Go deep if we need to - if (app && options.deep) { - await app.init(); - await lando.engine.list({project: app.project}) - .filter(container => filterServices(container.service, options.service)) - .each(container => lando.engine.scan(container).then(data => console.log(lando.cli.formatData(data, options)))); - // otherwise just do the normal - } else if (app && !options.deep) { + // helper to get raw services data + const getData = async () => { + // go deep + if (options.deep) { + return await lando.engine.list({project: app.project}) + .map(async container => await lando.engine.scan(container)) + .filter(container => { + if (!options.service) return true; + return options.service.map(service => `/${app.project}_${service}_1`).includes(container.Name); + }); + + // normal info + } else { + return app.info.filter(service => options.service ? options.service.includes(service.service) : true); + } + }; + + // only continue if we have an app + if (app) { + // init await app.init(); - const data = _.filter(app.info, service => filterServices(service.service, options.service)); - console.log(lando.cli.formatData(data, options)); + // get the data + options.data = await getData(); + // and filter it if needed + if (options.filter) { + for (const filter of options.filter) { + options.data = _.filter(options.data, item => { + return String(_.get(item, filter.split('=')[0])) == filter.split('=')[1]; + }); + } + delete options.filter; + } + } + + // if we have a path and a single service then just do that + if (options.path && options.data.length === 1) { + console.log(lando.cli.formatData(options.data[0], options)); + + // if we do not have an otable then just print + } else if (options.format !== 'otable') { + console.log(lando.cli.formatData(options.data, options)); + + // otherwise iterate and print table info + } else { + for (const datum of options.data) console.log(lando.cli.formatData(datum, options)); } }, }); diff --git a/tasks/init.js b/tasks/init.js index b1006fabd..0c10b6dfa 100644 --- a/tasks/init.js +++ b/tasks/init.js @@ -65,7 +65,20 @@ module.exports = lando => { return { command: 'init', level: 'app', - describe: 'Initializes code for use with lando', + describe: 'Fetched code and/or initializes a Landofile for use with lando', + usage: `$0 init + [--name ] + [--recipe ] + [--source ] + [--full] + [--github-auth==] + [--github-repo==] + [--option=...] + [--remote-options=] + [--remote-url=] + [--webroot=] + [--yes] + [--other-plugin-provided-options...]`, options: _.merge(getInitBaseOpts(recipes, sources), configOpts, getInitOveridesOpts(inits, recipes, sources)), run: options => { // Parse options abd and configs diff --git a/tasks/list.js b/tasks/list.js index 392a4f5ab..ca53737b6 100644 --- a/tasks/list.js +++ b/tasks/list.js @@ -7,28 +7,53 @@ module.exports = lando => { return { command: 'list', describe: 'Lists all running lando apps and containers', + usage: '$0 list [--all] [--filter ...] [--format ] [--path ]', + examples: [ + '$0 config', + '$0 config --format table --path env', + ], level: 'engine', options: _.merge({}, lando.cli.formatOptions(), { all: { - describe: 'Show all containers, even those not running', + describe: 'Shows all containers, even those not running', alias: ['a'], boolean: true, }, app: { - describe: 'Show containers for only a particular app', + describe: 'Shows containers for only a particular app', string: true, }, }), run: async options => { - // List all the apps - await lando.engine.list(options) - // Map each app to a summary and print results - .then(containers => console.log(lando.cli.formatData( - _(containers) - .map(container => _.omit(container, ['lando', 'id', 'instance'])) - .value(), - options, - ))); + // if options is a table then map it over to the new otable + if (options.format === 'table') options.format = 'otable'; + + // List all the apps but avoid lists built-in filtering for this one + options.data = await lando.engine.list({...options, filter: []}) + .map(container => _.omit(container, ['lando', 'id', 'instance'])); + + // if filters then do the filters first + if (options.filter) { + for (const filter of options.filter) { + options.data = _.filter(options.data, item => { + return String(_.get(item, filter.split('=')[0])) == filter.split('=')[1]; + }); + } + delete options.filter; + } + + // if we have a path and a single service then just do that + if (options.path && options.data.length === 1) { + console.log(lando.cli.formatData(options.data[0], options)); + + // if we do not have an otable then just print + } else if (options.format !== 'otable') { + console.log(lando.cli.formatData(options.data, options)); + + // otherwise iterate and print table info + } else { + for (const datum of options.data) console.log(lando.cli.formatData(datum, options)); + } }, }; }; diff --git a/tasks/logs.js b/tasks/logs.js index f771989aa..69ba757d6 100644 --- a/tasks/logs.js +++ b/tasks/logs.js @@ -5,20 +5,25 @@ const _ = require('lodash'); module.exports = lando => ({ command: 'logs', describe: 'Displays logs for your app', + usage: '$0 logs [--follow] [--service ...] [--timestamps]', + examples: [ + '$0 logs', + '$0 logs --follow --service appserver', + ], options: { follow: { - describe: 'Follow the logs', + describe: 'Follows the logs', alias: ['f'], default: false, boolean: true, }, service: { - describe: 'Show logs for the specified services only', + describe: 'Shows logs for the specified services only', alias: ['s'], array: true, }, timestamps: { - describe: 'Show log timestamps', + describe: 'Shows log timestamps', alias: ['t'], default: false, boolean: true, diff --git a/tasks/plugin-add.js b/tasks/plugin-add.js index 25bd7919b..81ed14cb0 100644 --- a/tasks/plugin-add.js +++ b/tasks/plugin-add.js @@ -2,23 +2,35 @@ module.exports = lando => { return { - command: 'plugin-add [plugins...]', + command: 'plugin-add', + usage: '$0 plugin-add [plugin...] [--auth ...] [--registry ...] [--scope ...]', + examples: [ + '$0 plugin-add @lando/php@1.2.0', + '$0 plugin-add @lando/php@file:~/my-php-plugin lando/node#main https://github.com/pirog/plugin.git#v1.2.1', + '$0 plugin-add @myorg/php --auth "$TOKEN" --registry https://npm.pkg.github.com', + '$0 plugin-add @org/a @myorg/b --auth "//npm.pkg.github.com/:_authToken=$TOKEN" --scope myorg:registry=https://npm.pkg.github.com', // eslint-disable-line max-len + ], level: 'tasks', + positionals: { + plugin: { + describe: 'Installs these plugins', + type: 'string', + }, + }, options: { auth: { - describe: 'Use global or scoped auth', + describe: 'Sets global or scoped auth', alias: ['a'], array: true, default: [], }, registry: { - describe: 'Use global or scoped registry', + describe: 'Sets global or scoped registry', alias: ['r', 's', 'scope'], array: true, default: [], }, }, - run: async options => { const getPluginConfig = require('../utils/get-plugin-config'); const lopts2Popts = require('../utils/lopts-2-popts'); @@ -37,7 +49,7 @@ module.exports = lando => { Plugin.debug = require('../utils/debug-shim')(lando.log); // merge plugins together - const plugins = [options.plugin].concat(options.plugins); + const plugins = options._.slice(1); lando.log.debug('attempting to install plugins %j', plugins); // attempt to compute the destination to install the plugin diff --git a/tasks/plugin-login.js b/tasks/plugin-login.js index 9db30f935..e99a7084b 100644 --- a/tasks/plugin-login.js +++ b/tasks/plugin-login.js @@ -3,10 +3,15 @@ module.exports = lando => { return { command: 'plugin-login', + usage: '$0 plugin-login [--username ] [--password ] [--registry ]', + examples: [ + '$0 plugin-login --username riker --password "$TOKEN"', + '$0 plugin-login --registry https://npm.pkg.github.com', + ], level: 'tasks', options: { username: { - describe: 'The registry username', + describe: 'Sets the registry username', alias: ['u'], string: true, interactive: { @@ -19,7 +24,7 @@ module.exports = lando => { }, }, password: { - describe: 'The registry password', + describe: 'Sets the registry password', alias: ['p'], string: true, interactive: { @@ -32,18 +37,17 @@ module.exports = lando => { }, }, registry: { - describe: 'Use registry', + describe: 'Sets registry', alias: ['r'], string: true, default: 'https://registry.npmjs.org', }, scope: { - describe: 'Use scopes', + describe: 'Sets scopes', alias: ['s'], array: true, }, }, - run: async options => { const merge = require('lodash/merge'); const profile = require('npm-profile'); diff --git a/tasks/plugin-logout.js b/tasks/plugin-logout.js index 63726d423..2e9a40ea0 100644 --- a/tasks/plugin-logout.js +++ b/tasks/plugin-logout.js @@ -3,6 +3,7 @@ module.exports = lando => { return { command: 'plugin-logout', + usage: '$0 plugin-logout', level: 'tasks', run: async options => { const write = require('../utils/write-file'); diff --git a/tasks/plugin-remove.js b/tasks/plugin-remove.js index aee2ffc2b..f9e9efee3 100644 --- a/tasks/plugin-remove.js +++ b/tasks/plugin-remove.js @@ -2,7 +2,18 @@ module.exports = lando => { return { - command: 'plugin-remove [plugins...]', + command: 'plugin-remove', + usage: '$0 plugin-remove [plugin...]', + examples: [ + '$0 plugin-remove @lando/php @lando/node', + ], + level: 'tasks', + positionals: { + plugin: { + describe: 'Removes these plugins', + type: 'string', + }, + }, level: 'tasks', run: async options => { const Plugin = require('../components/plugin'); @@ -11,8 +22,7 @@ module.exports = lando => { Plugin.debug = require('../utils/debug-shim')(lando.log); // merge plugins together, parse/normalize their names and return only unique values - const plugins = [options.plugin] - .concat(options.plugins) + const plugins = options._.slice(1) .map(plugin => require('../utils/parse-package-name')(plugin).name) .filter((plugin, index, array) => array.indexOf(plugin) === index); lando.log.debug('attempting to remove plugins %j', plugins); diff --git a/tasks/poweroff.js b/tasks/poweroff.js index e53d9eee2..cbaa37b1f 100644 --- a/tasks/poweroff.js +++ b/tasks/poweroff.js @@ -3,8 +3,9 @@ module.exports = lando => { return { command: 'poweroff', - level: 'engine', describe: 'Spins down all lando related containers', + usage: '$0 poweroff', + level: 'engine', run: async () => { console.log(lando.cli.makeArt('poweroff', {phase: 'pre'})); // Get all our containers diff --git a/tasks/rebuild.js b/tasks/rebuild.js index a9bf1b671..5378c5728 100644 --- a/tasks/rebuild.js +++ b/tasks/rebuild.js @@ -7,9 +7,13 @@ module.exports = lando => { return { command: 'rebuild', describe: 'Rebuilds your app from scratch, preserving data', + usage: '$0 rebuild [--service ...] [--yes]', + examples: [ + '$0 rebuild --service php --service nginx --yes', + ], options: { service: { - describe: 'Rebuild only the specified services', + describe: 'Rebuilds only the specified services', alias: ['s'], array: true, }, @@ -27,29 +31,45 @@ module.exports = lando => { // Rebuild the app if (app) { - // If user has given us options then set those - if (!_.isEmpty(options.service)) app.opts = _.merge({}, app.opts, {services: options.service}); - - // rebuild hero console.log(lando.cli.makeArt('appRebuild', {name: app.name, phase: 'pre'})); - // rebuild - await app.rebuild(); - // determine legacy settings - const legacyScanner = _.get(lando, 'config.scanner', true) === 'legacy'; - // get scanner stuff - const type = !_.isEmpty(app.messages) ? 'report' : 'post'; - const phase = legacyScanner ? `${type}_legacy` : type; - const scans = _.find(app.checks, {type: 'url-scan-tasks'}); - - // rebuold tables - console.log(lando.cli.makeArt('appRebuild', {name: app.name, phase, messages: app.messages})); - console.log(lando.cli.formatData(utils.startTable(app, {legacyScanner}), {format: 'table'}, {border: false})); - - // if we are not in legacy scanner mode then run the scans - if (!legacyScanner && scans) await scans.test(...scans.args); - - // aesthetics, consistency - console.log(''); + + // run any setup if we need to but without common plugins or build engine + const sopts = lando?.config?.setup; + sopts.buildEngine = false; + sopts.skipCommonPlugins = true; + sopts.yes = true; + const setupTasks = await lando.getSetupStatus(sopts); + + try { + // run a limited setup if needed + if (setupTasks.length > 0) await lando.setup(sopts); + // If user has given us options then set those + if (!_.isEmpty(options.service)) app.opts = _.merge({}, app.opts, {services: options.service}); + // rebuild hero + await app.rebuild(); + // determine legacy settings + const legacyScanner = _.get(lando, 'config.scanner', true) === 'legacy'; + // get scanner stuff + const type = !_.isEmpty(app.messages) ? 'report' : 'post'; + const phase = legacyScanner ? `${type}_legacy` : type; + const scans = _.find(app.checks, {type: 'url-scan-tasks'}); + + // rebuold tables + console.log(lando.cli.makeArt('appRebuild', {name: app.name, phase, messages: app.messages})); + console.log(lando.cli.formatData(utils.startTable(app, {legacyScanner}), {format: 'table'}, {border: false})); + + // if we are not in legacy scanner mode then run the scans + if (!legacyScanner && scans) await scans.test(...scans.args); + // print error message and reject + } catch (error) { + app.log.error(error.message, error); + console.log(lando.cli.makeArt('appStart', {phase: 'error'})); + return lando.Promise.reject(error); + + // plenty of gapp + } finally { + console.log(' '); + } } }, }; diff --git a/tasks/restart.js b/tasks/restart.js index d5805ed93..e2d318e84 100644 --- a/tasks/restart.js +++ b/tasks/restart.js @@ -7,6 +7,7 @@ module.exports = lando => { return { command: 'restart', describe: 'Restarts your app', + usage: '$0 restart', run: async options => { // Try to get our app const app = lando.getApp(options._app.root); @@ -15,8 +16,18 @@ module.exports = lando => { if (app) { console.log(lando.cli.makeArt('appRestart', {name: app.name, phase: 'pre'})); + // run any setup if we need to but without common plugins or build engine + const sopts = lando?.config?.setup; + sopts.buildEngine = false; + sopts.skipCommonPlugins = true; + sopts.yes = true; + const setupTasks = await lando.getSetupStatus(sopts); + // Normal bootup try { + // run a limited setup if needed + if (setupTasks.length > 0) await lando.setup(sopts); + // then restart await app.restart(); // determine legacy settings const legacyScanner = _.get(lando, 'config.scanner', true) === 'legacy'; diff --git a/tasks/setup.js b/tasks/setup.js index 53105ad54..eb8f5b6d3 100644 --- a/tasks/setup.js +++ b/tasks/setup.js @@ -21,7 +21,7 @@ const getStatusGroups = (status = {}) => { // get not installed message const getNotInstalledMessage = item => { - // start with the action and fallbacks + // start with the action and fallbacks` const message = [item.comment || `Will install ${item.version}` || 'Will install']; // add a restart message if applicable if (item.restart) message.push('[Requires restart]'); @@ -71,17 +71,17 @@ module.exports = lando => { // default options const options = { 'build-engine': { - describe: `The version of the build engine (${buildEngine}) to install`, + describe: `Sets the version of the build engine (${buildEngine}) to install`, default: defaults.buildEngine, string: true, }, 'orchestrator': { - describe: 'The version of the orchestrator (docker-compose) to install', + describe: 'Sets the version of the orchestrator (docker-compose) to install', default: defaults.orchestrator, string: true, }, 'plugin': { - describe: 'Additional plugin(s) to install', + describe: 'Sets additional plugin(s) to install', default: require('../utils/parse-to-plugin-strings')(defaults.plugins), array: true, }, @@ -90,6 +90,17 @@ module.exports = lando => { default: defaults.skipCommonPlugins, boolean: true, }, + 'skip-install-ca': { + describe: 'Disables the installation of the Lando Certificate Authority (CA)', + default: defaults.skipInstallCA, + boolean: true, + }, + 'skip-networking': { + describe: 'Disables the installation of the Landonet', + default: defaults.skipNetworking, + boolean: true, + hidden: true, + }, 'yes': { describe: 'Runs non-interactively with all accepted default answers', alias: ['y'], @@ -114,6 +125,18 @@ module.exports = lando => { return { command: 'setup', + usage: `$0 setup + [--build-engine ] + [--build-engine-accept-license] + [--orchestrator ] + [--plugin ...] + [--skip-common-plugins] + [--skip-install-ca] + [--yes]`, + examples: [ + '$0 setup --skip-common-plugins --plugin @lando/php --plugin @lando/mysql --yes', + '$0 setup --skip-install-ca --build-engine 4.31.0 --build-engine-accept-license', + ], options, run: async options => { // @TODO: conditional visibility for lando setup re first time run succesfully? diff --git a/tasks/share.js b/tasks/share.js index 1d51682b5..b4853d3d3 100644 --- a/tasks/share.js +++ b/tasks/share.js @@ -3,7 +3,7 @@ module.exports = lando => { return { command: 'share', - describe: 'Shares your local site publicly', + usage: '$0 share', run: options => { console.log(lando.cli.makeArt('shareWait')); /* diff --git a/tasks/shellenv.js b/tasks/shellenv.js index ed21f7856..f2812e08c 100644 --- a/tasks/shellenv.js +++ b/tasks/shellenv.js @@ -6,15 +6,20 @@ const {color} = require('listr2'); module.exports = lando => { return { command: 'shellenv', + usage: '$0 shellenv [--check] [--shell ]', + examples: [ + '$0 shellenv --check', + '$0 shellenv --shell bash', + ], level: 'tasks', options: { add: { - describe: 'Add to shell profile if blank lando will attempt discovery', + describe: 'Adds to shell profile if blank lando will attempt discovery', alias: ['a'], string: true, }, check: { - describe: 'Check to see if lando is in PATH', + describe: 'Checks to see if lando is in PATH', alias: ['c'], boolean: true, }, diff --git a/tasks/ssh.js b/tasks/ssh.js index 84f50fa9e..105cd7753 100644 --- a/tasks/ssh.js +++ b/tasks/ssh.js @@ -5,28 +5,31 @@ const _ = require('lodash'); // Other things const bashme = ['/bin/sh', '-c', 'if ! type bash > /dev/null; then sh; else bash; fi']; -const task = { + +module.exports = (lando, app) => ({ command: 'ssh', - describe: 'Drops into a shell on a service, runs commands', + usage: '$0 ssh [--command ] [--service ] [--user ]', + examples: [ + '$0 ssh --command "env | grep LANDO_ | sort"', + '$0 ssh --command "apt update -y && apt install vim -y --user root --service appserver"', + ], + override: true, options: { service: { - describe: 'SSH into this service', + describe: 'SSHs into this service', alias: ['s'], - default: 'appserver', + default: app._defaultService ?? 'appserver', }, command: { - describe: 'Run a command in the service', + describe: 'Runs a command in the service', alias: ['c'], }, user: { - describe: 'Run as a specific user', + describe: 'Runs as a specific user', alias: ['u'], }, }, -}; - -module.exports = (lando, app) => { - task.run = ({appname = undefined, command = bashme, service = 'appserver', user = null, _app = {}} = {}) => { + run: ({command = bashme, service = 'appserver', user = null, _app = {}} = {}) => { // Try to get our app const app = lando.getApp(_app.root, false); @@ -35,6 +38,9 @@ module.exports = (lando, app) => { return app.init().then(() => { // get the service api if possible const api = _.get(_.find(app.info, {service}), 'api', 3); + // and whether it can exec + const canExec = api === 4 && _.get(_.find(app?.v4?.services, {id: service}), 'canExec', false); + // set additional opt defaults if possible const opts = [undefined, api === 4 ? undefined : '/app']; // mix any v4 service info on top of app.config.services @@ -53,6 +59,13 @@ module.exports = (lando, app) => { if (!config.appMount && _.has(config, 'config.working_dir')) opts[0] = config.config.working_dir; } + // if this is an api 4 service that canExec then we have special handling + if (api === 4 && canExec) { + if (command === bashme) command = 'bash'; + if (typeof command === 'string') command = require('string-argv')(command); + command = ['/etc/lando/exec.sh', ...command]; + } + // continue if (_.isNull(user)) user = require('../utils/get-user')(service, app.info); return lando.engine.run(require('../utils/build-tooling-runner')( @@ -60,7 +73,10 @@ module.exports = (lando, app) => { command, service, user, - {}, + { + DEBUG: lando.debuggy ? '1' : '', + LANDO_DEBUG: lando.debuggy ? '1' : '', + }, ...opts, )).catch(error => { error.hide = true; @@ -68,6 +84,5 @@ module.exports = (lando, app) => { }); }); } - }; - return task; -}; + }, +}); diff --git a/tasks/start.js b/tasks/start.js index 1e80eb2e6..dbd36917a 100644 --- a/tasks/start.js +++ b/tasks/start.js @@ -7,6 +7,7 @@ module.exports = lando => { return { command: 'start', describe: 'Starts your app', + usage: '$0 start', run: async options => { // Try to get our app const app = lando.getApp(options._app.root); @@ -15,8 +16,18 @@ module.exports = lando => { if (app) { console.log(lando.cli.makeArt('appStart', {name: app.name, phase: 'pre'})); + // run any setup if we need to but without common plugins or build engine + const sopts = lando?.config?.setup; + sopts.buildEngine = false; + sopts.skipCommonPlugins = true; + sopts.yes = true; + const setupTasks = await lando.getSetupStatus(sopts); + // Normal bootup try { + // run a limited setup if needed + if (setupTasks.length > 0) await lando.setup(sopts); + // then start up await app.start(); // determine legacy settings const legacyScanner = _.get(lando, 'config.scanner', true) === 'legacy'; diff --git a/tasks/stop.js b/tasks/stop.js index bf5fa062d..b593615fe 100644 --- a/tasks/stop.js +++ b/tasks/stop.js @@ -3,6 +3,7 @@ module.exports = lando => ({ command: 'stop', describe: 'Stops your app', + usage: '$0 stop', run: async options => { // Try to get our app const app = lando.getApp(options._app.root); diff --git a/tasks/update.js b/tasks/update.js index a000602a5..a88b8e35a 100644 --- a/tasks/update.js +++ b/tasks/update.js @@ -89,6 +89,10 @@ module.exports = lando => { return { command: 'update', describe: 'Updates lando', + usage: '$0 update [--yes]', + examples: [ + '$0 update --yes', + ], options, run: async options => { const sortBy = require('lodash/sortBy'); diff --git a/tasks/version.js b/tasks/version.js index 655ad1fe7..1a81a6df4 100644 --- a/tasks/version.js +++ b/tasks/version.js @@ -20,22 +20,29 @@ const normalize = name => { module.exports = lando => ({ command: 'version', + describe: 'Displays lando version information', + usage: '$0 version [--all] [--component ] [--full]', + examples: [ + '$0 version --all', + '$0 version --full', + '$0 version --component @lando/cli', + '$0 version --component cli', + ], level: 'tasks', - describe: 'Displays the lando version', options: { all: { - describe: 'Show all version information', + describe: 'Shows all version information', alias: ['a'], type: 'boolean', }, component: { - describe: 'Show version info for specific component', + describe: 'Shows version info for specific component', alias: ['c'], type: 'string', default: '@lando/core', }, full: { - describe: 'Show full version string', + describe: 'Shows full version string', alias: ['f'], type: 'boolean', }, diff --git a/utils/build-docker-exec.js b/utils/build-docker-exec.js index 8f859bedf..ee57d469e 100644 --- a/utils/build-docker-exec.js +++ b/utils/build-docker-exec.js @@ -24,6 +24,24 @@ const getExecOpts = (docker, datum) => { exec.push('--env'); exec.push(`${key}=${value}`); }); + + // Assess the intention to detach for execers + if (datum.cmd[0] === '/etc/lando/exec.sh' && datum.cmd[datum.cmd.length - 1] === '&') { + datum.cmd.pop(); + exec.push('--detach'); + // Assess the intention to detach for shell wrappers + } else if (datum.cmd[0].endsWith('sh') && datum.cmd[1] === '-c' && datum.cmd[2].endsWith('&')) { + datum.cmd[2] = datum.cmd[2].slice(0, -1).trim(); + exec.push('--detach'); + } else if (datum.cmd[0].endsWith('bash') && datum.cmd[1] === '-c' && datum.cmd[2].endsWith('&')) { + datum.cmd[2] = datum.cmd[2].slice(0, -1).trim(); + exec.push('--detach'); + // Assess the intention to detach for everything else + } else if (datum.cmd[datum.cmd.length - 1] === '&') { + datum.cmd.pop(); + exec.push('--detach'); + } + // Add id exec.push(datum.id); return exec; diff --git a/utils/build-tooling-runner.js b/utils/build-tooling-runner.js index 699c1f1e1..6436548c7 100644 --- a/utils/build-tooling-runner.js +++ b/utils/build-tooling-runner.js @@ -3,9 +3,10 @@ const _ = require('lodash'); const path = require('path'); -/* - * Helper to map the cwd on the host to the one in the container - */ +const getContainer = (app, service) => { + return app?.containers?.[service] ?? `${app.project}_${service}_1`; +}; + const getContainerPath = (appRoot, appMount = undefined) => { // if appmount is undefined then dont even try if (appMount === undefined) return undefined; @@ -20,7 +21,7 @@ const getContainerPath = (appRoot, appMount = undefined) => { }; module.exports = (app, command, service, user, env = {}, dir = undefined, appMount = undefined) => ({ - id: app.containers[service], + id: getContainer(app, service), compose: app.compose, project: app.project, cmd: command, diff --git a/utils/build-tooling-task.js b/utils/build-tooling-task.js index 8f19c0f24..bf808dcf3 100644 --- a/utils/build-tooling-task.js +++ b/utils/build-tooling-task.js @@ -3,10 +3,18 @@ const _ = require('lodash'); module.exports = (config, injected) => { - const getToolingDefaults = require('./get-tooling-defaults'); - // Get our defaults and such + const getToolingDefaults = require('./get-tooling-defaults'); const {name, app, appMount, cmd, describe, dir, env, options, service, stdio, user} = getToolingDefaults(config); + + // add debug stuff if debuggy + env.DEBUG = injected.debuggy ? '1' : ''; + env.LANDO_DEBUG = injected.debuggy ? '1' : ''; + + // service api 4 services that canExec + const canExec = Object.fromEntries((config?.app?.info ?? []) + .map(service => ([service.service, config?.app?.executors?.[service.service] ?? false]))); + // Handle dynamic services and passthrough options right away // Get the event name handler const eventName = name.split(' ')[0]; @@ -14,7 +22,7 @@ module.exports = (config, injected) => { // Kick off the pre event wrappers .then(() => app.events.emit(`pre-${eventName}`, config, answers)) // Get an interable of our commandz - .then(() => _.map(require('./parse-tooling-config')(cmd, service, options, answers))) + .then(() => _.map(require('./parse-tooling-config')(cmd, service, options, answers, canExec))) // Build run objects .map(({command, service}) => require('./build-tooling-runner')(app, command, service, user, env, dir, appMount)) // Try to run the task quickly first and then fallback to compose launch diff --git a/utils/generate-build-script.js b/utils/generate-build-script.js deleted file mode 100644 index ae7dd1608..000000000 --- a/utils/generate-build-script.js +++ /dev/null @@ -1,48 +0,0 @@ -/* eslint-disable max-len */ -'use strict'; - -module.exports = (contents, user, gid, socket, mount = '/app') => ` -#!/bin/sh - -retry_with_backoff() { - local max_attempts=\${MAX_ATTEMPTS-10} - local initial_delay=\${INITIAL_DELAY-1} - local factor=\${FACTOR-2} - local attempt=1 - local delay=$initial_delay - - while true; do - "$@" - local status=$? - if [ $status -eq 0 ]; then - return 0 - fi - - if [ $attempt -ge $max_attempts ]; then - echo "Attempt $attempt failed and there are no more attempts left!" - return $status - fi - - echo "Attempt $attempt failed! Retrying in $delay seconds..." - sleep $delay - attempt=$((attempt + 1)) - delay=$((delay * factor)) - done -} - -# clean up and setup ssh-auth -if command -v socat >/dev/null 2>&1 && command -v sudo >/dev/null 2>&1; then - if [ -S "${socket}" ]; then - rm -rf /run/ssh-${user}.sock - sudo socat UNIX-LISTEN:/run/ssh-${user}.sock,fork,user=${user},group=${gid},mode=777 UNIX-CONNECT:${socket} & - retry_with_backoff ssh-add -l > /dev/null 2>&1; - fi -fi - -# temp stuff for demo purposes -if command -v git > /dev/null 2>&1; then - git config --global --add safe.directory ${mount} -fi - -${contents} -`; diff --git a/utils/get-app.js b/utils/get-app.js index fc66918fb..383a6aac6 100644 --- a/utils/get-app.js +++ b/utils/get-app.js @@ -25,7 +25,7 @@ module.exports = (files, userConfRoot) => { project: _.toLower(config.name).replace(/_|-|\.+/g, ''), root: path.dirname(files[0]), composeCache: path.join(userConfRoot, 'cache', `${config.name}.compose.cache`), - toolingCache: path.join(userConfRoot, 'cache', `${config.name}.tooling.cache`), + recipeCache: path.join(userConfRoot, 'cache', `${config.name}.recipe.cache`), toolingRouter: path.join(userConfRoot, 'cache', `${config.name}.tooling.router`), }); }; diff --git a/utils/get-buildx-error.js b/utils/get-buildx-error.js index 5ed1736f6..d860b1211 100644 --- a/utils/get-buildx-error.js +++ b/utils/get-buildx-error.js @@ -28,8 +28,10 @@ module.exports = ({code = 1, stderr = '', stdout = '', messages = ''} = {}) => { code = typeof errorcode === 'string' ? parseInt(errorcode.trim()) : code; } - // now generate a string of the most relevant errors - messages = faillines.map(line => line.split(' ').slice(2).join(' ')); + // now generate a string of the most relevant errors and strip any debug messages + messages = faillines + .map(line => line.split(' ').slice(2).join(' ')) + .filter(line => !line.startsWith('debug')); // return a lando error return new LandoError(messages.join(' '), {code, stdout, stderr}); diff --git a/utils/get-compose-x.js b/utils/get-compose-x.js index c4a0be82d..790771b94 100644 --- a/utils/get-compose-x.js +++ b/utils/get-compose-x.js @@ -27,7 +27,7 @@ const getDockerBin = (bin, base, pathFallback = true) => { } }; -module.exports = ({orchestratorVersion = '2.27.0', userConfRoot = os.tmpdir()} = {}) => { +module.exports = ({orchestratorVersion = '2.27.1', userConfRoot = os.tmpdir()} = {}) => { const orchestratorBin = `docker-compose-v${orchestratorVersion}`; switch (process.platform) { case 'darwin': diff --git a/utils/get-config-defaults.js b/utils/get-config-defaults.js index 36d2fce96..3ed3643f1 100644 --- a/utils/get-config-defaults.js +++ b/utils/get-config-defaults.js @@ -5,10 +5,21 @@ const browsers = ['electron', 'chrome', 'atom-shell']; const path = require('path'); const os = require('os'); +const getBuildEngineVersion = () => { + switch (process.platform) { + case 'darwin': + return '4.32.0'; + case 'linux': + return '27.0.3'; + case 'win32': + return '4.32.0'; + } +}; + // Default config const defaultConfig = options => ({ orchestratorSeparator: '_', - orchestratorVersion: '2.27.0', + orchestratorVersion: '2.27.1', configSources: [], disablePlugins: [], dockerBin: require('../utils/get-docker-x')(), @@ -32,7 +43,7 @@ const defaultConfig = options => ({ // this governs both autosetup and the defaults of lando setup // @TODO: orchestrator works a bit differently because it predates lando.setup() we set it elsewhere setup: { - buildEngine: process.platform === 'linux' ? '26.1.1' : '4.30.0', + buildEngine: getBuildEngineVersion(), buildEngineAcceptLicense: !require('is-interactive')(), commonPlugins: { '@lando/acquia': 'latest', @@ -76,6 +87,8 @@ const defaultConfig = options => ({ plugins: {}, tasks: [], skipCommonPlugins: _.get(options, 'fatcore', false), + skipInstallCA: false, + skipNetworking: false, }, }); diff --git a/utils/get-docker-x.js b/utils/get-docker-x.js index 026970423..0164789da 100644 --- a/utils/get-docker-x.js +++ b/utils/get-docker-x.js @@ -26,7 +26,6 @@ const getDockerBin = (bin, base, pathFallback = true) => { } }; - module.exports = () => { const base = (process.platform === 'linux') ? '/usr/bin' : require('./get-docker-bin-path')(); return getDockerBin('docker', base); diff --git a/utils/get-executors.js b/utils/get-executors.js new file mode 100644 index 000000000..14e912cac --- /dev/null +++ b/utils/get-executors.js @@ -0,0 +1,9 @@ +'use strict'; + +const _ = require('lodash'); + +module.exports = (services = {}) => _(services) + .map((service, id) => _.merge({}, {id}, service)) + .map(service => ([service.id, service.canExec ?? false])) + .fromPairs() + .value(); diff --git a/utils/get-exposed-localhosts.js b/utils/get-exposed-localhosts.js index 03df92993..c56390ac9 100644 --- a/utils/get-exposed-localhosts.js +++ b/utils/get-exposed-localhosts.js @@ -3,7 +3,7 @@ const _ = require('lodash'); const url = require('url'); -module.exports = (data, scan = ['80, 443'], secured = ['443'], bindAddress = '127.0.0.1') => { +module.exports = (data, scan = ['80', '443'], secured = ['443'], bindAddress = '127.0.0.1') => { return _(_.merge(_.get(data, 'Config.ExposedPorts', []), {'443/tcp': {}})) .map((value, port) => ({ port: _.head(port.split('/')), diff --git a/utils/get-fingerprint.js b/utils/get-fingerprint.js new file mode 100644 index 000000000..69127c132 --- /dev/null +++ b/utils/get-fingerprint.js @@ -0,0 +1,14 @@ +'use strict'; + +const fs = require('fs'); +const forge = require('node-forge'); +const read = require('./read-file'); + +module.exports = (input, sha = 'sha1') => { + const contents = (fs.existsSync(input)) ? read(input) : input; + const cert = forge.pki.certificateFromPem(contents); + const der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes(); + const md = forge.md[sha].create(); + md.update(der); + return md.digest().toHex(); +}; diff --git a/utils/get-mounts.js b/utils/get-mounts.js index 8679ab0e4..6ea63fda9 100644 --- a/utils/get-mounts.js +++ b/utils/get-mounts.js @@ -2,7 +2,6 @@ const _ = require('lodash'); -// adds required methods to ensure the lando v3 debugger can be injected into v4 things module.exports = (services = {}) => _(services) .map((service, id) => _.merge({}, {id}, service)) .map(service => ([service.id, service.appMount])) diff --git a/utils/get-system-cas.js b/utils/get-system-cas.js new file mode 100644 index 000000000..ae1060604 --- /dev/null +++ b/utils/get-system-cas.js @@ -0,0 +1,29 @@ +'use strict'; + +module.exports = (format = 'fingerprint') => { + const fingerprints = []; + + switch (process.platform) { + case 'darwin': + return require('mac-ca').get({format}); + case 'linux': + const {systemCertsSync} = require('system-ca'); + for (const cert of systemCertsSync()) { + try { + fingerprints.push(require('./get-fingerprint')(cert)); + } catch {} + } + + return fingerprints; + case 'win32': + const winCA = require('win-ca'); + + for (const cert of [...winCA({generator: true, store: ['root'], format: winCA.der2.pem})]) { + try { + fingerprints.push(require('./get-fingerprint')(cert)); + } catch {} + } + + return fingerprints; + } +}; diff --git a/utils/get-tasks.js b/utils/get-tasks.js index 63c32bc3e..a925b3ea0 100644 --- a/utils/get-tasks.js +++ b/utils/get-tasks.js @@ -94,6 +94,11 @@ const engineRunner = (config, command) => (argv, lando) => { }; module.exports = (config = {}, argv = {}, tasks = []) => { + // merge in recipe cache config first + if (fs.existsSync(config.recipeCache) && _.has(config, 'recipe')) { + config = _.merge({}, JSON.parse(fs.readFileSync(config.recipeCache, {encoding: 'utf-8'})), config); + } + // If we have a tooling router lets rebase on that if (fs.existsSync(config.toolingRouter)) { // Get the closest route @@ -111,9 +116,6 @@ module.exports = (config = {}, argv = {}, tasks = []) => { config.tooling = _.merge({}, config.tooling, closestRoute.tooling); config.route = closestRoute; } - // Or we have a recipe lets rebase on that - } else if (_.has(config, 'recipe')) { - config.tooling = _.merge({}, loadCacheFile(config.toolingCache), config.tooling); } // lets add ids to help match commands with args? @@ -129,13 +131,16 @@ module.exports = (config = {}, argv = {}, tasks = []) => { _.forEach(_.get(config, 'tooling', {}), (task, command) => { if (_.isObject(task)) { tasks.push({ - command, id: command.split(' ')[0], - level, + command, + delegate: _.isEmpty(_.get(task, 'options', {})) && _.isEmpty(_.get(task, 'positionals', {})), describe: _.get(task, 'description', `Runs ${command} commands`), + examples: _.get(task, 'examples', []), + level, options: _.get(task, 'options', {}), + positionals: _.get(task, 'positionals', {}), + usage: _.get(task, 'usage', command), run: (level === 'app') ? appRunner(command) : engineRunner({...config, argv}, command, task), - delegate: _.isEmpty(_.get(task, 'options', {})), }); } }); @@ -143,12 +148,18 @@ module.exports = (config = {}, argv = {}, tasks = []) => { // get core tasks const coreTasks = _(loadCacheFile(process.landoTaskCacheFile)).map(t => ([t.command, t])).fromPairs().value(); - // and apply any overrides if we have them + // mix in any relevant compose cache things if (fs.existsSync(config.composeCache)) { try { const composeCache = JSON.parse(fs.readFileSync(config.composeCache, {encoding: 'utf-8'})); - const overrides = _(_.get(composeCache, 'overrides.tooling', [])).map(t => ([t.command, t])).fromPairs().value(); - _.merge(coreTasks, overrides); + + // merge in additional tooling; + Object.assign(coreTasks, composeCache?.overrides?.tooling ?? {}); + + // add additional items + config.allServices = composeCache.allServices ?? []; + config.info = composeCache.info ?? []; + config.primary = composeCache.primary ?? 'appserver'; } catch (e) { throw new Error(`There was a problem with parsing ${config.composeCache}. Ensure it is valid JSON! ${e}`); } diff --git a/utils/parse-events-config.js b/utils/parse-events-config.js index 7279be668..2e9b1d0ed 100644 --- a/utils/parse-events-config.js +++ b/utils/parse-events-config.js @@ -48,16 +48,33 @@ module.exports = (cmds, app, data = {}) => _.map(cmds, cmd => { _.get(app, 'v4.servicesList', []), ]).flatten().compact().uniq().value(); + // attempt to ascertain whether this is a v4 "exec" service + const canExec = _.get(app, 'v4.services', []).find(s => s.id === service)?.canExec + ?? _.get(app, `executors.${service}`, undefined) + ?? _.get(data, `executors.${service}`, undefined) + ?? false; + + // reset the cmd based on exec situation + if (canExec) { + cmd = _.isArray(command) ? command : require('string-argv')(command); + cmd = ['/etc/lando/exec.sh', ...cmd]; + } else { + cmd = ['/bin/sh', '-c', _.isArray(command) ? command.join(' ') : command]; + } + // Validate the service if we can // @NOTE fast engine runs might not have this data yet - if (app.services && !_.includes(app.services, service)) { + if ( + (Array.isArray(app.services) && !_.includes(app.services, service)) && + (Array.isArray(v4s) && !_.includes(v4s, service)) + ) { throw new Error(`This app has no service called ${service}`); } // Add the build command return { id: app.containers[service], - cmd: ['/bin/sh', '-c', _.isArray(command) ? command.join(' ') : command], + cmd, compose: app.compose, project: app.project, api: _.includes(v4s, service) ? 4 : 3, @@ -66,6 +83,10 @@ module.exports = (cmds, app, data = {}) => _.map(cmds, cmd => { mode: 'attach', user: require('./get-user')(service, app.info), services: [service], + environment: { + DEBUG: app.debuggy ? '1' : '', + LANDO_DEBUG: app.debuggy ? '1' : '', + }, }, }; }); diff --git a/utils/parse-setup-task.js b/utils/parse-setup-task.js index acdbdc710..759b7dbae 100644 --- a/utils/parse-setup-task.js +++ b/utils/parse-setup-task.js @@ -49,6 +49,7 @@ module.exports = otask => { // update title to reflect pending task.title = `${initialTitle} ${color.dim(`[Needs ${dids.join(', ')}]`)}`; + task.task.state = 'WAITING'; // wait until all tasks close, for good or ill try { @@ -72,6 +73,7 @@ module.exports = otask => { // main event task.title = initialTitle; + task.task.state = 'STARTED'; const result = await orunner(ctx, task); // harvest if (otask.count) ctx.results.push(result); diff --git a/utils/parse-tooling-config.js b/utils/parse-tooling-config.js index a4ba9772f..489fcd9fa 100644 --- a/utils/parse-tooling-config.js +++ b/utils/parse-tooling-config.js @@ -19,13 +19,15 @@ const getDynamicKeys = (answer, answers = {}) => _(answers) * Set SERVICE from answers and strip out that noise from the rest of * stuff, check answers/argv for --service or -s, validate and then remove */ -const handleDynamic = (config, options = {}, answers = {}) => { +const handleDynamic = (config, options = {}, answers = {}, execs = {}) => { if (_.startsWith(config.service, ':')) { const answer = answers[config.service.split(':')[1]]; // Remove dynamic service option from argv _.remove(process.argv, arg => _.includes(getDynamicKeys(answer, answers).concat(answer), arg)); + // get the service + const service = answers[config.service.split(':')[1]]; // Return updated config - return _.merge({}, config, {service: answers[config.service.split(':')[1]]}); + return _.merge({}, config, {exec: execs[service] ?? false, service}); } else { return config; } @@ -59,21 +61,22 @@ const handlePassthruOpts = (options = {}, answers = {}) => _(options) /* * Helper to convert a command into config object */ -const parseCommand = (cmd, service) => ({ +const parseCommand = (cmd, service, execs) => ({ + exec: execs[service] ?? false, command: (_.isObject(cmd)) ? cmd[_.first(_.keys(cmd))] : cmd, service: (_.isObject(cmd)) ? _.first(_.keys(cmd)) : service, }); // adds required methods to ensure the lando v3 debugger can be injected into v4 things -module.exports = (cmd, service, options = {}, answers = {}) => _(cmd) +module.exports = (cmd, service, options = {}, answers = {}, execs = {}) => _(cmd) // Put into an object so we can handle "multi-service" tooling - .map(cmd => parseCommand(cmd, service)) + .map(cmd => parseCommand(cmd, service, execs)) // Handle dynamic services - .map(config => handleDynamic(config, options, answers)) + .map(config => handleDynamic(config, options, answers, execs)) // Add in any argv extras if they've been passed in .map(config => handleOpts(config, handlePassthruOpts(options, answers))) // Wrap the command in /bin/sh if that makes sense - .map(config => _.merge({}, config, {command: require('./shell-escape')(config.command, true, config.args)})) + .map(config => _.merge({}, config, {command: require('./shell-escape')(config.command, true, config.args, config.exec)})) // eslint-disable-line max-len // Add any args to the command and compact to remove undefined .map(config => _.merge({}, config, {command: _.compact(config.command.concat(config.args))})) // Put into an object diff --git a/utils/parse-v4-pkginstall-opts.js b/utils/parse-v4-pkginstall-opts.js new file mode 100644 index 000000000..0555296e5 --- /dev/null +++ b/utils/parse-v4-pkginstall-opts.js @@ -0,0 +1,17 @@ + +'use strict'; + +const isObject = require('lodash/isPlainObject'); + +module.exports = options => { + // if options are a string then make into an array + if (typeof options === 'string') options = [options]; + // if options are an object then break into options pairs + if (isObject(options)) { + options = Object.entries(options).map(([key, value]) => [`--${key}`, value]).flat(); + } + // if options are an array then combine into a string and return + if (Array.isArray(options)) return options.join(' '); + // otherwise just return an empty string? + return ''; +}; diff --git a/utils/parse-v4-ports.js b/utils/parse-v4-ports.js new file mode 100644 index 000000000..403161aed --- /dev/null +++ b/utils/parse-v4-ports.js @@ -0,0 +1,48 @@ +'use strict'; + +const isObject = require('lodash/isPlainObject'); +const range = require('lodash/range'); + +// @TODO: func to get a protocol type +// -> :PORT but handle :PORT-PORT + +const getPorts = (port = {}) => { + // map to a string for easier stuff + if (isObject(port)) port = port.published; + // get the correct part + port = port.split(':')[port.split(':').length - 1]; + // cut off the protocol + port = port.split('/')[0]; + // range me + port = range(port.split('-')[0], parseInt(port.split('-')[1] ?? port.split('-')[0]) + 1); + return port; +}; + +const getProtocolPorts = (ports = [], protocol = 'http') => { + return ports + .filter(port => { + if (typeof port === 'string') return port.endsWith(`/${protocol}`); + if (isObject(port)) return port.app_protocol === protocol; + return false; + }) + .map(port => getPorts(port)) + .flat(Number.POSITIVE_INFINITY); +}; + +module.exports = (ports = []) => { + // get the http/https protocols + const http = getProtocolPorts(ports, 'http'); + const https = getProtocolPorts(ports, 'https'); + + // normalize http/https -> tcp + ports = ports + .map(port => { + if (typeof port === 'string') { + port = port.replace('/https', '/tcp'); + port = port.replace('/http', '/tcp'); + } + return port; + }); + + return {http, https, ports}; +}; diff --git a/utils/parse-v4-services.js b/utils/parse-v4-services.js index 0ab23caa3..3d5dc2fd1 100644 --- a/utils/parse-v4-services.js +++ b/utils/parse-v4-services.js @@ -12,11 +12,6 @@ module.exports = services => _(services) api: require('./get-service-api-version')(service.api), builder: type.split(':')[0], config: _.omit(service, ['api', 'meUser', 'moreHttpPorts', 'primary', 'scanner', 'sport', 'type']), - legacy: { - meUser: service.meUser ?? 'www-data', - moreHttpPorts: service.moreHttpPorts ?? [], - sport: service.sport ?? '443', - }, primary: service.primary ?? false, router: type.split(':')[1], scanner: service.scanner ?? false, diff --git a/utils/parse-v4-user.js b/utils/parse-v4-user.js new file mode 100644 index 000000000..06141056f --- /dev/null +++ b/utils/parse-v4-user.js @@ -0,0 +1,35 @@ + +'use strict'; + +const isObject = require('lodash/isPlainObject'); + +module.exports = user => { + // if user is nully then return empty object + if (user === undefined || user === null || user === false) return {}; + + // if user is a string then lets break it into parts and put it into an object + if (typeof user === 'string') { + const parts = user.split(':'); + user = {gid: parts[2], uid: parts[1], name: parts[0]}; + } + + // if user is an object + if (isObject(user)) { + // we want user.name to the canonical ones + user.name = user.name ?? user.user ?? user.username; + delete user.user; + delete user.username; + + // remove undefined keys + for (const key in user) { + if (user[key] === undefined) delete user[key]; + } + + // return + return user; + } + + // if we get here i guess just return an empty object? + // throw an error? + return {}; +}; diff --git a/utils/run-elevated.js b/utils/run-elevated.js index 6fe0f4f2a..f2c24ef3b 100644 --- a/utils/run-elevated.js +++ b/utils/run-elevated.js @@ -11,12 +11,14 @@ const {spawn} = require('child_process'); // get the bosmang const defaults = { - notify: true, + env: process.env, debug: require('debug')('@lando/run-elevated'), ignoreReturnCode: false, isInteractive: require('is-interactive')(), - password: undefined, method: process.platform === 'win32' ? 'run-elevated' : 'sudo', + notify: true, + password: undefined, + user: 'root', }; const getChild = (command, options) => { diff --git a/utils/shell-escape.js b/utils/shell-escape.js index d75078a1a..c0c7496f0 100644 --- a/utils/shell-escape.js +++ b/utils/shell-escape.js @@ -1,16 +1,21 @@ 'use strict'; const _ = require('lodash'); -const parse = require('string-argv'); -module.exports = (command, wrap = false, args = process.argv.slice(3)) => { +module.exports = (command, wrap = false, args = process.argv.slice(3), v4Exec = false) => { + // if api 4 then just prepend and we will handle it downstream + if (v4Exec) { + if (_.isString(command)) command = require('string-argv')(command); + return ['/etc/lando/exec.sh', ...command]; + } + // If no args and is string then just wrap and return if (_.isString(command) && _.isEmpty(args)) { return ['/bin/sh', '-c', command]; } // Parse the command if its a string - if (_.isString(command)) command = parse(command); + if (_.isString(command)) command = require('string-argv')(command); // Wrap in shell if specified if (wrap && !_.isEmpty(_.intersection(command, ['&', '&&', '|', '||', '<<', '<', '>', '>>', '$']))) { diff --git a/utils/to-posix-path.js b/utils/to-posix-path.js new file mode 100644 index 000000000..6d5fdd935 --- /dev/null +++ b/utils/to-posix-path.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = path => { + return path.replace(/\\/g, '/').replace(/^([a-zA-Z]):/, '/$1'); +}; diff --git a/utils/write-file.js b/utils/write-file.js index cf337e330..3a3dc194f 100644 --- a/utils/write-file.js +++ b/utils/write-file.js @@ -10,6 +10,11 @@ module.exports = (file, data, options = {}) => { // set extension if not set const extension = options.extension || path.extname(file); + // linux line endings + const forcePosixLineEndings = options.forcePosixLineEndings ?? false; + + // data is a string and posixOnly then replace + if (typeof data === 'string' && forcePosixLineEndings) data = data.replace(/\r\n/g, '\n'); switch (extension) { case '.yaml': @@ -39,6 +44,6 @@ module.exports = (file, data, options = {}) => { break; default: if (!fs.existsSync(file)) fs.mkdirSync(path.dirname(file), {recursive: true}); - fs.writeFileSync(file, data, {encoding: 'utf8'}); + fs.writeFileSync(file, data, {encoding: 'utf-8'}); } };