URGENT : cfengine community skipping bundles

49 views
Skip to first unread message

Rama

unread,
Dec 4, 2016, 8:01:08 PM12/4/16
to help-cfengine
We continue to encounter this issue with community versions in our current production.

Nothing related comes in a google search. The enterprise version doesn't appear to have this issue in our testing so far.

We have this issue in a large production installation with versions 3.6.5 as well as 3.2.4-1 on linux flavors.

We observe that every alternate log in the outputs folder is much smaller than a full run. Checking the contents of the "shorter" logs leads us to conclude that most bundles (other than the common bundles) are skipped every alternate run, e.g.,

________________
-rw-------    1 root     root         50117 Dec  5 07:08 cf_fpdg_92101132__1480881683_Mon_Dec__5_07_01_23_2016_407a7490
-rw-------    1 root     root          2318 Dec  5 07:30 cf_fpdg_92101132__1480882901_Mon_Dec__5_07_21_41_2016_407a7490
-rw-------    1 root     root         12941 Dec  5 07:48 cf_fpdg_92101132__1480884119_Mon_Dec__5_07_41_59_2016_407a7490
-rw-------    1 root     root         40659 Dec  5 08:10 cf_fpdg_92101132__1480885277_Mon_Dec__5_08_01_17_2016_407a7490
-rw-------    1 root     root         16587 Dec  5 08:31 cf_fpdg_92101132__1480886495_Mon_Dec__5_08_21_35_2016_407a7490
-rw-------    1 root     root          2126 Dec  5 08:50 cf_fpdg_92101132__1480887714_Mon_Dec__5_08_41_54_2016_407a7490
_____________________________

Is there something obvious that we could try, in order to overcome this ? our schedule is set as follows in executor control :

