├── .gitignore ├── go.mod ├── assets └── wait-for-it.png ├── execute.go ├── LICENSE ├── main.go ├── services.go ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── .github └── workflows │ └── release.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ./wait-for-it -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/roerohan/wait-for-it 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /assets/wait-for-it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roerohan/wait-for-it/HEAD/assets/wait-for-it.png -------------------------------------------------------------------------------- /execute.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "os/exec" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | // Execute is used to run a command and print the value in stdout and stderr. 12 | // 13 | // The return value contains the command's exit code. 14 | func Execute(command []string) int { 15 | cmd := exec.Command(command[0], command[1:]...) 16 | cmd.Stdout = os.Stdout 17 | cmd.Stderr = os.Stderr 18 | err := cmd.Start() 19 | if err != nil { 20 | Log("unable to start " + err.Error()) 21 | return -1 22 | } 23 | 24 | sigint := make(chan os.Signal, 1) 25 | signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) 26 | <-sigint 27 | 28 | err = cmd.Process.Signal(os.Interrupt) 29 | if err != nil { 30 | Log("unable send interrupt " + err.Error()) 31 | } 32 | 33 | err = cmd.Wait() 34 | if err != nil { 35 | var exitErr *exec.ExitError 36 | if errors.As(err, &exitErr) { 37 | return exitErr.ExitCode() 38 | } 39 | return -1 40 | } 41 | return 0 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rohan Mukherjee 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var ( 10 | timeout int 11 | services Services 12 | quiet bool 13 | strict bool 14 | ) 15 | 16 | func init() { 17 | flag.IntVar(&timeout, "t", 15, "Timeout in seconds, zero for no timeout") 18 | flag.BoolVar(&quiet, "q", false, "Quiet, don't output any status messages") 19 | flag.BoolVar(&strict, "s", false, "Only execute subcommand if the test succeeds") 20 | flag.Var(&services, "w", "Services to be waiting for, in the form `host:port`") 21 | } 22 | 23 | // Log is used to log with prefix wait-for-it: 24 | func Log(message string) { 25 | if quiet { 26 | return 27 | } 28 | 29 | fmt.Println("wait-for-it: " + message) 30 | } 31 | 32 | func main() { 33 | flag.Parse() 34 | args := os.Args 35 | 36 | if len(services) != 0 { 37 | Log(fmt.Sprintf("waiting %d seconds for %s", timeout, services.String())) 38 | ok := services.Wait(timeout) 39 | 40 | if !ok { 41 | Log(fmt.Sprintf("timeout occured after waiting for %d seconds", timeout)) 42 | if strict { 43 | Log("strict mode, refusing to execute subprocess") 44 | os.Exit(1) 45 | } 46 | } 47 | } 48 | 49 | var command []string 50 | 51 | for i, arg := range args { 52 | if arg == "--" { 53 | if (i + 1) < len(args) { 54 | command = args[i+1:] 55 | } 56 | 57 | break 58 | } 59 | } 60 | 61 | if len(command) == 0 { 62 | return 63 | } 64 | 65 | os.Exit(Execute(command)) 66 | } 67 | -------------------------------------------------------------------------------- /services.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // Services is a string array storing 12 | // the services that are to be waited for 13 | type Services []string 14 | 15 | // Set is used to append a string 16 | // to the service, to implement 17 | // the interface flag.Value 18 | func (s *Services) Set(value string) error { 19 | *s = append(*s, value) 20 | return nil 21 | } 22 | 23 | // String returns a string 24 | // representation of the flag, 25 | // to implement the interface 26 | // flag.Value 27 | func (s *Services) String() string { 28 | return strings.Join(*s, ", ") 29 | } 30 | 31 | // Wait waits for all services 32 | func (s *Services) Wait(tSeconds int) bool { 33 | t := time.Duration(tSeconds) * time.Second 34 | now := time.Now() 35 | 36 | var wg sync.WaitGroup 37 | wg.Add(len(*s)) 38 | 39 | success := make(chan bool, 1) 40 | 41 | go func() { 42 | for _, service := range services { 43 | go waitOne(service, &wg, now) 44 | } 45 | wg.Wait() 46 | success <- true 47 | }() 48 | 49 | select { 50 | case <-success: 51 | return true 52 | case <-time.After(t): 53 | return false 54 | } 55 | } 56 | 57 | func waitOne(service string, wg *sync.WaitGroup, start time.Time) { 58 | defer wg.Done() 59 | for { 60 | _, err := net.Dial("tcp", service) 61 | if err == nil { 62 | Log(fmt.Sprintf("%s is available after %s", service, time.Since(start))) 63 | break 64 | } 65 | time.Sleep(time.Second) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for contributing! :smile: 4 | 5 | The following is a set of guidelines for contributing. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | > Note: Contributions should be made via pull requests to the `main` branch of the repository. 8 | 9 | ## Table of Contents 10 | 11 | 1. [Styleguides](#styleguides) 12 | 2. [What should I know before I get started?](#what-should-i-know-before-i-get-started) 13 | 3. [How Can I contribute?](#how-can-i-contribute) 14 | 15 | # Guidelines 16 | The following are the guidelines we request you to follow in order to contribute to this project. 17 | 18 | ## Styleguides 19 | 20 | ### Commit Messages 21 | 22 | The commit messages should follow the following pattern: 23 | ```bash 24 | feat: Description # if a new feature is added 25 | fix: Description # if a bug is fixed 26 | refactor: Description # if code is refactored 27 | docs: Description # if documentation is added 28 | lint: Description # if a lint issue is fixed 29 | ``` 30 | 31 | ### Issues 32 | 33 | ```bash 34 | update: Description # if an update is required for a feature 35 | bug: Description # if there is a bug in a particular feature 36 | suggestion: Description # if you want to suggest a better way to implement a feature 37 | ``` 38 | 39 | ### Code Styleguide 40 | The code should satisfy the following: 41 | - Have meaningful variable names, either in `snake_case` or `camelCase`. 42 | - Have no `lint` issues. 43 | - Have meaningful file names, directory names and directory structure. 44 | - Have a scope for easy fixing, refactoring and scaling. 45 | 46 | ### Pull Requests 47 | Pull requests should have: 48 | - A concise commit message. 49 | - A description of what was changed/added. 50 | - Mention if it's a breaking change 51 | 52 | ## What should I know before I get started 53 | You can contribute to any of the features you want, here's what you need to know: 54 | - How the project works. 55 | - The technology stack used for the project. 56 | - A brief idea about writing documentation. 57 | 58 | ## How Can I Contribute 59 | 60 | You can contribute by: 61 | - Reporting Bugs 62 | - Suggesting Enhancements 63 | - Code Contribution 64 | - Pull Requests 65 | 66 | A pull request with all contributions well documented makes it easier to review :smile:. 67 | 68 | > It is not compulsory to follow the guidelines mentioned above, but it is strongly recommended. 69 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at roerohan@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build from source and make a new release on GitHub 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | generate-linux: 12 | name: Generate Linux binary 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: actions/setup-go@v1 18 | with: 19 | go-version: "1.17.1" 20 | 21 | - name: Build from source 22 | run: | 23 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/wait-for-it 24 | 25 | - name: Upload artifact 26 | uses: actions/upload-artifact@v2 27 | with: 28 | name: wait-for-it 29 | path: ./bin/wait-for-it 30 | 31 | generate-windows: 32 | name: Generate Windows binary 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v2 36 | 37 | - uses: actions/setup-go@v1 38 | with: 39 | go-version: "1.17.1" 40 | 41 | - name: Build from source 42 | run: | 43 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/wait-for-it_win64.exe 44 | 45 | - name: Upload artifact 46 | uses: actions/upload-artifact@v2 47 | with: 48 | name: wait-for-it_windows 49 | path: ./bin/wait-for-it_win64.exe 50 | 51 | generate-mac: 52 | name: Generate MacOS binary 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v2 56 | 57 | - uses: actions/setup-go@v1 58 | with: 59 | go-version: "1.17.1" 60 | 61 | - name: Build from source 62 | run: | 63 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/wait-for-it_mac 64 | 65 | - name: Upload artifact 66 | uses: actions/upload-artifact@v2 67 | with: 68 | name: wait-for-it_mac 69 | path: ./bin/wait-for-it_mac 70 | 71 | 72 | release: 73 | name: Release and Upload Asset 74 | runs-on: ubuntu-latest 75 | needs: [generate-linux, generate-windows, generate-mac] 76 | steps: 77 | - uses: actions/checkout@v2 78 | with: 79 | fetch-depth: 0 80 | 81 | - name: Download Linux Artifact 82 | uses: actions/download-artifact@v2 83 | with: 84 | name: wait-for-it 85 | path: ./bin 86 | 87 | - name: Download Windows Artifact 88 | uses: actions/download-artifact@v2 89 | with: 90 | name: wait-for-it_windows 91 | path: ./bin 92 | 93 | - name: Download MacOS Artifact 94 | uses: actions/download-artifact@v2 95 | with: 96 | name: wait-for-it_mac 97 | path: ./bin 98 | 99 | 100 | - name: Increment version 101 | id: increment_version 102 | run: | 103 | git config --global user.email "roerohan@gmail.com" 104 | git config --global user.name "roerohan" 105 | 106 | wget https://gist.githubusercontent.com/roerohan/17e08ee9ed42d97bb841033b038117bd/raw/3e73b37c5f3a3c48fc9cf6d35e52f123dea0438b/gitautotag.sh 107 | chmod +x ./gitautotag.sh 108 | ./gitautotag.sh --bug 109 | 110 | echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV 111 | echo "VERSION=$VERSION" 112 | 113 | - name: Create Release 114 | id: create_release 115 | uses: actions/create-release@v1 116 | env: 117 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 118 | with: 119 | tag_name: ${{ env.VERSION }} 120 | release_name: Release ${{ env.VERSION }} 121 | body: | 122 | The binary executable compiled using the following command: 123 | CGO_ENABLED=0 GOOS={os} GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/wait-for-it_{os} 124 | 125 | For any other Operating System, you can run the command above with your OS and ARCH to generate a binary. 126 | 127 | draft: false 128 | prerelease: false 129 | 130 | - name: Upload Release Asset 131 | id: upload-release-asset 132 | run: | 133 | hub release edit -a ./bin/wait-for-it -a ./bin/wait-for-it_mac -a ./bin/wait-for-it_win64.exe -m "" ${{ env.VERSION }} 134 | env: 135 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Issues][issues-shield]][issues-url] 2 | 3 | 4 |
5 |

