Commit c6105649 authored by Thomas Goirand's avatar Thomas Goirand

Merge tag '3.19.0' into debian/stein

python-keystoneclient 3.19.0 release

meta:version: 3.19.0
meta:diff-start: -
meta:series: stein
meta:release-type: release
meta:pypi: yes
meta:first: no
meta:release:Author: Colleen Murphy <colleen.murphy@suse.de>
meta:release:Commit: Colleen Murphy <colleen.murphy@suse.de>
meta:release:Change-Id: Ie9bfa5123fe09d840f3001ab93e7fed23fb202f8
meta:release:Code-Review+1: Lance Bragstad <lbragstad@gmail.com>
meta:release:Code-Review+2: Thierry Carrez <thierry@openstack.org>
meta:release:Code-Review+2: Sean McGinnis <sean.mcginnis@gmail.com>
meta:release:Workflow+1: Sean McGinnis <sean.mcginnis@gmail.com>
parents e3fd8018 6c4bb8b5
.coverage
.testrepository
.stestr/
subunit.log
.venv
*,cover
......
[DEFAULT]
test_path=${OS_TEST_PATH:-./keystoneclient/tests/unit}
top_dir=./
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./keystoneclient/tests/unit} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
- job:
name: keystoneclient-devstack-functional
parent: devstack-minimal
timeout: 4200
required-projects:
- openstack/keystone
- openstack/python-keystoneclient
run: playbooks/run-ds-tox.yaml
post-run: playbooks/tox-post.yaml
vars:
devstack_localrc:
USE_PYTHON3: True
devstack_services:
key: true
tox_envlist: functional
zuul_work_dir: src/git.openstack.org/openstack/python-keystoneclient
- project:
templates:
- openstack-cover-jobs
- openstack-lower-constraints-jobs
- openstack-python-jobs
- openstack-python35-jobs
- openstack-python36-jobs
- openstack-python37-jobs
- publish-openstack-docs-pti
- check-requirements
- lib-forward-testing
- lib-forward-testing-python3
- release-notes-jobs-python3
check:
jobs:
- openstack-tox-lower-constraints
- keystoneclient-devstack-functional
gate:
jobs:
- openstack-tox-lower-constraints
- keystoneclient-devstack-functional
......@@ -27,6 +27,7 @@ OpenStack's Identity Service. For command line interface support, use
* `Source`_
* `Specs`_
* `How to Contribute`_
* `Release Notes`_
.. _PyPi: https://pypi.org/project/python-keystoneclient
.. _Online Documentation: https://docs.openstack.org/python-keystoneclient/latest/
......@@ -37,6 +38,7 @@ OpenStack's Identity Service. For command line interface support, use
.. _OpenStackClient: https://pypi.org/project/python-openstackclient
.. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html
.. _Specs: https://specs.openstack.org/openstack/keystone-specs/
.. _Release Notes: https://docs.openstack.org/releasenotes/python-keystoneclient
.. contents:: Contents:
:local:
......
......@@ -102,6 +102,31 @@ For more information on Sessions refer to: `Using Sessions`_.
.. _`Using Sessions`: using-sessions.html
Getting Metadata Responses
==========================
Instantiating :py:class:`keystoneclient.v3.client.Client` using
`include_metadata=True` will cause manager response to return
:py:class:`keystoneclient.base.Response` instead of just the data.
The metadata property will be available directly to the
:py:class:`keystoneclient.base.Response` and the response data will
be available as property `data` to it.
>>> from keystoneauth1.identity import v3
>>> from keystoneauth1 import session
>>> from keystoneclient.v3 import client
>>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3',
... user_id='myuserid',
... password='mypassword',
... project_id='myprojectid')
>>> sess = session.Session(auth=auth)
>>> keystone = client.Client(session=sess, include_metadata=True)
>>> resp = keystone.projects.list()
>>> resp.request_ids[0]
req-1234-5678-...
>>> resp.data
[<Project ...>, <Project ...>, ...]
Non-Session Authentication (deprecated)
=======================================
......
......@@ -554,9 +554,9 @@ class AccessInfoV2(AccessInfo):
'scoped is deprecated as of the 1.7.0 release in favor of '
'project_scoped and may be removed in the 2.0.0 release.',
DeprecationWarning)
if ('serviceCatalog' in self
and self['serviceCatalog']
and 'tenant' in self['token']):
if ('serviceCatalog' in self and
self['serviceCatalog'] and
'tenant' in self['token']):
return True
return False
......
This diff is collapsed.
......@@ -389,6 +389,11 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
user_agent=user_agent,
connect_retries=connect_retries)
# NOTE(dstanek): This allows me to not have to change keystoneauth or
# to write an adapter to the adapter here. Splitting thing into
# multiple project isn't always all sunshine and roses.
self._adapter.include_metadata = kwargs.pop('include_metadata', False)
# keyring setup
if use_keyring and keyring is None:
_logger.warning('Failed to load keyring modules.')
......
#!/bin/bash -xe
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# This script is executed inside post_test_hook function in devstack gate.
function generate_testr_results {
if [ -f .testrepository/0 ]; then
sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit
sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit
sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html
sudo gzip -9 $BASE/logs/testrepository.subunit
sudo gzip -9 $BASE/logs/testr_results.html
sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
fi
}
export KEYSTONECLIENT_DIR="$BASE/new/python-keystoneclient"
# Get admin credentials
cd $BASE/new/devstack
source openrc admin admin
# Go to the keystoneclient dir
cd $KEYSTONECLIENT_DIR
sudo chown -R $USER:stack $KEYSTONECLIENT_DIR
# Run tests
echo "Running keystoneclient functional test suite"
set +e
# Preserve env for OS_ credentials
sudo -E -H -u $USER tox -efunctional
EXIT_CODE=$?
set -e
# Collect and parse result
generate_testr_results
exit $EXIT_CODE
......@@ -11,14 +11,29 @@
# License for the specific language governing permissions and limitations
# under the License.
import uuid
import fixtures
from keystoneauth1.identity import v2
from keystoneauth1 import session
import requests
from keystoneclient import base
from keystoneclient import exceptions
from keystoneclient.tests.unit import utils
from keystoneclient import utils as base_utils
from keystoneclient.v2_0 import client
from keystoneclient.v2_0 import roles
from keystoneclient.v3 import users
TEST_REQUEST_ID = uuid.uuid4().hex
TEST_REQUEST_ID_1 = uuid.uuid4().hex
def create_response_with_request_id_header():
resp = requests.Response()
resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID
return resp
class HumanReadable(base.Resource):
......@@ -202,3 +217,209 @@ class ManagerTest(utils.TestCase):
management=True)
put_mock.assert_called_once_with(self.url, management=True, body=None)
self.assertEqual(rsrc.hi, 1)
class ManagerRequestIdTest(utils.TestCase):
url = "/test-url"
resp = create_response_with_request_id_header()
def setUp(self):
super(ManagerRequestIdTest, self).setUp()
auth = v2.Token(auth_url='http://127.0.0.1:5000',
token=self.TEST_TOKEN)
session_ = session.Session(auth=auth)
self.client = client.Client(session=session_,
include_metadata='True')._adapter
self.mgr = base.Manager(self.client)
self.mgr.resource_class = base.Resource
def mock_request_method(self, request_method, body):
return self.useFixture(fixtures.MockPatchObject(
self.client, request_method, autospec=True,
return_value=(self.resp, body))
).mock
def test_get(self):
body = {"hello": {"hi": 1}}
get_mock = self.mock_request_method('get', body)
rsrc = self.mgr._get(self.url, "hello")
get_mock.assert_called_once_with(self.url)
self.assertEqual(rsrc.data.hi, 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_list(self):
body = {"hello": [{"name": "admin"}, {"name": "admin"}]}
get_mock = self.mock_request_method('get', body)
returned_list = self.mgr._list(self.url, "hello")
self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID)
get_mock.assert_called_once_with(self.url)
def test_list_with_multiple_response_objects(self):
body = {"hello": [{"name": "admin"}, {"name": "admin"}]}
resp_1 = requests.Response()
resp_1.headers['x-openstack-request-id'] = TEST_REQUEST_ID
resp_2 = requests.Response()
resp_2.headers['x-openstack-request-id'] = TEST_REQUEST_ID_1
resp_result = [resp_1, resp_2]
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
return_value=(resp_result, body))
).mock
returned_list = self.mgr._list(self.url, "hello")
self.assertIn(returned_list.request_ids[0], [
TEST_REQUEST_ID, TEST_REQUEST_ID_1])
self.assertIn(returned_list.request_ids[1], [
TEST_REQUEST_ID, TEST_REQUEST_ID_1])
get_mock.assert_called_once_with(self.url)
def test_post(self):
body = {"hello": {"hi": 1}}
post_mock = self.mock_request_method('post', body)
rsrc = self.mgr._post(self.url, body, "hello")
post_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hi, 1)
post_mock.reset_mock()
rsrc = self.mgr._post(self.url, body, "hello", return_raw=True)
post_mock.assert_called_once_with(self.url, body=body)
self.assertNotIsInstance(rsrc, base.Response)
self.assertEqual(rsrc["hi"], 1)
def test_put(self):
body = {"hello": {"hi": 1}}
put_mock = self.mock_request_method('put', body)
rsrc = self.mgr._put(self.url, body, "hello")
put_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hi, 1)
put_mock.reset_mock()
rsrc = self.mgr._put(self.url, body)
put_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hello["hi"], 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_head(self):
get_mock = self.mock_request_method('head', None)
rsrc = self.mgr._head(self.url)
get_mock.assert_called_once_with(self.url)
self.assertFalse(rsrc.data)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_delete(self):
delete_mock = self.mock_request_method('delete', None)
resp, base_resp = self.mgr._delete(self.url, name="hello")
delete_mock.assert_called_once_with('/test-url', name='hello')
self.assertEqual(base_resp.request_ids[0], TEST_REQUEST_ID)
self.assertEqual(base_resp.data, None)
self.assertTrue(isinstance(resp, requests.Response))
def test_patch(self):
body = {"hello": {"hi": 1}}
patch_mock = self.mock_request_method('patch', body)
rsrc = self.mgr._patch(self.url, body, "hello")
patch_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hi, 1)
patch_mock.reset_mock()
rsrc = self.mgr._patch(self.url, body)
patch_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hello["hi"], 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_update(self):
body = {"hello": {"hi": 1}}
patch_mock = self.mock_request_method('patch', body)
put_mock = self.mock_request_method('put', body)
rsrc = self.mgr._update(
self.url, body=body, response_key="hello", method="PATCH",
management=False)
patch_mock.assert_called_once_with(
self.url, management=False, body=body)
self.assertEqual(rsrc.data.hi, 1)
rsrc = self.mgr._update(
self.url, body=None, response_key="hello", method="PUT",
management=True)
put_mock.assert_called_once_with(self.url, management=True, body=None)
self.assertEqual(rsrc.data.hi, 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
class ManagerWithFindRequestIdTest(utils.TestCase):
url = "/fakes"
resp = create_response_with_request_id_header()
def setUp(self):
super(ManagerWithFindRequestIdTest, self).setUp()
auth = v2.Token(auth_url='http://127.0.0.1:5000',
token=self.TEST_TOKEN)
session_ = session.Session(auth=auth)
self.client = client.Client(session=session_,
include_metadata='True')._adapter
def test_find_resource(self):
body = {"roles": [{"name": 'entity_one'}, {"name": 'entity_one_1'}]}
request_resp = requests.Response()
request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
side_effect=[exceptions.NotFound, (request_resp, body)])
).mock
mgr = roles.RoleManager(self.client)
mgr.resource_class = roles.Role
response = base_utils.find_resource(mgr, 'entity_one')
get_mock.assert_called_with('/OS-KSADM/roles')
self.assertEqual(response.request_ids[0], TEST_REQUEST_ID)
class CrudManagerRequestIdTest(utils.TestCase):
resp = create_response_with_request_id_header()
request_resp = requests.Response()
request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID
def setUp(self):
super(CrudManagerRequestIdTest, self).setUp()
auth = v2.Token(auth_url='http://127.0.0.1:5000',
token=self.TEST_TOKEN)
session_ = session.Session(auth=auth)
self.client = client.Client(session=session_,
include_metadata='True')._adapter
def test_find_resource(self):
body = {"users": [{"name": 'entity_one'}]}
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
side_effect=[exceptions.NotFound, (self.request_resp, body)])
).mock
mgr = users.UserManager(self.client)
mgr.resource_class = users.User
response = base_utils.find_resource(mgr, 'entity_one')
get_mock.assert_called_with('/users?name=entity_one')
self.assertEqual(response.request_ids[0], TEST_REQUEST_ID)
def test_list(self):
body = {"users": [{"name": "admin"}, {"name": "admin"}]}
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
return_value=(self.request_resp, body))
).mock
mgr = users.UserManager(self.client)
mgr.resource_class = users.User
returned_list = mgr.list()
self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID)
get_mock.assert_called_once_with('/users?')
......@@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import fixtures
import uuid
import mock
......@@ -277,6 +278,53 @@ class AuthenticateWithOAuthTests(utils.TestCase, TokenTests):
oauth_client)
class OauthRequestIdTests(utils.TestRequestId, TokenTests):
def setUp(self):
super(OauthRequestIdTests, self).setUp()
self.mgr = consumers.ConsumerManager(self.client)
def _mock_request_method(self, method=None, body=None):
return self.useFixture(fixtures.MockPatchObject(
self.client, method, autospec=True,
return_value=(self.resp, body))
).mock
def test_get_consumers(self):
body = {"consumer": {"name": "admin"}}
get_mock = self._mock_request_method(method='get', body=body)
response = self.mgr.get("admin")
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
get_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin')
def test_create_consumers(self):
body = {"consumer": {"name": "admin"}}
post_mock = self._mock_request_method(method='post', body=body)
response = self.mgr.create(name="admin", description="fake")
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
post_mock.assert_called_once_with('/OS-OAUTH1/consumers', body={
'consumer': {'name': 'admin', 'description': 'fake'}})
def test_update_consumers(self):
body = {"consumer": {"name": "admin"}}
patch_mock = self._mock_request_method(method='patch', body=body)
self._mock_request_method(method='post', body=body)
response = self.mgr.update("admin", "demo")
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
patch_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin', body={
'consumer': {'description': 'demo'}})
def test_delete_consumers(self):
get_mock = self._mock_request_method(method='delete')
_, resp = self.mgr.delete("admin")
self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID)
get_mock.assert_called_once_with('/OS-OAUTH1/consumers/admin')
class TestOAuthLibModule(utils.TestCase):
def test_no_oauthlib_installed(self):
......
......@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import uuid
from keystoneauth1 import exceptions as ksa_exceptions
......@@ -395,3 +396,63 @@ class ProjectTests(utils.ClientTestCase, utils.CrudTests):
"name": project_id}
]}
return ret
class ProjectsRequestIdTests(utils.TestRequestId):
url = "/projects"
def setUp(self):
super(ProjectsRequestIdTests, self).setUp()
self.mgr = projects.ProjectManager(self.client)
self.mgr.resource_class = projects.Project
def _mock_request_method(self, method=None, body=None):
return self.useFixture(fixtures.MockPatchObject(
self.client, method, autospec=True,
return_value=(self.resp, body))
).mock
def test_get_project(self):
body = {"project": {"name": "admin"}}
get_mock = self._mock_request_method(method='get', body=body)
response = self.mgr.get(project='admin')
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
get_mock.assert_called_once_with(self.url + '/admin')
def test_create_project(self):
body = {"project": {"name": "admin", "domain": "admin"}}
post_mock = self._mock_request_method(method='post', body=body)
response = self.mgr.create('admin', 'admin')
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
post_mock.assert_called_once_with(self.url, body={'project': {
'name': 'admin', 'enabled': True, 'domain_id': 'admin'}})
def test_list_project(self):
body = {"projects": [{"name": "admin"}, {"name": "admin"}]}
get_mock = self._mock_request_method(method='get', body=body)
returned_list = self.mgr.list()
self.assertEqual(returned_list.request_ids[0], self.TEST_REQUEST_ID)
get_mock.assert_called_once_with(self.url + '?')
def test_update_project(self):
body = {"project": {"name": "admin"}}
patch_mock = self._mock_request_method(method='patch', body=body)
put_mock = self._mock_request_method(method='put', body=body)
response = self.mgr.update("admin", domain='demo')
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
patch_mock.assert_called_once_with(self.url + '/admin', body={
'project': {'domain_id': 'demo'}})
self.assertFalse(put_mock.called)
def test_delete_project(self):
get_mock = self._mock_request_method(method='delete')
_, resp = self.mgr.delete("admin")
self.assertEqual(resp.request_ids[0], self.TEST_REQUEST_ID)
get_mock.assert_called_once_with(self.url + '/admin')
......@@ -656,7 +656,7 @@ class ImpliedRoleTests(utils.ClientTestCase, utils.CrudTests):
implied_role_id = uuid.uuid4().hex
self.stub_url('HEAD',
['roles', prior_role_id, 'implies', implied_role_id],
status_code=200)
status_code=204)
result = self.manager.check(prior_role_id, implied_role_id)
self.assertTrue(result)
......
......@@ -11,10 +11,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import testresources
from keystoneclient.tests.unit import client_fixtures
from keystoneclient.tests.unit.v3 import utils
from keystoneclient.v3.contrib import simple_cert
class SimpleCertTests(utils.ClientTestCase, testresources.ResourcedTestCase):
......@@ -36,5 +38,36 @@ class SimpleCertTests(utils.ClientTestCase, testresources.ResourcedTestCase):
self.assertEqual(self.examples.SIGNING_CERT, res)
class SimpleCertRequestIdTests(utils.TestRequestId):
def setUp(self):
super(SimpleCertRequestIdTests, self).setUp()
self.mgr = simple_cert.SimpleCertManager(self.client)
def _mock_request_method(self, method=None, body=None):
return self.useFixture(fixtures.MockPatchObject(
self.client, method, autospec=True,
return_value=(self.resp, body))
).mock
def test_list_ca_certificates(self):
body = {"certificates": [{"name": "admin"}, {"name": "admin2"}]}
get_mock = self._mock_request_method(method='get', body=body)
response = self.mgr.get_ca_certificates()
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
get_mock.assert_called_once_with(
'/OS-SIMPLE-CERT/ca', authenticated=False)
def test_list_certificates(self):
body = {"certificates": [{"name": "admin"}, {"name": "admin2"}]}
get_mock = self._mock_request_method(method='get', body=body)
response = self.mgr.get_certificates()
self.assertEqual(response.request_ids[0], self.TEST_REQUEST_ID)
get_mock.assert_called_once_with(
'/OS-SIMPLE-CERT/certificates', authenticated=False)
def load_tests(loader, tests, pattern):
return testresources.OptimisingTestSuite(tests)
......@@ -10,12 +10,16 @@
# License for the specific language governing permissions and limitations
# under the License.
import requests
import uuid
from six.moves.urllib import parse as urlparse
from keystoneauth1.identity import v3
from keystoneauth1 import session
from keystoneclient.tests.unit import client_fixtures
from keystoneclient.tests.unit import utils
from keystoneclient.v3 import client
def parameterize(ref):
......@@ -221,6 +225,8 @@ class CrudTests(object):
self.assertRequestBodyIs(json=self.encode(entity))
def test_create(self, ref=None, req_ref=None):
deprecations = self.useFixture(client_fixtures.Deprecations())
deprecations.expect_deprecations()
ref = ref or self.new_ref()
manager_ref = ref.copy()
manager_ref.pop('id')
......@@ -343,6 +349,8 @@ class CrudTests(object):
self.assertQueryStringIs('')
def test_update(self, ref=None, req_ref=None):
deprecations = self.useFixture(client_fixtures.Deprecations())
deprecations.expect_deprecations()
ref = ref or self.new_ref()
self.stub_entity('PATCH', id=ref['id'], entity=ref)
......@@ -371,3 +379,17 @@ class CrudTests(object):
self.stub_entity('DELETE', id=ref['id'], status_code=204)
self.manager.delete(ref['id'])
class TestRequestId(TestCase):
resp = requests.Response()
TEST_REQUEST_ID = uuid.uuid4().hex
resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID
def setUp(self):
super(TestRequestId, self).setUp()
auth = v3.Token(auth_url='http://127.0.0.1:5000',
token=self.TEST_TOKEN)
session_ = session.Session(auth=auth)
self.client = client.Client(session=session_,
include_metadata='True')._adapter
......@@ -39,17 +39,17 @@ class EndpointPolicyManager(base.Manager):
def create_policy_association_for_endpoint(self, policy, endpoint):
"""Create an association between a policy and an endpoint."""
self._act_on_policy_association_for_endpoint(
return self._act_on_policy_association_for_endpoint(
policy, endpoint, self._put)
def check_policy_association_for_endpoint(self, policy, endpoint):
"""Check an association between a policy and an endpoint."""
self._act_on_policy_association_for_endpoint(
return self._act_on_policy_association_for_endpoint(
policy, endpoint, self._head)
def delete_policy_association_for_endpoint(self, policy, endpoint):
"""Delete an association between a policy and an endpoint."""