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

Catalog roles within a collection #1059

Merged
merged 13 commits into from
Mar 11, 2022
3 changes: 3 additions & 0 deletions docs/changelog-fragments.d/1059.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Included roles from collections in `:collections`.

-- by {user}`cidrblock`
98 changes: 98 additions & 0 deletions share/ansible_navigator/utils/catalog_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
from json.decoder import JSONDecodeError
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import Dict
from typing import Generator
from typing import List
from typing import Tuple
from typing import Union

import yaml

Expand Down Expand Up @@ -97,6 +99,101 @@ def _catalog_plugins(self, collection: Dict) -> None:
collection,
)

def _catalog_roles(self, collection: Dict[str, Any]) -> None:
"""Catalog the roles within a collection.

:param collection: Details describing the collection
"""
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements

collection_name: str = collection["known_as"]
collection["roles"] = []
roles_directory = Path(collection["path"], "roles")
if not roles_directory.is_dir():
return

for role_directory in roles_directory.iterdir():
role: Dict[str, Union[str, Dict[str, Any]]] = {
cidrblock marked this conversation as resolved.
Show resolved Hide resolved
"short_name": role_directory.name,
"full_name": f"{collection_name}.{role_directory.name}",
}
error_cataloging_role = False

# Argument spec cataloging, it is not required
argspec_name = "argument_specs.yml"
argspec_path = role_directory / "meta" / argspec_name
role["argument_specs"] = {}
role["argument_specs_path"] = ""
error = {"path": str(argspec_path)}
try:
with argspec_path.open(encoding="utf-8") as fh:
role["argument_specs"] = yaml.load(fh, Loader=SafeLoader)["argument_specs"]
role["argument_specs_path"] = str(argspec_path)
except KeyError:
error["error"] = f"Malformed {argspec_name} for role in {collection_name}."
self._errors.append(error)
except FileNotFoundError:
error["error"] = f"Failed to find {argspec_name} for role in {collection_name}."
self._errors.append(error)
except YAMLError:
error["error"] = f"Failed to load {argspec_name} for role in {collection_name}."
self._errors.append(error)

# Defaults cataloging, it is not required
defaults_name = "main.yml"
defaults_path = role_directory / "defaults" / defaults_name
role["defaults"] = {}
role["defaults_path"] = ""
error = {"path": str(defaults_path)}
try:
with defaults_path.open(encoding="utf-8") as fh:
role["defaults"] = yaml.load(fh, Loader=SafeLoader)
role["defaults_path"] = str(defaults_path)
except FileNotFoundError:
pass
except YAMLError:
error["error"] = f"Failed to load {defaults_name} for role in {collection_name}."
self._errors.append(error)
error_cataloging_role = True

# Meta/main.yml cataloging, it is required
meta_name = "main.yml"
meta_path = role_directory / "meta" / meta_name
role["info"] = {}
role["info_path"] = ""
error = {"path": str(meta_path)}
try:
with meta_path.open(encoding="utf-8") as fh:
role["info"] = yaml.load(fh, Loader=SafeLoader)
role["info_path"] = str(meta_path)
except FileNotFoundError:
error["error"] = f"Failed to find {meta_name} for role in {collection_name}."
self._errors.append(error)
error_cataloging_role = True
except YAMLError:
error["error"] = f"Failed to load {meta_name} for role in {collection_name}."
self._errors.append(error)
error_cataloging_role = True

# Readme.md cataloging, it is required
readme_name = "README.md"
readme_path = role_directory / readme_name
role["readme"] = ""
role["readme_path"] = ""
error = {"path": str(readme_path)}
try:
with readme_path.open(encoding="utf-8") as fh:
role["readme"] = fh.read()
role["readme_path"] = str(readme_path)
except FileNotFoundError:
error["error"] = f"Failed to find {readme_name} for role in {collection_name}."
self._errors.append(error)
error_cataloging_role = True

if not error_cataloging_role:
collection["roles"].append(role)

@staticmethod
def _generate_checksum(file_path: Path, relative_path: Path) -> Dict:
"""Generate a standard checksum for a file.
Expand Down Expand Up @@ -229,6 +326,7 @@ def process_directories(self) -> Tuple[Dict, List]:
self._one_path(collection_directory)
for _collection_path, collection in self._collections.items():
self._catalog_plugins(collection)
self._catalog_roles(collection)
self._find_shadows()
return self._collections, self._errors

Expand Down
39 changes: 26 additions & 13 deletions src/ansible_navigator/actions/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def color_menu(colno: int, colname: str, entry: Dict[str, Any]) -> Tuple[int, in
"""
if entry.get("__shadowed") is True:
return 8, 0
if entry.get("__deprecated") is True:
if entry.get("__deprecated") == "True":
return 9, 0
return 2, 0

Expand Down Expand Up @@ -204,20 +204,20 @@ def _build_main_menu(self):
return Step(
name="all_collections",
columns=columns,
select_func=self._build_plugin_menu,
select_func=self._build_collection_content_menu,
step_type="menu",
value=self._collections,
)

