Skip to content

Conversation

@rossigee
Copy link
Contributor

@rossigee rossigee commented Jan 15, 2017

Takes the branch by @tim-seoss (PR #120), fixes a minor regression, moves the default handler code out to be more easily re-used/adapted, and adds a basic unit test for a custom handler.

A simple handler function for adding Basic Auth might then look like this...

#!/usr/bin/python

import os, base64

from prometheus_client.handlers.base import handler as default_handler

def handler(url, method, timeout, headers, data):
    '''Handler that implements HTTP Basic Auth using environment variables 'PUSHGW_USERNAME' and 'PUSHGW_PASSWORD'.'''
    username = os.environ['PUSHGW_USERNAME']
    password = os.environ['PUSHGW_PASSWORD']
    auth_value = "{0}:{1}".format(username, password)
    auth_header = "Basic {0}".format(base64.b64encode(bytes(auth_value)))
    headers.append(['Authorization', auth_header])
    default_handler(url, method, timeout, headers, data)

@brian-brazil - your thoughts?

tim-seoss and others added 5 commits November 23, 2016 18:12
Allow a custom handler to be provided, so that the caller can provide
code which carried out basic auth, https client certificate validation
or other arbitrary schemes and access methods such as using different
types of proxy.
Provide pydoc for the new handler function to the various
pushgateway functions, provide parameter descriptions in
the documentatation for the push_to_gateway function to
avoid excessive copy-and-paste.
@rossigee
Copy link
Contributor Author

I found I needed to pass additional arguments to the handler, such as the username/password, so I have updated the patch to include a 'handler_args' parameter, and included a simple 'basic_auth' handler too.

Usage then becomes something like this...

from prometheus_client.handlers.basic_auth import handler as http_basic_auth_handler
auth_args = None
if username is not None:
    auth_args = {
        'username': username,
        'password': password
    }

push_to_gateway(url, job=job, registry=registry, handler=http_basic_auth_handler, handler_args=self.auth_args)

@brian-brazil
Copy link
Contributor

Can you rebase this?

I'd have presumed that any args are embedded in the handler, so there's no need to pass args beyond that.

@rossigee
Copy link
Contributor Author

Rebased.

Re: 'args embedded in the handler' - I'm not sure what you mean or how to do that. The only way other way I could think to minimise the footprint was to change it from a function to an object, but that didn't feel right for some reason, so I used the 'kwargs' approach I've seen used elsewhere. My python isn't that sharp though, so I'm open to better suggestions.

Anyway, have now tested this branch more widely with several clients and they are now pushing data happily to push gateway services that are using https+basicauth, so I can at least report it works for me.

Also, I've just seen the py26 travis build failure (URLError), but I can't seem to figure out wtf would cause that, other than perhaps a python26/urllib2 bug. I can't see how it would be related to this patch, unless I'm missing something?

@brian-brazil
Copy link
Contributor

That's still not rebased.

Re: 'args embedded in the handler' - I'm not sure what you mean or how to do that.

Use a closure. So instead of:

def func(a): return a
func(x)

do

def func(): return x
func()

This works with classes too.

tim-seoss and others added 11 commits January 25, 2017 10:35
Allow a custom handler to be provided, so that the caller can provide
code which carried out basic auth, https client certificate validation
or other arbitrary schemes and access methods such as using different
types of proxy.
Provide pydoc for the new handler function to the various
pushgateway functions, provide parameter descriptions in
the documentatation for the push_to_gateway function to
avoid excessive copy-and-paste.
@rossigee
Copy link
Contributor Author

@brian-brazil - ok, i think i've got it - how does it look now?

def my_auth_handler(url, method, timeout, headers, data):
username = os.environ['PUSHGW_USERNAME']
password = os.environ['PUSHGW_PASSWORD']
return basic_auth_handler(url, method, timeout, headers, data, username, password)
Copy link
Contributor

@brian-brazil brian-brazil Jan 25, 2017

Choose a reason for hiding this comment

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

I'd hardcode the user/pw here to keep it simpler.

If you use an env example, some users will be confused and think it only works if it comes from the env.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Example updated.

raise IOError("error talking to pushgateway: {0} {1}".format(
resp.code, resp.msg))
headers=[('Content-Type', CONTENT_TYPE_LATEST)]
if handler is None:
Copy link
Contributor

Choose a reason for hiding this comment

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

The handler isn't mutable, so you can put it as a default in the def line.

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 I can see how that would work, as the function is called with a handler of None by other functions in exposition.py. Do you mean make 'default_handler' the default instead of None for all of them?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mean make 'default_handler' the default instead of None for all of them?

Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


from . import core

from .handlers.base import handler as default_handler
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd put these in this file to keep things all in one place.

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 I understand. Do you mean something like...

from .handlers.base import default_handler

and in base.py...

default_handler = handler

or something else???

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean put all of this in exposition.py

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


def handler(url, method, timeout, headers, data, username = None, password = None):
def handle():
'''Handler that implements HTTP Basic Auth by setting auth headers if
Copy link
Contributor

Choose a reason for hiding this comment

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

The first sentence in a docstring should be on one line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, done.



def push_to_gateway(gateway, job, registry, grouping_key=None, timeout=None):
def default_handler(url, method, timeout, headers, data):
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a docstring to indicate this is for use with the pgw functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


from prometheus_client.exposition import default_handler

def handler(url, method, timeout, headers, data, username = None, password = None):
Copy link
Contributor

Choose a reason for hiding this comment

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

This should go in exposition.py too.

No spaces around the = here

@brian-brazil
Copy link
Contributor

The unittests aren't passing on all version of Python, could you fix that up and we can get this submitted?

@rossigee
Copy link
Contributor Author

I'm not having much luck fixing the python2.6 test. I have no py26 environment locally to reproduce it with, and from what I can determine from the error/stacktrace, it's more of an issue with python2.6's urllib2 implementation on the server than it is a regression caused by this patch, unless you can see something that I'm missing.

@brian-brazil
Copy link
Contributor

Doing some testing, it seems to be the lack of a http:// on the url that's causing the issue.

@rossigee
Copy link
Contributor Author

That's the same conclusion I came to, hence the last patch and revert. I've just tried another workaround, which seems to have cleared it now. Anyway, I couldn't see anything in the patch that would affect that behaviour, so I'm guessing something changed in the py26 test environment since the test was passing. I'd be interested to see if the master branch (unpatched) still passes that test.

@brian-brazil brian-brazil reopened this Jan 31, 2017
@brian-brazil brian-brazil merged commit aa4b4fb into prometheus:master Jan 31, 2017
@brian-brazil
Copy link
Contributor

Thanks!

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.

3 participants