Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update variables from PRE_COMMANDS / POST_COMMANDS #3861

Merged
merged 9 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .automation/test/pre-post-test/.mega-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ PRE_COMMANDS:
cwd: "workspace"
- command: pip install flake8-cognitive-complexity
venv: flake8
- command: export MY_OUTPUT_VARIABLE="my output variable value" && export MY_OUTPUT_VARIABLE2="my output variable value2"
output_variables: ["MY_OUTPUT_VARIABLE", "MY_OUTPUT_VARIABLE2"]
cwd: "root"
POST_COMMANDS:
- command: npm run test
cwd: "workspace"
MARKDOWN_PRE_COMMANDS:
- command: echo 'descriptor pre-command has been run'
cwd: "root"
- command: export MY_OUTPUT_LINTER_VARIABLE="my output linter variable value" && export MY_OUTPUT_LINTER_VARIABLE2="my output linter variable value2"
output_variables: ["MY_OUTPUT_LINTER_VARIABLE", "MY_OUTPUT_LINTER_VARIABLE2"]
cwd: "root"
MARKDOWN_POST_COMMANDS:
- command: echo 'descriptor post-command has been run'
cwd: "root"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Note: Can be used with `oxsecurity/megalinter@beta` in your GitHub Action mega-l
- Core
- Allow to override CLI_LINT_MODE when defined as project
- Allow to use absolute paths for LINTER_RULES_PATH
- Allow to update variables from [PRE/POST Commands](https://megalinter.io/latest/config-precommands/) using `output_variables` property

- Media
- [MegaLinter: un linter pour les gouverner tous](https://blog.wescale.fr/megalinter-un-linter-pour-les-gouverner-tous) (FR), by [Guillaume Arnaud](https://www.linkedin.com/in/guillaume-arnaud/) from [WeScale](https://www.wescale.fr/)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,8 @@ PRE_COMMANDS:
continue_if_failed: False # Will stop the process if command is failed (return code > 0)
- command: pip install flake8-cognitive-complexity
venv: flake8 # Will be run within flake8 python virtualenv. There is one virtualenv per python-based linter, with the same name
- command: export MY_OUTPUT_VAR="my output var" && export MY_OUTPUT_VAR2="my output var2"
output_variables: ["MY_OUTPUT_VAR","MY_OUTPUT_VAR2"] # Will collect the values of output variables and update MegaLinter own ENV context
```

<!-- config-precommands-section-end -->
Expand Down
7 changes: 5 additions & 2 deletions megalinter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import yaml

RUN_CONFIGS = {} # type: ignore[var-annotated]
SKIP_DELETE_CONFIG = False


def init_config(request_id, workspace=None, params={}):
Expand Down Expand Up @@ -264,13 +265,15 @@ def copy(request_id):

def delete(request_id=None, key=None):
global RUN_CONFIGS
global SKIP_DELETE_CONFIG
# Global delete (used for tests)
if request_id is None:
RUN_CONFIGS = {}
return
if key is None:
del RUN_CONFIGS[request_id]
logging.debug("Cleared MegaLinter runtime config for request " + request_id)
if SKIP_DELETE_CONFIG is not True:
del RUN_CONFIGS[request_id]
logging.debug("Cleared MegaLinter runtime config for request " + request_id)
return
config = get_config(request_id)
if key in config:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"type": "string"
},
"continue_if_failed": {
"Description": "If command fails, continue MegaLinter process or not",
"description": "If command fails, continue MegaLinter process or not",
"default": true,
"title": "Continue if failed",
"type": "boolean"
Expand All @@ -31,6 +31,27 @@
],
"title": "Folder where to run the command",
"type": "string"
},
"output_variables": {
"description": "ENV variables to get from output after running the commands, and store in MegaLinter ENV context",
"title": "Output ENV variables",
"items": {
"type": "string"
},
"type": "array"
},
"secured_env": {
"description": "Apply filter of secured env variables before calling the command (default true). Be careful if you disable it.",
"default": true,
"title": "Secured ENV variables",
"type": "boolean"
},
"venv": {
"examples": [
"flake8"
],
"title": "Name of the python venv to run the command in.",
"type": "string"
}
},
"required": [],
Expand Down
32 changes: 32 additions & 0 deletions megalinter/pre_post_factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Class to manage MegaLinter plugins
import logging
import os
import re
import shutil
import subprocess
import sys
Expand Down Expand Up @@ -111,6 +112,31 @@ def run_command(command_info, log_key, mega_linter, linter=None):
raise Exception(
f"{log_key}: User command failed, stop running MegaLinter\n{return_stdout}"
)
# Get output variables if defined
if command_info.get("output_variables", None) is not None:
for output_variable in command_info["output_variables"]:
regex_start = f"output of {output_variable}:"
if regex_start in return_stdout:
match = re.search(rf"{regex_start}(.+)", return_stdout)
if match:
output_variable_value = match.group(1).strip()
if (
config.get(mega_linter.request_id, output_variable, None)
!= output_variable_value
):
config.set_value(
mega_linter.request_id,
output_variable,
output_variable_value,
)
add_in_logs(
linter,
log_key,
[
f"{log_key} updated ENV var {output_variable}] from command output"
],
)

return {
"command_info": command_info,
"status": return_code,
Expand All @@ -129,6 +155,12 @@ def complete_command(command_info: dict):
command_info["command"] = (
f"cd /venvs/{venv} && source bin/activate && {command} && deactivate"
)
# Handle output vars if present
if command_info.get("output_variables", None) is not None:
for output_variable in command_info["output_variables"]:
command_info[
"command"
] += f' && echo "output of {output_variable}:${output_variable}"'
return command_info


Expand Down
25 changes: 24 additions & 1 deletion megalinter/tests/test_megalinter/pre_post_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import unittest
import uuid

from megalinter import utilstest
from megalinter import config, utilstest


class PrePostTest(unittest.TestCase):
Expand All @@ -25,6 +25,7 @@ def setUp(self):
)

def test_pre_post_success(self):
config.SKIP_DELETE_CONFIG = True
mega_linter, output = utilstest.call_mega_linter(
{
"MULTI_STATUS": "false",
Expand All @@ -42,3 +43,25 @@ def test_pre_post_success(self):
self.assertIn("descriptor post-command has been run", output)
self.assertIn("linter pre-command has been run", output)
self.assertIn("linter post-command has been run", output)
self.assertTrue(
config.get(self.request_id, "MY_OUTPUT_VARIABLE", "")
== "my output variable value",
"MY_OUTPUT_VARIABLE should be found",
)
self.assertTrue(
config.get(self.request_id, "MY_OUTPUT_VARIABLE2", "")
== "my output variable value2",
"MY_OUTPUT_VARIABLE2 should be found",
)
self.assertTrue(
config.get(self.request_id, "MY_OUTPUT_LINTER_VARIABLE", "")
== "my output linter variable value",
"MY_OUTPUT_LINTER_VARIABLE should be found",
)
self.assertTrue(
config.get(self.request_id, "MY_OUTPUT_LINTER_VARIABLE2", "")
== "my output linter variable value2",
"MY_OUTPUT_LINTER_VARIABLE2 should be found",
)
config.SKIP_DELETE_CONFIG = False
config.delete(self.request_id)