________
body executor control
{
 any::

  splaytime  => "2";
 
  schedule => { "Min00", "Min20", "Min40" };
  #agent_expireafter => "15";

 #Default:
 #schedule => { "Min00", "Min15", "Min30", "Min45"};

.....
_____________________________________



Aleksey Tsalolikhin

unread,
Dec 4, 2016, 9:39:10 PM12/4/16
to Ramakant Duggal, help-cfengine

Hi Rama! Is turning on verbose mode for a couple of runs possible?  In body agent control?  We might get a clue there.


This email is confidential.  If you have received this email in error please notify us immediately by return email and delete this email and any attachments.  
VIX accepts no liability for any damage caused by this email or any attachments due to viruses, interference, interception, corruption or unauthorised access.

--
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-cfengine+unsubscribe@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.

Aleksey Tsalolikhin

unread,
Dec 4, 2016, 11:17:38 PM12/4/16
to Ramakant Duggal, help-cfengine

I hesitate to speculate without seeing the code base, but I believe verbose output would help here.

I suspect cfengine will tell you why it's skipping promises - e.g., ifelapsed lock, or inapplicable context.

The bit about agent bundles getting skipped is odd.


On Dec 4, 2016 6:39 PM, "Aleksey Tsalolikhin" <ale...@verticalsysadmin.com> wrote:

Hi Rama! Is turning on verbose mode for a couple of runs possible?  In body agent control?  We might get a clue there.

Ramakant Duggal

unread,
Dec 5, 2016, 1:40:01 AM12/5/16
to Aleksey Tsalolikhin, help-cfengine
Hey Aleksey,

Here is the verbose out put : out_sel is the one where a particular promise (a command promise). I made some attempt to isolate it in a simple bundle and reproduce it with no success. So I will have cite some of the actual policy fragments and the actual output logs from a test environment with same policies. Problem likely occurs only with all the complexity of other bundles present.

Notice how in the agent run where the promise was run, you see the command 'egrep -q .....' being run :
____________
 . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3>  -> Promiser string contains a valid executable (/bin/egrep) - ok
cf3> 
cf3>     .........................................................
cf3>     Promise handle: 
cf3>     Promise made by: /bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/fpds/terminals_to_upgrade
cf3>     .........................................................
cf3> 
cf3>  -> Executing '/bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/fpds/terminals_to_upgrade' ...(timeout=-678,owner=-1,group=-1)
cf3>  -> (Setting umask to 77)
cf3> -> Command related to promiser "/bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/fpds/terminals_to_upgrade" returned code defined as promise kept (0)
cf3>  ?> defining promise result class am_selected_for_upgrade
cf3>  -> Completed execution of /bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/fpds/terminals_to_upgrade
_______________________________________

This is not seen in the run where the promise was skipped.

Now let's look at the output where it didn't;t run :

_______________
f3>    =========================================================
cf3>    commands in bundle check_upgrade_target_list (3)
cf3>    =========================================================
cf3> 
cf3> 
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> Skipping whole next promise (/bin/egrep -q "^ *(ALL|All|all$) *|^ *23450 *,?|, *23450 *,|, *23450 *$" /var/lib/cfengine3/inputs/config/tram/vehicles_to_upgrade), as context have_tram_vehicles_to_upgrade_file.mode_TRAM is not relevant
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> 
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> Skipping whole next promise (/bin/egrep -q "^ *(ALL|All|all$) *|^ *23450 *,?|, *23450 *,|, *23450 *$" /var/lib/cfengine3/inputs/config/bus/vehicles_to_upgrade), as context have_bus_vehicles_to_upgrade_file.mode_BUS is not relevant
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> 
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> Skipping whole next promise (/bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/gac/terminals_to_upgrade), as context have_gac_terminals_to_upgrade_file.(terminal_GAC|terminal_gacreader) is not relevant
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> 
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> Skipping whole next promise (/bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/egk/terminals_to_upgrade), as context have_egk_terminals_to_upgrade_file.terminal_EGK is not relevant
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> 
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> Skipping whole next promise (/bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/fpdg/terminals_to_upgrade), as context have_fpdg_terminals_to_upgrade_file.terminal_FPDg is not relevant
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> 
cf3> . . . . . . . . . . . . . . . . . . . . . . . . . . . .
cf3> Skipping whole next promise (/bin/egrep -q "^ *(ALL|All|all$) *|^ *92100942 *,?|, *92100942 *,|, *92100942 *$" /var/lib/cfengine3/inputs/config/sem/terminals_to_upgrade), as context have_sem_terminals_to_upgrade_file.terminal_SEM is not relevant
_____________________

The skipped promise for terminal_FPDs occurs in the policy file sandwiched between the terminal_FPDg above it and terminal_SEM following it :

______________________
      have_fpdg_terminals_to_upgrade_file.terminal_FPDg::     
         "/bin/egrep -q \"^ *(ALL|All|all$) *|^ *$(host_groups.terminal_id) *,?|, *$(host_groups.terminal_id) *,|, *$(host_groups.terminal_
id) *$\" $(upgrade_specification.fpdg_terminals_to_upgrade_path)"      
        classes => if_selected_for_upgrade;
      
      have_fpds_terminals_to_upgrade_file.terminal_FPDs::       
         "/bin/egrep -q \"^ *(ALL|All|all$) *|^ *$(host_groups.terminal_id) *,?|, *$(host_groups.terminal_id) *,|, *$(host_groups.terminal_
id) *$\" $(upgrade_specification.fpds_terminals_to_upgrade_path)"      
        classes => if_selected_for_upgrade;  
 
      have_sem_terminals_to_upgrade_file.terminal_SEM::     
         "/bin/egrep -q \"^ *(ALL|All|all$) *|^ *$(host_groups.terminal_id) *,?|, *$(host_groups.terminal_id) *,|, *$(host_groups.terminal_
id) *$\" $(upgrade_specification.sem_terminals_to_upgrade_path)"     
        classes => if_selected_for_upgrade;
_____________________________

I suspect that the command was run in the previous agent run and skipped in the next one, implying some sort of caching ?





Ramakant Duggal
Senior Developer



 

0422156849
 
VIXTECHNOLOGY.COM

promise_not_run_cf_fpdg_92100941__1480918825_Mon_Dec__5_17_20_25_2016_407cf490.gz
promise_run_cf_fpdg_92100941__1480917666_Mon_Dec__5_17_01_06_2016_407cf490.gz

nick.a...@cfengine.com

