├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── homebrew.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── Makefile ├── README.md ├── hooks ├── hook-cookiecutter.py └── hook-localstack_core.py ├── main.py └── requirements.txt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | reviewers: 8 | - "alexrashed" 9 | labels: 10 | - "dependencies" 11 | groups: 12 | github-actions: 13 | patterns: 14 | - "*" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build / Release 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | tags: 11 | - '*' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - runner: macos-13 21 | os: darwin 22 | arch: amd64 23 | - runner: macos-13-xlarge 24 | os: darwin 25 | arch: arm64 26 | - runner: windows-2022 27 | os: windows 28 | arch: amd64 29 | - runner: ubuntu-22.04 30 | os: linux 31 | arch: amd64 32 | - runner: buildjet-2vcpu-ubuntu-2204-arm 33 | os: linux 34 | arch: arm64 35 | 36 | runs-on: ${{ matrix.runner }} 37 | outputs: 38 | cli_version: ${{ steps.cli_version.outputs.cli_version }} 39 | steps: 40 | - name: Check out Git repository 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup Python (GitHub Runner) 44 | if: ${{ !contains(matrix.runner, 'buildjet') }} 45 | uses: actions/setup-python@v5 46 | with: 47 | python-version: '3.11.5' 48 | 49 | - name: Setup Python (BuildJet Runner) 50 | if: contains(matrix.runner, 'buildjet') 51 | uses: gabrielfalcao/pyenv-action@v18 52 | with: 53 | default: '3.11.4' 54 | 55 | # Add a retry to avoid issues when this action is running 56 | # right after the package is published on PyPi 57 | # (and might not be distributed in the CDN yet) 58 | - name: Create virtual environment 59 | uses: nick-fields/retry@v3 60 | with: 61 | timeout_minutes: 5 62 | max_attempts: 5 63 | retry_wait_seconds: 120 64 | command: make clean-venv venv 65 | 66 | - name: Build using pyinstaller 67 | shell: bash 68 | run: make clean all 69 | 70 | - name: Setup Docker on MacOS 71 | # Install docker and start Colima on MacOS (except it's the large runner) 72 | # GitHub xlarge MacOS runner cannot run Docker containers: 73 | # - https://docs.github.com/en/actions/using-github-hosted-runners/about-larger-runners/ 74 | # if: matrix.os == 'darwin' && matrix.runner != 'macos-13-xlarge' 75 | # TODO re-enable when mac11 docker gets more stable 76 | if: ${{ false }} 77 | run: | 78 | brew install docker 79 | colima start 80 | 81 | - name: Community Non-Docker Smoke tests 82 | shell: bash 83 | run: | 84 | ls dist-bin/ 85 | cd dist-bin 86 | # show the help 87 | ./localstack --help 88 | # show the config 89 | ./localstack config show 90 | 91 | - name: Pro Non-Docker Smoke tests 92 | shell: bash 93 | # Skip these checks for forks (forks do not have access to the LocalStack Pro API key) 94 | if: ${{ ! github.event.pull_request.head.repo.fork }} 95 | run: | 96 | # create an extension with default parameters (enter all new lines to use defaults) 97 | printf "\n\n\n\n\n\n\n\n\n" | LOCALSTACK_AUTH_TOKEN=${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} DEBUG=1 ./dist-bin/localstack extensions dev new 98 | # print the directory output 99 | ls -al my-localstack-extension 100 | # remove it again 101 | rm -rf my-localstack-extension 102 | 103 | - name: Community Docker Smoke tests (Linux, MacOS) 104 | shell: bash 105 | # GitHub Windows and xlarge MacOS runner cannot run Docker containers: 106 | # - https://github.com/orgs/community/discussions/25491 107 | # - https://docs.github.com/en/actions/using-github-hosted-runners/about-larger-runners/ 108 | # TODO re-enable for mac when mac11 docker gets more stable, and for buildjet when the tests there get more stable 109 | if: matrix.os != 'windows' && matrix.os != 'darwin' && matrix.runner != 'buildjet-2vcpu-ubuntu-2204-arm' 110 | run: | 111 | # Pull images to avoid making smoke tests vulnerable to system behavior (docker pull speed) 112 | docker pull localstack/localstack 113 | cd dist-bin 114 | # start community 115 | ./localstack start -d 116 | ./localstack wait -t 180 117 | ./localstack status services --format plain 118 | ./localstack status services --format plain | grep "s3=available" 119 | ./localstack stop 120 | 121 | - name: Pro Docker Smoke tests (Linux, MacOS) 122 | shell: bash 123 | # GitHub Windows and xlarge MacOS runner cannot run Docker containers: 124 | # - https://github.com/orgs/community/discussions/25491 125 | # - https://docs.github.com/en/actions/using-github-hosted-runners/about-larger-runners/ 126 | # Skip these checks for forks (forks do not have access to the LocalStack Pro API key) 127 | # TODO re-enable for mac when mac11 docker gets more stable, and for buildjet when the tests there get more stable 128 | if: matrix.os != 'windows' && matrix.os != 'darwin' && matrix.runner != 'buildjet-2vcpu-ubuntu-2204-arm' && !github.event.pull_request.head.repo.fork 129 | run: | 130 | # Pull images to avoid making smoke tests vulnerable to system behavior (docker pull speed) 131 | docker pull localstack/localstack-pro 132 | cd dist-bin 133 | # start pro with an auth token and extensions dev mode (see issue #20) 134 | LOCALSTACK_AUTH_TOKEN=${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} EXTENSION_DEV_MODE=1 ./localstack start -d 135 | ./localstack wait -t 180 136 | ./localstack logs | grep "extension developer mode enabled" 137 | ./localstack status services --format plain 138 | ./localstack status services --format plain | grep "xray=available" 139 | ./localstack stop 140 | 141 | - name: Set CLI version output 142 | id: cli_version 143 | shell: bash 144 | run: | 145 | VERSION_OUTPUT=$(dist-bin/localstack --version) 146 | echo $VERSION_OUTPUT 147 | # using bash parameter expansion to remove the part after the last space, since sed won't work on MacOS 148 | echo "cli_version=${VERSION_OUTPUT##* }" >> $GITHUB_OUTPUT 149 | 150 | - name: Archive distribution (Linux, MacOS) 151 | if: matrix.os != 'windows' 152 | run: | 153 | cd dist-bin/ 154 | tar -czf ${{github.event.repository.name}}-${{steps.cli_version.outputs.cli_version}}-${{ matrix.os }}-${{ matrix.arch }}-onefile.tar.gz localstack 155 | rm localstack 156 | cd ../dist-dir/ 157 | tar -czf ${{github.event.repository.name}}-${{steps.cli_version.outputs.cli_version}}-${{ matrix.os }}-${{ matrix.arch }}.tar.gz localstack 158 | rm -r localstack 159 | 160 | - name: Archive distribution (Windows) 161 | if: matrix.os == 'windows' 162 | run: | 163 | cd dist-bin/ 164 | Compress-Archive localstack.exe ${{github.event.repository.name}}-${{steps.cli_version.outputs.cli_version}}-${{ matrix.os }}-${{ matrix.arch }}-onefile.zip 165 | rm localstack.exe 166 | cd ../dist-dir/ 167 | Compress-Archive localstack ${{github.event.repository.name}}-${{steps.cli_version.outputs.cli_version}}-${{ matrix.os }}-${{ matrix.arch }}.zip 168 | rm -r localstack 169 | 170 | - name: Upload binary artifacts 171 | uses: actions/upload-artifact@v4 172 | with: 173 | name: ${{github.event.repository.name}}-${{steps.cli_version.outputs.cli_version}}-${{ matrix.os }}-${{ matrix.arch }}-onefile 174 | path: 'dist-bin/*' 175 | 176 | - name: Upload folder artifacts 177 | uses: actions/upload-artifact@v4 178 | with: 179 | name: ${{github.event.repository.name}}-${{steps.cli_version.outputs.cli_version}}-${{ matrix.os }}-${{ matrix.arch }} 180 | path: 'dist-dir/*' 181 | 182 | release: 183 | runs-on: ubuntu-latest 184 | if: startsWith(github.ref, 'refs/tags/') 185 | needs: 186 | - build 187 | permissions: 188 | contents: write 189 | steps: 190 | - name: Download Builds 191 | uses: actions/download-artifact@v4 192 | with: 193 | path: builds 194 | 195 | - name: Generate Checksums 196 | run: | 197 | # move all files from the builds subdirectories to the builds root folder 198 | find ./builds/ -type f -print0 | xargs -0 mv -t ./builds/ 199 | # remove all (empty) subdirectories 200 | find ./builds/ -mindepth 1 -maxdepth 1 -type d -print0 | xargs -r0 rm -R 201 | # generate the checksums 202 | cd builds 203 | sha256sum *.{tar.gz,zip} > ${{github.event.repository.name}}-${{needs.build.outputs.cli_version}}-checksums.txt 204 | 205 | - name: Release 206 | uses: softprops/action-gh-release@v2 207 | with: 208 | files: 'builds/*' 209 | draft: true 210 | token: ${{ secrets.LOCALSTACK_GITHUB_TOKEN }} 211 | -------------------------------------------------------------------------------- /.github/workflows/homebrew.yml: -------------------------------------------------------------------------------- 1 | name: Release Homebrew Tap 2 | 3 | on: 4 | release: 5 | # Start Homebrew Releaser when a new GitHub release of the CLI package is _published_ 6 | types: [published] 7 | 8 | jobs: 9 | homebrew-releaser: 10 | runs-on: ubuntu-latest 11 | name: homebrew-releaser 12 | steps: 13 | - name: Add published release to Homebrew Tap 14 | uses: Justintime50/homebrew-releaser@v2 15 | with: 16 | # Explicitly set the version to avoid Justintime50/homebrew-releaser#39 (wrong auto-detection of "64" as version) 17 | version: ${{ github.event.release.tag_name }} 18 | 19 | # The name of the homebrew tap to publish your formula to as it appears on GitHub. 20 | # Required - strings 21 | homebrew_owner: localstack 22 | homebrew_tap: homebrew-tap 23 | 24 | # Logs debugging info to console. 25 | # Default is shown - boolean 26 | debug: true 27 | 28 | # The name of the folder in your homebrew tap where formula will be committed to. 29 | # Default is shown - string 30 | formula_folder: Formula 31 | 32 | # The Personal Access Token (saved as a repo secret) that has `repo` permissions for the repo running the action AND Homebrew tap you want to release to. 33 | # Required - string 34 | github_token: ${{ secrets.LOCALSTACK_GITHUB_TOKEN }} 35 | 36 | 37 | # Git author info used to commit to the homebrew tap. 38 | # Defaults are shown - strings 39 | commit_owner: localstack-bot 40 | commit_email: 88328844+localstack-bot@users.noreply.github.com 41 | 42 | # Custom install command for your formula. 43 | # Required - string 44 | # The indentation is on purpose to fix the multiline indentation in the final formula 45 | install: | 46 | libexec.install Dir["*"] 47 | bin.install_symlink libexec/"localstack" 48 | generate_completions_from_executable(bin/"localstack", "completion") 49 | 50 | # Custom test command for your formula so you can run `brew test`. 51 | # Optional - string 52 | test: | 53 | assert_match /LocalStack Command Line Interface/, shell_output("#{bin}/localstack --help", 0) 54 | 55 | # Adds URL and checksum targets for different OS and architecture pairs. Using this option assumes 56 | # a tar archive exists on your GitHub repo with the following URL pattern (this cannot be customized): 57 | # https://github.com/{GITHUB_OWNER}/{REPO_NAME}/releases/download/{TAG}/{REPO_NAME}-{VERSION}-{OPERATING_SYSTEM}-{ARCHITECTURE}.tar.gz' 58 | # Darwin AMD pre-existing path example: https://github.com/justintime50/myrepo/releases/download/v1.2.0/myrepo-1.2.0-darwin-amd64.tar.gz 59 | # Linux ARM pre-existing path example: https://github.com/justintime50/myrepo/releases/download/v1.2.0/myrepo-1.2.0-linux-arm64.tar.gz 60 | # Optional - booleans 61 | target_darwin_amd64: true 62 | target_darwin_arm64: true 63 | target_linux_amd64: true 64 | target_linux_arm64: true 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build dist folders 2 | dist-bin 3 | dist-dir 4 | 5 | # IntelliJ 6 | .idea/ 7 | *.iml 8 | *~ 9 | 10 | # General 11 | .DS_Store 12 | .AppleDouble 13 | .LSOverride 14 | 15 | # Icon must end with two \r 16 | Icon 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear in the root of a volume 22 | .DocumentRevisions-V100 23 | .fseventsd 24 | .Spotlight-V100 25 | .TemporaryItems 26 | .Trashes 27 | .VolumeIcon.icns 28 | .com.apple.timemachine.donotpresent 29 | 30 | # Directories potentially created on remote AFP share 31 | .AppleDB 32 | .AppleDesktop 33 | Network Trash Folder 34 | Temporary Items 35 | .apdisk 36 | 37 | # Byte-compiled / optimized / DLL files 38 | __pycache__/ 39 | *.py[cod] 40 | *$py.class 41 | 42 | # C extensions 43 | *.so 44 | 45 | # Distribution / packaging 46 | .Python 47 | build/ 48 | develop-eggs/ 49 | dist/ 50 | downloads/ 51 | eggs/ 52 | .eggs/ 53 | lib/ 54 | lib64/ 55 | parts/ 56 | sdist/ 57 | var/ 58 | wheels/ 59 | *.egg-info/ 60 | .installed.cfg 61 | *.egg 62 | MANIFEST 63 | 64 | # PyInstaller 65 | # Usually these files are written by a python script from a template 66 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 67 | *.manifest 68 | *.spec 69 | 70 | # Installer logs 71 | pip-log.txt 72 | pip-delete-this-directory.txt 73 | 74 | # Unit test / coverage reports 75 | htmlcov/ 76 | .tox/ 77 | .coverage 78 | .coverage.* 79 | .cache 80 | nosetests.xml 81 | coverage.xml 82 | *.cover 83 | .hypothesis/ 84 | 85 | # Translations 86 | *.mo 87 | *.pot 88 | 89 | # Django stuff: 90 | *.log 91 | .static_storage/ 92 | .media/ 93 | local_settings.py 94 | 95 | # Flask stuff: 96 | instance/ 97 | .webassets-cache 98 | 99 | # Scrapy stuff: 100 | .scrapy 101 | 102 | # Sphinx documentation 103 | docs/_build/ 104 | 105 | # PyBuilder 106 | target/ 107 | 108 | # Jupyter Notebook 109 | .ipynb_checkpoints 110 | 111 | # pyenv 112 | .python-version 113 | 114 | # celery beat schedule file 115 | celerybeat-schedule 116 | 117 | # SageMath parsed files 118 | *.sage.py 119 | 120 | # Environments 121 | .venv 122 | env/ 123 | venv/ 124 | ENV/ 125 | env.bak/ 126 | venv.bak/ 127 | 128 | # Spyder project settings 129 | .spyderproject 130 | .spyproject 131 | 132 | # Rope project settings 133 | .ropeproject 134 | 135 | # mkdocs documentation 136 | /site 137 | 138 | # mypy 139 | .mypy_cache/ 140 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Manual Build 4 | ### python3-dev 5 | You need Python developer version libraries in your path to be able to build the distribution. 6 | For most of us who use pyenv, this is done with: 7 | - MacOS: 8 | ```bash 9 | env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 3.11-dev 10 | ``` 11 | - Linux: 12 | ```bash 13 | env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.11-dev 14 | ``` 15 | 16 | Activate the version: 17 | ``` 18 | pyenv local 3.11-dev 19 | python --version 20 | 21 | ``` 22 | This should print something like `Python 3.11.5+`. 23 | 24 | ### Building 25 | You can build the specific versions by calling the respective make target: 26 | ```bash 27 | make clean dist-bin/localstack 28 | # or: 29 | make clean dist-dir/localstack 30 | # or both: 31 | make clean all 32 | ``` 33 | You can find the binary assets in `dist-bin/` and `dist-dir`. 34 | The single binary has a slower startup time than the binary distribution. 35 | 36 | ## Creating a Release 37 | In order to create a release, just perform the following tasks: 38 | - Create a commit which sets a new explicit version for `localstack` in the `requirements.txt`. 39 | - For example: `localstack==2.1.0` 40 | - Create a tag for the commit: `v`. 41 | - For example: `git tag v2.1.0` 42 | - Push the tag (`git push origin v`) 43 | - This will trigger the following actions: 44 | - The tag will trigger the ["Build / Release"](.github/workflows/build.yml) GitHub workflow. 45 | - It will build the binaries for the different systems and create a GitHub release draft. 46 | - Publish the GitHub release draft. 47 | - This will trigger the ["Release Homebrew Tap"](.github/workflows/homebrew.yml) GitHub workflow. 48 | - It will take the release artifacts and update the Homebrew formula in [localstack/homebrew-tap](https://github.com/localstack/homebrew-tap). 49 | 50 | ### Dev Releases 51 | If a dev release is created, the tag name has to have the same name as the version of `localstack-core` being used (because this is the output of `localstack --version`). 52 | Otherwise, the ["Release Homebrew Tap"](.github/workflows/homebrew.yml) GitHub workflow will not be able to find the artifacts. 53 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017+ LocalStack contributors 2 | Copyright (c) 2016 Atlassian Pty Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VENV_BIN = python3 -m venv 2 | VENV_DIR ?= .venv 3 | PYINSTALLER_ARGS = --distpath=dist-bin --onefile 4 | 5 | ifeq ($(OS), Windows_NT) 6 | VENV_ACTIVATE = $(VENV_DIR)/Scripts/activate 7 | else 8 | VENV_ACTIVATE = $(VENV_DIR)/bin/activate 9 | endif 10 | 11 | VENV_RUN = . $(VENV_ACTIVATE) 12 | 13 | all: dist-bin/localstack dist-dir/localstack 14 | 15 | venv: $(VENV_ACTIVATE) 16 | 17 | $(VENV_ACTIVATE): requirements.txt 18 | test -d $(VENV_DIR) || $(VENV_BIN) $(VENV_DIR) 19 | $(VENV_RUN); pip install --upgrade setuptools wheel 20 | $(VENV_RUN); pip install -r requirements.txt 21 | touch $(VENV_ACTIVATE) 22 | 23 | dist-bin/localstack build: $(VENV_ACTIVATE) main.py 24 | $(VENV_RUN); pyinstaller main.py \ 25 | --log-level=DEBUG \ 26 | $(PYINSTALLER_ARGS) -n localstack \ 27 | --hidden-import cookiecutter.main \ 28 | --hidden-import cookiecutter.extensions \ 29 | --hidden-import localstack.dev.run.configurators \ 30 | --hidden-import localstack.pro.core.plugins \ 31 | --hidden-import localstack.pro.core.cli.localstack \ 32 | --hidden-import localstack.pro.core.extensions.plugins \ 33 | --copy-metadata localstack_ext \ 34 | --collect-data localstack.pro.core \ 35 | --additional-hooks-dir hooks 36 | 37 | dist-dir/localstack: PYINSTALLER_ARGS=--distpath=dist-dir 38 | dist-dir/localstack: $(VENV_ACTIVATE) main.py build 39 | 40 | clean: 41 | rm -rf build/ 42 | rm -rf dist-bin/ 43 | rm -rf dist-dir/ 44 | 45 | clean-venv: 46 | rm -rf $(VENV_DIR) 47 | 48 | .PHONY: all build clean clean-venv 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LocalStack CLI 2 | ======================= 3 | 4 | This repository contains building instructions for binary builds of the LocalStack CLI. 5 | It does not contain the actual source for the CLI, since the LocalStack CLI is basically just the Python package `localstack` (published on PyPi) with it's install dependencies (and without any extras). 6 | This is why this repository just contains the build config and pipeline that packages the LocalStack CLI python package into a standalone binary using PyInstaller. 7 | 8 | ## Installation 9 | Please make sure that you have a working [`docker` environment](https://docs.docker.com/get-docker/) on your machine before moving on. 10 | 11 | ### Brew (MacOS or Linux with Homebrew) 12 | Install the LocalStack CLI by using our [official LocalStack Brew Tap](https://github.com/localstack/homebrew-tap): 13 | ``` 14 | $ brew install localstack/tap/localstack-cli 15 | ``` 16 | 17 | ### Binary download (MacOS, Linux, Windows) 18 | If you do not have Brew on your machine, you can directly download the pre-built LocalStack CLI binary for your system: 19 | - [Download the latest release for your platform](https://github.com/localstack/localstack-cli/releases/latest) 20 | - Extract the archive to a folder in your `PATH` variable: 21 | - MacOS / Linux: ```sudo tar xvzf ~/Downloads/localstack-cli-*-darwin-*-onefile.tar.gz -C /usr/local/bin``` -------------------------------------------------------------------------------- /hooks/hook-cookiecutter.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PyInstaller.utils.hooks import collect_data_files 4 | 5 | datas = collect_data_files('cookiecutter') -------------------------------------------------------------------------------- /hooks/hook-localstack_core.py: -------------------------------------------------------------------------------- 1 | from PyInstaller.utils.hooks import copy_metadata 2 | 3 | # make sure to add the entrypoints data for localstack-core 4 | datas = copy_metadata('localstack_core') 5 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from multiprocessing import freeze_support 3 | freeze_support() 4 | 5 | os.environ["SKIP_PATCH_MOTO_ACCOUNT_ID"] = "1" 6 | from localstack.cli import main 7 | 8 | if __name__ == '__main__': 9 | main.main() 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyinstaller 2 | localstack==4.6.0 3 | cookiecutter 4 | --------------------------------------------------------------------------------