Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "^1.22"
go-version: "^1.23"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
go-version:
- 1.22.x
- 1.23.x
steps:
- name: checkout
uses: actions/checkout@v4
Expand All @@ -21,4 +21,4 @@ jobs:
- name: vet
run: go vet ./...
- name: test
run: go test -v -race ./...
run: go test -v -race ./...
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ groups:
# How often should we talk to Hetzner to look for changes w.r.t. the infra itself?
poll_interval: 60s

# How long to wait after applying a plan before allowing another plan. Default is 0 (disabled).
post_plan_delay: 1s

hetzner:
api_token: "abc123" # Your Hetzner API token.
project_id: 123456 # Your Hetzner's project ID (you can find it in the URL in the Hetzner dashboard).
Expand Down
10 changes: 10 additions & 0 deletions config/cfgmodel/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ type GroupConfig struct {
// This defaults to 30 seconds.
PlanApplyTimeout time.Duration `koanf:"plan_apply_timeout"`

// PostPlanDelay is the time to wait after a plan is applied before allowing another plan.
// This is useful to prevent plans happening too quickly in succession.
// This defaults to 0 (disabled).
PostPlanDelay time.Duration `koanf:"post_plan_delay"`

// PlanApplyWithUnknownStatus is a flag that indicates that the group should apply plans
// even if the status of one or more servers is unknown.
PlanApplyWithUnkownStatus bool `koanf:"plan_apply_with_unknown_status"`
Expand Down Expand Up @@ -90,6 +95,11 @@ func (c GroupConfig) PlanApplyTimeoutOrDefault() time.Duration {
return c.PlanApplyTimeout
}

// PostPlanDelayOrDefault returns the post-plan delay (default is 0, disabled).
func (c GroupConfig) PostPlanDelayOrDefault() time.Duration {
return c.PostPlanDelay
}

// ServiceConfig describes the service. The name is used in metrics and logs.
type ServiceConfig struct {
Name string `koanf:"name"`
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/gzuidhof/flipper

go 1.22
go 1.23

require github.com/go-ozzo/ozzo-validation/v4 v4.3.0

Expand Down
21 changes: 13 additions & 8 deletions monitor/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,20 @@ func (g *Group) executePlan(ctx context.Context, logger *slog.Logger, state plan
)
}

if g.provider.Name() == "hetzner" {
// Hetzner keeps the floating IPs locked for a short while after assigning them.
// So we add a small sleep here to prevent a potential plan that happens right after from failing.
// This is a bit of a hack, but it's the simplest solution for now. A potential future solution
// could be to retry on lock errors (within the Hetzner provider perhaps).
time.Sleep(time.Second)
}

return nil
}

func (g *Group) applyPostPlanDelay(ctx context.Context) {
delay := g.cfg.PostPlanDelayOrDefault()
if delay <= 0 {
return
}
select {
case <-ctx.Done():
case <-time.After(delay):
Comment thread
jamielinux marked this conversation as resolved.
}
}

// Start watching the resources and performing health checks.
// This function blocks until the context is cancelled.
func (g *Group) Start(ctx context.Context) error {
Expand Down Expand Up @@ -201,6 +204,8 @@ func (g *Group) Start(ctx context.Context) error {
_ = g.notifier.Notify(ctx, msg)
logger.InfoContext(ctx, "Plan executed successfully.",
slog.Int("num_unhealthy_servers", numUnhealthy))

g.applyPostPlanDelay(ctx)
}

minSequence = g.watcher.performUpdate(ctx, updateChan, errChan, true)
Expand Down