unread,
Dec 5, 2016, 2:38:58 AM12/5/16
to Aleksey Tsalolikhin, Ramakant Duggal, help-cfengine
First thought is promise locking. By default all promises are locked for 1 minute. This might cause issues if your agent run times are long or you are executing the agent multiple times in quick succession.

You can override this per promise with an action body (look for immediate in the stdlib) or for all promises with -K.

Hope this helps!

Sent from my android device.
To unsubscribe from this group and stop receiving emails from it, send an email to help-cfengin...@googlegroups.com.

Ramakant Duggal

unread,
Dec 5, 2016, 2:56:26 AM12/5/16
to Nick Anderson, Aleksey Tsalolikhin, help-cfengine
Thanks Nick. 

Is there a time delay between completion of a promise (or completion of all bundles in a scheduled run) and release of promise level locks ?




Ramakant Duggal
Senior Developer



 

0422156849
 
VIXTECHNOLOGY.COM


Ramakant Duggal

unread,
Dec 5, 2016, 6:32:11 AM12/5/16
to Nick Anderson, Aleksey Tsalolikhin, help-cfengine
Nick,

Using immediate ensures the promise runs each scheduled run, need to look at side effects rather than willy nilly use it on every promise, what do you guys think ?

I was assuming the promise level locks are released when cf-agent exits ?

Cheers

Rama





Ramakant Duggal
Senior Developer



 

0422156849
 
VIXTECHNOLOGY.COM


Aleksey Tsalolikhin

unread,
Dec 5, 2016, 10:51:15 AM12/5/16
to Ramakant Duggal, help-cfengine

Thank you,. Your promises are getting skipped because contexts they target don't match the current context.  In other words, the class you are expecting isn't set.

It's not an issue of the command is not going to be run again because it's been run recently-- I don't see anything in your verbose logs that would indicate that.

Also, cfengine does not cache command output inherently.

I recommend you take a closer look at what's happening with your classes.

If you study the entire verbose log you should be able to trace where classes are and aren't getting set.

Ramakant Duggal

unread,
Dec 5, 2016, 4:10:37 PM12/5/16
to Aleksey Tsalolikhin, help-cfengine
Aleksey,

Thanks for your reply, and for making me check the verbose outputs again. It is common to make such mistakes. 

But in this case, teh command promise SHOULD run every time. The classes on which the command promise is to run are the same in each run, they are based on presence & content of certain configuration files that are present and have the same content during the time interval spanning the two consecutive agent runs that produce a run and a skip of the command promise. As I pointed out in previous post, the 'grep -q' command promise is not even listed in the verbose out put when it is skipped as being skipped due to context not being applicable. 

Once "action => immediate" was added to this command promise last night, didn't get a single instance of that promise being skipped, which is proof enough that a skip based on locks is what was happening.

________________________________
# grep 'R: I am not selected for upgrade' *Dec__6_*
# grep 'R: I am not selected for upgrade' *Dec__5_*
cf_fpdg_92100941__1480856458_Mon_Dec__5_00_00_58_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480858838_Mon_Dec__5_00_40_38_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480860058_Mon_Dec__5_01_00_58_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480862438_Mon_Dec__5_01_40_38_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480864818_Mon_Dec__5_02_20_18_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480867258_Mon_Dec__5_03_00_58_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480869639_Mon_Dec__5_03_40_39_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480870859_Mon_Dec__5_04_00_59_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480873240_Mon_Dec__5_04_40_40_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480875620_Mon_Dec__5_05_20_20_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480876839_Mon_Dec__5_05_40_39_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480878059_Mon_Dec__5_06_00_59_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480880439_Mon_Dec__5_06_40_39_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480882819_Mon_Dec__5_07_20_19_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480884039_Mon_Dec__5_07_40_39_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480886419_Mon_Dec__5_08_20_19_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480888860_Mon_Dec__5_09_01_00_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480891239_Mon_Dec__5_09_40_39_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480892459_Mon_Dec__5_10_00_59_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480894839_Mon_Dec__5_10_40_39_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480896059_Mon_Dec__5_11_00_59_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480898440_Mon_Dec__5_11_40_40_2016_407cf490:R: I am not selected for upgrade. My entry point id is 23450
cf_fpdg_92100941__1480900820_Mon_Dec__5_12_20_20_2016_407cf490:R: I am not selected for upgrade. My terminal id is 92100942
cf_fpdg_92100941__1480904420_Mon_Dec__5_13_20_20_2016_407cf490:R: I am not selected for upgrade. My terminal id is 92100942
cf_fpdg_92100941__1480906861_Mon_Dec__5_14_01_01_2016_407cf490:cf3> R: I am not selected for upgrade. My terminal id is 92100942
cf_fpdg_92100941__1480910465_Mon_Dec__5_15_01_05_2016_407cf490:cf3> R: I am not selected for upgrade. My terminal id is 92100942
cf_fpdg_92100941__1480915225_Mon_Dec__5_16_20_25_2016_407cf490:cf3> R: I am not selected for upgrade. My terminal id is 92100942
cf_fpdg_92100941__1480922425_Mon_Dec__5_18_20_25_2016_407cf490:cf3> R: I am not selected for upgrade. My terminal id is 92100942
__________________________________________________________________

All logs after the change show the command promise being run, as verified from the verbose logs.

If this is indeed the case, what worries me is that the locks appear to last beyond the duration of the cf-agent run.

The question to ask now is :

Is it correct to infer that agent run completes around the time of the timestamp on each output log  for that run ? Should one therefore expect all promise level lokcs to be released at that time ?

If not, then how does one arrive at a more predictable run duration ? 



Ramakant Duggal
Senior Developer



 

0422156849
 
VIXTECHNOLOGY.COM


On Tue, Dec 6, 2016 at 2:51 AM, Aleksey Tsalolikhin <ale...@verticalsysadmin.com> wrote:

Thank you,. Your promises are getting skipped because contexts they target don't match the current context.  In other words, the class you are expecting isn't set.

It's not an issue of the command is not going to be run again because it's been run recently-- I don't see anything in your verbose logs that would indicate that.

Also, cfengine does not cache command output inherently.

I recommend you take a closer look at what's happening with your classes.

If you study the entire verbose log you should be able to trace where classes are and aren't getting set.

mike.w...@verticalsysadmin.com

unread,
Dec 5, 2016, 5:09:58 PM12/5/16
to help-cfengine
As for the last run time and run duration, in the "cf-info" script I use for quick diagnostics on CFEngine, I wrote the following command:

    awk -F '[:,]' -v time="$(date +%s)" 'END {printf "Last cf-agent run started %d minutes ago and lasted %d seconds\n", (time - $1)/60, $2 - $1}' /var/cfengine/promise_summary.log

You might find that useful for checking cf-agent runtimes.

------

As for the fancy "grep" command, I want to mention that the CFEngine policy language is really not well designed for text parsing.  For declaring configuration states, yes; for text parsing, not so much.

I had great success in improving the performance and understandability of a complex CFEngine policy with lots of text processing (where the whole end product was merely specific vars and classes being defined) by replacing the whole thing with a single "commands" promise, calling Sed, and using CFEngine module protocol to define the vars and classes that I needed.

For example, rather than using a combination of "readstringarray()" and complex iteration within the CFEngine policy language for DNS nameserver inventory, the following is sufficient:

        commands:

          any::

            "/bin/sed -f $(this.promise_dirname)/nameservers.sed /etc/resolv.conf"

              classes => kept_successful_command,

              module => "true";


Where "nameservers.sed" contains:


    #!/bin/sed -f

    1i\

    ^context=inventory_module

    1i\

    ^meta=inventory,attribute_name=Nameservers

    /^nameserver[[:space:]]*/!d

    s///

    s/[[:space:]]*$//

    s/.*/=nameserver[&]=&/


One nice thing about this commands promise is that you can run the script entirely independently of CFEngine to see the module protocol output, which CFEngine will be reading in.


Seeing that long "grep -q" commands promise in your code, I am entirely confident that something similar could be done with your policy (using Sed, or perhaps Awk if really necessary), with an improvement in performance as well as making it easier to reason about the promises correctly.


Best,

--Mike Weilgart

Vertical Sysadmin, Inc.

Ramakant Duggal

