Skip to content
Open
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
86 changes: 63 additions & 23 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ concurrency:

on: # yamllint disable-line rule:truthy
pull_request:
branches: [master]
branches: [main, devel, master]
push:
branches: [main, devel, master]
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
Expand All @@ -16,12 +18,17 @@ jobs:
changelog:
uses: ansible/ansible-content-actions/.github/workflows/changelog.yaml@main
if: github.event_name == 'pull_request'

build-import:
name: build-import-collection
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
collection: [juniper/device, junipernetworks/junos]
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4

- name: Ensure ansible-core and galaxy-importer is installed
shell: bash
Expand All @@ -36,19 +43,26 @@ jobs:

- name: Build the collection tarball and run galaxy importer on it
shell: bash
working-directory: ansible_collections/juniper/device
working-directory: ansible_collections/${{ matrix.collection }}
run: |
python -m galaxy_importer.main --git-clone-path . --output-path /tmp

ansible-lint:
uses: ansible/ansible-content-actions/.github/workflows/ansible_lint.yaml@main
strategy:
fail-fast: false
matrix:
collection: [juniper/device, junipernetworks/junos]
with:
working_directory: ansible_collections/juniper/device
working_directory: ansible_collections/${{ matrix.collection }}

sanity:
name: Sanity Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
collection: [juniper/device, junipernetworks/junos]
include:
- python: "3.10"
ansible: "2.17"
Expand All @@ -65,12 +79,12 @@ jobs:
- python: "3.12"
ansible: "2.19"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
ref: "${{ github.event.pull_request.head.sha }}"
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python }}"
- name: "Install tox-ansible, includes tox"
Expand All @@ -80,20 +94,22 @@ jobs:
- name: Copy tox-ansible.ini to collection directory
run: |
if [ -f tox-ansible.ini ]; then
cp tox-ansible.ini ansible_collections/juniper/device/
cp tox-ansible.ini ansible_collections/${{ matrix.collection }}/
fi
- name: Run tox sanity tests
working-directory: ansible_collections/juniper/device
working-directory: ansible_collections/${{ matrix.collection }}
run: >-
python -m tox --ansible -e sanity-py${{ matrix.python }}-${{ matrix.ansible }}
python -m tox --ansible -c tox-ansible.ini -e sanity-py${{ matrix.python }}-${{ matrix.ansible }}
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

unit-galaxy:
name: Unit Tests (Galaxy)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
collection: [juniper/device, junipernetworks/junos]
include:
- python: "3.10"
ansible: "2.17"
Expand All @@ -110,35 +126,50 @@ jobs:
- python: "3.12"
ansible: "2.19"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
ref: "${{ github.event.pull_request.head.ref }}"
repository: "${{ github.event.pull_request.head.repo.full_name }}"
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python }}"
- name: Install Dependencies
if: matrix.collection == 'junipernetworks/junos'
run: |
# 1. Install ansible.netcommon
ansible-galaxy collection install ansible.netcommon

# 2. Install ncclient (Required to fix the 'new_ele' bug in netcommon)
python -m pip install ncclient

# 3. Install juniper.device directly from the local folder
# This links the local code without creating a tarball
ansible-galaxy collection install ansible_collections/juniper/device/ --force

- name: "Install tox-ansible, includes tox"
run: python -m pip install tox-ansible
- name: "Check for tox-ansible.ini file, else add default"
uses: ansible/ansible-content-actions/.github/actions/add_tox_ansible@main
- name: Copy tox-ansible.ini to collection directory
run: |
if [ -f tox-ansible.ini ]; then
cp tox-ansible.ini ansible_collections/juniper/device/
cp tox-ansible.ini ansible_collections/${{ matrix.collection }}/
fi
- name: Run tox unit tests
working-directory: ansible_collections/juniper/device
working-directory: ansible_collections/${{ matrix.collection }}
run: >-
python -m tox --ansible -e unit-py${{ matrix.python }}-${{ matrix.ansible }}
python -m tox --ansible -c tox-ansible.ini -e unit-py${{ matrix.python }}-${{ matrix.ansible }}
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

unit-source:
name: Unit Tests (Source)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
collection: [juniper/device, junipernetworks/junos]
include:
- ansible_version: "stable-2.16"
python_version: "3.11"
Expand All @@ -155,9 +186,9 @@ jobs:
- ansible_version: "devel"
python_version: "3.12"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python_version }}"
- name: Install ansible-compat, for tests
Expand All @@ -172,31 +203,40 @@ jobs:
else
python -m pip install git+https://github.com/ansible/ansible.git@${{ matrix.ansible_version }}
fi
- name: Pre install collections dependencies first so the collection install does not

- name: Install collection dependencies
run: |
ansible-galaxy collection install git+https://github.com/ansible-collections/ansible.utils.git
ansible-galaxy collection install git+https://github.com/ansible-collections/ansible.netcommon.git
# Using '-p .' installs the collections into ./ansible_collections/
# This structure allows ansible-test to find the dependencies (netcommon/utils)
# without needing them installed in the system/user path.
ansible-galaxy collection install ansible.netcommon ansible.utils -p .

- name: Read collection metadata from galaxy.yml
working-directory: ansible_collections/juniper/device
working-directory: ansible_collections/${{ matrix.collection }}
run: |
python -c "import yaml; print(yaml.safe_load(open('galaxy.yml'))['version'])"

