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

Commit

Permalink
Raise only on connection and authentication errors, ignore permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Vasilyev committed Jul 9, 2019
1 parent 44602c1 commit 2118b8e
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 20 deletions.
20 changes: 16 additions & 4 deletions kopf/clients/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,36 @@ def login_client(verify=False):
def verify_pykube():
"""
Verify if login has succeeded, and the access configuration is still valid.
All other errors (e.g. 403, 404) are ignored: it means, the host and port
are configured and are reachable, the authentication token is accepted,
and the rest are authorization or configuration errors (not a showstopper).
"""
try:
api = get_pykube_api()
rsp = api.get(version="", base="/")
rsp.raise_for_status()
api.raise_for_status(rsp) # replaces requests's HTTPError with its own.
except requests.exceptions.ConnectionError as e:
raise AccessError("Cannot connect to the Kubernetes API. "
"Please configure the cluster access.")
except pykube.exceptions.HTTPError as e:
if e.code == 401:
raise AccessError("Cannot authenticate to the Kubernetes API. "
"Please login or configure the tokens.")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
raise AccessError("Cannot authenticate to the Kubernetes API. "
"Please login or configure the tokens.")
else:
raise


def verify_client():
"""
Verify if login has succeeded, and the access configuration is still valid.
All other errors (e.g. 403, 404) are ignored: it means, the host and port
are configured and are reachable, the authentication token is accepted,
and the rest are authorization or configuration errors (not a showstopper).
"""
import kubernetes.client.rest
try:
Expand All @@ -110,8 +124,6 @@ def verify_client():
if e.status == 401:
raise AccessError("Cannot authenticate to the Kubernetes API. "
"Please login or configure the tokens.")
else:
raise


