Classification endpoint
The classification
endpoint takes a node name and a set
of facts, and returns information about how that node is classified. The output can
help you test your classification rules.
POST /v1/classified/nodes/<name>
Use the /v1/classified/nodes/\<name\>
endpoint to retrieve the classification
information for the node with the given name and facts as supplied in the body of the
request.
Request format
The request body can contain a JSON object describing the facts and trusted facts of the node to be classified. The object can have these keys:
Key | Definition |
---|---|
fact |
The regular, non-trusted facts of the node. The value of this key is a further object, whose keys are fact names, and whose values are the fact values. Fact values can be a string, number, boolean, array, or object. |
trusted |
The trusted facts of the node. The values
of this key are subject to the same restrictions as those on the value of the
fact key. |
Response format
The response is a JSON object describing the node post-classification, using these keys:
Key | Definition |
---|---|
name
|
The name of the node (a string). |
groups
|
An array of the group-ids (strings) that this node was classified into. |
environment
|
The name of the environment that this node uses, which is taken from the node groups the node was classified into. |
classes
|
An object where the keys are class names and the values are objects that map parameter names to values. |
parameters
|
An object where the keys are top-level variable names and the values are the values assigned to those variables. |
This is an example of a response from this endpoint:
{
"name": "foo.example.com",
"groups": ["9c0c7d07-a199-48b7-9999-3cdf7654e0bf", "96d1a058-225d-48e2-a1a8-80819d31751d"],
"environment": "staging",
"parameters": {},
"classes": {
"apache": {
"keepalive_timeout": 30,
"log_level": "notice"
}
}
}
Error responses
If the node is classified into multiple node groups that define conflicting classifications for the node, the service returns a 500 Server Error response.
The body of this response contains the usual JSON error object described in the errors documentation.
The kind
key of the error is "classification-conflict", the msg
describes generally why this happens, and the details
key contains an object that describes the
specific conflicts encountered.
The details object can have these keys:
Key | Definition |
---|---|
environment
|
Maps directly to an array of value detail objects (described below). |
variables
|
Contains an object with a key for each conflicting variable, whose values are an array of value detail objects. |
classes
|
Contains an object with a key for each class that had conflicting parameter definitions, whose values are further objects that describe the conflicts for that class's parameters. |
A value details object describes one of the conflicting values defined for the environment, a variable, or a class parameter. Each object contains these keys:
Key | Definition |
---|---|
value
|
The defined value, which is a string for environment and class parameters, but for a variable can be any JSON value. |
from
|
The node group that the node was classified into that caused this value to be added to the node's classification. This group cannot define the value, because it can be inherited from an ancestor of this group. |
defined_by
|
The node group that actually defined this
value. This is often the from
group, but could instead be an ancestor of that group. |
This example shows a classification conflict error object with node groups truncated for clarity:
{
"kind": "classification-conflict",
"msg": "The node was classified into multiple unrelated node groups that defined conflicting class parameters or top-level variables. See `details` for a list of the specific conflicts.",
"details": {
"classes": {
"songColors": {
"blue": [
{
"value": "Blue Suede Shoes",
"from": {
"name": "Elvis Presley",
"classes": {},
"rule": ["=", "nodename", "the-node"],
...
},
"defined_by": {
"name": "Carl Perkins",
"classes": {"songColors": {"blue": "Blue Suede Shoes"}},
"rule": ["not", ["=", "nodename", "the-node"]],
...
}
},
{
"value": "Since You've Been Gone",
"from": {
"name": "Aretha Franklin",
"classes": {"songColors": {"blue": "Since You've Been Gone"}},
...
},
"defined_by": {
"name": "Aretha Franklin",
"classes": {"songColors": {"blue": "Since You've Been Gone"}},
...
}
}
]
}
}
}
}
In this example, the conflicting "Blue Suede Shoes" value was included in the classification because the node matched the "Elvis Presley" node group (because that is the value of the "from" key), but that node group doesn't define the "Blue Suede Shoes" value. That value is defined by the "Carl Perkins" node group, which is an ancestor of the "Elvis Presley" node group, causing the latter to inherit the value from the former. The other conflicting value, "Since You've Been Gone", is defined by the same node group that the node matched.
POST /v1/classified/nodes/<name>/explanation
Use the /v1/classified/nodes/\<name\>/explanation
endpoint to retrieve an explanation
of how a node is classified by submitting its facts.
Request format
The body of the request must be a JSON object describing the node's facts. This object uses these keys:
Key | Definition |
---|---|
fact
|
Describes the regular (non-trusted) facts of the node. Its value must be a further object whose keys are fact names, and whose values are the corresponding fact values. Structured facts can be included here; structured fact values are further objects or arrays. |
trusted
|
Optional key that describes the trusted
facts of the node. Its value has exactly the same format as the fact key's value. |
name
|
The node's name from the request's URL is merged into this object under this key. |
This is an example of a valid request body:
{
"fact": {
"ear-tips": "pointed",
"eyebrow pitch": "40",
"hair": "dark",
"resting bpm": "120",
"blood oxygen transporter": "hemocyanin",
"anterior tricuspids": "2",
"appendices": "1",
"spunk": "10"
}
}
Response format
The response is a JSON object that describes how the node would be classified.
- If the node would be successfully classified, this object contains the final classification.
- If the classification would fail due to conflicts, this object contains a description of the conflicts.
This response is intended to provide insight into the entire classification process, so that if a node isn't classified as expected, you can trace the deviation.
Classification proceeds in this order:
- All node group rules are tested on the node's facts and name, and groups that don't match the node are culled, leaving the matching groups.
- Inheritance relations are used to further cull the matching groups, by removing any matching node group that has a descendant that is also a matching node group. Those node groups that are left over are leaf groups.
- Each leaf group is transformed into its inherited classification by adding all the inherited values from its ancestors.
- All of the inherited classifications and individual node classifications are inspected for conflicts. A conflict occurs whenever two inherited classifications define different values for the environment, a class parameter, or a top-level variable.
Any individual node classification, including classes, class parameters, configuration data, and variables, is added.
Individual node classification is applied to the group classification, forming the final classification, which is then returned to the client.
The JSON object returned by this endpoint uses these keys:
Key | Definition |
---|---|
match_explanations
|
Corresponds to step 1 of classification, finding the matching node groups. This key's value is an explanation object just like those found in node check-ins, which maps between a matching group's ID and an explained condition object that demonstrates why the node matched that group's rule. |
leaf_groups |
Corresponds to step 2 of classification, finding the leaves. This key's value is an array of the leaf groups (that is, those groups that are not related to any of the other matching groups). |
inherited_classifications |
Corresponds to step 3 of classification, adding inherited values. This key's value is an object mapping from a leaf group's ID to the classification values provided by that group (after inheritance). |
conflicts
|
Corresponds to step 4 of classification.
This key is present only if there are conflicts between the inherited
classifications. Its value is similar to a classification object, but wherever there
was a conflict there's an array of conflict details instead of a single
classification value. Each of these details is an object with three keys: value , from , and defined_by . The value
key is a conflicting value, the from key is the group whose inherited classification provided this
value, and the defined_by key is
the group that actually defined the value (which can be an ancestor of the from group). |
individual_classification |
Corresponds to step 5 of classification. Its value includes classes, class parameters, configuration data, and variables applied directly during this stage. |
final_classification
|
Corresponds to step 6 of classification, present only if there are no conflicts between the inherited classifications. Its value is the result of merging all individual node classification and group classification. |
node_as_received
|
The submitted node object as received by
the service, after adding the name
and, if not supplied by the client, an empty trusted object. This key does not correspond to any of the
classification steps. |
classification_source |
An annotated version of the classification that has the environment and every class parameter and variable replaced with an "annotated value" object. This key does not correspond to any of the classification steps. |
This example shows a response the endpoint could return in the case of a successful classification:
{
"node_as_received": {
"name": "Tuvok",
"trusted": {},
"fact": {
"ear-tips": "pointed",
"eyebrow pitch": "30",
"blood oxygen transporter": "hemocyanin",
"anterior tricuspids": "2",
"hair": "dark",
"resting bpm": "200",
"appendices": "0",
"spunk": "0"
}
},
"match_explanations": {
"00000000-0000-4000-8000-000000000000": {
"value": true,
"form": ["~", {"path": "name", "value": "Tuvok"}, ".*"]
},
"8aeeb640-8dca-4b99-9c40-3b75de6579c2": {
"value": true,
"form": ["and",
{
"value": true,
"form": [">=", {"path": ["fact", "eyebrow pitch"], "value": "30"}, "25"]
},
{
"value": true,
"form": ["=", {"path": ["fact", "ear-tips"], "value": "pointed"}, "pointed"]
},
{
"value": true,
"form": ["=", {"path": ["fact", "hair"], "value": "dark"}, "dark"]
},
{
"value": true,
"form": [">=", {"path": ["fact", "resting bpm"], "value": "200"}, "100"]
},
{
"value": true,
"form": ["=",
{
"path": ["fact", "blood oxygen transporter"],
"value": "hemocyanin"
},
"hemocyanin"
]
}
]
}
},
"leaf_groups": {
"8aeeb640-8dca-4b99-9c40-3b75de6579c2": {
"name": "Vulcans",
"id": "8aeeb640-8dca-4b99-9c40-3b75de6579c2",
"parent": "00000000-0000-4000-8000-000000000000",
"rule": ["and", [">=", ["fact", "eyebrow pitch"], "25"],
["=", ["fact", "ear-tips"], "pointed"],
["=", ["fact", "hair"], "dark"],
[">=", ["fact", "resting bpm"], "100"],
["=", ["fact", "blood oxygen transporter"], "hemocyanin"]
],
"environment": "alpha-quadrant",
"variables": {},
"classes": {
"emotion": {"importance": "ignored"},
"logic": {"importance": "primary"}
},
"config_data": {
"USS::Voyager": {"designation": "subsequent"}
}
}
},
"inherited_classifications": {
"8aeeb640-8dca-4b99-9c40-3b75de6579c2": {
"environment": "alpha-quadrant",
"variables": {},
"classes": {
"logic": {"importance": "primary"},
"emotion": {"importance": "ignored"}
},
"config_data": {
"USS::Enterprise": {"designation": "original"},
"USS::Voyager": {"designation": "subsequent"}
}
}
},
"individual_classification": {
"classes": {
"emotion": {
"importance": "secondary"
}
},
"variables": {
"full_name": "S'chn T'gai Spock"
}
},
"final_classification": {
"environment": "alpha-quadrant",
"variables": {
"full_name": "S'chn T'gai Spock"
},
"classes": {
"logic": {"importance": "primary"},
"emotion": {"importance": "secondary"}
},
"config_data": {
"USS::Enterprise": {"designation": "original"},
"USS::Voyager": {"designation": "subsequent"}
}
},
"classification_sources": {
"environment": {
"value": "alpha-quadrant",
"sources": ["8aeeb640-8dca-4b99-9c40-3b75de6579c2"]
},
"variables": {},
"classes": {
"emotion": {
"puppetlabs.classifier/sources": ["8aeeb640-8dca-4b99-9c40-3b75de6579c2"],
"importance": {
"value": "secondary",
"sources": ["node"]
}
},
"logic": {
"puppetlabs.classifier/sources": ["8aeeb640-8dca-4b99-9c40-3b75de6579c2"],
"importance": {
"value": "primary",
"sources": ["8aeeb640-8dca-4b99-9c40-3b75de6579c2"]
}
},
"config_data": {
"USS::Enterprise": {
"designation": {
"value": "original",
"sources": ["00000000-0000-4000-8000-000000000000"]
}
},
"USS::Voyager": {
"designation": {
"value": "subsequent",
"sources": ["8aeeb640-8dca-4b99-9c40-3b75de6579c2"]
}
}
}
}
}
}
This example shows a response that resulted from conflicts:
{
"node_as_received": {
"name": "Spock",
"trusted": {},
"fact": {
"ear-tips": "pointed",
"eyebrow pitch": "40",
"blood oxygen transporter": "hemocyanin",
"anterior tricuspids": "2",
"hair": "dark",
"resting bpm": "120",
"appendices": "1",
"spunk": "10"
}
},
"match_explanations": {
"00000000-0000-4000-8000-000000000000": {
"value": true,
"form": ["~", {"path": "name", "value": "Spock"}, ".*"]
},
"a130f715-c929-448b-82cd-fe21d3f83b58": {
"value": true,
"form": [">=", {"path": ["fact", "spunk"], "value": "10"}, "5"]
},
"8aeeb640-8dca-4b99-9c40-3b75de6579c2": {
"value": true,
"form": ["and",
{
"value": true,
"form": [">=", {"path": ["fact", "eyebrow pitch"], "value": "30"}, "25"]
},
{
"value": true,
"form": ["=", {"path": ["fact", "ear-tips"], "value": "pointed"}, "pointed"]
},
{
"value": true,
"form": ["=", {"path": ["fact", "hair"], "value": "dark"}, "dark"]
},
{
"value": true,
"form": [">=", {"path": ["fact", "resting bpm"], "value": "200"}, "100"]
},
{
"value": true,
"form": ["=",
{
"path": ["fact", "blood oxygen transporter"],
"value": "hemocyanin"
},
"hemocyanin"
]
}
]
}
},
"leaf_groups": {
"a130f715-c929-448b-82cd-fe21d3f83b58": {
"name": "Humans",
"id": "a130f715-c929-448b-82cd-fe21d3f83b58",
"parent": "00000000-0000-4000-8000-000000000000",
"rule": [">=", ["fact", "spunk"], "5"],
"environment": "alpha-quadrant",
"variables": {},
"classes": {
"emotion": {"importance": "primary"},
"logic": {"importance": "secondary"}
}
},
"8aeeb640-8dca-4b99-9c40-3b75de6579c2": {
"name": "Vulcans",
"id": "8aeeb640-8dca-4b99-9c40-3b75de6579c2",
"parent": "00000000-0000-4000-8000-000000000000",
"rule": ["and", [">=", ["fact", "eyebrow pitch"], "25"],
["=", ["fact", "ear-tips"], "pointed"],
["=", ["fact", "hair"], "dark"],
[">=", ["fact", "resting bpm"], "100"],
["=", ["fact", "blood oxygen transporter"], "hemocyanin"]
],
"environment": "alpha-quadrant",
"variables": {},
"classes": {
"emotion": {"importance": "ignored"},
"logic": {"importance": "primary"}
}
}
},
"inherited_classifications": {
"a130f715-c929-448b-82cd-fe21d3f83b58": {
"environment": "alpha-quadrant",
"variables": {},
"classes": {
"logic": {"importance": "secondary"},
"emotion": {"importance": "primary"}
}
},
"8aeeb640-8dca-4b99-9c40-3b75de6579c2": {
"environment": "alpha-quadrant",
"variables": {},
"classes": {
"logic": {"importance": "primary"},
"emotion": {"importance": "ignored"}
}
}
},
"conflicts": {
"classes": {
"logic": {
"importance": [
{
"value": "secondary",
"from": {
"name": "Humans",
"id": "a130f715-c929-448b-82cd-fe21d3f83b58",
...
},
"defined_by": {
"name": "Humans",
"id": "a130f715-c929-448b-82cd-fe21d3f83b58",
...
}
},
{
"value": "primary",
"from": {
"name": "Vulcans",
"id": "8aeeb640-8dca-4b99-9c40-3b75de6579c2",
...
},
"defined_by": {
"name": "Vulcans",
"id": "8aeeb640-8dca-4b99-9c40-3b75de6579c2",
...
}
}
]
},
"emotion": {
"importance": [
{
"value": "ignored",
"from": {
"name": "Vulcans",
"id": "8aeeb640-8dca-4b99-9c40-3b75de6579c2",
...
},
"defined_by": {
"name": "Vulcans",
"id": "8aeeb640-8dca-4b99-9c40-3b75de6579c2",
...
}
},
{
"value": "primary",
"from": {
"name": "Humans",
"id": "a130f715-c929-448b-82cd-fe21d3f83b58",
...
},
"defined_by": {
"name": "Humans",
"id": "a130f715-c929-448b-82cd-fe21d3f83b58",
...
}
}
]
}
}
},
"individual_classification": {
"classes": {
"emotion": {
"importance": "secondary"
}
},
"variables": {
"full_name": "S'chn T'gai Spock"
}
}
}