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

Feature Addition: Adding ENV for Cert Validation #345

Merged
merged 3 commits into from
Apr 26, 2024
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
8 changes: 7 additions & 1 deletion plugins/action/query_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from ansible_collections.networktocode.nautobot.plugins.module_utils.utils import (
NautobotApiBase,
NautobotGraphQL,
is_truthy,
)
from ansible.utils.display import Display

Expand All @@ -46,7 +47,12 @@ def nautobot_action_graphql(args):

token = args.get("token") or os.getenv("NAUTOBOT_TOKEN")
api_version = args.get("api_version")
ssl_verify = args.get("validate_certs", True)
if args.get("validate_certs") is not None:
ssl_verify = args.get("validate_certs")
elif os.getenv("NAUTOBOT_VALIDATE_CERTS") is not None:
ssl_verify = is_truthy(os.getenv("NAUTOBOT_VALIDATE_CERTS"))
else:
ssl_verify = True
Display().vv("Verify Certificates: %s" % ssl_verify)

# Verify SSL Verify is of boolean
Expand Down
3 changes: 3 additions & 0 deletions plugins/doc_fragments/fragments.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ class ModuleDocFragment(object):
url:
description:
- "The URL of the Nautobot instance resolvable by the Ansible host (for example: http://nautobot.example.com:8000)"
- "Can be omitted if the E(NAUTOBOT_URL) environment variable is configured."
required: true
type: str
token:
description:
- "The token created within Nautobot to authorize API access"
- "Can be omitted if the E(NAUTOBOT_TOKEN) environment variable is configured."
required: true
type: str
state:
Expand All @@ -40,6 +42,7 @@ class ModuleDocFragment(object):
validate_certs:
description:
- "If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates."
- "Can be omitted if the E(NAUTOBOT_VALIDATE_CERTS) environment variable is configured."
required: false
default: true
type: raw
Expand Down
10 changes: 9 additions & 1 deletion plugins/lookup/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@
from ansible.parsing.splitter import parse_kv, split_args
from ansible.utils.display import Display
from ansible.module_utils.six import raise_from
from ansible_collections.networktocode.nautobot.plugins.module_utils.utils import (
is_truthy,
)

try:
import pynautobot
Expand Down Expand Up @@ -318,8 +321,13 @@ def run(self, terms, variables=None, **kwargs):

api_token = kwargs.get("token") or os.getenv("NAUTOBOT_TOKEN")
api_endpoint = kwargs.get("api_endpoint") or os.getenv("NAUTOBOT_URL")
if kwargs.get("validate_certs") is not None:
ssl_verify = kwargs.get("validate_certs")
elif os.getenv("NAUTOBOT_VALIDATE_CERTS") is not None:
ssl_verify = is_truthy(os.getenv("NAUTOBOT_VALIDATE_CERTS"))
else:
ssl_verify = True
num_retries = kwargs.get("num_retries", "0")
ssl_verify = kwargs.get("validate_certs", True)
api_filter = kwargs.get("api_filter")
raw_return = kwargs.get("raw_data")
plugin = kwargs.get("plugin")
Expand Down
10 changes: 9 additions & 1 deletion plugins/lookup/lookup_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
from ansible_collections.networktocode.nautobot.plugins.module_utils.utils import (
NautobotApiBase,
NautobotGraphQL,
is_truthy,
)
except ModuleNotFoundError:
# For testing
Expand Down Expand Up @@ -151,7 +152,14 @@ def nautobot_lookup_graphql(**kwargs):
raise AnsibleLookupError("Missing URL of Nautobot")

token = kwargs.get("token") or os.getenv("NAUTOBOT_TOKEN")
ssl_verify = kwargs.get("validate_certs", True)

if kwargs.get("validate_certs") is not None:
ssl_verify = kwargs.get("validate_certs")
elif os.getenv("NAUTOBOT_VALIDATE_CERTS") is not None:
ssl_verify = is_truthy(os.getenv("NAUTOBOT_VALIDATE_CERTS"))
else:
ssl_verify = True

api_version = kwargs.get("api_version")
Display().vv("Validate certs: %s" % ssl_verify)

Expand Down
32 changes: 31 additions & 1 deletion plugins/module_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,31 @@
)


def is_truthy(arg):
"""
Convert "truthy" strings into Booleans.

Examples:
>>> is_truthy('yes')
True

Args:
arg (str): Truthy string (True values are y, yes, t, true, on and 1; false values are n, no,
f, false, off and 0. Raises ValueError if val is anything else.
"""

if isinstance(arg, bool):
return arg

val = str(arg).lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return True
elif val in ("n", "no", "f", "false", "off", "0"):
return False
else:
raise ValueError(f"Invalid truthy value: `{arg}`")


