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

added unknown docs #6839

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelogs/unreleased/6056-docs-unknowns.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Added documentation about unknowns
change-type: patch
sections:
minor-improvement: "{{description}}"
issue-nr: 6056
destination-branches:
- master
- iso8
- iso7
4 changes: 4 additions & 0 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ Glossary
a cloud provider will not be known upfront. Inmanta marks this parameters as **unknown**.
The state of any resource that uses such an unknown parameter becomes undefined.

For more context, see
:ref:`how unknowns propagate through the configuration model <language_unknowns>` and
:ref:`how the exporter deals with them <model_export_format>`.

entity
Concepts in the infrastructure are modelled in the configuration with entities. An entity
defines a new type in the configuration model. See :ref:`lang-entity`.
Expand Down
87 changes: 87 additions & 0 deletions docs/language.rst
Original file line number Diff line number Diff line change
Expand Up @@ -923,3 +923,90 @@ Plug-ins
For more complex operations, python plugins can be used. Plugins are exposed in the Inmanta language as function calls, such as the template function call. A template
accepts parameters and returns a value that it computed out of the variables. Each module that is included can also provide plug-ins. These plug-ins are accessible within the namespace of the
module. The :ref:`module-plugins` section of the module guide provides more details about how to write a plugin.


.. _language_unknowns:
Unknowns
========

Wherever the configuration model interacts with the outside world (e.g. to fetch external values) :term:`unknown` values
may be present. These unknowns represent values we don't know yet, such as the IP address of a machine that hasn't been created yet. Because the compiler can handle unknowns, developers can write models as if all information is present up front, even when this is not the case. These unknowns are propagated through the model to finally end up in the resources that require these unknowns.
values. This section describes how unknown values flow through the model, and perhaps equally importantly, where they do not
flow at all.

.. note::
Unknowns are a subtle concept. Luckily, for the majority of model development you don't really need to take them into
account. However, for some advanced scenarios it may be important to know how and where they may occur.

For the most part, unknowns are simply propagated along the data flow: they're treated like any other value, except
when anything needs to be derived from them, the result simply becomes an unknown as well. More specifically: statements like
assignment statements, constructors
and lists simply include the unknown in their result like they would any other value. Any expression that can not produce
a definite result without knowing the value, will return another unknown. And finally, statements that expand the model
with new blocks based on some value, like the if statement and the for loop, simply do not expand the model with their
respective blocks for unknowns.

The model below presents some examples of how an unknown propagates.

.. code-block:: inmanta

# std::env returns an unknown if the environment variable is not (yet) set
my_unknown = std::get_env("THIS_ENV_VAR_DOES_NOT_EXIST")

a = my_unknown # a is unknown
b = [1, 2, my_unknown, 3] # b is a list with 1 unknown element
c = my_unknown is defined # we can not know if c is null, so c is also unknown
d = true or my_unknown # value of my_unknown is irrelevant -> d is true
e = my_unknown or true # lazy boolean operator can not compute result without knowing the value -> e is unknown
f = (e == my_unknown) # both e and my_unknown are unknown but they aren't necessarily the same value -> f is unknown

if my_unknown:
# this block is never executed
std::print("This message is never printed!")
else:
# neither is this one
std::print("This message is never printed!")
end

for x in my_unknown:
# neither is this one
std::print("This message is never printed!")
end

for x in [1, 2, my_unknown]:
# this block is executed twice: x=1 and x=2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my opinion on this is known. I would like to still have this power, but I leave it to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By now the behavior is no longer under discussion, it's simply about documenting it. I'll double check this now.

std::print(f"This message is printed twice! x={x}")
end

g = my_unknown ? true : false # condition is unknown -> neither branch is executed, result is unknown

entity E:
int n
end
implement E using std::none

h = [E(n=x) for x in [1, 2, my_unknown]] # the constructor is executed once with n=1 and once with n=2. Unknown is propagated as is -> h = [E(n=1), E(n=2), unknown]
i = [E(n=x) for x in [1, 2, my_unknown] if not std::is_unknown(x)] # the unknown is filtered out -> i = [E(n=1), E(n=2)]

Now that we've covered how unknowns flow through the model, we can discuss what an unknown value actually means. In most cases
it simply represents an unknown value. But because of the propagation semantics outlined above, if it happens to occur in a
list, it may in fact represent any number of values: not only the value is unknown, also its size.

For example, consider a list comprehension that filters a list on some condition. If the list contains an unknown, the compiler
can not know if the filter applies so it will propagate the unknown to the result. When the unknown eventually becomes known,
it might remain in the result, or it might be filtered out, depending on whether it matches the condition.

.. code-block:: inmanta

my_unknown = std::get_env("THIS_ENV_VAR_DOES_NOT_EXIST")
my_unknown2 = std::get_env("THIS_ENV_VAR_DOES_NOT_EXIST2")

l = [1, my_unknown, 3, my_unknown2, 5]
a = [x for x in l if x > 2] # l = [unknown, 3, unknown, 5]

# an unknown can even represent more than one unknown value
edvgui marked this conversation as resolved.
Show resolved Hide resolved
b = my_unknown == 0 ? [1, 2] : [3, 4] # b = unknown -> when it becomes known it will be either [1, 2] or [3, 4]
# or none at all
c = [x for x in l if x > 1000] # c = [unknown, unknown] -> would become [] if the env var values are <= 1000

d = std::len(l) # d = unknown (l contains unknowns, so its length is also unknown)
21 changes: 10 additions & 11 deletions docs/platform_developers/modelexportformat.rst
Original file line number Diff line number Diff line change
@@ -1,49 +1,48 @@
.. _model_export_format:
Model Export Format
========================




#. top level is a dict with one entry for each instance in the model
#. the key in this dict is the object reference handle
#. the value is the serialized instance
#. the serialized instance is a dict with three fields: type, attributes and relation.
#. type is the fully qualified name of the type
#. attributes is a dict, with as keys the names of the attributes and as values a dict with one entry.
#. An attribute can have one or more of tree keys: unknows, nones and values. The "values" entry has as value a list with the attribute values.
If any of the values is Unknown or None, it is removed from the values array and the index at which it was removed is recorded in respective the unknowns or nones value
#. relations is like attributes, but the list of values contains the reference handles to which this relations points
#. An attribute can have one or more of tree keys: unknows, nones and values. The "values" entry has as value a list with the attribute values.
If any of the values is :term:`unknown` or None, it is removed from the values array and the index at which it was removed is recorded in respective the unknowns or nones value
#. relations is like attributes, but the list of values contains the reference handles to which this relations points

Basic structure as pseudo jinja template
Basic structure as pseudo jinja template

.. code-block:: js+jinja

{
{% for instance in instances %}
'{{instance.handle}}':{
"type":"{{instance.type.fqn}}",
"attributes":[
"attributes":[
{% for attribute in instance.attributes %}
"{{attribute.name}}": [ {{ attribute.values | join(",") }} ]
{% endfor %}
]
"relations" : [
{% for relation in instance.relations %}
"{{relation.name}}": [
"{{relation.name}}": [
{% for value in relation.values %}
{{value.handle}}
{% endfor %}
]
{% endfor %}
]

{% endif %}
}
}

Type Export Format
========================

.. automodule:: inmanta.model
:members:
:private-members: