Feature Friday #27: Multiple outcomes

Posted by Nick Anderson
September 13, 2024

When promises are actuated, a class can be defined based on its result. For example, if a promise modifies a file’s content, you could define a class that indicates it has been repaired. However, did you know that promises can have multiple outcomes concurrently?

That’s right! Native promises (but not custom promises) can have multiple outcomes. For example, a promise can be both kept and repaired at the same time. Let’s take a look.

First, let’s create a policy /tmp/feature-friday-27.cf to ensure that a file exists with some specific content and use body classes results from the standard library.

/tmp/feature-friday-27.cf
body file control
{
  inputs => { "$(sys.libdir)/stdlib.cf" };
}
bundle agent __main__
{
  files:
    "/tmp/feature-friday-27.txt"
      create => "true",
      touch => "true",
      classes => results( "bundle", "feature_friday_27" );

  reports:
    "Feature Friday Classes: $(with)"
      with => join( ", ", classesmatching( "feature_friday_27.*" ) );
}

Notice how I’ve used body file control to include the standard library since I am not running this example as part of a larger policy set.1, 2

Next, we have our files promise that explicitly specifies the file should exist with create => "true". Furthermore, it specifies that we want to update the “last modified” timestamp for the file using touch => "true", and that bundle scoped classes prefixed with feature_friday_27 should be defined.3

Finally, we emit a report listing the classes starting with feature_friday_27.

Running this policy the first time:

command
rm -f /tmp/feature-friday-27.txt
cf-agent -KIf /tmp/feature-friday-27.cf
output
    info: Created file '/tmp/feature-friday-27.txt', mode 0600
    info: Touched (updated time stamps) for path '/tmp/feature-friday-27.txt'
R: Feature Friday Classes: feature_friday_27_repaired, feature_friday_27_reached

We can see that two classes were defined, feature_friday_27_repaired and feature_friday_27_reached.

Running the policy again:

command
cf-agent -KIf /tmp/feature-friday-27.cf
output
    info: Touched (updated time stamps) for path '/tmp/feature-friday-27.txt'
R: Feature Friday Classes: feature_friday_27_repaired, feature_friday_27_kept, feature_friday_27_reached

We see that this time, three classes were defined, feature_friday_27_repaired (because the file was touched), feature_friday_27_kept (because the file already existed), and feature_friday_27_reached (which is always defined by the results classes body, no matter the promise-outcome.

It’s even possible to have a promise that is kept, repaired, and not-kept concurrently. For example, we added ownership management:

/tmp/feature-friday-27.cf
body file control
{
  inputs => { "$(sys.libdir)/stdlib.cf" };
}
bundle agent __main__
{
  files:
    "/tmp/feature-friday-27.txt"
      create => "true",
      touch => "true",
      perms => owner( "root" ),
      classes => results( "bundle", "feature_friday_27" );

  reports:
    "Feature Friday classes: $(with)"
      with => join( ", ", classesmatching( "feature_friday_27.*" ) );
}

But, running unprivileged, cf-agent fails to change the ownership. As a result, we end up with classes for kept, repaired, and not kept:

command
cf-agent -KIf /tmp/feature-friday-27.cf
output
    info: Touched (updated time stamps) for path '/tmp/feature-friday-27.txt'
   error: Cannot set ownership on file '/tmp/feature-friday-27.txt'. (chown: Operation not permitted)
    info: Updated timestamps on '/tmp/feature-friday-27.txt'
    info: Touched (updated time stamps) for path '/tmp/feature-friday-27.txt'
   error: Cannot set ownership on file '/tmp/feature-friday-27.txt'. (chown: Operation not permitted)
    info: Updated timestamps on '/tmp/feature-friday-27.txt'
   error: Errors encountered when actuating files promise '/tmp/feature-friday-27.txt'
R: Feature Friday classes: feature_friday_27_denied, feature_friday_27_not_kept, feature_friday_27_error, feature_friday_27_repaired, feature_friday_27_kept, feature_friday_27_reached

In practice, this detail is often not that important. But it is good to understand in cases where you want to construct a class expression that takes these eventualities into consideration.

Happy Friday! 🎉


  1. In case you missed it, check out Feature Friday #09: body file control - inputs↩︎

  2. In case you missed it, check out Feature Friday #25: Unprivileged execution↩︎

  3. Find the implementation and documentation of the results classes body here↩︎