From 7af425c871546b1209461e289592767560b42509 Mon Sep 17 00:00:00 2001 From: Stefan Davis Date: Mon, 23 Jan 2023 20:36:42 +0000 Subject: [PATCH] write up some docs --- README.rst | 46 +++++++++++ docs/custom_modules.rst | 171 ++++++++++++++++++++++++++++++++++++++++ docs/overview.rst | 68 ++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 docs/custom_modules.rst create mode 100644 docs/overview.rst diff --git a/README.rst b/README.rst index acaee25..dee0e8d 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,49 @@ tkldev-detective is a simple framework for linting turnkeylinux appliances, it leverages existing linting tools, as well as allowing for custom handmade lints and provides a unified output format/interface for utilizing these tools. +Dependencies +~~~~~~~~~~~~ + +tkldev-detective has no mandatory dependencies + +Currently though there is 1 optional dependency: + + pyyaml - enables yaml lint + +Installation +------------ + +tkldev-detective can be installed as follows + +1. Clone somewhere, I recommend cloning into ``/turnkey/public`` (creating + intermediate directories as required) + + .. code-block:: bash + + mkdir -p /turnkey/public + git clone git@github.com:turnkeylinux/tkldev-detective /turnkey/public/tkldev-detective + +2. Add ``tkldev-detective`` to your path: + + .. code-block:: bash + + ln -s /turnkey/public/tkldev-detective/tkldev-detective /usr/local/bin + +Usage +----- + +Using ``tkldev-detective`` is as simple as running +``tkldev-detective lint `` where ```` can be the name of +an appliance or the path to an appliance, provided you have the appliance +build-code present on your tkldev. + +E.g. + +``tkldev-detective lint zoneminder`` + +For more information on how it works and how to develop more functionality, see +`overview`_ and `custom modules`_ + Copyright --------- @@ -15,3 +58,6 @@ tkldev-detective is free software: you can redistribute it and/or modify it unde tkldev-detective 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 tkldev-detective. If not, see . + +.. _overview: ./docs/overview.rst +.. _custom modules: ./docs/custom_modules.rst diff --git a/docs/custom_modules.rst b/docs/custom_modules.rst new file mode 100644 index 0000000..f5ceca4 --- /dev/null +++ b/docs/custom_modules.rst @@ -0,0 +1,171 @@ +TKLDev-Detective Custom Modules +=============================== + +Custom modules are simply python files, with 1 or more class definitions that +subclass ``FileClassifier`` or ``FileLinter`` residing inside of the +``tkldet_modules`` directory. + +(Non-file classifiers and linters might be possible but are not yet +officially supported) + +XXX ReportFilters are also possible to subclass once fully implemented + +Common Notes +------------ + +One thing that all custom operational models share is a concept of "weight" +which is used to determine the order that each check is done in. + +By default all of these classes contain a class-variable ``WEIGHT: int``. + +Counter-intuitively classes are used in ascending order of weight, the +recommended weight for situations that do not require specific ordering is +``100``. + +Custom Classifiers +------------------ + +Classifiers are very simple by design, here's a very simple example of a +classifier which tags directories, it's only job is to take an ``Item`` +and if it refers to a directory, tag it with a relevant tag. + +.. code-block:: python3 + + from libtkldet.classifier import FileClassifier, FileItem, register_classifier + from os.path import isdir + + @register_classifier # this line registers our custom class to be used as a classifier + class DirectoryClassifier(FileClassifier): + WEIGHT: int = 80 + # we want this to run earlier than most classifier, + # but not necessarily first so we reduce the weight by 20 + + def classify(self, item: FileItem): + # classify is the only method you MUST implement when deriving from + # FileClassifier, it does not return anything + + if isdir(item): + item.add_tags(self, ['directory']) + # we pass a reference to ourselves, so the item can keep track + # of which tags came from which classifiers + # + # we also pass a list of as many tags as we wish to add to this + # item. The exact value of our tag is unimportant, only that it + # doesn't conflict with any other tags. + +See ``filetype.py``, ``shebang.py`` for more examples of custom classifiers. + +Helper Classifiers +~~~~~~~~~~~~~~~~~~ + +There are currently 2 helper classifiers, ``ExactPathClassifier`` and +``SubdirClassifier``. + +ExactPathClassifier classifies only a single file item with a specific path, to +use it you simply need to set a few required class variables: + +.. code-block:: python3 + + from libtkldet.classifier import ExactPathClassifier, register_classifier + + @register_classifier + class CustomExactPathClassifier(ExactPathClassifier): + path: str = "path/to/some/file" + # exact location of file, relative to appliance root + + tags: list[str] = [ "tag1", "tag2" ] + # all these tags are added to the specific file matched + +SubdirClassifier is only marginally more complex, it will tag all files that are +directory descendants of the given path, optionally recursively. + +.. code-block:: python3 + + from libtkldet.classifier import SubdirClassifier, register_classifier + + @register_classifier + class CustomSubdirClassifier(SubdirClassifier): + path: str = "path/to/some/file" + # exact location of parent directory, relative to appliance root + + tags: list[str] = [ "tag1", "tag2" ] + # all these tags are added to children of directory + + recursive: bool = True + # recurse into child directories? + +Too see examples of these helper classifiers see the ``appliance_files.py`` +module. + +Custom Linters +-------------- + +Linters are a little more complex, but ultimately quite similar. + +Here's an example of a linter that produces an info report for every line +that contains "TODO" + +.. code-block:: python3 + + from libtkldet.linter import FileLinter, register_linter + from libtkldet.report import FileReport, ReportLevel + from typing import Generator + + @register_linter + class TodoLinter(FileLinter): + ENABLE_TAGS: set[str] = set() + DISABLE_TAGS: set[str] = set() + + # by default, linter will only run when either: + # + # 1. ENABLE_TAGS is empty and there is no overlap between item tags and + # DISABLE_TAGS + # 2. ENABLE_TAGS is not-empty, at least 1 tag from ENABLE_TAGS is + # included in the given item, and no items from DISABLE_TAGS are + # present + + + WEIGHT: int = 100 + # weight here works the same as classifiers + + + def check(self, item: Item) -> Generator[Report, None, None]: + # note this is a generator, we "yield" reports + + with open(item.abspath) as fob: + for i, line in enumerate(fob): + if 'TODO' in line: + yield FileReport( + item=item, # same item as input + + line=i, + column=line.find('TODO'), + # both line or column can be a single integer, a + # tuple of integers or ommited entirely. However + # if line number is ommited, column should be too + + location_metadata=None, + # currently populated by meta info that doessn't + # fit elseware, not output currently + + message = 'Found todo NOTE: " + line.split('TODO', 1)[1], + # the message shown to user + + fix = None, + # suggested fix as a string if known/relevant + # otherwise None + + source = 'TodoLinter', + # arbitrary string to represent the linter that + # found this issue + + level = ReportLevel.INFO + # kind of report level, 1 for all the standard log + # levels as well as "CONVENTION" and "REFACTOR" + # (possibly more as time goes on) + ) + +If the logic surrounding ``ENABLE_TAGS`` and ``DISABLE_TAGS`` is insufficient to +determine if the linter should run you can override ``Linter.should_check`` +method which actually performs those checks it takes the ``Item`` as an argument +and returns a boolean indicating if the linter should check the item. diff --git a/docs/overview.rst b/docs/overview.rst new file mode 100644 index 0000000..4fbcafa --- /dev/null +++ b/docs/overview.rst @@ -0,0 +1,68 @@ +TKLDev-Detective Overview +========================= + +tkldev-detective is actually pretty simple, it boils down to 6 concepts, 2 data +models and 4 "operational" models which function in serial. + +Data models +----------- + +Item + An item is just a piece of unspecified data accompanied by a set of tags. + + Subtypes of item are used to distinguish between concrete concepts, + currently the only type of ``Item`` supported is ``FileItem`` which (as you + might assume) contains a path as it's data. + +Report + A report is the final piece of data to be shown to the user. It contains + various pieces of information about a potential issue, how serious it is, + information relating to where it is, what potential fixes might be + available and what linter found this issue. + + Just like Item's this type is intended to be subclassed for other types of + data, and just like ``FileItem``; ``FileReport`` is currently the only such + subclass. It provides sufficient information to show file snippets related + to issues in a variety of ways contextual to how much information it's given + about the location of the issue inside the file. + +Operational models +------------------ + +Locator + the "Locator" determines which files are checked in the first place + in the future this will provide a broader concept of "checkable thing" than + just file, for now though we can safely only consider files. + + The locator produces "Items", these are a model that contains some kind of + "data" (here because they are just files, the data is the path itself) + and can be tagged. + +Classifier + the "Classifier" determines various qualities about a certain file, these + qualities or classifications are referred to internally as "tags". + + An example of a tag might be ``shebang:/usr/bin/python3`` which asserts that + this file has a shebang, and that the shebang is for python3. Note, the use + of ``shebang:`` prefix as there is infinite variation in shebangs. + + Some tags though have no variation, such is the case for + ``appliance-makefile`` which is only set for the top-level ``Makefile`` + found in an appliance. + +Linter + the "Linter" performs checks on the inputted "items" and yields any number + of "Reports". Linter's are the most diverse of these groups. + + Examples of linters go from ``PyLinter`` and ``Shellcheck``, linter's which + wrap the 3rd-party linters "pylint" and "shellcheck" respectively, all the + way through ``JsonLint`` which just loads json files and yields a report if + loading fails. + +ReportFilter + the "ReportFilter" is the last stop for reports before they are presented + to the user. A ``ReportFilter`` is mostly intended to remove false + positives but can also be used to add additional context to reports in a + way that might not make sense in the linter itself. + + XXX: currently report filters are untested