From a1df08596b1546ef6b76f1dc350b6437cdb9386f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 00:21:49 +0000 Subject: [PATCH 1/3] Initial plan From b4dafa4fb0be4bde5f545cdf0e2a0e0a77d75e9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 00:25:11 +0000 Subject: [PATCH 2/3] Initial analysis - prepare for comprehensive README documentation Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com> --- go.mod | 25 +++++++++++++++++++++---- go.sum | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 4ceae5d..6f615b9 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,31 @@ module github.com/go-waitfor/waitfor -go 1.17 +go 1.23.0 + +toolchain go1.24.6 require ( github.com/cenkalti/backoff v2.2.1+incompatible - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.10.0 ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + codeberg.org/chavacava/garif v0.2.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgechev/dots v1.0.0 // indirect + github.com/mgechev/revive v1.12.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + github.com/spf13/afero v1.14.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.36.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 79cde81..9ca88ab 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,49 @@ +codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= +codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mgechev/dots v1.0.0 h1:o+4OJ3OjWzgQHGJXKfJ8rbH4dqDugu5BiEy84nxg0k4= +github.com/mgechev/dots v1.0.0/go.mod h1:rykuMydC9t3wfkM+ccYH3U3ss03vZGg6h3hmOznXLH0= +github.com/mgechev/revive v1.12.0 h1:Q+/kkbbwerrVYPv9d9efaPGmAO/NsxwW/nE6ahpQaCU= +github.com/mgechev/revive v1.12.0/go.mod h1:VXsY2LsTigk8XU9BpZauVLjVrhICMOV3k1lpB3CXrp8= 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/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= From cc2721c13fbdaf684fb4d15c992ad7bcc6136397 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 00:31:06 +0000 Subject: [PATCH 3/3] Add comprehensive documentation to README.md Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com> --- README.md | 685 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- go.mod | 15 -- go.sum | 39 +--- 3 files changed, 665 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 4f01f6a..7ff9b2c 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,104 @@ # waitfor -> Test and wait on the availability of a remote resource. + +[![Build Status](https://github.com/go-waitfor/waitfor/workflows/Build/badge.svg)](https://github.com/go-waitfor/waitfor/actions) +[![Go Report Card](https://goreportcard.com/badge/github.com/go-waitfor/waitfor)](https://goreportcard.com/report/github.com/go-waitfor/waitfor) +[![GoDoc](https://godoc.org/github.com/go-waitfor/waitfor?status.svg)](https://godoc.org/github.com/go-waitfor/waitfor) +[![Go Version](https://img.shields.io/github/go-mod/go-version/go-waitfor/waitfor)](https://github.com/go-waitfor/waitfor) + +> Test and wait on the availability of remote resources before proceeding with your application logic. + +`waitfor` is a Go library that provides a robust way to test and wait for remote resource availability with built-in retry logic, exponential backoff, and extensible resource support. It's particularly useful for ensuring dependencies are ready before starting applications or running critical operations. + +## Table of Contents + +- [Features](#features) +- [Installation](#installation) +- [Supported Resources](#supported-resources) +- [Resource URLs](#resource-urls) +- [Quick Start](#quick-start) + - [Test Resource Availability](#test-resource-availability) + - [Test and Run Program](#test-and-run-program) + - [Custom Resource Types](#custom-resource-types) +- [API Reference](#api-reference) + - [Core Types](#core-types) + - [Functions](#functions) + - [Configuration Options](#configuration-options) +- [Advanced Usage](#advanced-usage) +- [Error Handling](#error-handling) +- [Best Practices](#best-practices) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [License](#license) ## Features -- Parallel availability tests. -- Exponential backoff. -- Extensibility. Different types of remote resource (``http(s)``, ``proc``, ``postgres``, ``mysql``). - -## Resources -- [File](https://github.com/go-waitfor/waitfor-fs) (``file://``) -- [OS Process](https://github.com/go-waitfor/waitfor-proc) (``proc://``) -- [HTTP(S) Endpoint](https://github.com/go-waitfor/waitfor-http) (``http://`` & ``https://``) -- [MongoDB](https://github.com/go-waitfor/waitfor-mongodb) (``mongodb://``) -- [Postgres](https://github.com/go-waitfor/waitfor-postgres) (``postgres://``) -- [MySQL/MariaDB](https://github.com/go-waitfor/waitfor-mysql) (``mysql://`` & ``mariadb://``) + +- **Parallel Testing**: Test multiple resources concurrently for faster startup times +- **Exponential Backoff**: Smart retry logic that prevents overwhelming resources +- **Extensible Architecture**: Support for custom resource types through a plugin system +- **Context Support**: Full context support for cancellation and timeouts +- **Zero Dependencies**: Minimal external dependencies for easy integration +- **Production Ready**: Battle-tested retry logic with configurable parameters + +## Installation + +Install `waitfor` using Go modules: + +```bash +go get github.com/go-waitfor/waitfor +``` + +For specific resource types, install the corresponding packages: + +```bash +# Database resources +go get github.com/go-waitfor/waitfor-postgres +go get github.com/go-waitfor/waitfor-mysql + +# File system and process resources +go get github.com/go-waitfor/waitfor-fs +go get github.com/go-waitfor/waitfor-proc + +# HTTP resources +go get github.com/go-waitfor/waitfor-http + +# NoSQL databases +go get github.com/go-waitfor/waitfor-mongodb +``` + +## Supported Resources + +The following resource types are available through separate packages: + +| Resource Type | Package | URL Schemes | Description | +|---------------|---------|-------------|-------------| +| [File System](https://github.com/go-waitfor/waitfor-fs) | `waitfor-fs` | `file://` | Test file/directory existence | +| [OS Process](https://github.com/go-waitfor/waitfor-proc) | `waitfor-proc` | `proc://` | Test process availability | +| [HTTP(S) Endpoint](https://github.com/go-waitfor/waitfor-http) | `waitfor-http` | `http://`, `https://` | Test HTTP endpoint availability | +| [PostgreSQL](https://github.com/go-waitfor/waitfor-postgres) | `waitfor-postgres` | `postgres://` | Test PostgreSQL database connectivity | +| [MySQL/MariaDB](https://github.com/go-waitfor/waitfor-mysql) | `waitfor-mysql` | `mysql://`, `mariadb://` | Test MySQL/MariaDB connectivity | +| [MongoDB](https://github.com/go-waitfor/waitfor-mongodb) | `waitfor-mongodb` | `mongodb://` | Test MongoDB connectivity | ## Resource URLs -All resource locations start with url schema type e.g. ``file://./myfile`` or ``postgres://locahost:5432/mydb?user=user&password=test`` -## Quick start +Resource locations are specified using standard URL format with scheme-specific parameters: + +**Format**: `scheme://[user[:password]@]host[:port][/path][?query]` + +**Examples**: +- `file://./myfile` - Local file path +- `file:///absolute/path/to/file` - Absolute file path +- `http://localhost:8080/health` - HTTP health check endpoint +- `https://api.example.com/status` - HTTPS endpoint with path +- `postgres://user:password@localhost:5432/mydb` - PostgreSQL database +- `mysql://user:password@localhost:3306/mydb` - MySQL database +- `mongodb://localhost:27017/mydb` - MongoDB database +- `proc://nginx` - Process by name -### Test resource availability +## Quick Start + +### Test Resource Availability + +Use `waitfor` to test if resources are available before proceeding: ```go package main @@ -49,9 +128,9 @@ func main() { ``` -### Test resource availability and run a program -``waitfor`` can be helpful to run external commands with dependencies. -It makes sure that any program's dependencies are ready and then executes a given command. +### Test and Run Program + +`waitfor` can ensure dependencies are ready before executing external commands, making it perfect for application startup scripts and deployment scenarios: ```go package main @@ -88,8 +167,9 @@ func main() { } ``` -### Extend -``waitfor`` allows register custom resource assertions: +### Custom Resource Types + +`waitfor` supports custom resource types through its extensible registry system. You can register your own resource checkers: ```go package main @@ -142,4 +222,567 @@ func main() { os.Exit(1) } } -``` \ No newline at end of file +``` + +## API Reference + +### Core Types + +#### Runner + +The main entry point for testing resources and running programs. + +```go +type Runner struct { + // registry contains all registered resource factories +} +``` + +#### Program + +Defines an external command with its dependencies. + +```go +type Program struct { + Executable string // Command to execute + Args []string // Command arguments + Resources []string // Dependencies to test before execution +} +``` + +#### Resource + +Interface that all resource types must implement. + +```go +type Resource interface { + Test(ctx context.Context) error +} +``` + +#### ResourceConfig + +Configuration for registering resource types. + +```go +type ResourceConfig struct { + Scheme []string // URL schemes this resource handles + Factory ResourceFactory // Factory function to create resource instances +} +``` + +#### ResourceFactory + +Function signature for creating resource instances. + +```go +type ResourceFactory func(u *url.URL) (Resource, error) +``` + +### Functions + +#### New + +Creates a new Runner with the specified resource configurations. + +```go +func New(configurators ...ResourceConfig) *Runner +``` + +**Parameters:** +- `configurators`: Variable number of ResourceConfig instances to register + +**Returns:** A new Runner instance + +**Example:** +```go +runner := waitfor.New(postgres.Use(), http.Use()) +``` + +#### (*Runner) Test + +Tests the availability of specified resources. + +```go +func (r *Runner) Test(ctx context.Context, resources []string, setters ...Option) error +``` + +**Parameters:** +- `ctx`: Context for cancellation and timeout control +- `resources`: Slice of resource URLs to test +- `setters`: Configuration options (WithAttempts, WithInterval, etc.) + +**Returns:** Error if any resource is unavailable after all retry attempts + +#### (*Runner) Run + +Tests resources and executes a program if all resources are available. + +```go +func (r *Runner) Run(ctx context.Context, program Program, setters ...Option) ([]byte, error) +``` + +**Parameters:** +- `ctx`: Context for cancellation and timeout control +- `program`: Program configuration with executable, args, and resource dependencies +- `setters`: Configuration options + +**Returns:** Combined stdout/stderr output and error + +#### (*Runner) Resources + +Returns the resource registry for advanced usage. + +```go +func (r *Runner) Resources() *Registry +``` + +**Returns:** The internal Registry instance + +#### Use + +Helper function to convert module functions to ResourceConfig. + +```go +func Use(mod Module) ResourceConfig +``` + +**Parameters:** +- `mod`: Module function that returns schemes and factory + +**Returns:** ResourceConfig ready for use with New() + +### Configuration Options + +All test and run operations accept configuration options to customize behavior: + +#### WithAttempts + +Sets the maximum number of retry attempts. + +```go +func WithAttempts(attempts uint64) Option +``` + +**Default:** 5 attempts + +**Example:** +```go +err := runner.Test(ctx, resources, waitfor.WithAttempts(10)) +``` + +#### WithInterval + +Sets the initial retry interval in seconds. + +```go +func WithInterval(interval uint64) Option +``` + +**Default:** 5 seconds + +**Example:** +```go +err := runner.Test(ctx, resources, waitfor.WithInterval(2)) +``` + +#### WithMaxInterval + +Sets the maximum retry interval for exponential backoff in seconds. + +```go +func WithMaxInterval(interval uint64) Option +``` + +**Default:** 60 seconds + +**Example:** +```go +err := runner.Test(ctx, resources, waitfor.WithMaxInterval(120)) +``` + +#### Combining Options + +Options can be combined for fine-tuned control: + +```go +err := runner.Test( + ctx, + resources, + waitfor.WithAttempts(15), + waitfor.WithInterval(1), + waitfor.WithMaxInterval(30), +) +``` + +## Advanced Usage + +### Multiple Resource Types + +Test different types of resources simultaneously: + +```go +package main + +import ( + "context" + "fmt" + "github.com/go-waitfor/waitfor" + "github.com/go-waitfor/waitfor-postgres" + "github.com/go-waitfor/waitfor-http" + "github.com/go-waitfor/waitfor-fs" +) + +func main() { + runner := waitfor.New( + postgres.Use(), + http.Use(), + fs.Use(), + ) + + resources := []string{ + "postgres://user:pass@localhost:5432/mydb", + "http://localhost:8080/health", + "file://./config.json", + } + + err := runner.Test(context.Background(), resources) + if err != nil { + fmt.Printf("Dependencies not ready: %v\n", err) + return + } + + fmt.Println("All dependencies are ready!") +} +``` + +### Context Cancellation and Timeouts + +Use context for timeout control and cancellation: + +```go +package main + +import ( + "context" + "time" + "github.com/go-waitfor/waitfor" + "github.com/go-waitfor/waitfor-postgres" +) + +func main() { + runner := waitfor.New(postgres.Use()) + + // Set a 30-second timeout + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + err := runner.Test( + ctx, + []string{"postgres://localhost:5432/mydb"}, + waitfor.WithAttempts(10), + ) + + if err != nil { + if ctx.Err() == context.DeadlineExceeded { + fmt.Println("Timeout waiting for resources") + } else { + fmt.Printf("Resource test failed: %v\n", err) + } + return + } + + fmt.Println("Resources are ready!") +} +``` + +### Dynamic Resource Registration + +Register resources at runtime: + +```go +package main + +import ( + "context" + "net/url" + "github.com/go-waitfor/waitfor" +) + +func main() { + runner := waitfor.New() + + // Register a custom resource type + err := runner.Resources().Register("custom", func(u *url.URL) (waitfor.Resource, error) { + return &MyCustomResource{url: u}, nil + }) + + if err != nil { + panic(err) + } + + // Now you can use the custom resource + err = runner.Test(context.Background(), []string{"custom://example.com"}) + // ... handle error +} +``` + +## Error Handling + +`waitfor` provides specific error types for different failure scenarios: + +### Error Types + +- `ErrWait`: Returned when resources are not available after all retry attempts +- `ErrInvalidArgument`: Returned for invalid input parameters + +### Error Handling Patterns + +```go +err := runner.Test(ctx, resources) +if err != nil { + // Check if it's a waitfor-specific error + if strings.Contains(err.Error(), waitfor.ErrWait.Error()) { + fmt.Println("Resources are not available after retries") + // Maybe wait longer or use different configuration + } else { + fmt.Printf("Configuration or setup error: %v\n", err) + } + return +} +``` + +### Timeout vs Resource Failure + +```go +ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) +defer cancel() + +err := runner.Test(ctx, resources) +if err != nil { + if ctx.Err() == context.DeadlineExceeded { + fmt.Println("Overall timeout exceeded") + } else { + fmt.Println("Resource-specific failure:", err) + } +} +``` + +## Best Practices + +### 1. Choose Appropriate Timeouts + +Set timeouts based on your application's requirements: + +```go +// For quick startup (development) +ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + +// For production startup +ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) +``` + +### 2. Configure Retry Behavior + +Adjust retry parameters based on resource characteristics: + +```go +// For fast resources (local files, processes) +waitfor.WithAttempts(3), +waitfor.WithInterval(1), +waitfor.WithMaxInterval(5) + +// For slow resources (remote databases, external APIs) +waitfor.WithAttempts(15), +waitfor.WithInterval(5), +waitfor.WithMaxInterval(60) +``` + +### 3. Group Related Resources + +Test related resources together for better error reporting: + +```go +// Test database cluster +databaseResources := []string{ + "postgres://localhost:5432/primary", + "postgres://localhost:5433/replica1", + "postgres://localhost:5434/replica2", +} + +// Test web services +webResources := []string{ + "http://localhost:8080/health", + "http://localhost:8081/ready", +} + +// Test separately for clearer error messages +if err := runner.Test(ctx, databaseResources); err != nil { + log.Fatal("Database cluster not ready:", err) +} + +if err := runner.Test(ctx, webResources); err != nil { + log.Fatal("Web services not ready:", err) +} +``` + +### 4. Use Structured Logging + +Integrate with structured logging for better observability: + +```go +logger := log.With().Str("component", "waitfor").Logger() + +logger.Info().Msg("Starting dependency checks") + +err := runner.Test(ctx, resources) +if err != nil { + logger.Error().Err(err).Msg("Dependencies not ready") + return +} + +logger.Info().Msg("All dependencies ready") +``` + +## Troubleshooting + +### Common Issues + +#### "resource with a given scheme is not found" + +This error occurs when you try to use a resource type that hasn't been registered. + +**Solution**: Import and register the appropriate resource package: + +```go +import "github.com/go-waitfor/waitfor-postgres" + +runner := waitfor.New(postgres.Use()) +``` + +#### "failed to wait for resource availability" + +This indicates that resources were not available after all retry attempts. + +**Solutions**: +1. Increase retry attempts: `waitfor.WithAttempts(20)` +2. Increase retry intervals: `waitfor.WithMaxInterval(120)` +3. Check if the resource URL is correct +4. Verify the resource is actually running and accessible + +#### Connection Refused or Timeout Errors + +These are typically network-related issues. + +**Solutions**: +1. Verify the resource is running: `telnet hostname port` +2. Check firewall rules and network connectivity +3. Verify DNS resolution for hostnames +4. Use IP addresses instead of hostnames if DNS is an issue + +### Debug Mode + +Enable verbose error reporting for troubleshooting: + +```go +err := runner.Test(ctx, resources) +if err != nil { + fmt.Printf("Detailed error: %+v\n", err) + + // Test each resource individually to isolate issues + for _, resource := range resources { + if testErr := runner.Test(ctx, []string{resource}); testErr != nil { + fmt.Printf("Failed resource: %s - %v\n", resource, testErr) + } + } +} +``` + +### Performance Considerations + +#### Parallel vs Sequential Testing + +By default, `waitfor` tests all resources in parallel for faster execution. For resource-constrained environments, consider testing sequentially: + +```go +// Test resources one by one +for _, resource := range resources { + err := runner.Test(ctx, []string{resource}) + if err != nil { + return fmt.Errorf("resource %s failed: %w", resource, err) + } +} +``` + +#### Memory Usage + +When testing many resources, be aware of goroutine overhead. For very large numbers of resources (100+), consider batching: + +```go +const batchSize = 10 + +for i := 0; i < len(resources); i += batchSize { + end := i + batchSize + if end > len(resources) { + end = len(resources) + } + + batch := resources[i:end] + err := runner.Test(ctx, batch) + if err != nil { + return err + } +} +``` + +## Contributing + +We welcome contributions! Here's how you can help: + +### Adding New Resource Types + +1. Create a new repository following the pattern `waitfor-{resourcetype}` +2. Implement the `Resource` interface: + ```go + type MyResource struct { + url *url.URL + } + + func (r *MyResource) Test(ctx context.Context) error { + // Implement your resource test logic + return nil + } + ``` +3. Provide a `Use()` function: + ```go + func Use() waitfor.ResourceConfig { + return waitfor.ResourceConfig{ + Scheme: []string{"myscheme"}, + Factory: func(u *url.URL) (waitfor.Resource, error) { + return &MyResource{url: u}, nil + }, + } + } + ``` + +### Reporting Issues + +When reporting issues, please include: +- Go version +- `waitfor` version +- Resource types and URLs being tested +- Complete error messages +- Minimal reproduction case + +### Development Setup + +```bash +git clone https://github.com/go-waitfor/waitfor.git +cd waitfor +go mod tidy +go test ./... +``` + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/go.mod b/go.mod index 6f615b9..4ba2bba 100644 --- a/go.mod +++ b/go.mod @@ -10,22 +10,7 @@ require ( ) require ( - codeberg.org/chavacava/garif v0.2.0 // indirect - github.com/BurntSushi/toml v1.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/fatih/structtag v1.2.0 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mgechev/dots v1.0.0 // indirect - github.com/mgechev/revive v1.12.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/afero v1.14.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9ca88ab..a9d32c9 100644 --- a/go.sum +++ b/go.sum @@ -1,49 +1,12 @@ -codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= -codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mgechev/dots v1.0.0 h1:o+4OJ3OjWzgQHGJXKfJ8rbH4dqDugu5BiEy84nxg0k4= -github.com/mgechev/dots v1.0.0/go.mod h1:rykuMydC9t3wfkM+ccYH3U3ss03vZGg6h3hmOznXLH0= -github.com/mgechev/revive v1.12.0 h1:Q+/kkbbwerrVYPv9d9efaPGmAO/NsxwW/nE6ahpQaCU= -github.com/mgechev/revive v1.12.0/go.mod h1:VXsY2LsTigk8XU9BpZauVLjVrhICMOV3k1lpB3CXrp8= 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/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=