Skip to content
This repository was archived by the owner on Aug 25, 2024. It is now read-only.

Commit 7729643

Browse files
committed
docs: example: shouldi
Fixes: #84 Signed-off-by: John Andersen <[email protected]>
1 parent b141cf0 commit 7729643

File tree

21 files changed

+657
-4
lines changed

21 files changed

+657
-4
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
inputs and outputs for operation implementations.
1414
- Async helpers got an `aenter_stack` method which creates and returns and
1515
`contextlib.AsyncExitStack` after entering all the context's passed to it.
16+
- Example of how to use Data Flow Facilitator / Orchestrator / Operations by
17+
writing a Python meta static analysis tool,
18+
[shouldi](https://pypi.org/project/shouldi/)
1619
### Changed
1720
- OperationImplementation add_label and add_orig_label methods now use op.name
1821
instead of ENTRY_POINT_ORIG_LABEL and ENTRY_POINT_NAME.

dffml/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
88
Version of DFFML
99
"""
10-
VERSION = "0.2.0"
10+
VERSION = "0.2.1"

docs/usage/operations.rst

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
Example Data Flow Usage
2+
=======================
3+
4+
This example will show you how to generate a dataset using operations.
5+
6+
Operations are the core of DFFML, they have inputs and outputs, are configurable
7+
and are run by the Data Flow Facilitator in what amounts to a large event loop.
8+
The events in the event loop are pieces of data entering the network. When a
9+
piece of data which matches the data types of one of the operations inputs
10+
enters the network, that operation is then run.
11+
12+
We're going to write a few operations which will run some Python static analysis
13+
tools. With the goal being to create a command line utility called ``shouldi``
14+
which will provide us with the information we need to make the decision, should
15+
I install Python package X? When it's done it'll look like this
16+
17+
.. code-block:: console
18+
19+
$ shouldi install insecure-package bandit
20+
bandit is okay to install
21+
Do not install insecure-package! {'safety_check_number_of_issues': 1}
22+
23+
Creating our Package
24+
--------------------
25+
26+
Clone a copy of DFFML and navigate the top of the source directory.
27+
28+
Create a new package using the create script.
29+
30+
.. code-block:: console
31+
32+
$ ./scripts/create.sh operations shouldi
33+
34+
You can now move this to another directory if you wish (the copy for this
35+
example is located under ``examples/shouldi``.
36+
37+
.. code-block:: console
38+
39+
$ mv operations/shouldi ../shouldi
40+
$ cd ../shouldi
41+
42+
We're going to change the name of the package to ``shouldi`` instead of the
43+
default, ``dffml_operations_shouldi``.
44+
45+
**setup.py**
46+
47+
.. code-block:: python
48+
49+
NAME = "shouldi"
50+
51+
We need to rename the directory as well.
52+
53+
.. code-block:: console
54+
55+
$ mv dffml_operations_shouldi shouldi
56+
57+
And the directory within the coveragerc file
58+
59+
**.coveragerc**
60+
61+
.. code-block:: python
62+
63+
source =
64+
shouldi
65+
tests
66+
67+
Now install your freshly renamed module!
68+
69+
.. code-block:: console
70+
71+
$ python3.7 -m pip install -e .
72+
73+
Installing Static Analysis Tools
74+
--------------------------------
75+
76+
For simplicities sake the beginning of this example will use subprocesses to
77+
interact with command line Python static analysis tools. Let's install them all
78+
via ``pip``.
79+
80+
.. code-block:: console
81+
82+
$ python3.7 -m pip install -U safety pylint bandit
83+
84+
We need to make http requests so let's install ``aiohttp``.
85+
86+
**setup.py**
87+
88+
.. code-block:: python
89+
90+
INSTALL_REQUIRES = [
91+
"aiohttp>=3.5.4"
92+
]
93+
94+
Our Zeroth Operation
95+
--------------------
96+
97+
We'll write an operation to check for CVEs in a package by using ``safety``.
98+
99+
Safety uses the package name and version to tell us if there are any security
100+
issues in the package for that version.
101+
102+
To use safety, we have to have the version of the package we want to check.
103+
104+
Let's write an operation to grab the version of a package.
105+
106+
.. literalinclude:: /../examples/shouldi/shouldi/pypi.py
107+
108+
Write a test for it
109+
110+
.. literalinclude:: /../examples/shouldi/tests/test_pypi.py
111+
112+
Run the tests
113+
114+
.. code-block:: console
115+
116+
$ python3.7 setup.py test -s tests.test_pypi
117+
118+
Safety Operation
119+
----------------
120+
121+
The output of the last operation will automatticly be combined with the package
122+
name to create a call you our new operation, ``SafetyCheck``.
123+
124+
This is how running safety on the command line works.
125+
126+
.. code-block:: console
127+
128+
$ echo insecure-package==0.1.0 | safety check --stdin
129+
╒══════════════════════════════════════════════════════════════════════════════╕
130+
│ │
131+
│ /$$$$$$ /$$ │
132+
│ /$$__ $$ | $$ │
133+
│ /$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$ │
134+
│ /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ │
135+
│ | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ │
136+
│ \____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ │
137+
│ /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ │
138+
│ |_______/ \_______/|__/ \_______/ \___/ \____ $$ │
139+
│ /$$ | $$ │
140+
│ | $$$$$$/ │
141+
│ by pyup.io \______/ │
142+
│ │
143+
╞══════════════════════════════════════════════════════════════════════════════╡
144+
│ REPORT │
145+
│ checked 1 packages, using default DB │
146+
╞════════════════════════════╤═══════════╤══════════════════════════╤══════════╡
147+
│ package │ installed │ affected │ ID │
148+
╞════════════════════════════╧═══════════╧══════════════════════════╧══════════╡
149+
│ insecure-package │ 0.1.0 │ <0.2.0 │ 25853 │
150+
╘══════════════════════════════════════════════════════════════════════════════╛
151+
152+
We want parsable output, so let's try it with the ``--json`` flag.
153+
154+
.. code-block:: console
155+
156+
$ echo insecure-package==0.1.0 | safety check --stdin --json
157+
[
158+
[
159+
"insecure-package",
160+
"<0.2.0",
161+
"0.1.0",
162+
"This is an insecure package with lots of exploitable security vulnerabilities.",
163+
"25853"
164+
]
165+
]
166+
167+
Let's now write the operation to call ``safety`` via a subprocess.
168+
169+
.. literalinclude:: /../examples/shouldi/shouldi/safety.py
170+
171+
Write a test for it
172+
173+
.. literalinclude:: /../examples/shouldi/tests/test_safety.py
174+
175+
Run the tests
176+
177+
.. code-block:: console
178+
179+
$ python3.7 setup.py test -s tests.test_safety
180+
181+
.. TODO Add they operations to setup.py entry_points
182+
183+
.. TODO Add bandit
184+
185+
.. TODO Add pylint
186+
187+
CLI
188+
---
189+
190+
Writing the CLI is as simple as importing our operations and having the memory
191+
orchestrator run them. DFFML also provides a quick and dirty CLI abstraction
192+
based on :py:mod:`argparse` which will speed things up.
193+
194+
.. TODO explain more about writing the CLI and the orchestrator
195+
196+
**shouldi/cli.py**
197+
198+
.. literalinclude:: /../examples/shouldi/shouldi/cli.py
199+
200+
Let's test out the code in ``shouldi.cli`` before making it accessable via the
201+
command line.
202+
203+
.. literalinclude:: /../examples/shouldi/tests/test_cli.py
204+
205+
Run the all the tests this time
206+
207+
.. code-block:: console
208+
209+
$ python3.7 setup.py test
210+
211+
If you have coverage installed (``pip install coverage``) you can also check the
212+
code coverage.
213+
214+
.. code-block:: console
215+
216+
$ python3.7 -m coverage run setup.py test
217+
running test
218+
running egg_info
219+
writing shouldi.egg-info/PKG-INFO
220+
writing dependency_links to shouldi.egg-info/dependency_links.txt
221+
writing entry points to shouldi.egg-info/entry_points.txt
222+
writing requirements to shouldi.egg-info/requires.txt
223+
writing top-level names to shouldi.egg-info/top_level.txt
224+
reading manifest file 'shouldi.egg-info/SOURCES.txt'
225+
reading manifest template 'MANIFEST.in'
226+
writing manifest file 'shouldi.egg-info/SOURCES.txt'
227+
running build_ext
228+
test_install (tests.test_cli.TestCLI) ... ok
229+
test_run (tests.test_safety.TestSafetyCheck) ... ok
230+
test_run (tests.test_pypi.TestPyPiLatestPackageVersion) ... ok
231+
232+
----------------------------------------------------------------------
233+
Ran 3 tests in 2.314s
234+
235+
OK
236+
$ python3.7 -m coverage report -m
237+
Name Stmts Miss Branch BrPart Cover Missing
238+
--------------------------------------------------------------------
239+
shouldi/__init__.py 0 0 0 0 100%
240+
shouldi/cli.py 30 0 11 0 100%
241+
shouldi/definitions.py 5 0 2 0 100%
242+
shouldi/pypi.py 12 0 2 0 100%
243+
shouldi/safety.py 18 0 0 0 100%
244+
shouldi/version.py 1 0 0 0 100%
245+
tests/__init__.py 0 0 0 0 100%
246+
tests/test_cli.py 11 0 0 0 100%
247+
tests/test_pypi.py 9 0 0 0 100%
248+
tests/test_safety.py 9 0 0 0 100%
249+
--------------------------------------------------------------------
250+
TOTAL 95 0 15 0 100%
251+
252+
We want this to be usable as a command line utility, Python's
253+
:py:mod:`setuptools` allows us to define console ``entry_points``. All we have
254+
to do is tell :py:mod:`setuptools` what Python function we want it to call when
255+
a user runs a given command line application. The name of our CLI is ``shouldi``
256+
and the function we want to run is ``main`` in the ``ShouldI`` class which is in
257+
the ``shouldi.cli`` module.
258+
259+
**setup.py**
260+
261+
.. code-block:: python
262+
263+
entry_points={"console_scripts": ["shouldi = shouldi.cli:ShouldI.main"]},
264+
265+
Re-install the package via pip
266+
267+
.. code-block:: console
268+
269+
$ python3.7 -m pip install -e .
270+
271+
Now we should be able to run our new tool via the CLI! (Provided your ``$PATH``
272+
is set up correctly.
273+
274+
.. code-block:: console
275+
276+
$ shouldi install insecure-package bandit
277+
bandit is okay to install
278+
Do not install insecure-package! {'safety_check_number_of_issues': 1}

examples/shouldi/.coveragerc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[run]
2+
source =
3+
shouldi
4+
tests
5+
branch = True
6+
7+
[report]
8+
exclude_lines =
9+
no cov
10+
no qa
11+
noqa
12+
pragma: no cover
13+
if __name__ == .__main__.:

examples/shouldi/.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
*.log
2+
*.pyc
3+
.cache/
4+
.coverage
5+
.idea/
6+
.vscode/
7+
*.egg-info/
8+
build/
9+
dist/
10+
docs/build/
11+
venv/
12+
wheelhouse/
13+
*.egss
14+
.mypy_cache/
15+
*.swp
16+
.venv/
17+
.eggs/
18+
*.modeldir
19+
*.db
20+
htmlcov/

examples/shouldi/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Copyright (c) 2017-2019 Intel
2+
3+
MIT License
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

examples/shouldi/MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include README.md
2+
include LICENSE

examples/shouldi/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# shouldi
2+
3+
![shouldi](https://github.com/intel/dffml/raw/master/examples/shouldi/shouldi.jpg)
4+
5+
## Usage
6+
7+
```console
8+
$ shouldi install insecure-package bandit
9+
bandit is okay to install
10+
Do not install insecure-package! {'safety_check_number_of_issues': 1}
11+
```
12+
13+
## Dependencies
14+
15+
`shouldi` depends on safety, pylint, and bandit being installed separately.
16+
17+
```console
18+
$ python3.7 -m pip install -U safety pylint bandit
19+
```
20+
21+
## WTF is this
22+
23+
`shouldi` is a tool that runs static analysis tools to let you know if there are
24+
any issues in any of the python packages you were thinking of installing.
25+
26+
`shouldi` is similar to things like [Go Report Card](https://goreportcard.com/).
27+
28+
Right now `shouldi` runs the following static analysis tools and complains if:
29+
30+
- [safety](https://pyup.io/safety/)
31+
- Any issues are found
32+
- TODO: [bandit](https://pypi.org/project/bandit/)
33+
- TODO: [pylint](https://pypi.org/project/pylint/)
34+
- TDB (something about the number of errors)
35+
36+
## License
37+
38+
shouldi is distributed under the [MIT License](LICENSE).

0 commit comments

Comments
 (0)