Skip to content
This repository has been archived by the owner on Jul 22, 2020. It is now read-only.

Add support for proxying user connection to Alertmanager #202

Merged
merged 8 commits into from Jan 9, 2018
Merged

Conversation

prymitive
Copy link
Contributor

Fixes #190.

With this feature unsee can be configured to proxy requests to selected Alertmanager instances, if it's enabled unsee silence form will send a request via unsee rather than directly. This allows users to manage silences in environments where they have access to unsee but not to Alertmanager. Only silences endpoints on Alertmanager API are proxied.

proxy.go Outdated
log "github.com/sirupsen/logrus"
)

func newProxyServer(domain string) *httputil.ReverseProxy {

Choose a reason for hiding this comment

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

You may wish to take an http.RoundTripper (to set as ReverseProxy.Transport) as an option here so you can observe and influence the behavior of the proxy in test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if that's really needed for tests, I got some now and didn't need it for that

proxy.go Outdated
} else if !upstream.ProxyRequests {
c.String(http.StatusBadRequest, "Alertmanager instance named '%s' does not allow proxying\n", name)
} else {
proxy := newProxyServer(upstream.URI)
Copy link

@terinjokes terinjokes Dec 8, 2017

Choose a reason for hiding this comment

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

You probably don't want to create a whole new proxy for each request. An alternative architecture could rely on the fact that ReverseProxy satisfies the http.Handler interface, and that you can add an http.Handler to a Gin router with gin.WrapH(http.Handler) gin.HandlerFunc. Then you can direct the two routes to the upstream by implementing ReverseProxy.Director.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated, is this better? I also got some tests this time

@prymitive prymitive removed this from the v0.9 milestone Dec 29, 2017
Fixes #190.

With this feature unsee can be configured to proxy requests to selected Alertmanager instances, if it's enabled unsee silence form will send a request via unsee rather than directly. This allows users to manage silences in environments where they have access to unsee but not to Alertmanager. Only silences endpoints on Alertmanager API are proxied.
Copy link

@terinjokes terinjokes left a comment

Choose a reason for hiding this comment

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

Looking better, but I think there's room for some improvements!

proxy.go Outdated
if err != nil {
return err
}
router.POST(fmt.Sprintf("%s/api/v1/silences", proxyPathPrefix(alertmanager.Name)), gin.WrapH(proxy))

Choose a reason for hiding this comment

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

Is this not clearer without nesting Sprintfs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not very readable, how about now?

proxy.go Outdated
Director: func(req *http.Request) {
req.URL.Scheme = upstreamURL.Scheme
req.URL.Host = upstreamURL.Host
req.URL.Path = strings.TrimPrefix(req.URL.Path, proxyPathPrefix(alertmanager.Name))

Choose a reason for hiding this comment

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

Use net/http.StripPrefix instead.

// upstream, there's a gzip middleware that's global so we don't want it
// to gzip twice
req.Header.Del("Accept-Encoding")
log.Debugf("[%s] Proxy request for %s", alertmanager.Name, req.URL.Path)

Choose a reason for hiding this comment

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

It is possible to accept a logger?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't see how that would be useful right now

Choose a reason for hiding this comment

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

Mostly useful for configuring the logging for proxy specifically. But mostly a nit.

proxy_test.go Outdated

var proxyTests = []proxyTest{
// valid alertmanager and methods
proxyTest{

Choose a reason for hiding this comment

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

struct names are unneeded inside a slice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TIL

)

// httptest.NewRecorder() doesn't implement http.CloseNotifier
type closeNotifyingRecorder struct {

Choose a reason for hiding this comment

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

I don't see you using the CloseNotify method. Is this recorder necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not 100% sure as I didn't go that deep into the issue, but testes were failing because of it and after some research I wasn't sure if this is gin issue or httptest

Choose a reason for hiding this comment

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

I don't have time to research myself, but if you eventually figure it out, I'd like to know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is that blocking this merge?

Choose a reason for hiding this comment

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

No.

@@ -15,7 +15,7 @@ var (
)

// NewAlertmanager creates a new Alertmanager instance
func NewAlertmanager(name, uri string, timeout time.Duration) error {
func NewAlertmanager(name, uri string, timeout time.Duration, proxyRequests bool) error {

Choose a reason for hiding this comment

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

I'd suggest using the functional options pattern instead. While reviewing consumer code, it's not clear what the lonesome false means.

Choose a reason for hiding this comment

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

// a custom timeout for Alertmanager upstream requests
func WithRequestTimeout(timeout time.Duration) Option {
return func(am *Alertmanager) {
am.Timeout = timeout

Choose a reason for hiding this comment

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

Any reason not to change the struct member name as well? Any reason to set a request timeout rather than using a context.WithTimeout?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. None, renamed
  2. I know about context but didn't really use it for anything so lack any experience and obvious patterns I could use it for

@cloudflare cloudflare deleted a comment from prymitive Jan 8, 2018
Copy link

@terinjokes terinjokes left a comment

Choose a reason for hiding this comment

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

Assuming the CI tests pass, I think this is reasonable to merge. See my note about a good follow-up PR.

var (
upstreams = map[string]*Alertmanager{}
)

// NewAlertmanager creates a new Alertmanager instance
func NewAlertmanager(name, uri string, timeout time.Duration) error {
func NewAlertmanager(name, uri string, opts ...Option) error {

Choose a reason for hiding this comment

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

nit: I'd argue this should be AddAlertmanager because while you do allocate you don't actually get the Alertmanager instance back.

For testing reasons (you're creating the Alertmanger manually in test) perhaps it should, and leave it up to the consumer to add it to the upstreams slice/type. I'll accept this as a separate PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'm refactoring this

@prymitive
Copy link
Contributor Author

If it doesn't passed I can't merge it anyway. 983c7f5 should address last comment

log.Infof("[%s] Configured Alertmanager source at %s", name, uri)
// RegisterAlertmanager will add an Alertmanager instance to the list of
// instances used when pulling alerts from upstreams
func RegisterAlertmanager(am *Alertmanager) error {

Choose a reason for hiding this comment

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

Would be great if this was on a collection type, rather than modifying a global variable. Again, happy to see it as another change.

@prymitive prymitive merged commit a3af1ec into master Jan 9, 2018
@prymitive prymitive deleted the proxy branch January 9, 2018 00:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants