Skip to content

ENT-14194: Run policy on event - #6362

Open
victormlg wants to merge 4 commits into
cfengine:masterfrom
victormlg:ENT-14463-part-3
Open

victormlg wants to merge 4 commits into
cfengine:masterfrom
victormlg:ENT-14463-part-3

Conversation

@victormlg

@victormlg victormlg commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@victormlg victormlg changed the title ENT-14463: Run policy on event ENT-14194: Run policy on event Sep 24, 2026
@victormlg
victormlg force-pushed the ENT-14463-part-3 branch 2 times, most recently from b7d8916 to 33ee80a Compare September 29, 2026 11:26
VarRefDestroy(ref);
}

static PromiseResult KeepAgentPromise(EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param)
Comment thread cf-agent/agent_operations.c Fixed
Comment thread cf-agent/agent_operations.c Fixed
Comment thread cf-agent/agent_operations.c Fixed
Comment thread cf-agent/agent_operations.c Dismissed
Comment thread cf-agent/agent_operations.c Fixed
Comment thread cf-agent/agent_operations.c Fixed
Comment thread cf-agent/agent_operations.c Fixed
Comment thread cf-agent/agent_operations.c Fixed
Comment thread libpromises/eval_context.c Fixed
Comment thread cf-reactor/reactor_transform.c Fixed
Comment thread cf-reactor/reactor_transform.c Fixed
Comment thread cf-reactor/reactor_transform.c Fixed
@victormlg
victormlg force-pushed the ENT-14463-part-3 branch 3 times, most recently from f98bd05 to 36fa38f Compare September 30, 2026 15:11
Signed-off-by: Victor Moene <victor.moene@northern.tech>
@victormlg
victormlg marked this pull request as ready for review September 30, 2026 15:18
Comment thread cf-reactor/reactor_transform.c Fixed
Comment thread cf-reactor/reactor_transform.c Fixed
Comment thread cf-reactor/reactor_transform.c Fixed

@larsewi larsewi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please do a review with Claude. For me it found a few things that seem plausible:

  1. A remote copy_from in a then bundle crashes cf-reactor
  2. State builds up across repeated runs of the same bundle
  3. The EvalAborted branch is dead code

Comment thread cf-reactor/reactor_transform.c
Comment thread cf-reactor/reactor_transform.c Outdated
Comment thread cf-reactor/reactor_transform.c Outdated
@victormlg
victormlg force-pushed the ENT-14463-part-3 branch 2 times, most recently from 1bb39a3 to 5a8c477 Compare October 1, 2026 18:13
Comment thread cf-reactor/reactor_transform.c Fixed

@craigcomstock craigcomstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks good generally

Comment thread cf-agent/agent_operations.c
Comment thread cf-agent/agent_operations.c
Comment thread cf-reactor/reactor_transform.c
Comment thread cf-reactor/reactor_transform.c Outdated
Comment thread cf-reactor/reactor_transform.c Outdated
Comment thread cf-reactor/watcher.c Outdated
Comment thread cf-reactor/reactor_transform.c Outdated
Comment thread tests/asan-check/Makefile Outdated
Comment thread cf-reactor/reactor_transform.c Outdated
Comment thread libpromises/attributes.c Outdated
Better concurrency for cf-reactor static state
- No need for map anymore, since watcher keeps track of the promise
- No need to keep key as variable anymore, since we do not use a map
- No need to run bundle inside watcher.c
- No risk of running a bundle from a wrong key between policy reads

Signed-off-by: Victor Moene <victor.moene@northern.tech>
Promises are skipped to prevent running them several times. However, on event, we want to run them every single time:
- we clear the promise lock cache
- we set the default if_elapsed time for bundles run from an events promise to be 0, so it doesn't skip the promises.

Fixed also connection cache and custom promise prologue and epilogue. Clear function cache before "then" bundle run

Signed-off-by: Victor Moene <victor.moene@northern.tech>
Signed-off-by: Victor Moene <victor.moene@northern.tech>
@olehermanse

Copy link
Copy Markdown
Member

@cf-bottom jenkins, please

@olehermanse
olehermanse self-requested a review October 5, 2026 12:37
@cf-bottom

Copy link
Copy Markdown

@victormlg

victormlg commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Sure, I triggered a build:

Build Status

Jenkins: https://ci.cfengine.com/job/pr-pipeline/14750/

Packages: http://buildcache.cfengine.com/packages/testing-pr/jenkins-pr-pipeline-14750/

Added a fix for the windows failure in enterprise. Restarted it Build Status

@craigcomstock

Copy link
Copy Markdown
Contributor

@victormlg for a rebuild in jenkins, it would be nice if we updated the PR with the links... so here I will do it manually:

Build Status

Jenkins: https://ci.cfengine.com/job/pr-pipeline/14753/

Packages: http://buildcache.cfengine.com/packages/testing-pr/jenkins-pr-pipeline-14753/

included file COSL.txt.
*/

/* ScheduleAgentOperations() and the promise actuator it uses: this is what

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe say something more clear here like "This file, agent_operations.c, implements ScheduleAgentOperations() and ..."

* AGENT_TYPESEQUENCE order, keeping each one with KeepAgentPromise()). It is
* kept separate from cf-agent.c so that other components (e.g. cf-reactor,
* running the bundle named in an events promise's "then") can run a bundle
* the same way cf-agent does, without linking cf-agent's main(). */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe refer to a follow-up/todo ticket to refactor/move this code to libpromises.

Comment on lines +67 to +70
int CFA_BACKGROUND = 0; /* GLOBAL_X */
int CFA_BACKGROUND_LIMIT = 1; /* GLOBAL_P */