def _build_plugin_menu(self):
def _build_collection_content_menu(self):
"""Build the menu of plugins.

:returns: The plugin menu definition
"""
self._collection_cache.open()
selected_collection = self._collections[self.steps.current.index]
collection_name = f"__{selected_collection['known_as']}"
plugins = []
collection_contents = []
for plugin_checksum, details in selected_collection["plugin_checksums"].items():
try:
plugin_json = self._collection_cache[plugin_checksum]
Expand All @@ -241,38 +241,51 @@ def _build_plugin_menu(self):
plugin["__description"] = plugin["doc"]["short_description"]

runtime_section = "modules" if details["type"] == "module" else details["type"]
plugin["__deprecated"] = False
plugin["__deprecated"] = "False"
try:
routing_info = selected_collection["runtime"]["plugin_routing"]
runtime_info = routing_info[runtime_section][short_name]
plugin["additional_information"] = runtime_info
if "deprecation" in runtime_info:
plugin["__deprecated"] = True
plugin["__deprecated"] = "True"
except KeyError:
plugin["additional_information"] = {}

plugins.append(plugin)
collection_contents.append(plugin)
except (KeyError, JSONDecodeError) as exc:
self._logger.error("error loading plugin doc %s", details)
self._logger.debug("error was %s", str(exc))
plugins = sorted(plugins, key=lambda i: i[collection_name])

self._collection_cache.close()

for role in selected_collection["roles"]:
role[collection_name] = role["short_name"]
try:
role["__description"] = role["info"]["galaxy_info"]["description"]
except KeyError:
role["__description"] = ""
role["__deprecated"] = "Unknown"
role["__added"] = "Unknown"
role["__type"] = "role"
collection_contents.append(role)

collection_contents = sorted(collection_contents, key=lambda i: i[collection_name])

return Step(
name="all_plugins",
name="all_collection_content",
columns=[collection_name, "__type", "__added", "__deprecated", "__description"],
select_func=self._build_plugin_content,
select_func=self._build_collection_content,
step_type="menu",
value=plugins,
value=collection_contents,
)

def _build_plugin_content(self):
def _build_collection_content(self):
"""Build the content for one plugin.

:returns: The plugin's content
"""
return Step(
name="plugin_content",
name="collection_content",
step_type="content",
value=self.steps.current.value,
index=self.steps.current.index,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Role full

This is a role with:

- defaults/main.yml
- meta/argument_spec.yml
- meta/main.yml
- README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
role_full_bool: True
role_full_int: 42
role_full_str: Test string
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
argument_specs:
# roles/role_full/tasks/main.yml entry point
main:
short_description: The main entry point for the role_full role.
options:
role_full_int:
description: "The integer value, defaulting to 42."
default: 42
required: false
type: "int"

role_full_str:
description: "The string value"
required: true
type: "str"

# roles/role_full/tasks/alternate.yml entry point
alternate:
short_description: The alternate entry point for the role_full role.
options:
role_full_int:
description: "The integer value, defaulting to 1024."
default: 1024
required: false
type: "int"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
galaxy_info:
author: Ansible team (ansible-navigator)
description: Complete sample role.
company: Ansible
license: LICENSE
min_ansible_version: 2.9.0
galaxy_tags: ["tag_1", "tag_2"]
platforms:
- name: eos
versions:
- all
dependencies: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Role minimum

This is a role with:

- meta/main.yml
- README.md

This is a role without:

- defaults/main.yml
- meta/argument_spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
galaxy_info:
author: Ansible team (ansible-navigator)
description: Minimal sample role.
company: Ansible
license: LICENSE
min_ansible_version: 2.9.0
galaxy_tags: ["tag_1", "tag_2"]
platforms:
- name: eos
versions:
- all
dependencies: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Role missing

This is a role without:

- defaults/main.yml
- meta/argument_spec.yml
- meta/main.yml
- README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"name": "test[0-ansible-navigator collections --execution-environment true-ansible-navigator collections browse window]",
"name": "test[00-ansible-navigator collections --ee True --ll debug --mode interactive-ansible-navigator collections top window]",
"index": 0,
"comment": "ansible-navigator collections browse window",
"comment": "ansible-navigator collections top window",
"additional_information": {
"present": [],
"absent": [],
"compared_fixture": true
},
"output": [
" NAME VERSION SHADOWED TYPE PATH",
"0│company_name.coll_1 1.0.0 False bind_mount FIXTURES_COLLECTION_DIR/test_direct_interactive_ee.py/collections/ansible_collections/company_name/coll_1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
{
"name": "test[1-:0-Browse company_name.coll_1 plugins window]",
"name": "test[01-:0-Browse company_name.coll_1 plugins window]",
"index": 1,
"comment": "Browse company_name.coll_1 plugins window",
"additional_information": {
"present": [],
"absent": [],
"compared_fixture": true
},
"output": [
" COMPANY NAME.COLL 1 TYPE ADDED DEPRECATED DESCRIPTION",
"0│lookup_1 lookup 1.0.0 False This is test lookup plugin",
"1│mod_1 module 1.0.0 False This is a test module",
" COMPANY NAME.COLL 1 TYPE ADDED DEPRECATED DESCRIPTION",
"0│lookup_1 lookup 1.0.0 False This is test lookup plugin",
"1│mod_1 module 1.0.0 False This is a test module",
"2│role_full role Unknown Unknown Complete sample role.",
"3│role_minimum role Unknown Unknown Minimal sample role.",
"^f/PgUp page up ^b/PgDn page down ↑↓ scroll esc back [0-9] goto :help help"
]
}
Loading