6 | 9 | 10 |

wait-for-it

11 | 12 |

13 | A Golang package to wait on the availability of a TCP host and port. 14 |
15 | Explore the docs » 16 |
17 |
18 | View Demo 19 | · 20 | Report Bug 21 | · 22 | Request Feature 23 |

24 |

25 | 26 | 27 | 28 | 29 | ## Table of Contents 30 | 31 | * [About the Project](#about-the-project) 32 | * [Built With](#built-with) 33 | * [Getting Started](#getting-started) 34 | * [Prerequisites](#prerequisites) 35 | * [Installation](#installation) 36 | * [Usage](#usage) 37 | * [Roadmap](#roadmap) 38 | * [Contributing](#contributing) 39 | * [License](#license) 40 | * [Contributors](#contributors-) 41 | 42 | 43 | 44 | 45 | ## About The Project 46 | 47 | wait-for-it 48 | 49 | This package is adapted from [vishnubob/wait-for-it](https://github.com/vishnubob/wait-for-it), a popular project used to wait for TCP connections until a service is up. This is commonly used in `docker-compose` files to make one service wait for another, for example, to make a web server wait for a `mysql` database. 50 | 51 | Since [vishnubob/wait-for-it](https://github.com/vishnubob/wait-for-it) is a bash script, it does not work directly with minimal containers like [scratch](https://hub.docker.com/_/scratch), which are commonly used to run binaries. 52 | 53 | With the help of this package, you can generate a binary, which can run inside minimal Docker containers and wait for a TCP connection such as a `mysql` database. You can find an example here: [csivitu/bl0b](https://github.com/csivitu/bl0b/blob/master/docker-compose.yml). 54 | 55 | 56 | ### Built With 57 | 58 | * [Go](https://golang.org/) 59 | 60 | 61 | 62 | 63 | ## Getting Started 64 | 65 | A `amd64` (64-bit) executables for `linux`, `darwin`, and `windows` are available in the [GitHub releases](https://github.com/roerohan/wait-for-it/releases/). 66 | 67 | If you want to build a binary for a different Operating System / Architecture, you can follow the procedure below. 68 | 69 | ### Prerequisites 70 | 71 | The only prerequisite is `golang` which you can get [here](https://golang.org/). 72 | 73 | * go 74 | 75 | ### Installation 76 | 77 | 1. Get the package using `go install`. 78 | ```bash 79 | go install github.com/roerohan/wait-for-it 80 | ``` 81 | 82 | Alternatively, you can follow these steps: 83 | 84 | 1. Clone the repository. 85 | ```bash 86 | git clone https://github.com/roerohan/wait-for-it 87 | ``` 88 | 89 | 2. Build a go binary from source. 90 | ```bash 91 | cd wait-for-it 92 | go build -o ./bin/wait-for-it 93 | ``` 94 | 95 | 3. Use the binary inside the bin folder. 96 | ```bash 97 | ./bin/wait-for-it google.com:80 -- echo "It works\!" 98 | ``` 99 | 100 | 101 | 102 | ## Usage 103 | 104 | The usage is similar to [vishnubob/wait-for-it](https://github.com/vishnubob/wait-for-it). 105 | 106 | Use `wait-for-it -h` to display the following list. 107 | 108 | ``` 109 | Usage of wait-for-it: 110 | -q Quiet, don't output any status messages 111 | -s Only execute subcommand if the test succeeds 112 | -t int 113 | Timeout in seconds, zero for no timeout (default 15) 114 | -w host:port 115 | Services to be waiting for, in the form host:port 116 | ``` 117 | 118 | You can run any executable after passing ` -- `, like in the examples below. 119 | 120 | ### Examples: 121 | 122 | 1. Waiting for multiple services in parallel. 123 | 124 | ```sh 125 | wait-for-it -w google.com:80 -w localhost:27017 -t 30 -- echo "Waiting for 30 seconds for google.com:80 and localhost:27017" 126 | ``` 127 | 128 | 2. Strict mode will not execute the subcommand only if TCP connection was successful. 129 | 130 | ```sh 131 | $ wait-for-it -w abcd:80 -s -t 5 -- echo "Done\!" 132 | wait-for-it: waiting 5 seconds for abcd:80 133 | wait-for-it: timeout occured after waiting for 5 seconds 134 | wait-for-it: strict mode, refusing to execute subprocess 135 | ``` 136 | 137 | 138 | 139 | ## Roadmap 140 | 141 | See the [open issues](https://github.com/roerohan/wait-for-it/issues) for a list of proposed features (and known issues). 142 | 143 | 144 | 145 | 146 | ## Contributing 147 | 148 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 149 | 150 | 1. Fork the Project 151 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 152 | 3. Commit your Changes (`git commit -m 'feat: Add some AmazingFeature'`) 153 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 154 | 5. Open a Pull Request 155 | 156 | You are requested to follow the contribution guidelines specified in [CONTRIBUTING.md](./CONTRIBUTING.md) while contributing to the project :smile:. 157 | 158 | 159 | ## License 160 | 161 | Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information. 162 | 163 | 164 | 165 | 166 | 167 | 168 | [roerohan-url]: https://roerohan.github.io 169 | [issues-shield]: https://img.shields.io/github/issues/roerohan/wait-for-it.svg?style=flat-square 170 | [issues-url]: https://github.com/roerohan/wait-for-it/issues 171 | --------------------------------------------------------------------------------