├── .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 |
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 |
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 |
--------------------------------------------------------------------------------