body file control
{
inputs => {
"context_shared/some_ubuntu_promises.cf",
"context_shared/some_aix_promises.cf
}
bundle agent context_shared
{
methods:
ubuntu_16_04::
"any" usebundle => some_ubuntu_promises.cf;
aix:
"any" usebundle => some_aix_promises.cf
}
verbose: END parsing file: /var/cfengine/inputs/context_shared/some_ubuntu_promises.cf
error: Proposed executable file '/bin/systemctl' doesn't exist.
--
You received this message because you are subscribed to the Google Groups "help-cfengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to help-cfengin...@googlegroups.com.
To post to this group, send email to help-c...@googlegroups.com.
Visit this group at https://groups.google.com/group/help-cfengine.
For more options, visit https://groups.google.com/d/optout.
If you have platform specific policy files I would recommend only adding them to inputs on that platform. That way you don't waste time parsing policy that is not applicable.I usually use body file control to extend inputs. You can setup a variable to include the proper policies for the platform ( I commonly use classic arrays with class guards and getindics and getvalues).
body file control
{
ubuntu_16_04::
inputs => {
...
}
}
mt...@quoininc.com writes:
Got a strange thing here. I'm hoping there's a simple way to fix without a lot
of refactoring. But who knows?
Running CFengine 3.10.3. Running on a redhat hub, and some of the clients
bootstrapped will be Ubuntu or AIX.
I have a bundle called "contextshared" where shared promises will be run. But
OK, you have several options.
For example in your some_ubuntu_promises.cf
use class expressions to restrict
the promises to the platforms you intend them to be used on. This will prevent
bundle agent some_ubuntu_promises:: { vars: ubuntu.!(ubuntu_14|ubuntu_15):: "units" string => execresult( "/bin/systemctl list-units", noshell); classes: ubuntu.systemd:: "xxx_is_enabled" string => returnzero( "/bin/systemctl is-enabled xxx.service", noshell); commands: # Since commands are only run when a bundle is getting its 3 pass # convergence this technically doesn't need a guard. It's only subject to # run if it is part of the bundlesequence, or if its used by a methods # type promise. # ubuntu.!(ubuntu_14|ubuntu_15):: "/bin/systemctl daemon-reload" if => "systemd_unit_xxx_repaired.!systemd_unit_xxx_notkept"; }
This is considered a best practice. I however am guilty of rarely adhering to
it. I dislike the level of maintenance this can require, so when I do it I try
to use a single class per bundle that represents what is supported. But deep
guarding is the best way to prevent things from happening on platforms where
they should not.
bundle agent some_ubuntu_promises:: { classes: "$(this.bundle)_platform_supported" expression => "ubuntu.!(ubuntu_14|ubuntu_15).systemd"; vars: "$(this.budle)_platform_supported":: "units" string => execresult( "/bin/systemctl list-units", noshell); classes: "$(this.budle)_platform_supported":: "xxx_is_enabled" string => returnzero( "/bin/systemctl is-enabled xxx.service", noshell); commands: # Since commands are only run when a bundle is getting its 3 pass # convergence this technically doesn't need a guard. It's only subject to # run if it is part of the bundlesequence, or if its used by a methods # type promise. "$(this.budle)_platform_supported":: "/bin/systemctl daemon-reload" if => "systemd_unit_xxx_repaired.!systemd_unit_xxx_notkept"; }
If you are careful that a policy is only ever added to inputs on the host it
should run, then the agent will not have the opportunity to error because it
will not spend time parsing the file.
body file control { ubuntu_16:: inputs => { "context_shared/some_ubuntu_promises.cf", }; aix:: inputs => {
"context_shared/some_aix_promises.cf" }; bundle agent context_shared { methods: ubuntu_16_04
:: "any" usebundle => some_ubuntu_promises; aix: "any" usebundle => some_aix_promises; }
The above may give you errors about undefined bundles. You can disable this
sanity check by setting ignore_missing_bundles
in body common control
to
true
. See Components and Common Control in the Reference Manual.
Off topic: I am curious, what versions of AIX are you using?
This has the added advantage of being able to leverage $(this.promise_dirname)
to load policy files based on relative location.
body file control { inputs => { @(some_ubuntu_promises.inputs) }; } bundle common some_ubuntu_promises_file_control { vars: any:: "policy[my_always]" string => "$(this.promsie_dirname)/my_always.cf"; ubutu:: "policy[some_ubuntu_promises]" string => "$(this.promise_dirname)/some_ubuntu_promises.cf"; aix:: "policy[some_aix_promises]" string => "$(this.promise_dirname)/some_aix_promises.cf"; any:: "inputs" slist => getvalues( policy );
} bundle agent context_shared { methods: ubuntu_16_04
:: "any" usebundle => some_ubuntu_promises; aix: "any" usebundle => some_aix_promises; }
Again, this policy will error if your using a bundle that isn't part of inputs.
However, we can leverage the data structure, and the consistency in no bundle
params to run the bundles automatically.
body file control { inputs => { @(some_ubuntu_promises.inputs) }; } bundle common some_ubuntu_promises_file_control { vars: any:: "policy[my_always]" string => "$(this.promsie_dirname)/my_always.cf"; ubutu:: "policy[some_ubuntu_promises]" string => "$(this.promise_dirname)/some_ubuntu_promises.cf"; aix:: "policy[some_aix_promises]" string => "$(this.promise_dirname)/some_aix_promises.cf"; any:: "inputs" slist => getvalues( policy ); "bundles" slist => getvalues( policy ); # This assumes that we named the # key for each file the bundle we # want to call from that file. } bundle agent context_shared { methods: "any" usebundle => $(some_ubuntu_promises_file_control.bundles); }
The data structure for inputs can also make use of consistently naming. If you
are maintaining a file for each possible sys.flavor
for example. In this
example we load platform and hostname specific policy if the policy files exist.
body file control { inputs => { @(some_ubuntu_promises.inputs) }; } bundle common some_ubuntu_promises_file_control { vars: any:: "policy[my_always]" string => "$(this.promsie_dirname)/my_always.cf"; # Load platform specific policy if it exists "policy[$(sys.flavor)]" string => "$(this.promsie_dirname)/$(sys.flavor).cf", if => fileexists( "$(this.promsie_dirname)/$(sys.flavor).cf" ); # Load host specific policy if it exists "policy[$(sys.fqhost)]" string => "$(this.promsie_dirname)/$(sys.flavor).cf", if => fileexists( "$(this.promsie_dirname)/$(sys.flavor).cf" ); "inputs" slist => getvalues( policy ); "bundles" slist => getvalues( policy ); # This assumes that we named the # key for each file the bundle we # want to call from that file. } bundle agent context_shared { methods: "any" usebundle => $(some_ubuntu_promises_file_control.bundles); }
These kinds of changes to enable dynamic policies can result in great speedups
and have benefits in policy maintenance. However, the platform split is not good
for all things, pushing everything into platform specific policies can
drastically increase and complicate your policy management. Additionally,
introducing dynamic inputs minimizes the hubs policy check protection. You
should be sure that your CI is testing these permutations before they are
released or you could distribute broken policy to a few hosts.
I hope these examples help!
–
Nick Anderson
Doer of things, CFEngine
# ...
body common control => {
bundlesequence => {
# ...
@(context_shared.bundles),
# ...
};
inputs => {
# ...
"context_shared/context_shared.cf",
@(context_shared.inputs),
# ...
};
# ...
}
# ...
bundle common context_shared
{
vars:
any::
"inputs" slist => { };
"bundles" slist => { };
ubuntu_16_4::
"inputs" slist => { "context_shared/ubuntu_16_4.cf" };
"bundles" slist => { "ubuntu_16_4" };
# ... (additional os-specific definitions)
}
body file control
{
inputs => {
"context_shared/ubuntu_specific_stuff.cf"
}
}
bundle agent ubuntu_16_4
{
methods:
"any" usebundle => ubuntu_specific_stuff;
}
bundle agent ubuntu_specific_stuff
{
vars:
"audit1" string => execresult("/bin/systemctl is-enabled nfs-server", "noshell");
classes:
"audit1_failed" expression => strcmp("enabled", "$(audit1)");
reports:
audit1_failed::
"ERROR: $(this.bundle): FAILED.";
}
mt...@quoininc.com writes:
Just following up. Here's what ended up working for me:
Thanks for sharing what you converged on.
I have a couple things you might consider.
First, instead of modifying promises.cf
directly to add your additional inputs,
consider using augments (def.json
).
Instead of this in promises.cf
:
body common control {
bundlesequence => { # ... @(context_shared.bundles), # ... }; inputs => { # ... "context_shared/context_shared.cf", @(context_shared.inputs), }; # ... }
You could extend the base inputs and append to the default bundlesequence from
def.json
:
{ "inputs": [ "context_shared/context_shared.cf" ], "vars": { "control_common_bundlesequence_end": [ "mtaft" ] } }
context_shared/context_shared.cf
could contain:
body file control { inputs => { @(context_shared.inputs) ); } bundle common context_shared { vars: any:: "inputs" slist => { }; "bundles" slist => { }; ubuntu_16_4:: # "policy[usebundle]" string => "inputfile"; "policy[ubuntu_16_4]" string => "context_shared/ubuntu_16_4.cf"; any:: # We sort this for predictibility which can aid debugging "inputs" slist => sort( getvalues( policy ), lex); "bundles" slist => sort( getindices( policy ), lex); } bundle agent mtaft { methods: # Activate all the context specific bundles. "$(context_shared.bundles)" usebundle => $(context_shared.bundles); }
This bundle mtaft
could be organized somewhere else if it makes more sense.
I think minimizing the number of changes you make to the vendored policy will
ease future policy framework upgrades. Also mapping bundle names to policy
inputs is useful so that you can maintain just the data structure instead of
multiple lists independently.