├── action.yml ├── .github └── workflows │ └── build.yml ├── LICENSE.txt ├── README.md └── index.js /action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Elasticsearch 2 | inputs: 3 | elasticsearch-version: 4 | description: The Elasticsearch version to download (if necessary) and use 5 | plugins: 6 | description: Elasticsearch plugins to install 7 | config: 8 | description: Set config 9 | runs: 10 | using: node24 11 | main: index.js 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | os: [ubuntu-24.04, ubuntu-24.04-arm, ubuntu-22.04, ubuntu-22.04-arm, macos-26, macos-15, macos-15-intel, macos-14, windows-2025, windows-2022] 10 | elasticsearch-version: [9, 8] 11 | steps: 12 | - uses: actions/checkout@v5 13 | - uses: ./. 14 | with: 15 | elasticsearch-version: ${{ matrix.elasticsearch-version }} 16 | plugins: | 17 | analysis-kuromoji 18 | analysis-smartcn 19 | config: | 20 | cluster.name: my-cluster 21 | http.port: 9201 22 | - run: curl -s localhost:9201 23 | - run: curl -s localhost:9201 | grep my-cluster 24 | - run: which elasticsearch 25 | - run: elasticsearch-plugin list 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2025 Andrew Kane 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # setup-elasticsearch 2 | 3 | The missing action for Elasticsearch :tada: 4 | 5 | - Simpler than containers 6 | - Works on Linux, Mac, and Windows 7 | - Supports different versions 8 | 9 | [![Build Status](https://github.com/ankane/setup-elasticsearch/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/setup-elasticsearch/actions) 10 | 11 | ## Getting Started 12 | 13 | Add it as a step to your workflow 14 | 15 | ```yml 16 | - uses: ankane/setup-elasticsearch@v1 17 | ``` 18 | 19 | ## Versions 20 | 21 | Specify a version (defaults to the latest) 22 | 23 | ```yml 24 | - uses: ankane/setup-elasticsearch@v1 25 | with: 26 | elasticsearch-version: 9 27 | ``` 28 | 29 | Supports major versions (`9`, `8`), minor versions (`9.0`, `8.18`, etc), and full versions (`9.0.0`, `8.18.0`, etc) 30 | 31 | Test against multiple versions 32 | 33 | ```yml 34 | strategy: 35 | matrix: 36 | elasticsearch-version: [9, 8] 37 | steps: 38 | - uses: ankane/setup-elasticsearch@v1 39 | with: 40 | elasticsearch-version: ${{ matrix.elasticsearch-version }} 41 | ``` 42 | 43 | ## Options 44 | 45 | Install plugins 46 | 47 | ```yml 48 | - uses: ankane/setup-elasticsearch@v1 49 | with: 50 | plugins: | 51 | analysis-kuromoji 52 | analysis-smartcn 53 | ``` 54 | 55 | Set `elasticsearch.yml` config 56 | 57 | ```yml 58 | - uses: ankane/setup-elasticsearch@v1 59 | with: 60 | config: | 61 | http.port: 9200 62 | ``` 63 | 64 | ## Caching [experimental] 65 | 66 | Add a step to your workflow **before** the `setup-elasticsearch` one 67 | 68 | ```yml 69 | - uses: actions/cache@v4 70 | with: 71 | path: ~/elasticsearch 72 | key: ${{ runner.os }}-elasticsearch-${{ matrix.elasticsearch-version }} 73 | ``` 74 | 75 | ## Related Actions 76 | 77 | - [setup-postgres](https://github.com/ankane/setup-postgres) 78 | - [setup-mysql](https://github.com/ankane/setup-mysql) 79 | - [setup-mariadb](https://github.com/ankane/setup-mariadb) 80 | - [setup-mongodb](https://github.com/ankane/setup-mongodb) 81 | - [setup-sqlserver](https://github.com/ankane/setup-sqlserver) 82 | 83 | ## Contributing 84 | 85 | Everyone is encouraged to help improve this project. Here are a few ways you can help: 86 | 87 | - [Report bugs](https://github.com/ankane/setup-elasticsearch/issues) 88 | - Fix bugs and [submit pull requests](https://github.com/ankane/setup-elasticsearch/pulls) 89 | - Write, clarify, or fix documentation 90 | - Suggest or add new features 91 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const spawnSync = require('child_process').spawnSync; 2 | const fs = require('fs'); 3 | const os = require('os'); 4 | const path = require('path'); 5 | const process = require('process'); 6 | 7 | const versionMap = { 8 | '9': '9.2.2', 9 | '8': '8.19.8', 10 | '9.2': '9.2.2', 11 | '9.1': '9.1.8', 12 | '9.0': '9.0.7', 13 | '8.19': '8.19.8', 14 | '8.18': '8.18.7', 15 | '8.17': '8.17.10', 16 | '8.16': '8.16.6', 17 | '8.15': '8.15.5', 18 | '8.14': '8.14.3', 19 | '8.13': '8.13.4', 20 | '8.12': '8.12.2', 21 | '8.11': '8.11.4', 22 | '8.10': '8.10.4', 23 | '8.9': '8.9.2', 24 | '8.8': '8.8.2', 25 | '8.7': '8.7.1', 26 | '8.6': '8.6.2', 27 | '8.5': '8.5.3', 28 | '8.4': '8.4.3', 29 | '8.3': '8.3.3', 30 | '8.2': '8.2.3', 31 | '8.1': '8.1.3', 32 | '8.0': '8.0.1' 33 | }; 34 | 35 | const env = Object.assign({}, process.env); 36 | delete env.JAVA_HOME; 37 | 38 | function run() { 39 | const args = Array.from(arguments); 40 | console.log(args.join(' ')); 41 | const command = args.shift(); 42 | // spawn is safer and more lightweight than exec 43 | const ret = spawnSync(command, args, {stdio: 'inherit', env: env}); 44 | if (ret.status !== 0) { 45 | throw ret.error; 46 | } 47 | } 48 | 49 | // only use with validated input 50 | // https://github.com/nodejs/node/issues/52554 51 | function runBat() { 52 | const args = Array.from(arguments); 53 | console.log(args.join(' ')); 54 | const command = args.shift(); 55 | if (!fs.existsSync(command)) { 56 | throw 'Bat not found'; 57 | } 58 | const ret = spawnSync(command, args, {stdio: 'inherit', env: env, shell: true}); 59 | if (ret.status !== 0) { 60 | throw ret.error; 61 | } 62 | } 63 | 64 | function addToEnv(value) { 65 | fs.appendFileSync(process.env.GITHUB_ENV, `${value}\n`); 66 | } 67 | 68 | function addToPath(value) { 69 | fs.appendFileSync(process.env.GITHUB_PATH, `${value}\n`); 70 | } 71 | 72 | function getVersion() { 73 | let version = process.env['INPUT_ELASTICSEARCH-VERSION'] || '9'; 74 | if (versionMap[version]) { 75 | version = versionMap[version]; 76 | } 77 | if (!/^[89]\.\d{1,2}\.\d{1,2}$/.test(version)) { 78 | throw `Elasticsearch version not supported: ${version}`; 79 | } 80 | return version; 81 | } 82 | 83 | function isWindows() { 84 | return process.platform == 'win32'; 85 | } 86 | 87 | // no JDK version is ideal, but deprecated 88 | function getUrl() { 89 | let url; 90 | if (process.platform == 'darwin') { 91 | if (process.arch == 'arm64') { 92 | url = `https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${elasticsearchVersion}-darwin-aarch64.tar.gz`; 93 | } else { 94 | url = `https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${elasticsearchVersion}-darwin-x86_64.tar.gz`; 95 | } 96 | } else if (isWindows()) { 97 | url = `https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${elasticsearchVersion}-windows-x86_64.zip`; 98 | } else { 99 | if (process.arch == 'arm64') { 100 | url = `https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${elasticsearchVersion}-linux-aarch64.tar.gz`; 101 | } else { 102 | url = `https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${elasticsearchVersion}-linux-x86_64.tar.gz`; 103 | } 104 | } 105 | return url; 106 | } 107 | 108 | function download() { 109 | const url = getUrl(); 110 | if (isWindows()) { 111 | run('curl', '-s', '-o', 'elasticsearch.zip', url); 112 | run('unzip', '-q', 'elasticsearch.zip'); 113 | } else { 114 | run('wget', '-q', '-O', 'elasticsearch.tar.gz', url); 115 | run('tar', 'xfz', 'elasticsearch.tar.gz'); 116 | } 117 | if (!fs.existsSync(cacheDir)) { 118 | fs.mkdirSync(cacheDir, {recursive: true}); 119 | } 120 | if (isWindows()) { 121 | // fix for: cross-device link not permitted 122 | run('mv', `elasticsearch-${elasticsearchVersion}`, esHome) 123 | } else { 124 | fs.renameSync(`elasticsearch-${elasticsearchVersion}`, esHome); 125 | } 126 | } 127 | 128 | function installPlugins() { 129 | let plugins = (process.env['INPUT_PLUGINS'] || '').trim(); 130 | if (plugins.length > 0) { 131 | console.log('Installing plugins'); 132 | 133 | // split here instead of above since JS returns [''] for empty array 134 | plugins = plugins.split(/\s*[,\n]\s*/); 135 | 136 | // validate 137 | // do not change without checking impact on runBat 138 | plugins.forEach( function(plugin) { 139 | if (!/^\w(\w|-)+$/.test(plugin)) { 140 | throw `Invalid plugin: ${plugin}`; 141 | } 142 | }); 143 | 144 | let pluginCmd = path.join(esHome, 'bin', 'elasticsearch-plugin'); 145 | let runCmd = run; 146 | if (isWindows()) { 147 | pluginCmd += '.bat'; 148 | runCmd = runBat; 149 | } 150 | runCmd(pluginCmd, 'install', '--silent', '--batch', ...plugins); 151 | } 152 | } 153 | 154 | function setConfig(dir) { 155 | let config = process.env['INPUT_CONFIG'] || ''; 156 | config += '\n'; 157 | config += 'discovery.type: single-node\n'; 158 | 159 | const [majorVersion, minorVersion, patchVersion] = elasticsearchVersion.split('.'); 160 | if (parseInt(majorVersion) >= 8 || parseInt(minorVersion) >= 13) { 161 | config += 'xpack.security.enabled: false\n'; 162 | } 163 | 164 | const file = path.join(dir, 'config', 'elasticsearch.yml'); 165 | // overwrite instead of append to play nicely with caching 166 | // alternatively, could append to copy of original file 167 | fs.writeFileSync(file, config); 168 | } 169 | 170 | function startServer() { 171 | if (isWindows()) { 172 | const serviceCmd = path.join(esHome, 'bin', 'elasticsearch-service.bat'); 173 | runBat(serviceCmd, 'install'); 174 | runBat(serviceCmd, 'start'); 175 | } else { 176 | run(path.join(esHome, 'bin', 'elasticsearch'), '-d'); 177 | } 178 | } 179 | 180 | function getPort() { 181 | const config = process.env['INPUT_CONFIG'] || ''; 182 | const match = config.match(/\bhttp\.port: +(\d{4,5})\b/); 183 | return match ? parseInt(match[1]) : 9200; 184 | } 185 | 186 | function waitForReady() { 187 | console.log("Waiting for server to be ready"); 188 | for (let i = 0; i < 30; i++) { 189 | let ret = spawnSync('curl', ['-s', `localhost:${getPort()}`]); 190 | if (ret.status === 0) { 191 | break; 192 | } 193 | spawnSync('sleep', ['1']); 194 | } 195 | } 196 | 197 | const elasticsearchVersion = getVersion(); 198 | const cacheDir = path.join(os.homedir(), 'elasticsearch'); 199 | const esHome = path.join(cacheDir, elasticsearchVersion); 200 | 201 | if (!fs.existsSync(esHome)) { 202 | const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'elasticsearch-')); 203 | process.chdir(tmpDir); 204 | download(); 205 | installPlugins(); 206 | } else { 207 | console.log('Elasticsearch cached'); 208 | } 209 | 210 | setConfig(esHome); 211 | startServer(); 212 | 213 | waitForReady(); 214 | 215 | addToEnv(`ES_HOME=${esHome}`); 216 | addToPath(path.join(esHome, 'bin')); 217 | --------------------------------------------------------------------------------