configd - add optional cache_ttl for script_output actions, closes https://github.com/opnsense/core/issues/7515

Add a generic time based cache option for configd sscript action events, example usage:

[action]
command:/path/to/my/command
type:script_output
cache_ttl=5

cache_ttl is specified in seconds after last started execution.
This commit is contained in:
Ad Schellevis 2024-06-06 18:44:43 +02:00
parent 0dc1f2186a
commit 06d507704e
2 changed files with 37 additions and 1 deletions

View File

@ -42,6 +42,10 @@ class BaseAction:
self.parameters = action_parameters.get('parameters', None)
self.message = action_parameters.get('message', None)
self.description = action_parameters.get('description', '')
if action_parameters.get('cache_ttl', '').isdigit():
self.cache_ttl = int(action_parameters['cache_ttl'])
else:
self.cache_ttl = None
self.allowed_groups = set()
for item in action_parameters.get('allowed_groups', '').split(','):
if item:

View File

@ -23,7 +23,12 @@
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import fcntl
import glob
import hashlib
import os
import tempfile
import time
import traceback
import subprocess
from .. import syslog_error
@ -31,16 +36,43 @@ from .base import BaseAction
class Action(BaseAction):
temp_prefix = 'tmpcfd_'
cached_results = None
def execute(self, parameters, message_uuid, *args, **kwargs):
super().execute(parameters, message_uuid, *args, **kwargs)
try:
script_command = self._cmd_builder(parameters)
script_hash = hashlib.sha256(script_command.encode()).hexdigest() if self.cache_ttl else None
except TypeError as e:
return str(e)
if Action.cached_results is None:
# cache cleanup on startup (first executed script_output action)
for filename in glob.glob("%s/%s*"% (tempfile.gettempdir(), Action.temp_prefix)):
os.remove(filename)
Action.cached_results = {}
elif self.cache_ttl is not None and len(Action.cached_results) > 0:
# cache expire
now = time.time()
for key in list(Action.cached_results.keys()):
if Action.cached_results[key]['expire'] < now:
del Action.cached_results[key]
try:
if script_hash in Action.cached_results and os.path.isfile(Action.cached_results[script_hash]['filename']):
with open(Action.cached_results[script_hash]['filename']) as output_stream:
fcntl.flock(output_stream, fcntl.LOCK_EX)
output_stream.seek(0)
return output_stream.read()
with tempfile.NamedTemporaryFile() as error_stream:
with tempfile.NamedTemporaryFile() as output_stream:
tparm = {'prefix': Action.temp_prefix, 'delete': script_hash is None}
with tempfile.NamedTemporaryFile(**tparm) as output_stream:
fcntl.flock(output_stream, fcntl.LOCK_EX)
if script_hash:
Action.cached_results[script_hash] = {
'filename': output_stream.name,
'expire': time.time() + self.cache_ttl
}
subprocess.check_call(script_command, env=self.config_environment, shell=True,
stdout=output_stream, stderr=error_stream)
output_stream.seek(0)