├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── VERSION └── scripts └── ebcli_installer.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report describing the problem you are facing as precisely as possible 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **1. Please specify the following:** 11 | - OS: [e.g. OS X, Ubuntu, Windows, etc.]: ___ 12 | - Shell (Bash, PowerShell, Zsh, etc.): ____ 13 | 14 | - [ ] **I have reviewed the troubleshooting tips described [here]() and they do not solve my problem** 15 | 16 | **2. Description** 17 | Please describe the problem you are facing as precisely as possible in this section 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc 3 | *.swp 4 | *.swo 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-elastic-beanstalk-cli-setup/issues), or [recently closed](https://github.com/aws/aws-elastic-beanstalk-cli-setup/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-elastic-beanstalk-cli-setup/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/aws-elastic-beanstalk-cli-setup/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Elastic Beanstalk CLI Setup 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elastic Beanstalk CLI Installer 2 | - [1. Overview](#1-overview) 3 | - [1.1. Prerequisites](#11-prerequisites) 4 | - [2. Quick start](#2-quick-start) 5 | - [2.1. Clone this repository](#21-clone-this-repository) 6 | - [2.2. Install/Upgrade the EB CLI](#22-installupgrade-the-eb-cli) 7 | - [MacOS/Linux](#macoslinux) 8 | - [Windows](#windows) 9 | - [2.3. After installation](#23-after-installation) 10 | - [3. Usage](#3-usage) 11 | - [3.1 Advanced usage](#31-advanced-usage) 12 | - [3.2 Options](#32-options) 13 | - [4. Troubleshooting](#4-troubleshooting) 14 | - [5. Frequently asked questions](#5-frequently-asked-questions) 15 | - [5.1. For the **experienced Python developer**, what's the advantage of this mode of installation instead of regular `pip` inside a `virtualenv`?](#51-for-the-experienced-python-developer-whats-the-advantage-of-this-mode-of-installation-instead-of-regular-pip-inside-a-virtualenv) 16 | - [5.2. On macOS (or Linux systems with `brew`), is this better than `brew install awsebcli`?](#52-on-macos-or-linux-systems-with-brew-is-this-better-than-brew-install-awsebcli) 17 | - [5.3. I already have the EB CLI installed. Can I still execute `ebcli_installer.py`?](#53-i-already-have-the-eb-cli-installed-can-i-still-execute-ebcli_installerpy) 18 | - [5.4. How does `ebcli_installer.py` work?](#54-how-does-ebcli_installerpy-work) 19 | - [5.5. Are there dependency problems that this mode of installation doesn't solve?](#55-are-there-dependency-problems-that-this-mode-of-installation-doesnt-solve) 20 | - [6. License](#6-license) 21 | 22 | ## 1. Overview 23 | 24 | This repository hosts scripts to generate self-contained installations of the [EB CLI](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3.html). 25 | 26 | ### 1.1. Prerequisites 27 | 28 | You will need to have the following prerequisites installed before running the install script. 29 | 30 | * **Git** 31 | * If not already installed you can download git from the [Git downloads page](https://git-scm.com/downloads). 32 | * **Python** 33 | * We recommend that you install Python using the [pyenv](https://github.com/pyenv/pyenv) Python version manager. Alternately, you can download Python from the [Python downloads page](https://www.python.org/downloads/). 34 | * **virtualenv** 35 | * Follow the [virtualenv documentation](https://virtualenv.pypa.io/en/latest/installation.html) to install virtualenv. 36 | 37 | ## 2. Quick start 38 | 39 | ### 2.1. Clone this repository 40 | 41 | Use the following: 42 | 43 | ``` 44 | git clone https://github.com/aws/aws-elastic-beanstalk-cli-setup.git 45 | ``` 46 | 47 | ### 2.2. Install/Upgrade the EB CLI 48 | 49 | #### MacOS/Linux 50 | On **Bash** or **Zsh**: 51 | 52 | ``` 53 | python ./aws-elastic-beanstalk-cli-setup/scripts/ebcli_installer.py 54 | ``` 55 | 56 | #### Windows 57 | In **PowerShell** or in a **Command Prompt** window: 58 | 59 | ``` 60 | python .\aws-elastic-beanstalk-cli-setup\scripts\ebcli_installer.py 61 | ``` 62 | 63 | ### 2.3. After installation 64 | 65 | On Linux and macOS, the output contains instructions to add the EB CLI (and Python) executable file to the shell's `$PATH` variable, if it isn't already in it. 66 | 67 | ## 3. Usage 68 | 69 | The `ebcli_installer.py` Python script will install the [awsebcli](https://pypi.org/project/awsebcli/) package in a virtual environment to prevent potential conflicts with other Python packages. 70 | 71 | For most use cases you can execute the `ebcli_installer.py` script with no arguments. 72 | 73 | ``` 74 | python ./aws-elastic-beanstalk-cli-setup/scripts/ebcli_installer.py 75 | ``` 76 | 77 | ### 3.1 Advanced usage 78 | 79 | - To install a **specific version** of the EB CLI: 80 | 81 | ```shell 82 | python scripts/ebcli_installer.py --version 3.14.13 83 | ``` 84 | 85 | - To install the EB CLI with a specific **version of Python** (the Python version doesn't need to be in `$PATH`): 86 | 87 | ```shell 88 | python scripts/ebcli_installer.py --python-installation /path/to/some/python/on/your/computer 89 | ``` 90 | 91 | - To install the EB CLI **from source** (Git repository, .tar file, .zip file): 92 | ```shell 93 | python scripts/ebcli_installer.py --ebcli-source /path/to/awsebcli.zip 94 | 95 | python scripts/ebcli_installer.py --ebcli-source /path/to/EBCLI/codebase/on/your/computer 96 | ``` 97 | - To install the EB CLI at a **specific location**, instead of in the standard `.ebcli-virtual-env` directory in the user's home directory: 98 | 99 | ```shell 100 | python scripts/ebcli_installer.py --location /path/to/ebcli/installation/location 101 | ``` 102 | ### 3.2 Options 103 | 104 | ``` 105 | options: 106 | -h, --help show this help message and exit 107 | -e VIRTUALENV_EXECUTABLE, --virtualenv-executable VIRTUALENV_EXECUTABLE 108 | path to the virtualenv installation to use to create the EBCLI's virtualenv 109 | -i, --hide-export-recommendation 110 | boolean to hide recommendation to modify PATH 111 | -l LOCATION, --location LOCATION 112 | location to store the awsebcli packages and its dependencies in 113 | -p PYTHON_INSTALLATION, --python-installation PYTHON_INSTALLATION 114 | path to the python installation under which to install the awsebcli and its 115 | dependencies 116 | -q, --quiet enable quiet mode to display only minimal, necessary output 117 | -s EBCLI_SOURCE, --ebcli-source EBCLI_SOURCE 118 | filesystem path to a Git repository of the EBCLI, or a .zip or .tar file of 119 | the EBCLI source code; useful when testing a development version of the EBCLI. 120 | -v VERSION, --version VERSION 121 | version of EBCLI to install 122 | ``` 123 | 124 | ## 4. Troubleshooting 125 | 126 | - **Linux** 127 | 128 | Most installation problems have been due to missing libraries such as `OpenSSL`. 129 | 130 | - On **Ubuntu and Debian**, run the following command to install dependencies. 131 | 132 | ```shell 133 | apt-get install \ 134 | build-essential zlib1g-dev libssl-dev libncurses-dev \ 135 | libffi-dev libsqlite3-dev libreadline-dev libbz2-dev 136 | ``` 137 | 138 | - On **Amazon Linux and Fedora**, run the following command to install dependencies. 139 | 140 | ```shell 141 | yum group install "Development Tools" 142 | yum install \ 143 | zlib-devel openssl-devel ncurses-devel libffi-devel \ 144 | sqlite-devel.x86_64 readline-devel.x86_64 bzip2-devel.x86_64 145 | ``` 146 | 147 | - **macOS** 148 | 149 | Most installation problems on macOS are related to loading and linking OpenSSL and zlib. The following command installs the necessary packages and tells the Python installer where to find them: 150 | 151 | ``` 152 | brew install zlib openssl readline 153 | CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib" 154 | ``` 155 | Run `brew info` to get the latest environment variable export suggestions, such as `brew info zlib` 156 | 157 | - **Windows** 158 | 159 | - In PowerShell, if you encounter an error with the message "execution of scripts is disabled on this system", set the [execution policy](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-6) to `"RemoteSigned"` and then rerun `bundled_installer`. 160 | 161 | ```ps1 162 | Set-ExecutionPolicy RemoteSigned 163 | ``` 164 | - If you encounter an error with the message "No module named 'virtualenv'", use the following commands to install `virtualenv` and the EB CLI: 165 | ```ps1 166 | pip uninstall -y virtualenv 167 | pip install virtualenv 168 | python .\aws-elastic-beanstalk-cli-setup\scripts\ebcli_installer.py 169 | ``` 170 | ## 5. Frequently asked questions 171 | 172 | ### 5.1. For the **experienced Python developer**, what's the advantage of this mode of installation instead of regular `pip` inside a `virtualenv`? 173 | 174 | Even within a `virtualenv`, a developer might need to install multiple packages whose dependencies are in conflict. For example, at times the AWS CLI and the EB CLI have used conflicting versions of `botocore`. [One such instance](https://github.com/aws/aws-cli/issues/3550) was particularly egregious. When there are conflicts, users have to manage separate `virtualenvs` for each of the conflicting packages, or find a combination of the packages without conflicts. 175 | 176 | Both of these workarounds become unmanageable over time, and as the number of packages that are in conflict increases. 177 | 178 | ### 5.2. On macOS (or Linux systems with `brew`), is this better than `brew install awsebcli`? 179 | 180 | **Yes**, for these reasons: 181 | 182 | - The AWS Elastic Beanstalk team has no control over how `brew` operates. 183 | - The `brew install ...` mechanism doesn't solve the problem of dependency conflicts, which is a primary goal of this project. 184 | 185 | ### 5.3. I already have the EB CLI installed. Can I still execute `ebcli_installer.py`? 186 | 187 | **Yes**. 188 | 189 | Consider the following two cases: 190 | 191 | - `ebcli_installer.py` was previously run, creating `.ebcli-virtual-env` in the user's home directory (or the user's choice of a directory indicated through the `--location` argument). In this case, the EB CLI will overwrite `.ebcli-virtual-env` and attempt to install the latest version of the EB CLI in the `virtualenv` within it. 192 | 193 | - `eb` is in `$PATH`, however, it wasn't installed by `ebcli_installer.py`. In this case, the installer will install `eb` within `.ebcli-virtual-env` in the 194 | user's home directory (or the user's choice of a directory indicated through the `--location` argument), and prompt the user to prefix 195 | `/path-to/.ebcli-virtual-env/executables` to `$PATH`. Until you perform this action, the older `eb` executable file will continue to be referenced when you type `eb`. 196 | 197 | ### 5.4. How does `ebcli_installer.py` work? 198 | 199 | When executing the Python script, `ebcli_installer.py` does the following: 200 | 201 | - Creates a `virtualenv` exclusive to the `eb` installation. 202 | - Installs `eb` inside that `virtualenv`. 203 | - In the `/executables` directory, it generates: 204 | - A `.py` wrapper for `eb` on Linux or macOS. 205 | - `.bat` and `.ps1` wrappers for `eb` on Windows. 206 | - When complete, you will be prompted to add `/executables` to `$PATH`, only if the directory is not already in it. 207 | 208 | ### 5.5. Are there dependency problems that this mode of installation doesn't solve? 209 | 210 | Unfortunately, **yes**. 211 | 212 | Suppose the dependencies of `eb`, say `Dep A` and `Dep B`, are in conflict. Because `pip` lacks dependency management capabilities, the resulting `eb` installation might not work. 213 | 214 | ## 6. License 215 | 216 | This library is licensed under the Apache-2.0 License. 217 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | -------------------------------------------------------------------------------- /scripts/ebcli_installer.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | This script generates a self-contained installation of the EBCLI. 6 | 7 | Prerequisites: 8 | 9 | 1. Python + pip (preferably Python 3.7) 10 | 2. virtualenv 11 | 3. Bash/Zsh on Linux/MacOS ; PowerShell/CMD Prompt on Windows 12 | 13 | Usage: 14 | 15 | To execute script: 16 | 17 | # let the script find Python in PATH 18 | python ./scripts/ebcli_installer.py 19 | 20 | # specify Python executable 21 | python ./scripts/ebcli_installer.py -p ~/.pyenv/versions/3.7.12/bin/python 22 | 23 | To view help text: 24 | 25 | python ./scripts/ebcli_installer.py --help 26 | 27 | """ 28 | import argparse 29 | import os 30 | import subprocess 31 | import sys 32 | 33 | 34 | if sys.version_info < (3, 0): 35 | input = raw_input 36 | 37 | 38 | EBCLI_INSTALLER_STAMP = '.ebcli_installer_stamp' 39 | 40 | PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 41 | with open(os.path.join(PROJECT_ROOT, 'VERSION')) as version_file: 42 | EBCLI_INSTALLER_VERSION = version_file.read().strip() 43 | 44 | 45 | EXECUTABLE_WRAPPERS = { 46 | 'bat': '\n'.join( 47 | [ 48 | '@echo off', 49 | 'REM Safe way to consolidate CMD line arguments to pass to `eb`', 50 | 'set args=%1', 51 | 'shift', 52 | ':start', 53 | 'if [%1] == [] goto done', 54 | 'set args=%args% %1', 55 | 'shift', 56 | 'goto start', 57 | ':done', 58 | '', 59 | 'REM activate virtualenv, call eb and deactivate virtualenv', 60 | 'CALL {bin_location}\\activate.bat', 61 | '@start CALL {bin_location}\\eb.exe %args%', 62 | '@echo off', 63 | 'deactivate' 64 | ] 65 | ), 66 | 'ps1': '\n'.join( 67 | [ 68 | '{bin_location}\\activate.ps1', 69 | '{bin_location}\\eb $args', 70 | 'deactivate' 71 | ] 72 | ), 73 | 'py': """#!/usr/bin/env python 74 | import subprocess 75 | import sys 76 | 77 | 78 | def _exec_cmd(args): 79 | \"\"\" 80 | Function invokes the real `eb` executable within the EBCLI-specifc 81 | virtualenv. `KeyboardInterrupt`s are in this parent process as showing 82 | the stacktrace associated with this script has no information for 83 | the user and is confusing. 84 | 85 | Note that any of the stack traces the subprocess (the real `eb` process) 86 | wishes to print will be printed to STDOUT. As such, this wrapper strives 87 | to be completely non-intervening. 88 | 89 | In the event of a `KeyboardInterrupt`s, it is possible that the child might 90 | not have returned leaving the returncode `None`. In this situation, it 91 | is prudent to wait for the subprocess to exit normally. 92 | 93 | Outside `KyeboardInterrupts`s, it is unclear whether it is possible for 94 | the return code of the subprocess to remain None. In all such cases, the 95 | wrapper will assume a failure and return a non-zero exit code. 96 | \"\"\" 97 | p = subprocess.Popen(args) 98 | try: 99 | p.communicate() 100 | except KeyboardInterrupt: 101 | p.wait() 102 | 103 | if p.returncode is None: 104 | print('Assuming failure because `eb` returned with an indeterminate exit-code.') 105 | return 1 106 | 107 | return p.returncode 108 | 109 | 110 | activate_this = "{bin_location}/activate_this.py" 111 | 112 | if sys.version_info < (3, 0): 113 | execfile(activate_this, dict(__file__=activate_this)) 114 | else: 115 | exec(open(activate_this).read(), dict(__file__=activate_this)) 116 | 117 | exit(_exec_cmd(['{bin_location}/eb'] + sys.argv[1:])) 118 | """ 119 | } 120 | 121 | 122 | PATH_EXPORTER_SCRIPTS = { 123 | 'bat': 'WSCript {path_exporter_script}\n', 124 | 'vbs': '\n'.join( 125 | [ 126 | 'Set wshShell = CreateObject( "WScript.Shell" )', 127 | 'Set wshUserEnv = wshShell.Environment( "USER" )', 128 | 'Dim pathVar', 129 | 'pathVar = wshUserEnv( "Path" )', 130 | '', 131 | 'If InStr(pathVar, "{new_location}") = 0 Then', 132 | 'wshUserEnv( "Path" ) = wshUserEnv( "Path" ) + ";{new_location}"', 133 | 'End If', 134 | '', 135 | 'Set wshUserEnv = Nothing', 136 | 'Set wshShell = Nothing', 137 | '', 138 | ] 139 | ) 140 | } 141 | 142 | 143 | INSTALLATION_SUCCESS_MESSAGE_WITH_EXPORT_RECOMMENDATION__NON_WINDOWS = """ 144 | Note: To complete installation, ensure `eb` is in PATH. You can ensure this by executing: 145 | 146 | 1. Bash: 147 | 148 | echo 'export PATH="{eb_location}:$PATH"' >> ~/.bash_profile && source ~/.bash_profile 149 | 150 | 2. Zsh: 151 | 152 | echo 'export PATH="{eb_location}:$PATH"' >> ~/.zshenv && source ~/.zshenv 153 | """ 154 | 155 | 156 | INSTALLATION_SUCCESS_MESSAGE_WITH_EXPORT_RECOMMENDATION__WINDOWS = """ 157 | To complete installation, ensure `eb` is in PATH. You can ensure this by executing: 158 | 159 | 160 | 1. CMD Prompt: 161 | 162 | cmd.exe /c "{path_exporter_bat}" 163 | 164 | 2. PowerShell: 165 | 166 | & "{path_exporter}" 167 | 168 | 169 | NOTE: Additionally, you would need to **restart this shell** 170 | """ 171 | 172 | 173 | MINIMAL_INSTALLATION_SUCCESS_MESSAGE = """Success! 174 | 175 | EBCLI has been installed. 176 | """ 177 | 178 | 179 | PIP_AND_VIRTUALENV_NOT_FOUND = ' '.join( 180 | [ 181 | 'ERROR: Could not find "pip" and "virtualenv" installed.' 182 | 'Ensure "pip" and "virtualenv" are installed and that they' 183 | 'are in PATH before executing this script.' 184 | ] 185 | ) 186 | 187 | 188 | VIRTUALENV_DIR_NAME = '.ebcli-virtual-env' 189 | 190 | 191 | VIRTUALENV_NOT_FOUND = ' '.join( 192 | [ 193 | 'ERROR: Could not find and "virtualenv" installed. Ensure' 194 | 'virtualenv is installed and that it is in PATH before executing' 195 | 'this script.' 196 | ] 197 | ) 198 | 199 | 200 | GREEN_COLOR_CODE = 10 201 | 202 | 203 | RED_COLOR_CODE = 9 204 | 205 | 206 | YELLOW_COLOR_CODE = 11 207 | 208 | 209 | class ArgumentError(Exception): 210 | pass 211 | 212 | 213 | class Step(object): 214 | """ 215 | Class labels an installation Step and is expected to be invoked as 216 | the decorator of Step functions. 217 | """ 218 | Step_number = 1 219 | 220 | def __init__(self, title): 221 | self.title = title 222 | 223 | def __call__(self, func): 224 | def wrapped(*args): 225 | title = '{0}. {1}'.format(Step.Step_number, self.title) 226 | marker = '*' * len(title) 227 | print('\n{0}\n{1}\n{0}'.format(marker, title)) 228 | return_value = func(*args) 229 | Step.Step_number += 1 230 | 231 | return return_value 232 | return wrapped 233 | 234 | 235 | @Step('Activating virtualenv') 236 | def _activate_virtualenv(virtualenv_location): 237 | """ 238 | Function activates virtualenv, ".ebcli-virtual-env", created apriori for the 239 | rest of the lifetime of this script. 240 | :param virtualenv_location: the relative or absolute path to the location 241 | where the virtualenv, ".ebcli-virtual-env", was 242 | created by this script. 243 | :return None 244 | """ 245 | if sys.platform.startswith('win32'): 246 | activate_script_directory = 'Scripts' 247 | else: 248 | activate_script_directory = 'bin' 249 | 250 | activate_this_path = os.path.join( 251 | virtualenv_location, 252 | VIRTUALENV_DIR_NAME, 253 | activate_script_directory, 254 | 'activate_this.py' 255 | ) 256 | 257 | if sys.version_info < (3, 0): 258 | execfile(activate_this_path, dict(__file__=activate_this_path)) 259 | else: 260 | exec(open(activate_this_path).read(), dict(__file__=activate_this_path)) 261 | 262 | 263 | @Step('Finishing up') 264 | def _announce_success(virtualenv_location, hide_export_recommendation): 265 | """ 266 | Function checks whether the installation location is already in PATH. 267 | 268 | If the location is not in PATH, the user is recommended that the location 269 | be added to PATH along with the precise commands and instructions to do so. 270 | 271 | The commands are specific to OS + shell combinations: 272 | 273 | 1. Windows + PowerShell: execute a visual basic script to amend PATH 274 | 2. Windows + CMD Prompt: execute a BAT script to invoke the above visual 275 | basic script 276 | 3. UNIX + Bash: echo PATH modifier to .bash_profile and source it 277 | 4. UNIX + Zsh: echo PATH modifier to .zshenv and source it 278 | 279 | On Windows, users will be expected to run either a visual basic script 280 | (if using PowerShell) or a Batch script (which wraps the above visual basic 281 | script) to amend their PATH variable. This function also generates these 282 | scripts but only when `virtualenv_location` is not already in PATH. 283 | 284 | :param virtualenv_location: the relative or absolute path to the location 285 | where the virtualenv, ".ebcli-virtual-env", was 286 | created. 287 | :param hide_export_recommendation: boolean indicating whether or not to 288 | hide PATH export instructions. Useful 289 | when invoked from shells that allow 290 | modifying system PATH variable 291 | conveniently. 292 | :return: None 293 | """ 294 | new_location = _eb_wrapper_location(virtualenv_location) 295 | 296 | if new_location in os.environ['PATH']: 297 | content = MINIMAL_INSTALLATION_SUCCESS_MESSAGE.format( 298 | path=new_location, 299 | new_eb_path=os.path.join(new_location, 'eb') 300 | ) 301 | 302 | _print_success_message(content) 303 | else: 304 | path_exporter = os.path.join(new_location, 'path_exporter.vbs') 305 | path_exporter_wrapper = os.path.join(new_location, 'path_exporter.bat') 306 | if sys.platform.startswith('win32'): 307 | with open(path_exporter, 'w') as file: 308 | file.write( 309 | PATH_EXPORTER_SCRIPTS['vbs'].format( 310 | new_location=new_location 311 | ) 312 | ) 313 | 314 | with open(path_exporter_wrapper, 'w') as file: 315 | file.write( 316 | PATH_EXPORTER_SCRIPTS['bat'].format( 317 | path_exporter_script=path_exporter 318 | ) 319 | ) 320 | 321 | _print_success_message('Success!') 322 | 323 | if not hide_export_recommendation: 324 | _print_recommendation_message( 325 | INSTALLATION_SUCCESS_MESSAGE_WITH_EXPORT_RECOMMENDATION__WINDOWS 326 | .format( 327 | path_exporter_bat=path_exporter_wrapper, 328 | path_exporter=path_exporter 329 | ) 330 | ) 331 | else: 332 | _print_success_message('Success!') 333 | _print_recommendation_message( 334 | INSTALLATION_SUCCESS_MESSAGE_WITH_EXPORT_RECOMMENDATION__NON_WINDOWS 335 | .format(eb_location=new_location) 336 | ) 337 | 338 | 339 | def _print_in_foreground(message, color_number): 340 | """ 341 | Function prints a given `message` on the terminal in the foreground. `color_number` 342 | is a number between and including 0 and 255. FOr a list of color codes see: 343 | 344 | https://misc.flogisoft.com/bash/tip_colors_and_formatting#background1 345 | 346 | On Windows, `color_number` is rejected, and hence not used. At present, PowerShell 347 | is able to recognize ANSI/VT100 escape sequences, however, CMD prompt is not. 348 | 349 | :param message: a string to print in the foreground on the terminal 350 | :param color_number: an integer between and including 0 and 255 representing 351 | a color 352 | :return: None 353 | """ 354 | if sys.platform.startswith('win32'): 355 | import colorama 356 | colorama.init() 357 | if color_number == GREEN_COLOR_CODE: 358 | print(colorama.Fore.GREEN + message) 359 | elif color_number == RED_COLOR_CODE: 360 | print(colorama.Fore.RED + message) 361 | elif color_number == YELLOW_COLOR_CODE: 362 | print(colorama.Fore.LIGHTYELLOW_EX + message) 363 | else: 364 | print(message) 365 | print(colorama.Style.RESET_ALL) 366 | 367 | else: 368 | # Courtesy https://misc.flogisoft.com/bash/tip_colors_and_formatting 369 | print( 370 | "\033[38;5;{color_number}m{message}\033[0m".format( 371 | color_number=color_number, 372 | message=message, 373 | ) 374 | ) 375 | 376 | def _print_recommendation_message(message): 377 | _print_in_foreground(message, YELLOW_COLOR_CODE) 378 | 379 | 380 | def _print_success_message(message): 381 | _print_in_foreground(message, GREEN_COLOR_CODE) 382 | 383 | 384 | def _print_error_message(message): 385 | _print_in_foreground(message, RED_COLOR_CODE) 386 | 387 | 388 | @Step('Creating exclusive virtualenv for EBCLI') 389 | def _create_virtualenv( 390 | virtualenv_executable, 391 | virtualenv_location, 392 | python_installation, 393 | quiet 394 | ): 395 | """ 396 | Function creates a new virtualenv at path `virtualenv_location` 397 | using the Python at path `python_installation`, if one is provided. 398 | If `virtualenv_location` is not provided, the user's HOME directory 399 | is assumed as the location to create the virtualenv in. If 400 | `python_installation` is not provided, virtualenv attempts to 401 | find a Python in $PATH to use. If no Python executable is found in 402 | PATH, and yet if execution managed to get this far, it could mean: 403 | - the Python executable was made unavailable between the start 404 | of execution of this script and now 405 | - this script was executed with a Python executable not in PATH 406 | 407 | Prior to creation of the virtualenv, this function checks whether 408 | one by name `.ebcli-virtual-env` already exists. If this directory 409 | was not created by this installer, installation halts and the user 410 | is asked to either delete the directory or to specify an alternate 411 | location using the `--location` argument of this script. 412 | 413 | In all other cases, `.ebcli-virtual-env` is (re)created and a file 414 | to denote that the installer created `.ebcli-virtual-env` is added. 415 | 416 | :param virtualenv_executable: the name of the virtualenv executable 417 | :param virtualenv_location: the relative or absolute path to the location 418 | where the virtualenv, ".ebcli-virtual-env", must 419 | be created. 420 | :param python_installation: the relative or absolute path to the location 421 | of a Python executable to use to create the 422 | virtualenv with 423 | :param quiet: whether to display the output of virtualenv creation in 424 | STDOUT or not 425 | 426 | :return the relative or absolute path to the location where the 427 | virtualenv, ".ebcli-virtual-env", was created. 428 | """ 429 | virtualenv_location = virtualenv_location or _user_local_directory() 430 | virtualenv_directory = os.path.join(virtualenv_location, VIRTUALENV_DIR_NAME) 431 | python_installation = python_installation or sys.executable 432 | 433 | if ( 434 | os.path.exists(virtualenv_directory) 435 | and not _directory_was_created_by_installer(virtualenv_directory) 436 | ): 437 | _error( 438 | 'Installation cannot proceed because "{virtualenv_location}" already exists ' 439 | 'but was not created by this EBCLI installer.' 440 | '\n' 441 | '\n' 442 | 'You can either:\n' 443 | '\n' 444 | '1. Delete "{virtualenv_location}" after verifying you don\'t need it; OR\n' 445 | '2. Specify an alternate location to install the EBCLI and its artifacts in ' 446 | 'using the `--location` argument of this script .\n'.format( 447 | virtualenv_location=virtualenv_directory 448 | ) 449 | ) 450 | 451 | virtualenv_args = [ 452 | virtualenv_executable or 'virtualenv', 453 | '"{}"'.format(virtualenv_directory) 454 | ] 455 | 456 | python_installation and virtualenv_args.extend( 457 | ['-p', '"{}"'.format(python_installation)] 458 | ) 459 | 460 | if _exec_cmd(virtualenv_args, quiet) != 0: 461 | exit(1) 462 | 463 | _add_ebcli_stamp(virtualenv_directory) 464 | 465 | return virtualenv_location 466 | 467 | 468 | @Step('Locating virtualenv installation') 469 | def _locate_virtualenv_executable(): 470 | """ 471 | Function attempts to find the location at which `virtualenv` is installed. 472 | 473 | If such a location is found, the function returns normally. Otherwise, the 474 | function further proceeds to check for `pip` and informs the user that either 475 | or both of `pip` and `virtualenv` are missing. 476 | :param quiet: a boolean indicating whether minimal output printed should be 477 | non-verbose, minimal. 478 | :return: None 479 | :side-effect: script will exit with a non-0 return code if a 480 | virtualenv and/or pip executables haven't been found. 481 | """ 482 | virtualenv_executables = ['virtualenv'] 483 | 484 | if sys.platform.startswith('win32'): 485 | virtualenv_executables += ['virtualenv.cmd', 'virtualenv.exe'] 486 | virtualenv_executable = None 487 | for _virtualenv_executable in virtualenv_executables: 488 | if _executable_found(_virtualenv_executable, True): 489 | virtualenv_executable = _virtualenv_executable 490 | 491 | if not virtualenv_executable: 492 | if not _pip_executable_found(True): 493 | print(PIP_AND_VIRTUALENV_NOT_FOUND) 494 | else: 495 | print(VIRTUALENV_NOT_FOUND) 496 | 497 | return virtualenv_executable 498 | 499 | 500 | @Step('Creating EB wrappers') 501 | def _generate_ebcli_wrappers(virtualenv_location): 502 | """ 503 | Function generates: 504 | - a Python wrapper for the awsebcli on Unix/Linux computers; OR 505 | - Powershell and CMD Prompt wrappers on Windows 506 | 507 | within a "executables" directory inside ".ebcli-virtual-env". Further, 508 | on Unix/Linux, the wrapper is made an executable. 509 | 510 | :param virtualenv_location: the relative or absolute path to the location 511 | where the virtualenv, ".ebcli-virtual-env", 512 | exists. 513 | :return None 514 | """ 515 | executables_dir = _eb_wrapper_location(virtualenv_location) 516 | not os.path.exists(executables_dir) and os.mkdir(executables_dir) 517 | 518 | ebcli_script_path = os.path.join(executables_dir, 'eb') 519 | ebcli_ps1_script_path = os.path.join(executables_dir, 'eb.ps1') 520 | ebcli_bat_script_path = os.path.join(executables_dir, 'eb.bat') 521 | 522 | if sys.platform.startswith('win32'): 523 | with open(ebcli_ps1_script_path, 'w') as script: 524 | script.write(_powershell_script_body(virtualenv_location)) 525 | 526 | with open(ebcli_bat_script_path, 'w') as script: 527 | script.write(_bat_script_body(virtualenv_location)) 528 | else: 529 | with open(ebcli_script_path, 'w') as script: 530 | script.write(_python_script_body(virtualenv_location)) 531 | _exec_cmd(['chmod', '+x', ebcli_script_path], False) 532 | 533 | 534 | @Step('Installing EBCLI') 535 | def _install_ebcli(quiet, version, ebcli_source): 536 | """ 537 | Function installs the awsebcli presumably within the virtualenv, 538 | ".ebcli-virtual-env", created and activated by this script apriori. 539 | If `version` is passed, the specific version of the EBCLI is installed. 540 | 541 | The presence of `version` and `ebcli_source` will lead to an exception 542 | as they represent two different ways of installing the EBCLI. 543 | 544 | :param quiet: whether to display the output of awsebcli installation to 545 | the terminal or not 546 | :param version: the specific version of awsebcli to install 547 | :return None 548 | """ 549 | if ebcli_source: 550 | install_args = ['pip', 'install', '{}'.format(ebcli_source.strip())] 551 | elif version: 552 | install_args = ['pip', 'install', 'awsebcli=={}'.format(version.strip())] 553 | else: 554 | install_args = [ 555 | 'pip', 'install', 'awsebcli', 556 | '--upgrade', 557 | '--upgrade-strategy', 'eager', 558 | ] 559 | returncode = _exec_cmd(install_args, quiet) 560 | 561 | if returncode != 0: 562 | exit(returncode) 563 | 564 | 565 | def _add_ebcli_stamp(virtualenv_directory): 566 | """ 567 | Function adds a stamp in the form of a file, `EBCLI_INSTALLER_STAMP` 568 | to recognize during future executions of this script that it created 569 | it. 570 | 571 | :param virtualenv_directory: The directory where the EBCLI and its artifacts 572 | will be installed 573 | :return: None 574 | """ 575 | with open( 576 | os.path.join( 577 | virtualenv_directory, 578 | EBCLI_INSTALLER_STAMP 579 | ), 580 | 'w' 581 | ) as file: 582 | file.write('\n') 583 | 584 | 585 | def _bat_script_body(virtualenv_location): 586 | """ 587 | Function returns a CMD Prompt (bat) script which essentially will 588 | wrap the `eb` executable such that the executable is invoked within 589 | the virtualenv, ".ebcli-virtual-env", created apriori. 590 | :param virtualenv_location: the relative or absolute path to the location 591 | where the virtualenv, ".ebcli-virtual-env", was 592 | created. 593 | :return: None 594 | """ 595 | return EXECUTABLE_WRAPPERS['bat'].format( 596 | bin_location=_original_eb_location(virtualenv_location) 597 | ) 598 | 599 | 600 | def _directory_was_created_by_installer(virtualenv_directory): 601 | """ 602 | Function checks whether `virtualenv_directory` was previously created 603 | by this script. 604 | 605 | :param virtualenv_directory: The directory where the EBCLI and its 606 | artifacts will be installed. 607 | :return: Boolean indicating whether `virtualenv_directory` was created 608 | by this script or not. 609 | """ 610 | return os.path.exists( 611 | os.path.join( 612 | virtualenv_directory, 613 | EBCLI_INSTALLER_STAMP 614 | ) 615 | ) 616 | 617 | 618 | def _eb_wrapper_location(virtualenv_location): 619 | """ 620 | Function returns the location of the directory within the virtualenv, 621 | ".ebcli-virtual-env", where the wrapper of the `eb` executable should 622 | be installed. This is `executables` on all OSes. 623 | :param virtualenv_location: the relative or absolute path to the location 624 | where the virtualenv, ".ebcli-virtual-env", was 625 | created. 626 | :return: the location of the directory within the virtualenv where 627 | the wrapper of the `eb` executable should be installed. 628 | """ 629 | return os.path.join( 630 | os.path.abspath(virtualenv_location), 631 | VIRTUALENV_DIR_NAME, 632 | 'executables' 633 | ) 634 | 635 | 636 | def _ensure_not_inside_virtualenv_to_begin_with(): 637 | """ 638 | Function checks whether the `VIRTUAL_ENV` environment variable has 639 | already been set in effect checking whether a virtualenv is presently 640 | active in the context of this shell. If so, this script is exited 641 | immediately with a non-0 exit code. 642 | 643 | Rationale: 644 | 1. when a virtualenv is entered current `PATH` is saved 645 | 2. real `PATH` is amended so that the `bin`/`Scripts` directory inside 646 | the virtualenv is at the head of PATH 647 | 3. it is possible that the user (or a script such as this one) may 648 | further amend the head of PATH. 649 | 4. all of the changes made to PATH will be lost upon deactivation 650 | of the virtualenv thereby leading to unpredictable behaviour. 651 | 652 | :return: None 653 | :side-effect: script will exit with a non-0 return code if a 654 | virtualenv has already been activated within the shell. 655 | """ 656 | if os.environ.get('VIRTUAL_ENV'): 657 | _error('This script cannot be executed inside a virtual environment.') 658 | 659 | 660 | def _error(message): 661 | _print_error_message('ERROR: {}'.format(message)) 662 | exit(1) 663 | 664 | 665 | def _exec_cmd(args, quiet): 666 | """ 667 | Function invokes `subprocess.Popen` in the `shell=True` mode and returns 668 | the return code of the subprocess. 669 | 670 | :param args: the args to pass to `subprocess.Popen` 671 | :param quiet: Whether to avoid displaying output of the subprocess to 672 | STDOUT 673 | :return: the return code of the subprocess 674 | """ 675 | stdout = subprocess.PIPE if quiet else None 676 | p = subprocess.Popen( 677 | ' '.join(args), 678 | stdout=stdout, 679 | stderr=stdout, 680 | shell=True 681 | ) 682 | 683 | p.communicate() 684 | 685 | return p.returncode 686 | 687 | 688 | def _executable_found(executable, quiet): 689 | """ 690 | Function attempts to locate `executable` and returns True 691 | if it can find it installed, else False. 692 | :param executable: The executable to find on the computer 693 | :return: True/False 694 | """ 695 | return _exec_cmd([executable, '--version'], quiet) == 0 696 | 697 | 698 | def _user_local_directory(): 699 | """ 700 | Function attempts to find the home of the current user. On Unix/Linux, 701 | this is $HOME or "~". On Windows, this can be one of $USERPROFILE, 702 | $LOCALAPPDATA, or $APPDATA. In the event that the home cannot be 703 | determined, execution will continue normally. 704 | :return: the home of the current user 705 | """ 706 | if sys.platform.startswith('win32'): 707 | identified_location = ( 708 | os.environ.get('USERPROFILE') 709 | or os.environ.get('LOCALAPPDATA') 710 | or os.environ.get('APPDATA') 711 | ) 712 | else: 713 | identified_location = os.environ.get('HOME') 714 | 715 | if not identified_location: 716 | _error( 717 | "Could not determine user's HOME directory. " 718 | "Pass a location explicitly using the `--location` argument." 719 | ) 720 | return identified_location 721 | 722 | 723 | def _original_eb_location(virtualenv_location): 724 | """ 725 | Function returns the location of the directory within the virtualenv, 726 | ".ebcli-virtual-env", where the original `eb` executable is expected 727 | to be found. This is `bin` on Unix/Linux and `Scripts` on Windows. 728 | :param virtualenv_location: the relative or absolute path to the location 729 | where the virtualenv, ".ebcli-virtual-env", was 730 | created. 731 | :return: the location of the directory within the virtualenv where 732 | the original `eb` executable is expected to be found 733 | """ 734 | if sys.platform.startswith('win32'): 735 | scripts_directory = 'Scripts' 736 | else: 737 | scripts_directory = 'bin' 738 | 739 | return os.path.join( 740 | os.path.abspath(virtualenv_location), 741 | VIRTUALENV_DIR_NAME, 742 | scripts_directory 743 | ) 744 | 745 | 746 | def _parse_arguments(): 747 | """ 748 | Function creates an `ArgumentParser`, parses arguments, and returns 749 | the parsed arguments. 750 | 751 | :return: an instance of argparse.Namespace representing the command-line 752 | arguments passed by the user. 753 | """ 754 | parser = argparse.ArgumentParser( 755 | description='EBCLI installer {}\n\n' 756 | 'This program creates an isolated virtualenv solely meant for invoking ' 757 | '`eb` within.'.format(EBCLI_INSTALLER_VERSION), 758 | usage='python {file_name} [optional arguments]'.format(file_name=__file__), 759 | formatter_class=argparse.RawTextHelpFormatter 760 | ) 761 | parser.add_argument( 762 | '-e', '--virtualenv-executable', 763 | help="path to the virtualenv installation to use to create the EBCLI's virtualenv" 764 | ) 765 | parser.add_argument( 766 | '-i', '--hide-export-recommendation', 767 | action='store_true', 768 | help="boolean to hide recommendation to modify PATH" 769 | ) 770 | parser.add_argument( 771 | '-l', '--location', 772 | help='location to store the awsebcli packages and its dependencies in' 773 | ) 774 | parser.add_argument( 775 | '-p', '--python-installation', 776 | help='path to the python installation under which to install the ' 777 | 'awsebcli and its \ndependencies' 778 | ) 779 | parser.add_argument( 780 | '-q', '--quiet', 781 | action='store_true', 782 | help='enable quiet mode to display only minimal, necessary output' 783 | ) 784 | parser.add_argument( 785 | '-s', '--ebcli-source', 786 | help='filesystem path to a Git repository of the EBCLI, or a .zip or .tar file of \n' 787 | 'the EBCLI source code; useful when testing a development version of the EBCLI.' 788 | ) 789 | parser.add_argument( 790 | '-v', '--version', 791 | help='version of EBCLI to install' 792 | ) 793 | 794 | arguments = parser.parse_args() 795 | 796 | if arguments.version and arguments.ebcli_source: 797 | raise ArgumentError( 798 | '"--version" and "--ebcli-source" cannot be used together ' 799 | 'because they represent two distinct sources of the EBCLI.' 800 | ) 801 | return arguments 802 | 803 | 804 | def _pip_executable_found(quiet): 805 | """ 806 | Function attempts to locate one of pip, pip2, and pip3 and returns True 807 | if it can find one of them, else False. 808 | 809 | In addition to the form `pip`, `pip` may also be installed as `pip`. 810 | We avoid checking for these as the likelihood that `pip` got installed as 811 | `pip` but not as `pip` 812 | :return: True/False 813 | """ 814 | pip_executables = ['pip', 'pip2', 'pip3'] 815 | if sys.platform.startswith('win32'): 816 | pip_executables += [ 817 | 'pip.exe', 'pip2.exe', 'pip3.exe', 818 | 'pip.cmd', 'pip2.cmd', 'pip3.cmd', 819 | ] 820 | for executable in pip_executables: 821 | if _executable_found(executable, quiet): 822 | if not quiet: 823 | print('Found {}'.format(executable)) 824 | return True 825 | 826 | 827 | def _powershell_script_body(virtualenv_location): 828 | """ 829 | Function returns a Powershell (PS1) script which essentially will 830 | wrap the `eb` executable such that the executable is invoked within 831 | the virtualenv, ".ebcli-virtual-env", created apriori. 832 | :param virtualenv_location: the relative or absolute path to the location 833 | where the virtualenv, ".ebcli-virtual-env", was 834 | created. 835 | :return: None 836 | """ 837 | return EXECUTABLE_WRAPPERS['ps1'].format( 838 | bin_location=_original_eb_location(virtualenv_location) 839 | ) 840 | 841 | 842 | def _python_script_body(virtualenv_location): 843 | """ 844 | Function returns a Python script which essentially will wrap 845 | the `eb` executable such that the executable is invoked within 846 | the virtualenv, ".ebcli-virtual-env", created apriori. 847 | :param virtualenv_location: the relative or absolute path to the location 848 | where the virtualenv, ".ebcli-virtual-env", was 849 | created. 850 | :return: None 851 | """ 852 | return EXECUTABLE_WRAPPERS['py'].format( 853 | bin_location=_original_eb_location(virtualenv_location) 854 | ) 855 | 856 | 857 | if __name__ == '__main__': 858 | _ensure_not_inside_virtualenv_to_begin_with() 859 | arguments_context = _parse_arguments() 860 | virtualenv = ( 861 | arguments_context.virtualenv_executable 862 | or _locate_virtualenv_executable() 863 | ) 864 | virtualenv_location = _create_virtualenv( 865 | virtualenv, 866 | arguments_context.location, 867 | arguments_context.python_installation, 868 | arguments_context.quiet 869 | ) 870 | _activate_virtualenv(virtualenv_location) 871 | _install_ebcli( 872 | arguments_context.quiet, 873 | arguments_context.version, 874 | arguments_context.ebcli_source 875 | ) 876 | _generate_ebcli_wrappers(virtualenv_location) 877 | _announce_success( 878 | virtualenv_location, 879 | arguments_context.hide_export_recommendation 880 | ) 881 | --------------------------------------------------------------------------------