unread,
Dec 5, 2016, 7:31:20 PM12/5/16
to mike.w...@verticalsysadmin.com, help-cfengine
Hi Mike,

Thanks for the useful suggestions in your post. Modules would be the way to for for the reasons you gave. 

Also I did some read-up on ifelapsed. Setting it to 0 for a promise may not have the side effects that I was worried about. My understanding is that even with ifelapsed set to 0 for a given promise, there will never be 2 agents executing that promise simultaneously due to the promise being locked. I guess the ifelapsed serves to avoid a promise being run repeatedly in quick succession ? That implies there would be a default global value for ifelapsed which would explain the promise being skipped in this case. 

I couldn't find what  the default global value is in online docco. Any ideas ?

Cheers

Rama



Ramakant Duggal
Senior Developer



 

0422156849
 
VIXTECHNOLOGY.COM


--
You received this message because you are subscribed to a topic in the Google Groups "help-cfengine" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/help-cfengine/qQdZUSFwO6c/unsubscribe.
To unsubscribe from this group and all its topics, send an email to help-cfengine+unsubscribe@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.

Aleksey Tsalolikhin

unread,
Dec 5, 2016, 11:51:01 PM12/5/16
to Ramakant Duggal, help-cfengine, mike.w...@verticalsysadmin.com
I seem to recall that the default value for ifelapsed is 1 minute.

Well done, Rama.

I'd love to know how to inspect the locks database. I seem to remember it's on disk.

Sorry, I am on the go or else I would provide more detail.

On Dec 5, 2016 4:31 PM, "Ramakant Duggal" <ramakan...@vixtechnology.com> wrote:
Hi Mike,

Thanks for the useful suggestions in your post. Modules would be the way to for for the reasons you gave. 

Also I did some read-up on ifelapsed. Setting it to 0 for a promise may not have the side effects that I was worried about. My understanding is that even with ifelapsed set to 0 for a given promise, there will never be 2 agents executing that promise simultaneously due to the promise being locked. I guess the ifelapsed serves to avoid a promise being run repeatedly in quick succession ? That implies there would be a default global value for ifelapsed which would explain the promise being skipped in this case. 

I couldn't find what  the default global value is in online docco. Any ideas ?

Cheers

Rama



Ramakant Duggal
Senior Developer



 

0422156849
 
VIXTECHNOLOGY.COM


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-cfengine+unsubscribe@googlegroups.com.

Aleksey Tsalolikhin

unread,
Dec 6, 2016, 12:20:00 AM12/6/16
to Ramakant Duggal, mike.w...@verticalsysadmin.com, help-cfengine
On Dec 5, 2016 8:50 PM, "Aleksey Tsalolikhin" <ale...@verticalsysadmin.com> wrote:
I seem to recall that the default value for ifelapsed is 1 minute.

Well done, Rama.

I'd love to know how to inspect the locks database. I seem to remember it's on disk.

Sorry, I am on the go or else I would provide more detail.

Ramakant Duggal

unread,
Dec 6, 2016, 6:19:08 AM12/6/16
to Aleksey Tsalolikhin, mike.w...@verticalsysadmin.com, help-cfengine
Thank you Aleksey.

Resource spam protection sounds like a good thing to have.

Going back to Nick's post, "By default all promises are locked for 1 minute." and also, "override this per promise with an action body (look for immediate in the stdlib) or for all promises with -K". Makes sense.

We do have a report promise that has dependency on the "am_selected_for_upgrade" class, apparently that is not enough to force a re-check. I gather if I am to override locking with if elapsed of 0, any downstream dependencies should either be tolerant of simultaneous multiple instances or protect themselves against multiple instances, using own locks.

Hence organizing workflows in cfe policies is not a great idea. Perhaps I should convey that message (to someone who listens). :-(


Ramakant Duggal
Senior Developer



 

0422156849
 
VIXTECHNOLOGY.COM


Aleksey Tsalolikhin

unread,
Dec 6, 2016, 7:42:45 AM12/6/16
to Ramakant Duggal, help-cfengine, mike.w...@verticalsysadmin.com
I understand. 

Cfengine is a declarative language so organizing workflows, while possible, is counter to the language purpose.
Reply all
Reply to author
Forward
0 new messages