Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GCE] Support default credentials #22723

Merged
merged 1 commit into from
Mar 17, 2017
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
35 changes: 25 additions & 10 deletions lib/ansible/module_utils/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ def _get_gcp_credentials(module, require_valid_json=True, check_libcloud=False):
Additionally, flags may be set to require valid json and check the libcloud
version.

AnsibleModule.fail_json is called only if the project_id cannot be found.

:param module: initialized Ansible module object
:type module: `class AnsibleModule`

Expand Down Expand Up @@ -190,19 +192,27 @@ def _get_gcp_credentials(module, require_valid_json=True, check_libcloud=False):

if credentials_file is None or project_id is None or service_account_email is None:
if check_libcloud is True:
# TODO(supertom): this message is legacy and integration tests depend on it.
module.fail_json(msg='Missing GCE connection parameters in libcloud '
'secrets file.')
return None
if project_id is None:
# TODO(supertom): this message is legacy and integration tests depend on it.
module.fail_json(msg='Missing GCE connection parameters in libcloud '
'secrets file.')
else:
if credentials_file is None or project_id is None:
if project_id is None:
module.fail_json(msg=('GCP connection error: unable to determine project (%s) or '
'credentials file (%s)' % (project_id, credentials_file)))
# Set these fields to empty strings if they are None
# consumers of this will make the distinction between an empty string
# and None.
if credentials_file is None:
credentials_file = ''
if service_account_email is None:
service_account_email = ''

# ensure the credentials file is found and is in the proper format.
_validate_credentials_file(module, credentials_file,
require_valid_json=require_valid_json,
check_libcloud=check_libcloud)
if credentials_file:
_validate_credentials_file(module, credentials_file,
require_valid_json=require_valid_json,
check_libcloud=check_libcloud)

return {'service_account_email': service_account_email,
'credentials_file': credentials_file,
Expand Down Expand Up @@ -279,6 +289,9 @@ def get_google_cloud_credentials(module, scopes=[]):
"""
Get credentials object for use with Google Cloud client.

Attempts to obtain credentials by calling _get_gcp_credentials. If those are
not present will attempt to connect via Application Default Credentials.

To connect via libcloud, don't use this function, use gcp_connect instead. For
Google Python API Client, see get_google_api_auth for how to connect.

Expand Down Expand Up @@ -314,8 +327,10 @@ def get_google_cloud_credentials(module, scopes=[]):
if scopes:
credentials = credentials.with_scopes(scopes)
else:
credentials = google.auth.default(
scopes=scopes)[0]
(credentials, project_id) = google.auth.default(
scopes=scopes)
if project_id is not None:
conn_params['project_id'] = project_id

return (credentials, conn_params)
except Exception as e:
Expand Down
61 changes: 47 additions & 14 deletions test/units/module_utils/gcp/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# (c) 2016, Tom Melendez <tom@supertom.com>
# (c) 2016, Tom Melendez (@supertom) <tom@supertom.com>
#
# This file is part of Ansible
#
Expand All @@ -18,8 +18,10 @@
import os
import sys

import pytest

from ansible.compat.tests import mock, unittest
from ansible.module_utils.gcp import (_get_gcp_ansible_credentials, _get_gcp_environ_var,
from ansible.module_utils.gcp import (_get_gcp_ansible_credentials, _get_gcp_credentials, _get_gcp_environ_var,
_get_gcp_libcloud_credentials, _get_gcp_environment_credentials,
_validate_credentials_file)

Expand All @@ -31,21 +33,32 @@ def fake_get_gcp_environ_var(var_name, default_value):
else:
return fake_env_data[var_name]

# Fake AnsibleModule for use in tests
class FakeModule(object):
class Params():
data = {}

def get(self, key, alt=None):
if key in self.data:
return self.data[key]
else:
return alt

def __init__(self, data={}):
self.params = FakeModule.Params()
self.params.data = data

def fail_json(self, **kwargs):
raise ValueError("fail_json")

class GCPAuthTestCase(unittest.TestCase):
"""Tests to verify different Auth mechanisms."""

def setup_method(self, method):
global fake_env_data
fake_env_data = {'GCE_EMAIL': 'gce-email'}

def test_get_gcp_ansible_credentials(self):
# create a fake (AnsibleModule) object to pass to the function
class FakeModule(object):
class Params():
data = {}
def get(self, key, alt=None):
if key in self.data:
return self.data[key]
else:
return alt
def __init__(self, data={}):
self.params = FakeModule.Params()
self.params.data = data
input_data = {'service_account_email': 'mysa',
'credentials_file': 'path-to-file.json',
'project_id': 'my-cool-project'}
Expand Down Expand Up @@ -179,3 +192,23 @@ def test_get_gcp_environment_credentials(self, mockobj):
expected = tuple(['my-sa-email', '/path/to/creds.json', 'my-project'])
actual = _get_gcp_environment_credentials('my-sa-email', '/path/to/creds.json', None)
self.assertEqual(expected, actual)

@mock.patch('ansible.module_utils.gcp._get_gcp_environ_var',
side_effect=fake_get_gcp_environ_var)
def test_get_gcp_credentials(self, mockobj):
global fake_env_data

fake_env_data = {}
module = FakeModule()
module.params.data = {}
# Nothing is set, calls fail_json
with pytest.raises(ValueError):
_get_gcp_credentials(module)

# project_id (only) is set from Ansible params.
module.params.data['project_id'] = 'my-project'
actual = _get_gcp_credentials(module, require_valid_json=True, check_libcloud=False)
expected = {'service_account_email': '',
'project_id': 'my-project',
'credentials_file': ''}
self.assertEqual(expected, actual)