policy
The policy is intended to be written by someone on your team or organization who is considering available licenses.
The policy file must implements def evaluate(material, result)
written by python.
evaluate
The def evaluate(material, result)
is evaluate policy. If the evaluation fails, you call result.add_error(message)
in this function.
example polocy.py
#!/usr/bin/python
allowed_licenses = [
"Apache-2.0",
"BSD-3-Clause",
"MIT",
"Unlicense",
]
def evaluate(material, result):
for license in material.licenses:
if license not in allowed_licenses:
result.add_error(f"{license} is not allowed")
This policy allows Apache-2.0
, BSD-3-Clause
, MIT
and Unlicense
.
The evaluate argment material
is curated Material by curation, and result
is EvaluateResult.
evaluate using annotations
The Material set some kind of annotation, you can evaluate the policy more flexibly. For example, there may be cases where you want to ignore a license that is normally deny due to some special circumstances.
def evaluate(material, result):
if "ignore" in material.annotations:
return
# your policy check
In the above, when ignore
is set on annotation, the evaluation is always ignored.
The rules for granting annotations are free, so you will need to decide the person who created the policy. For example, the above rule of ignore by ignore
annotation should be in your policy document.
As another example, let's take a policy evaluate of the AGPL 3.0 license.
Suppose your team or organization may use AGPL 3.0 software in a service and you want to check whether the composed software is source distributed according to the AGPL 3.0 license.
For example, you can write the following.
def evaluate(material, result):
for license in material.licenses:
if license.startswith("AGPL"):
usage = material.annotations.get("usage", "unknown")
if usage == "unknown":
result.add_error(f"you must set usage for {license} software")
elif usage == "service" and "project-source-distributed" not in material.annotations:
result.add_error(f"you must project-source-distribute on {license} software")
WARNING:
that the above policy does not check for compliance with all AGPL 3.0 license terms by legal perspective. For example, AGPL 3.0 also requires source code distribution when distributing a composed software even if it is not for use in a service.
The method of compliance with the terms of a particular license must be determined by your team or organization.
Even in the above example, your team or organization should decide under what conditions usage
annotation and project-source-distribute
will be granted and with what values.
EvaluateResult
EvaluateResult
is result of evaluate. If EvaluateResult
contains any errors, hatto evaluate is failed. Conversely, EvaluateResult
not contains errors, hatto evaluate is success.
It can also contain warnings. If the EvaluateResult
contains any warnings, hatto evaluate is not failed.
Methods
EvaluateResult.add_errors(message)
The add_errors
is add error to result.
def evaluate(material, result):
result.add_error("Any licences is not allowed")
$ hatto evaluate --policy=policy.py example.tsv
NG foo 1.0.0 licenses:["MIT"] annotations:{}
ERROR Any licences is not allowed
Failure: evaluate failed
EvaluateResult.add_warnings(message)
The add_warnings
is add warning to result.
def evaluate(material, result):
result.add_warning("Any licences is warning")
$ hatto evaluate --policy=policy.py example.tsv
OK foo 1.0.0 licenses:["MIT"] annotations:{}
WARNING Any licences is warning
default policy
If the --policy
option is not set, the following default policy is executed.
The default policy allows at least what is commonly used in some Permissive License. This policy may change in future versions. So, we recommend that it be not use as possible.
The policy for your team or organization should be written by your team or organization.
#!/usr/bin/python
allowed_licenses = [
"Apache-2.0",
"MIT",
"BSD-3-Clause",
"Unlicense",
]
def evaluate(material, result):
for license in material.licenses:
if license not in allowed_licenses:
result.add_error(f"{license} is not allowed")
tests
The policy is python code. So, it can be test in common way of python tests.
example test_polocy.py
from policy import evaluate
import pytest
class Material:
def __init__(self, name="", version="", licenses=[], annotations={}):
self.name = name
self.version = version
self.licenses = licenses
self.annotations = annotations
def update_annotation(self, key, value):
self.annotations[key] = value
class EvaluateResult:
def __init__(self):
self.errors = []
self.warnings = []
def add_error(self, message):
self.errors.append(message)
def add_warning(self, message):
self.warnings.append(message)
@pytest.mark.parameterize("license_name", ["Apache-2.0", "BSD-3-Clause", "MIT", "Unlicense"])
def test_evaluate_allow(license_name):
result = EvaluateResult()
material = Material("foo", "v0.1.0", [license_name])
evaluate()
assert len(result.errors) == 0
@pytest.mark.parameterize("license_name", ["UNKNOWN"])
def test_evaluate_deny(license_name):
result = EvaluateResult()
material = Material("foo", "v0.1.0", [license_name])
evaluate()
assert len(result.errors) == 1