- name: Build and install the collection
working-directory: ansible_collections/juniper/device
working-directory: ansible_collections/${{ matrix.collection }}
run: |
ansible-galaxy collection build --force
ansible-galaxy collection install juniper-device-*.tar.gz --force
ansible-galaxy collection install *-*.tar.gz --force

- name: Print the ansible version
run: ansible --version
- name: Print the python dependencies
run: python -m pip list
- name: Run unit tests
working-directory: ansible_collections/juniper/device
working-directory: ansible_collections/${{ matrix.collection }}
run: |
if [ -d "tests/unit" ]; then
# Fix pycrypto here as well for unit-source
if [ -f "requirements.txt" ]; then sed -i 's/pycrypto/pycryptodome/g' requirements.txt; fi
ansible-test units --python ${{ matrix.python_version }} --local --requirements
else
echo "No unit tests directory found, skipping"
fi

all_green:
if: ${{ always() && (github.event_name != 'schedule') }}
needs:
Expand Down
168 changes: 168 additions & 0 deletions ansible_collections/junipernetworks/junos/plugins/action/junos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#
# (c) 2016 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function


__metaclass__ = type

import copy
import sys

from ansible.utils.display import Display
from ansible_collections.ansible.netcommon.plugins.action.network import (
ActionModule as ActionNetworkModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
load_provider,
)

from ansible_collections.juniper.device.plugins.module_utils.network.junos.junos import (
junos_provider_spec,
)


display = Display()

CLI_SUPPORTED_MODULES = ["junos_netconf", "junos_ping", "junos_command"]


class ActionModule(ActionNetworkModule):
def run(self, tmp=None, task_vars=None):
del tmp # tmp no longer has any effect

module_name = self._task.action.split(".")[-1]
self._config_module = True if module_name in ["junos_config", "config"] else False
persistent_connection = self._play_context.connection.split(".")[-1]
warnings = []

if self._play_context.connection == "local":
provider = load_provider(junos_provider_spec, self._task.args)
pc = copy.deepcopy(self._play_context)
pc.network_os = "junipernetworks.junos.junos"
pc.remote_addr = provider["host"] or self._play_context.remote_addr

if provider["transport"] == "cli" and module_name not in CLI_SUPPORTED_MODULES:
return {
"failed": True,
"msg": "Transport type '%s' is not valid for '%s' module. "
"Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html"
% (provider["transport"], module_name),
}

if module_name == "junos_netconf" or (
provider["transport"] == "cli" and module_name == "junos_command"
):
pc.connection = "ansible.netcommon.network_cli"
pc.port = int(
provider["port"] or self._play_context.port or 22,
)
else:
pc.connection = "ansible.netcommon.netconf"
pc.port = int(
provider["port"] or self._play_context.port or 830,
)

pc.remote_user = provider["username"] or self._play_context.connection_user
pc.password = provider["password"] or self._play_context.password
pc.private_key_file = provider["ssh_keyfile"] or self._play_context.private_key_file

connection = self._shared_loader_obj.connection_loader.get(
"ansible.netcommon.persistent",
pc,
sys.stdin,
task_uuid=self._task._uuid,
)

# TODO: Remove below code after ansible minimal is cut out
if connection is None:
pc.network_os = "junos"
if pc.connection.split(".")[-1] == "netconf":
pc.connection = "netconf"
else:
pc.connection = "network_cli"

connection = self._shared_loader_obj.connection_loader.get(
"persistent",
pc,
sys.stdin,
task_uuid=self._task._uuid,
)

display.vvv(
"using connection plugin %s (was local)" % pc.connection,
pc.remote_addr,
)

command_timeout = (
int(provider["timeout"])
if provider["timeout"]
else connection.get_option("persistent_command_timeout")
)
connection.set_options(
direct={"persistent_command_timeout": command_timeout},
)

socket_path = connection.run()
display.vvvv("socket_path: %s" % socket_path, pc.remote_addr)
if not socket_path:
return {
"failed": True,
"msg": "unable to open shell. Please see: "
+ "https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell",
}

task_vars["ansible_socket"] = socket_path
warnings.append(
[
"connection local support for this module is deprecated and will be removed in version 2.14, use connection %s"
% pc.connection,
],
)
elif persistent_connection in ("netconf", "network_cli"):
provider = self._task.args.get("provider", {})
if any(provider.values()):
# for legacy reasons provider value is required for junos_facts(optional) and junos_package
# modules as it uses junos_eznc library to connect to remote host
if not (
module_name == "junos_facts"
or module_name == "junos_package"
or module_name == "junos_scp"
):
display.warning(
"provider is unnecessary when using %s and will be ignored"
% self._play_context.connection,
)
del self._task.args["provider"]

if (
persistent_connection == "network_cli" and module_name not in CLI_SUPPORTED_MODULES
) or (persistent_connection == "netconf" and module_name in CLI_SUPPORTED_MODULES[0:2]):
return {
"failed": True,
"msg": "Connection type '%s' is not valid for '%s' module. "
"Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html"
% (self._play_context.connection, module_name),
}
result = super(ActionModule, self).run(task_vars=task_vars)
if warnings:
if "warnings" in result:
result["warnings"].extend(warnings)
else:
result["warnings"] = warnings
return result
Loading