diff --git a/.dockerignore b/.dockerignore index 3d7a85f..758f84a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,2 @@ .env.test -.env \ No newline at end of file +*.env \ No newline at end of file diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 95ae943..aea7d3c 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -22,8 +22,14 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Run Tests run: | - export BOT_TOKEN=${{ secrets.CI_BOT_TOKEN }} - export BOT_TOKENS=${{ secrets.CI_BOT_TOKENS }} - export DRAFT_CHAT_ID=${{ secrets.CI_DRAFT_CHAT_ID }} - export CHAT_ID=${{ secrets.CI_CHAT_ID }} + ( + echo BOT_TOKEN=${{ secrets.CI_BOT_TOKEN_1 }} + echo BOT_TOKENS=${{ secrets.CI_BOT_TOKEN_1 }},${{ secrets.CI_BOT_TOKEN_2 }},${{ secrets.CI_BOT_TOKEN_3 }} + echo TOKENS=${{ secrets.CI_BOT_TOKEN_1 }},${{ secrets.CI_BOT_TOKEN_2 }},${{ secrets.CI_BOT_TOKEN_3 }} + echo DRAFT_CHAT_ID=${{ secrets.CI_DRAFT_CHAT_ID }} + echo CHAT_ID=${{ secrets.CI_CHAT_ID }} + ) > .env + cp .env .env.test make test + chmod 777 ./scripts/wait-for-it/wait-for-it.sh + make docker-e2etest diff --git a/.gitignore b/.gitignore index 35189d0..a5dda83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea .env.test -.env \ No newline at end of file +*.env \ No newline at end of file diff --git a/Makefile b/Makefile index b0ab791..e96d773 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,32 @@ APPNAME=tg-bot-storage ## test: run tests on cmd and pkg files. .PHONY: test test: vet fmt - go test ./... + CI="false" go test ./... ## build: build application binary. .PHONY: build build: go build -o $(APPNAME) +## run: run the api +.PHONY: run +run: + go run ./cmd/main.go + +## e2etest-compose: run end to end tests in the docker-compose.test.yaml. Basically this is the test for the rest-client package +.PHONY: e2etest-compose +e2etest-compose: + cd ./pkg/rest-client/ && CI="true" go test -v -count=1 . + +## e2etest: run end to end tests against local api +.PHONY: e2etest +e2etest: + cd ./pkg/rest-client/ && api_host="http://localhost:7000/" CI="true" go test -v -count=1 . + +## docker-e2etest: run e2etests in a docker compose +docker-e2etest: + docker-compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from e2etests + ## docker-build: build the api docker image .PHONY: docker-build docker-build: diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..6da9e23 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,30 @@ +version: '3' + +services: + api: + restart: always + build: + context: . + dockerfile: Dockerfile + env_file: + - ./.env + environment: + api_key: "12345" + ports: + - 7000:7000 + extra_hosts: + - host.docker.internal:host-gateway + + e2etests: + depends_on: + - api + image: golang:buster + command: /app/scripts/wait-for-it/wait-for-it.sh api:7000 -t 300 -- make -C /app e2etest-compose + env_file: + - ./.env + environment: + CI: "true" + api_host: "http://api:7000/" + api_key: "12345" + volumes: + - .:/app diff --git a/go.mod b/go.mod index b191eb2..4792d5a 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,7 @@ require ( github.com/gin-gonic/gin v1.8.1 // indirect github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/joho/godotenv v1.4.0 + github.com/stretchr/objx v0.4.0 // indirect + github.com/stretchr/testify v1.7.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8d420c5..4d3cde6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= @@ -50,15 +51,20 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= @@ -95,3 +101,5 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/bot/client_test.go b/pkg/bot/client_test.go index 5413b5d..4651606 100644 --- a/pkg/bot/client_test.go +++ b/pkg/bot/client_test.go @@ -106,7 +106,7 @@ func Test_DownloadFileReader(t *testing.T) { draftChatId, _ := strconv.ParseInt(os.Getenv(ENVDRAFTCHATID), 10, 64) lock := sync.Mutex{} count := 0 - total := 100 + total := 5 for i := 0; i < total; i++ { wg.Add(1) go func() { diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go index 43b02f0..b069d80 100644 --- a/pkg/manager/manager_test.go +++ b/pkg/manager/manager_test.go @@ -38,7 +38,7 @@ func Test_UploadFileReaderWithOneBot(t *testing.T) { lock := sync.Mutex{} chatId, _ := strconv.ParseInt(os.Getenv(ENVCHATID), 10, 64) count := 0 - total := 25 + total := 5 for i := 0; i < total; i++ { wg.Add(1) go func(i int) { @@ -75,7 +75,7 @@ func Test_UploadFileReaderWithMultipleBot(t *testing.T) { lock := sync.Mutex{} chatId, _ := strconv.ParseInt(os.Getenv(ENVCHATID), 10, 64) count := 0 - total := 60 + total := 5 for i := 0; i < total; i++ { wg.Add(1) go func(i int) { @@ -126,7 +126,7 @@ func Test_DownloadFileReader(t *testing.T) { wg := sync.WaitGroup{} lock := sync.Mutex{} count := 0 - total := 60 + total := 5 for i := 0; i < total; i++ { wg.Add(1) go func() { diff --git a/pkg/rest-client/client.go b/pkg/rest-client/client.go new file mode 100644 index 0000000..26c90dc --- /dev/null +++ b/pkg/rest-client/client.go @@ -0,0 +1,121 @@ +package rest_client + +import ( + "bytes" + "encoding/json" + "fmt" + v1 "github.com/DipandaAser/tg-bot-storage/pkg/models/v1" + "io" + "mime" + "net/http" + "net/url" + "strconv" + "strings" +) + +type RestClient struct { + apiKey string + apiUrl string + client http.Client +} + +type apiError struct { + Error string `json:"error"` +} + +func NewRestClient(apiUrl, apiKey string) RestClient { + return RestClient{ + apiKey: apiKey, + apiUrl: strings.TrimSuffix(apiUrl, "/"), + client: http.Client{}, + } +} + +func (rc *RestClient) getApiUrl() string { + return rc.apiUrl + "/api" +} + +func (rc *RestClient) UploadFileReader(chatId int64, fileName string, fileReader io.Reader) (v1.MessageIdentifier, error) { + params := url.Values{} + params.Set("chat_id", fmt.Sprintf("%d", chatId)) + params.Set("file_name", fileName) + params.Set("api-key", rc.apiKey) + var finalUrl = fmt.Sprintf("%s/%s?%s", rc.getApiUrl(), "files", params.Encode()) + response, err := rc.client.Post(finalUrl, "application/octet-stream", fileReader) + if err != nil { + return v1.MessageIdentifier{}, err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + var apiErr apiError + if err := json.NewDecoder(response.Body).Decode(&apiErr); err != nil { + return v1.MessageIdentifier{}, fmt.Errorf("could not upload file. status code: %d", response.StatusCode) + } + return v1.MessageIdentifier{}, fmt.Errorf("could not upload file. status code: %d, error: %s", response.StatusCode, apiErr.Error) + } + var fileIdentifier v1.MessageIdentifier + err = json.NewDecoder(response.Body).Decode(&fileIdentifier) + if err != nil { + return v1.MessageIdentifier{}, err + } + return fileIdentifier, nil +} + +func (rc *RestClient) UploadFileBuffer(chatId int64, fileName string, fileData []byte) (v1.MessageIdentifier, error) { + reader := bytes.NewReader(fileData) + return rc.UploadFileReader(chatId, fileName, reader) +} + +func (rc *RestClient) DownloadFileReader(identifier v1.MessageIdentifier, copyChat int64) (*v1.DownloadReaderResult, error) { + params := url.Values{} + params.Set("chat_id", fmt.Sprintf("%d", identifier.ChatId)) + params.Set("msg_id", fmt.Sprintf("%d", identifier.MessageId)) + params.Set("draft_chat_id", fmt.Sprintf("%d", copyChat)) + params.Set("api-key", rc.apiKey) + var finalUrl = fmt.Sprintf("%s/%s?%s", rc.getApiUrl(), "files", params.Encode()) + response, err := rc.client.Get(finalUrl) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + var apiErr apiError + if err := json.NewDecoder(response.Body).Decode(&apiErr); err != nil { + return nil, fmt.Errorf("could not upload file. status code: %d", response.StatusCode) + } + return nil, fmt.Errorf("could not upload file. status code: %d, error: %s", response.StatusCode, apiErr.Error) + } + // get the file size in header content-length and convert it to int64 + var fileSize int64 = 0 + if contentLength := response.Header.Get("Content-Length"); contentLength != "" { + fileSize, _ = strconv.ParseInt(contentLength, 10, 64) + } + + _, contentDispParams, err := mime.ParseMediaType(response.Header.Get("Content-Disposition")) + return &v1.DownloadReaderResult{ + Data: response.Body, + FileInfo: v1.FileInfo{ + Size: fileSize, + Name: contentDispParams["filename"], + ContentType: response.Header.Get("Content-Type"), + }, + }, nil +} + +func (rc *RestClient) DownloadFileBuffer(identifier v1.MessageIdentifier, copyChat int64) (*v1.DownloadBufferResult, error) { + result, err := rc.DownloadFileReader(identifier, copyChat) + if err != nil { + return nil, err + } + + defer result.Data.Close() + + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(result.Data); err != nil { + return nil, err + } + + return &v1.DownloadBufferResult{ + Data: buf.Bytes(), + FileInfo: result.FileInfo, + }, nil +} diff --git a/pkg/rest-client/client_test.go b/pkg/rest-client/client_test.go new file mode 100644 index 0000000..3d914d0 --- /dev/null +++ b/pkg/rest-client/client_test.go @@ -0,0 +1,126 @@ +package rest_client + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "os" + "strconv" + "strings" + "testing" +) + +func init() { + if n, err := strconv.ParseInt(os.Getenv("CHAT_ID"), 10, 64); err == nil { + chatId = n + } + if n, err := strconv.ParseInt(os.Getenv("DRAFT_CHAT_ID"), 10, 64); err == nil { + draftChatId = n + } +} + +const skipMsg = "Skipping testing in non CI environment" + +var ( + apiHost = os.Getenv("api_host") + apiKey = os.Getenv("api_key") + chatId int64 + draftChatId int64 +) + +func ckeckSkip(t *testing.T) { + if os.Getenv("CI") != "true" { + t.Skip(skipMsg) + } +} + +func TestRestClient_DownloadFileBuffer(t *testing.T) { + // check if we are in a CI environnement with the api runinng + ckeckSkip(t) + client := NewRestClient(apiHost, apiKey) + fileContent := []byte("test") + fileName := "test.txt" + msgIdentifier, err := client.UploadFileBuffer(chatId, fileName, fileContent) + if err != nil { + t.Error(err) + return + } + + if !assert.NotEmpty(t, msgIdentifier) { + return + } + + downloadResult, err := client.DownloadFileBuffer(msgIdentifier, draftChatId) + if err != nil { + t.Error(err) + return + } + + if assert.NotEmpty(t, downloadResult) { + assert.Equal(t, downloadResult.FileInfo.Name, fileName) + assert.Equal(t, downloadResult.Data, fileContent) + } +} + +func TestRestClient_DownloadFileReader(t *testing.T) { + // check if we are in a CI environnement with the api runinng + ckeckSkip(t) + client := NewRestClient(apiHost, apiKey) + fileContent := "test" + fileName := "test.txt" + msgIdentifier, err := client.UploadFileReader(chatId, fileName, strings.NewReader(fileContent)) + if err != nil { + t.Error(err) + return + } + + if !assert.NotEmpty(t, msgIdentifier) { + return + } + + downloadResult, err := client.DownloadFileReader(msgIdentifier, draftChatId) + if err != nil { + t.Error(err) + return + } + + if !assert.NotEmpty(t, downloadResult) { + return + } + + assert.Equal(t, downloadResult.FileInfo.Name, fileName) + buffer := bytes.Buffer{} + _, err = buffer.ReadFrom(downloadResult.Data) + if err != nil { + t.Error(err) + return + } + assert.Equal(t, buffer.String(), fileContent) +} + +func TestRestClient_UploadFileBuffer(t *testing.T) { + // check if we are in a CI environnement with the api runinng + ckeckSkip(t) + client := NewRestClient(apiHost, apiKey) + + msgIdentifier, err := client.UploadFileBuffer(chatId, "test.txt", []byte("test")) + if err != nil { + t.Error(err) + return + } + + assert.NotEmpty(t, msgIdentifier) +} + +func TestRestClient_UploadFileReader(t *testing.T) { + // check if we are in a CI environnement with the api runinng + ckeckSkip(t) + client := NewRestClient(apiHost, apiKey) + + msgIdentifier, err := client.UploadFileReader(chatId, "test.txt", strings.NewReader("test")) + if err != nil { + t.Error(err) + return + } + + assert.NotEmpty(t, msgIdentifier) +} diff --git a/scripts/wait-for-it/wait-for-it.sh b/scripts/wait-for-it/wait-for-it.sh new file mode 100644 index 0000000..d990e0d --- /dev/null +++ b/scripts/wait-for-it/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi