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

Fix lxc plugin options #7369

Merged
merged 7 commits into from
Oct 9, 2023
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
3 changes: 3 additions & 0 deletions changelogs/fragments/7369-fix-lxc-options.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
bugfixes:
- lxc connection plugin - properly evaluate options (https://github.com/ansible-collections/community.general/pull/7369).
8 changes: 5 additions & 3 deletions plugins/connection/lxc.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,22 @@ class Connection(ConnectionBase):
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)

self.container_name = self._play_context.remote_addr
self.container_name = None
self.container = None

def _connect(self):
""" connect to the lxc; nothing to do here """
super(Connection, self)._connect()

if not HAS_LIBLXC:
msg = "lxc bindings for python2 are not installed"
msg = "lxc python bindings are not installed"
raise errors.AnsibleError(msg)

if self.container:
return

self.container_name = self.get_option('remote_addr')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since the container can change from task to task, the check if self.container above should probably also check whether self.get_option('remote_addr') didn't change. But that's something that shouldn't happen in this PR, since it changes the behavior of the plugin (which so far also ignored this).


self._display.vvv("THIS IS A LOCAL LXC DIR", host=self.container_name)
self.container = _lxc.Container(self.container_name)
if self.container.state == "STOPPED":
Expand Down Expand Up @@ -118,7 +120,7 @@ def exec_command(self, cmd, in_data=None, sudoable=False):
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)

# python2-lxc needs bytes. python3-lxc needs text.
executable = to_native(self._play_context.executable, errors='surrogate_or_strict')
executable = to_native(self.get_option('executable'), errors='surrogate_or_strict')
local_cmd = [executable, '-c', to_native(cmd, errors='surrogate_or_strict')]

read_stdout, write_stdout = None, None
Expand Down
77 changes: 69 additions & 8 deletions tests/unit/plugins/connection/test_lxc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,81 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import pytest
import sys

from io import StringIO

from ansible_collections.community.general.tests.unit.compat import unittest
from ansible_collections.community.general.plugins.connection import lxc
from ansible.errors import AnsibleError
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import connection_loader
from ansible_collections.community.general.tests.unit.compat import mock


@pytest.fixture(autouse=True)
def lxc(request):
"""Fixture to import/load the lxc plugin module.

The fixture parameter is used to determine the presence of liblxc.
When true (default), a mocked liblxc module is injected. If False,
no liblxc will be present.
"""
liblxc_present = getattr(request, 'param', True)

class ContainerMock(mock.MagicMock):
def __init__(self, name):
super(ContainerMock, self).__init__()
self.name = name
self.state = 'STARTED'

liblxc_module_mock = mock.MagicMock()
liblxc_module_mock.Container = ContainerMock

with mock.patch.dict('sys.modules'):
if liblxc_present:
sys.modules['lxc'] = liblxc_module_mock
elif 'lxc' in sys.modules:
del sys.modules['lxc']

from ansible_collections.community.general.plugins.connection import lxc as lxc_plugin_module

assert lxc_plugin_module.HAS_LIBLXC == liblxc_present
assert bool(getattr(lxc_plugin_module, '_lxc', None)) == liblxc_present

yield lxc_plugin_module


class TestLXCConnectionClass(unittest.TestCase):
class TestLXCConnectionClass():

def test_lxc_connection_module(self):
@pytest.mark.parametrize('lxc', [True, False], indirect=True)
def test_lxc_connection_module(self, lxc):
"""Test that a connection can be created with the plugin."""
play_context = PlayContext()
play_context.prompt = (
'[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: '
)
in_stream = StringIO()

self.assertIsInstance(lxc.Connection(play_context, in_stream), lxc.Connection)
conn = connection_loader.get('lxc', play_context, in_stream)
assert conn
assert isinstance(conn, lxc.Connection)

@pytest.mark.parametrize('lxc', [False], indirect=True)
def test_lxc_connection_liblxc_error(self, lxc):
"""Test that on connect an error is thrown if liblxc is not present."""
play_context = PlayContext()
in_stream = StringIO()
conn = connection_loader.get('lxc', play_context, in_stream)

with pytest.raises(AnsibleError, match='lxc python bindings are not installed'):
conn._connect()

def test_remote_addr_option(self):
"""Test that the remote_addr option is used"""
play_context = PlayContext()
in_stream = StringIO()
conn = connection_loader.get('lxc', play_context, in_stream)

container_name = 'my-container'
conn.set_option('remote_addr', container_name)
assert conn.get_option('remote_addr') == container_name

conn._connect()
assert conn.container_name == container_name