Skip to content

Make it easier to plug in other providers and add a mock provider#8

Merged
jamielinux merged 3 commits intomainfrom
jnguyen/mock
Dec 10, 2025
Merged

Make it easier to plug in other providers and add a mock provider#8
jamielinux merged 3 commits intomainfrom
jnguyen/mock

Conversation

@jamielinux
Copy link
Copy Markdown
Collaborator

mock provider

Before I submitted the Add post_plan_delay option PR, I wanted a way to test it locally. So I created a "mock" provider.

Otherwise I wasn't really sure how to easily test it without using actual hetzner credentials, unless I missed something!

$ ./flipper --config mock-flipper.example.yaml monitor
time=2025-12-08T18:26:22.268Z level=DEBUG msg="Starting resource watcher." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock
time=2025-12-08T18:26:22.268Z level=INFO msg="Resources changed, updating healthkeeper state." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock
time=2025-12-08T18:26:22.269Z level=DEBUG msg="Server health check completed." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock server_id=2 server_name=mock-server-2 check_id=health__ipv6 server_state=unknown rise=0 fall=1 duration=338.434µs
time=2025-12-08T18:26:22.269Z level=ERROR msg="Actions required." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock plan="Plan{ 100 -> 1 }" plan_id=c8418003-be35-4f4e-acf0-1c2d5049b8e6 unhealthy_floating_ips=[]
time=2025-12-08T18:26:22.269Z level=DEBUG msg="Server health check completed." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock server_id=1 server_name=mock-server-1 check_id=health__ipv6 server_state=unknown rise=0 fall=1 duration=266.671µs
time=2025-12-08T18:26:22.269Z level=ERROR msg="Actions required." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock plan="Plan{ 100 -> 1 }" plan_id=ebcc4c60-aeb0-4a13-81fd-2a6390e40d5d unhealthy_floating_ips=[]
time=2025-12-08T18:26:22.269Z level=INFO msg="Executing plan." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock plan_id=c8418003-be35-4f4e-acf0-1c2d5049b8e6
time=2025-12-08T18:26:22.269Z level=INFO msg="Floating IP assigned." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock plan_id=c8418003-be35-4f4e-acf0-1c2d5049b8e6 floating_ip_id=100 server_id=1
time=2025-12-08T18:26:22.269Z level=INFO msg="Plan executed successfully." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock plan_id=c8418003-be35-4f4e-acf0-1c2d5049b8e6 num_unhealthy_servers=0
time=2025-12-08T18:26:27.269Z level=INFO msg="Ignoring stale action with sequence lower than min sequence." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock sequence=1 min_sequence=3
time=2025-12-08T18:26:27.269Z level=INFO msg="Resources changed, updating healthkeeper state." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock
time=2025-12-08T18:26:27.269Z level=DEBUG msg="Server health check completed." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock server_id=2 server_name=mock-server-2 check_id=health__ipv4 server_state=unknown rise=0 fall=1 duration=1.000436195s
time=2025-12-08T18:26:27.269Z level=DEBUG msg="No actions required." version=<unknown> service=flipper group="Local Test" group_id=test_group provider=mock

HetznerID

I noticed that HetznerID could be more generic, so I changed that to ServerID (or something else appropriate).

Wait() called on empty errgroup

I think there's a typo here:

errgrp := errgroup.Group{}
errggrp, ctx := errgroup.WithContext(ctx)
for _, group := range w.groups {
group := group
errggrp.Go(func() error {
return group.Start(ctx)
})
}
//nolint:wrapcheck // we wrap the error in the subroutines.
return errgrp.Wait()
}

This causes a wait on the empty errgrp (instead of errggrp).

I noticed this because running ./flipper --config mock-flipper.example.yaml monitor was exiting immediately with no logs and 0 exit status.

I think this doesn't really affect production use:

  • Watch() returns immediately and the caller thinks monitoring is completed.
  • Monitoring keeps running because the Group.Start() goroutines are still blocking in their for-select loops (and their child goroutines like g.healthkeeper.Start() are also still running).
  • server.ListenAndServe() keeps the process alive, so those orphaned goroutines continue working sort of by accident.

It's a small fix, but I also added a test.

@jamielinux jamielinux requested a review from gzuidhof December 9, 2025 09:36
@jamielinux jamielinux self-assigned this Dec 9, 2025
Comment thread resource/server.go Outdated
// HetznerID is the unique ID of the server in Hetzner.
HetznerID int64
// ServerID is the unique ID of the server in the cloud provider.
ServerID int64
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for the HetznerID is that in Hetzner these IDs are int64s, but at a different provider it may be something else (e.g. I wouldn't be surprised if it's a string in most places).

I would propose to add a field per provider, so if we would add support for OVH (or whichever), the ID() function would have a switch on the ProviderName to decide which one to return. (That's also why ID() returns a string, which any ID should be castable to).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh right of course, thanks! 🚧

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've implemented your suggestion 🤔 Assuming I interpreted your approach correctly!

@jamielinux jamielinux requested a review from gzuidhof December 9, 2025 11:34
Copy link
Copy Markdown
Owner

@gzuidhof gzuidhof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great :)

Could you update the PR name before merging (just for bookkeeping so it's not confusing in the changelog)

@jamielinux jamielinux changed the title Make HetznerID more generic and add a mock provider Make it easier to plug in other cloud providers, and add a mock provider Dec 10, 2025
@jamielinux jamielinux changed the title Make it easier to plug in other cloud providers, and add a mock provider Make it easier to plug in other providers and add a mock provider Dec 10, 2025
@jamielinux jamielinux merged commit a54fd42 into main Dec 10, 2025
2 checks passed
@jamielinux jamielinux deleted the jnguyen/mock branch December 10, 2025 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants