It didn't do what I thought it did with a RevertableProperty; it always
returned Nothing because even if the input properties to <!> are NoInfo, it
casts them to HasInfo.
Even if it had worked, it lost type safety. Better to export the
Property NoInfo that is used in a RevertableProperty, so it can be used
directly.
The old one caused the actions to run in the right order, but with the
wrong description.
This problem was found by comparing the [Host] between this branch and
current joeyconfig, and printing out their properties, info, and also their
list of child properties.
The only other difference found is that onChange orders the child property
list differently. That does not have any real effect and would be difficult
to change, so I've left it as-is.
* Property has been converted to a GADT, and will be Property NoInfo
or Property HasInfo.
This was done to make sure that ensureProperty is only used on
properties that do not have Info.
Transition guide:
- Change all "Property" to "Property NoInfo" or "Property WithInfo"
(The compiler can tell you if you got it wrong!)
- To construct a RevertableProperty, it is useful to use the new
(<!>) operator
- Constructing a list of properties can be problimatic, since
Property NoInto and Property WithInfo are different types and cannot
appear in the same list. To deal with this, "props" has been added,
and can built up a list of properties of different types,
using the same (&) and (!) operators that are used to build
up a host's properties.
The problem this exposes has to do with requires. As implemented,
requires yields either a Property HasInfo or a Property NoInfo depending
on its inputs. That works. But look what happens when it's used:
*Propellor.Types> let foo = IProperty "foo" (return NoChange) mempty mempty
*Propellor.Types> let bar = IProperty "bar" (return NoChange) mempty mempty
*Propellor.Types> foo `requires` bar
<interactive>:17:5:
No instance for (Requires (Property HasInfo) (Property HasInfo) r0)
arising from a use of `requires'
The type variable `r0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
instance Requires
(Property HasInfo) (Property HasInfo) (Property HasInfo)
-- Defined at Propellor/Types.hs:167:10
Possible fix:
add an instance declaration for
(Requires (Property HasInfo) (Property HasInfo) r0)
In the expression: foo `requires` bar
In an equation for `it': it = foo `requires` bar
This can be avoided by specifying the result type:
*Propellor.Types> (foo `requires` bar) :: Property HasInfo
property "foo"
But then when multiple `requires` are given, the result type has to be
given each time:
*Propellor.Types> (foo `requires` bar `requires` bar) :: Property HasInfo
<interactive>:22:6:
No instance for (Requires (Property HasInfo) (Property HasInfo) x0)
arising from a use of `requires'
The type variable `x0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
instance Requires
(Property HasInfo) (Property HasInfo) (Property HasInfo)
-- Defined at Propellor/Types.hs:167:10
Possible fix:
add an instance declaration for
(Requires (Property HasInfo) (Property HasInfo) x0)
In the first argument of `requires', namely `foo `requires` bar'
In the expression:
(foo `requires` bar `requires` bar) :: Property HasInfo
In an equation for `it':
it = (foo `requires` bar `requires` bar) :: Property HasInfo
<interactive>:22:21:
No instance for (Requires x0 (Property HasInfo) (Property HasInfo))
arising from a use of `requires'
The type variable `x0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
instance Requires
(Property NoInfo) (Property HasInfo) (Property HasInfo)
-- Defined at Propellor/Types.hs:175:10
instance Requires
(Property HasInfo) (Property HasInfo) (Property HasInfo)
-- Defined at Propellor/Types.hs:167:10
Possible fix:
add an instance declaration for
(Requires x0 (Property HasInfo) (Property HasInfo))
In the expression:
(foo `requires` bar `requires` bar) :: Property HasInfo
In an equation for `it':
it = (foo `requires` bar `requires` bar) :: Property HasInfo
*Propellor.Types> (((foo `requires` bar) :: Property HasInfo) `requires` bar) :: Property HasInfo
property "foo"
Yuggh!
Properties now form a tree, instead of the flat list used before.
This simplifies propigation of Info from the Properties used inside a
container to the outer host; the Property that docks the container on the
host can just have as child properties all the inner Properties, and their
Info can then be gathered recursively. (Although in practice it still needs
to be filtered, since not all Info should propigate out of a container.)
Note that there is no change to how Properties are actually satisfied.
Just because a Property lists some child properties, this does not mean
they always have their propertySatisfy actions run. It's still up to the
parent property to run those actions.
That's necessary so that a container's properties can be satisfied inside
it, not outside. It also allows property combinators to
add the combined Properties to their childProperties list, even if,
like onChange, they don't always run the child properties at all.
Testing: I tested that the exact same Info is calculated before and after
this change, for every Host in my config file.
* Added CryptPassword to PrivDataField, for password hashes as produced
by crypt(3).
* User.hasPassword and User.hasSomePassword will now use either
a CryptPassword or a Password from privdata, depending on which is set.
The problem occurred because two lists of lines of the file can be
different, while representing the same file content. For example:
["foo", "bar"]
["foo\nbar"]
This is not a complete fix for the problem that Info doen't propigate
from the called property when code does something like:
do
hostname <- asks hostName
ensureProperty $ foo hostname
Instead, I just eliminated the need to implement hasPassword that way,
by making the PrivData Info use a HostContext which automatically
gets the right hostname passed to it.
All other uses of withPrivData don't have the problem. It's still possible
for the user to run into the problem if they write something like the
above, where foo is a property that uses privdata. However, all properties
that take a Context now also accept a HostContext, so it's at least less
likely the user needs to write that.
This avoids leaving the system in a broken state where some directories
have been renamed away any others not.
Future work: If the rename list contains (foo, bar) and (newfoo,foo),
reorder the list to gather those two actions together to minimize
the amount of time that foo is missing. In case of power loss or something.