Item *PROCESSREFRESH = NULL; /* GLOBAL_P */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what do these comments indicate? We might want to include a reference to GLOBALS file as well in each of these comments, even expanding them to GLOBAL_P is set during policy parsing and is OK where-as GLOBAL_X is unclassified and dealt with on a case-by-case basis and so here we should describe the case/reason why this global is OK.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The header file explains does an OK job of explaining the GLOBAL_P globals:

extern int CFA_BACKGROUND;              /* GLOBAL_X */
extern int CFA_BACKGROUND_LIMIT;        /* GLOBAL_P, body agent control: max_children */
extern Item *PROCESSREFRESH;            /* GLOBAL_P, body agent control: refresh_processes */

I see that CFA_BACKGROUND is a counter and is involved primarily in the function ParallelFindAndVerifyFilesPromises() and also referenced in cf-agent.c for a call to EndAudit(ctx, CFA_BACKGROUND) which then calls LogTotalCompliance(version, background_tasks (the value of CFA_BACKGROUND sent in EndAudit()).

LogTotalCompliance() is an enterprise only feature.

The introduction of CFA_BACKGROUND was for "protection against overt use of parallelism" in this commit from Mark in 2009. fd38443

CFA is a short term for cf-agent I believe since in cf3.defs.h in the above commit we also have cfex_ prefix for cf-execd enums.

I wasn't aware of this low-level implementation detail yet and wonder if it is exercised or not.

I think it would be helpful to come back with another commit that adds annotations to this code explaining a few things that we find during review.

return DefaultVarPromise(ctx, pp);
}

PromiseResult ScheduleAgentOperations(EvalContext *ctx, const Bundle *bp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since this is the crux of this file I think this function deserves some explanation of what it does and such.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see you have a comment above at the top, that's probably good. Maybe a comment here would include some sort of "API" like doc tags and such since it is expected to be used in multiple places now: cf-agent and cf-reactor.

*/

/* ScheduleAgentOperations() and the promise actuator it uses: this is what
* cf-agent does with a single bundle (evaluate its promises in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe "this is the function that cf-agent uses to evaluate a single bundle and it's promises in AGENT_TYPESEQUENCE order" or similar... as a way of making it clear what this function does with less "technical/internal" language like "promise actuator".

}

PromiseResult ScheduleAgentOperations(EvalContext *ctx, const Bundle *bp)
// NB - this function can be called recursively through "methods"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

a follow up tidy commit might change NB to just NOTE: I assume this NB means nota bene, latin for note well or pay close attention.

int save_pr_notkept = PR_NOTKEPT;
struct timespec start = BeginMeasure();

if (PROCESSREFRESH == NULL || (PROCESSREFRESH && IsRegexItemIn(ctx, PROCESSREFRESH, bp->name)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would be nice to know what manages PROCESSREFRESH global, I see that cf-agent.c does and there is a bit of TODO in that code in KeepControlPromises()

1027                if (strcmp(cp->lval, CFA_CONTROLBODY[AGENT_CONTROL_REFRESH_PROCESSES].lval) == 0)
1028                {
1029                    Log(LOG_LEVEL_VERBOSE, "Setting refresh_processes when starting to...");
1030                    for (const Rlist *rp = value; rp != NULL; rp = rp->next)
1031                    {
1032                        Log(LOG_LEVEL_VERBOSE, "%s", RlistScalarValue(rp));
1033                        // TODO: why is this only done in verbose mode?
1034                        // original commit says 'optimization'.
1035                        if (LogGetGlobalLevel() >= LOG_LEVEL_VERBOSE)
1036                        {
1037                            PrependItem(&PROCESSREFRESH, RlistScalarValue(rp), NULL);
1038                        }
1039                    }
1040                    continue;
1041                }

I see we have a condition of PROCESREFRESH == NULL so no impact here when we call this function from cf-reactor(or other binaries) which will likely not set the control promise to a value.

{
EvalContextStackPopFrame(ctx);
NoteBundleCompliance(bp, save_pr_kept, save_pr_repaired, save_pr_notkept, start);
return result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I notice that custom promise types don't create/delete a "type context" with NewTypeContext() and DeleteTypeContext(). I see that function only does anything for the deprecated environments promise type and the not so often used storage promise type. Would be nice to know why this type context is skipped for custom promise types. It does seem to be mostly a no-op practically speaking.

result = PromiseResultUpdate(result, promise_result);
if (EvalAborted(ctx) || BundleAbort(ctx))
{
EvalContextStackPopFrame(ctx);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here also, we are not calling the "type context" functions. I wonder if we should just remove them entirely or go ahead and include them always. One or the other seems more correct.

Comment thread cf-agent/agent_operations.c
break;
}
default:
/* TODO is CF_DATA_TYPE_NONE acceptable? Today all meta variables

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should ensure a ticket exists and is referenced here for this todo. add in a separate commit, like a batch of tidying i suggested.

@craigcomstock craigcomstock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

a bit more, will continue later today

struct timespec start = BeginMeasure();
PromiseResult result = PROMISE_RESULT_NOOP;

if (strcmp("meta", PromiseGetPromiseType(pp)) == 0 ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this would read easier if we captured the promise type string in a local var.

{
if (!LoadProcessTable())
{
Log(LOG_LEVEL_ERR, "Unable to read the process table - cannot keep processes: type promises");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

seems like it would be better for VerifyProcessesPromise() to handle the proc load function call and return fail

result = VerifyProcessesPromise(ctx, pp);
if (result != PROMISE_RESULT_SKIPPED)
{
EndMeasurePromise(start, pp);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why are we not calling EndMeasurePromise() for the types above here?

result = PROMISE_RESULT_NOOP;
}

BannerStatusEnd(result, PromiseGetPromiseType(pp), pp->promiser);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why not refactor all of the EndMeasurePromise function calls to here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

6 participants