def get_pykube_cfg() -> pykube.KubeConfig:
Expand Down
88 changes: 72 additions & 16 deletions tests/cli/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
"""
import pytest
import requests
import urllib3

from kopf.clients.auth import login, LoginError, AccessError

RESPONSE_401 = requests.Response()
RESPONSE_401.status_code = 401


@pytest.fixture(autouse=True)
def _auto_clean_kubernetes_client(clean_kubernetes_client):
Expand Down Expand Up @@ -85,10 +89,19 @@ def test_direct_auth_fails_on_errors_in_client(login_mocks, kubernetes):
assert login_mocks.client_from_file.called


def test_direct_check_fails_on_errors_in_pykube(login_mocks, any_kubernetes):
response = requests.Response()
response.status_code = 401
login_mocks.pykube_checker.side_effect = requests.exceptions.HTTPError(response=response)
def test_direct_check_fails_on_tcp_error_in_pykube(login_mocks, any_kubernetes):
login_mocks.pykube_checker.side_effect = requests.exceptions.ConnectionError()

with pytest.raises(AccessError):
login(verify=True)

assert login_mocks.pykube_in_cluster.called
assert not login_mocks.pykube_from_file.called
assert login_mocks.pykube_checker.called


def test_direct_check_fails_on_401_error_in_pykube(login_mocks, any_kubernetes):
login_mocks.pykube_checker.side_effect = requests.exceptions.HTTPError(response=RESPONSE_401)

with pytest.raises(AccessError):
login(verify=True)
Expand All @@ -98,7 +111,22 @@ def test_direct_check_fails_on_errors_in_pykube(login_mocks, any_kubernetes):
assert login_mocks.pykube_checker.called


def test_direct_check_fails_on_errors_in_client(login_mocks, kubernetes):
def test_direct_check_fails_on_tcp_error_in_client(login_mocks, kubernetes):
login_mocks.client_checker.side_effect = urllib3.exceptions.HTTPError()

with pytest.raises(AccessError):
login(verify=True)

assert login_mocks.pykube_in_cluster.called
assert not login_mocks.pykube_from_file.called
assert login_mocks.pykube_checker.called

assert login_mocks.client_in_cluster.called
assert not login_mocks.client_from_file.called
assert login_mocks.client_checker.called


def test_direct_check_fails_on_401_error_in_client(login_mocks, kubernetes):
login_mocks.client_checker.side_effect = kubernetes.client.rest.ApiException(status=401)

with pytest.raises(AccessError):
Expand Down Expand Up @@ -168,8 +196,8 @@ def test_clirun_auth_works_viaconfig_with_client(login_mocks, kubernetes,
assert login_mocks.client_checker.called


def test_clirun_auth_fails_on_errors_in_pykube(login_mocks, any_kubernetes,
invoke, preload, real_run):
def test_clirun_auth_fails_on_config_error_in_pykube(login_mocks, any_kubernetes,
invoke, preload, real_run):
login_mocks.pykube_in_cluster.side_effect = FileNotFoundError
login_mocks.pykube_from_file.side_effect = FileNotFoundError

Expand All @@ -182,8 +210,8 @@ def test_clirun_auth_fails_on_errors_in_pykube(login_mocks, any_kubernetes,
assert not login_mocks.pykube_checker.called


def test_clirun_auth_fails_on_errors_in_client(login_mocks, kubernetes,
invoke, preload, real_run):
def test_clirun_auth_fails_on_config_error_in_client(login_mocks, kubernetes,
invoke, preload, real_run):
login_mocks.client_in_cluster.side_effect = kubernetes.config.ConfigException
login_mocks.client_from_file.side_effect = kubernetes.config.ConfigException

Expand All @@ -200,11 +228,22 @@ def test_clirun_auth_fails_on_errors_in_client(login_mocks, kubernetes,
assert not login_mocks.client_checker.called


def test_clirun_check_fails_on_errors_in_pykube(login_mocks, any_kubernetes,
invoke, preload, real_run):
response = requests.Response()
response.status_code = 401
login_mocks.pykube_checker.side_effect = requests.exceptions.HTTPError(response=response)
def test_clirun_check_fails_on_tcp_error_in_pykube(login_mocks, any_kubernetes,
invoke, preload, real_run):
login_mocks.pykube_checker.side_effect = requests.exceptions.ConnectionError()

result = invoke(['run'])
assert result.exit_code != 0
assert 'Please configure the cluster access' in result.stdout

assert login_mocks.pykube_in_cluster.called
assert not login_mocks.pykube_from_file.called
assert login_mocks.pykube_checker.called


def test_clirun_check_fails_on_401_error_in_pykube(login_mocks, any_kubernetes,
invoke, preload, real_run):
login_mocks.pykube_checker.side_effect = requests.exceptions.HTTPError(response=RESPONSE_401)

result = invoke(['run'])
assert result.exit_code != 0
Expand All @@ -215,8 +254,25 @@ def test_clirun_check_fails_on_errors_in_pykube(login_mocks, any_kubernetes,
assert login_mocks.pykube_checker.called


def test_clirun_check_fails_on_errors_in_client(login_mocks, kubernetes,
invoke, preload, real_run):
def test_clirun_check_fails_on_tcp_error_in_client(login_mocks, kubernetes,
invoke, preload, real_run):
login_mocks.client_checker.side_effect = urllib3.exceptions.HTTPError()

result = invoke(['run'])
assert result.exit_code != 0
assert 'Please configure the cluster access' in result.stdout

assert login_mocks.pykube_in_cluster.called
assert not login_mocks.pykube_from_file.called
assert login_mocks.pykube_checker.called

assert login_mocks.client_in_cluster.called
assert not login_mocks.client_from_file.called
assert login_mocks.client_checker.called


def test_clirun_check_fails_on_401_error_in_client(login_mocks, kubernetes,
invoke, preload, real_run):
login_mocks.client_checker.side_effect = kubernetes.client.rest.ApiException(status=401)

result = invoke(['run'])
Expand Down

0 comments on commit 2118b8e

Please sign in to comment.