class NautobotModule:
"""
Initialize connection to Nautobot, sets AnsibleModule passed in to
Expand Down Expand Up @@ -994,7 +1019,12 @@ class NautobotApiBase:
def __init__(self, **kwargs):
self.url = kwargs.get("url") or os.getenv("NAUTOBOT_URL")
self.token = kwargs.get("token") or os.getenv("NAUTOBOT_TOKEN")
self.ssl_verify = kwargs.get("ssl_verify", True)
if kwargs.get("ssl_verify") is not None:
self.ssl_verify = kwargs.get("ssl_verify")
elif os.getenv("NAUTOBOT_VALIDATE_CERTS") is not None:
self.ssl_verify = is_truthy(os.getenv("NAUTOBOT_VALIDATE_CERTS"))
else:
self.ssl_verify = True
self.api_version = kwargs.get("api_version")

# Setup the API client calls
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def patch_pynautobot_version_check(monkeypatch):

@pytest.fixture
def nautobot_api_base():
return NautobotApiBase(url="https://nautobot.mock.com", token="abc123", valdiate_certs=False)
return NautobotApiBase(url="https://nautobot.mock.com", token="abc123", validate_certs=False)


@pytest.fixture
Expand Down
38 changes: 36 additions & 2 deletions tests/unit/module_utils/test_nautobot_base_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
from functools import partial
from unittest.mock import patch, MagicMock, Mock
from ansible.module_utils.basic import AnsibleModule
from ansible.errors import AnsibleError
import pynautobot

try:
from ansible_collections.networktocode.nautobot.plugins.module_utils.utils import NautobotModule
from ansible_collections.networktocode.nautobot.plugins.module_utils.utils import NautobotModule, NautobotApiBase
from ansible_collections.networktocode.nautobot.plugins.module_utils.dcim import NB_DEVICES
from ansible_collections.networktocode.nautobot.tests.test_data import load_test_data

Expand All @@ -29,7 +30,7 @@

sys.path.append("plugins/module_utils")
sys.path.append("tests")
from utils import NautobotModule
from utils import NautobotModule, NautobotApiBase
from dcim import NB_DEVICES
from test_data import load_test_data

Expand Down Expand Up @@ -332,3 +333,36 @@ def test_invalid_api_version_error_handling(mock_ansible_module, monkeypatch, ap
monkeypatch.setattr(pynautobot.api, "version", api_version)
module = NautobotModule(mock_ansible_module, "devices")
assert isinstance(module.nb, obj_type)


@patch.dict(os.environ, {})
def test_validate_certs_defaults_true():
"""Test that the default SSL verify is set as true and no environment variable is set."""
test_class = NautobotApiBase(url="https://nautobot.example.com", token="abc123")
assert os.getenv("NAUTOBOT_VALIDATE_CERTS") is None
assert test_class.ssl_verify is True


@patch.dict(os.environ, {"NAUTOBOT_VALIDATE_CERTS": "FALSE"})
def test_validate_certs_environment_var_false():
"""Test that the default SSL verify is set as false via environment variable."""
test_class = NautobotApiBase(url="https://nautobot.example.com", token="abc123")
assert os.getenv("NAUTOBOT_VALIDATE_CERTS") is not None
assert test_class.ssl_verify is False


@patch.dict(os.environ, {"NAUTOBOT_VALIDATE_CERTS": "FALSE"})
def test_validate_certs_override():
"""Test that the default SSL verify is set as true via API class, and overrides environment variable."""
test_class = NautobotApiBase(url="https://nautobot.example.com", token="abc123", ssl_verify=True)
assert os.getenv("NAUTOBOT_VALIDATE_CERTS") is not None
assert os.getenv("NAUTOBOT_VALIDATE_CERTS") == "FALSE"
assert test_class.ssl_verify is True


@patch.dict(os.environ, {"NAUTOBOT_VALIDATE_CERTS": "cheese"})
def test_validate_certs_invalid():
"""Test that the default SSL verify is set as false via environment variable."""
with pytest.raises(ValueError) as exc:
_ = NautobotApiBase(url="https://nautobot.example.com", token="abc123")
assert "Invalid truthy value" in str(exc.value)
54 changes: 54 additions & 0 deletions tests/unit/module_utils/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Tests for module_utils functions."""

from typing import Any

import pytest

try:
from plugins.module_utils.utils import is_truthy
except ImportError:
import sys

sys.path.append("plugins/module_utils")
sys.path.append("tests")
from utils import is_truthy


@pytest.mark.parametrize(
"value, expected",
[
(True, True),
(False, False),
("true", True),
("false", False),
("True", True),
("False", False),
("TRUE", True),
("FALSE", False),
("t", True),
("f", False),
("T", True),
("F", False),
("yes", True),
("no", False),
("Yes", True),
("No", False),
("YES", True),
("NO", False),
("y", True),
("n", False),
("Y", True),
("N", False),
("1", True),
("0", False),
],
)
def test_is_truthy(value: Any, expected: bool) -> None:
assert is_truthy(value) == expected


def test_is_truthy_raises_exception_on_invalid_type() -> None:
with pytest.raises(ValueError) as excinfo:
is_truthy("test")

assert "Invalid truthy value" in str(excinfo.value)
Loading