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

feat: add config mode to datadog-setup.php #1951

Merged
merged 47 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fc40344
[PROF-6905] extract `find_ini_files()` function
realFlowControl Mar 1, 2023
57a6966
[PROF-6905] add `config_list` implementation for `config list` command
realFlowControl Mar 10, 2023
1884373
[PROF-6905] add `config_get()` implementation for `config get` command
realFlowControl Mar 10, 2023
0829d9d
[PROF-6905] add `config_set` implementation for `config set` command
realFlowControl Mar 14, 2023
07f0b24
[PROF-6905] use `INI_SCANNER_RAW` mode for `parse_ini_file` as we nee…
realFlowControl Mar 14, 2023
9b96891
[PROF-6905] clenaup
realFlowControl Mar 14, 2023
cb84057
[PROF-6905] fix typo in variable name
realFlowControl Mar 14, 2023
507fff5
[PROF-6905] finish comment for `find_ini_files()` function
realFlowControl Mar 14, 2023
bcd306e
[PROF-6905] clenaup
realFlowControl Mar 14, 2023
c193f84
[PROF-6905] this is horrible, but it works, will optimize later this …
realFlowControl Mar 14, 2023
48a1a42
[PROF-6905] clenaup
realFlowControl Mar 14, 2023
df4da0f
[PROF-6905] clenaup
realFlowControl Mar 14, 2023
14c7c2c
[PROF-6905] clenaup
realFlowControl Mar 14, 2023
bcf02bb
[PROF-6905] fix output format to be INI parseable string
realFlowControl Mar 14, 2023
004fb7f
[PROF-6905] normalize `-d` argument
realFlowControl Mar 14, 2023
29d89bc
style: fix lint
morrisonlevi Mar 15, 2023
bc66682
[PROF-6905] optimize cli parsing
realFlowControl Mar 20, 2023
832100e
[PROF-6905] fix style
realFlowControl Mar 20, 2023
7efa099
help me CI, you are my last hope!
realFlowControl Mar 20, 2023
0e2d75f
[PROF-6905] add special case handling `--option=value:`
realFlowControl Mar 20, 2023
a6c924c
Merge branch 'master' into florian/PROF-6905-add-config-mode
realFlowControl Mar 20, 2023
9e12902
WIP refactoring
morrisonlevi Mar 21, 2023
72faab3
Merge branch 'master' into florian/PROF-6905-add-config-mode
realFlowControl Mar 22, 2023
e6b262b
add support for commented out ini settings in `config set` command
realFlowControl Mar 23, 2023
b40e41f
WIP: moar stuff, but still stuff todo
realFlowControl Mar 23, 2023
3b69c6a
fix style
realFlowControl Mar 24, 2023
d5f39de
Merge branch 'master' into florian/PROF-6905-add-config-mode
realFlowControl Mar 24, 2023
38204bb
add tests
realFlowControl Mar 24, 2023
d98dd21
fix lint
realFlowControl Mar 24, 2023
bd64267
add documentation
realFlowControl Mar 24, 2023
a20a301
remove error handling as our own parser fixes this
realFlowControl Mar 24, 2023
fa0ced4
fix lint
realFlowControl Mar 24, 2023
a127761
fix lint
realFlowControl Mar 24, 2023
c56cd69
WIP
realFlowControl Mar 26, 2023
ddbd1ee
error out on parsing errors in CLI
realFlowControl Mar 28, 2023
3eb5d77
remove whitespace to please linter
realFlowControl Mar 29, 2023
91fa4f0
handle missing --php-bin value
realFlowControl Mar 29, 2023
3733099
WIP
realFlowControl Mar 29, 2023
8a915de
Merge branch 'master' into florian/PROF-6905-add-config-mode
realFlowControl Apr 6, 2023
1ee5af9
cleanup
realFlowControl Apr 6, 2023
bde1176
optimised `find_all_ini_files()`
realFlowControl Apr 6, 2023
95e942b
Handle more edge cases
morrisonlevi Apr 14, 2023
caad1ce
fix typo
realFlowControl Apr 19, 2023
b1e791a
Merge branch 'master' into florian/PROF-6905-add-config-mode
realFlowControl Apr 19, 2023
ea51e18
extract line print for `IniRecord` into method and "fix" PHP binary
realFlowControl Apr 19, 2023
045371b
fix lint
realFlowControl Apr 19, 2023
02577f3
Merge branch 'master' into florian/PROF-6905-add-config-mode
realFlowControl Apr 27, 2023
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
343 changes: 309 additions & 34 deletions datadog-setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,182 @@ function print_help()
EOD;
}

/**
* This function will print out all Datadog specific PHP INI settings. The list
* of Datadog specific settings is retrieved by calling `get_ini_settings()`. It
* will also show the default and INI file this setting was found.
* The output will be grouped by PHP binary, example:
*
* $ php datadog-setup.php config list --php-bin all
* Searching for available php binaries, this operation might take a while.
* Datadog configuration for binary: php (/opt/php/8.2/bin/php)
* datadog.profiling.enabled => On // default: 1, INI file: /opt/php/etc/conf.d/98-ddtrace.ini
* datadog.profiling.experimental_allocation_enabled => On // default: 1, INI file: /opt/php/etc/conf.d/98-ddtrace.ini
realFlowControl marked this conversation as resolved.
Show resolved Hide resolved
*
* @see get_ini_settings
*/
function config_list(array $options): void
{
$selectedBinaries = require_binaries_or_exit($options);
$iniSettings = get_ini_settings('', '', '');
foreach ($selectedBinaries as $command => $fullPath) {
$binaryForLog = ($command === $fullPath) ? $fullPath : "$command ($fullPath)";
echo "Datadog configuration for binary: $binaryForLog", PHP_EOL;

$iniFilePaths = find_ini_files(
ini_values($fullPath)
);

foreach ($iniFilePaths as $iniFilePath) {
$iniFileSettings = parse_ini_file($iniFilePath);
foreach ($iniFileSettings as $iniFileSetting => $currentValue) {
foreach ($iniSettings as $iniSetting) {
if ($iniSetting['name'] !== $iniFileSetting) {
continue;
}
echo $iniSetting['name'], ' => ', $currentValue, ' // default: ',
$iniSetting['default'], ', INI file: ', $iniFilePath, PHP_EOL;
}
}
}
}
}

/**
* This function will print the specified PHP INI settings.
* The output will be grouped by PHP binary, example:
bwoebi marked this conversation as resolved.
Show resolved Hide resolved
*
* $ php datadog-setup.php config get --php-bin all \
* -ddatadog.profiling.experimental_allocation_enabled \
* -ddatadog.profiling.experimental_cpu_enabled \
* -dnonexisting
* Datadog configuration for binary: php (/opt/php/8.2/bin/php)
* datadog.profiling.experimental_allocation_enabled => On // INI file: /opt/php/etc/conf.d/98-ddtrace.ini
* datadog.profiling.experimental_cpu_enabled => On // INI file: /opt/php/etc/conf.d/98-ddtrace.ini
* nonexisting => undefined // is missing in INI files
*/
function config_get(array $options): void
realFlowControl marked this conversation as resolved.
Show resolved Hide resolved
{
$selectedBinaries = require_binaries_or_exit($options);
foreach ($selectedBinaries as $command => $fullPath) {
$binaryForLog = ($command === $fullPath) ? $fullPath : "$command ($fullPath)";
echo "Datadog configuration for binary: $binaryForLog", PHP_EOL;

$iniFilePaths = find_ini_files(
ini_values($fullPath)
);

if (count($iniFilePaths) === 0) {
echo 'No INI files for this binary found', PHP_EOL;
continue;
}

foreach ($options['d'] as $iniSetting) {
$found = false;
foreach ($iniFilePaths as $iniFilePath) {
$iniFileSettings = parse_ini_file($iniFilePath, false, INI_SCANNER_RAW);
if (array_key_exists($iniSetting, $iniFileSettings)) {
echo $iniSetting, ' => ', $iniFileSettings[$iniSetting], ' // INI file: ', $iniFilePath, PHP_EOL;
$found = true;
}
}
if (!$found) {
echo $iniSetting, ' => undefined // is missing in INI files', PHP_EOL;
}
}
}
}

/**
* This function will set the given INI settings for any given PHP binary
*
* $ php datadog-setup.php config set --php-bin all \
* -ddatadog.profiling.experimental_allocation_enabled=On \
* -ddatadog.profiling.experimental_cpu_enabled=On \
* Setting configuration for binary: php (/opt/php/8.2/bin/php)
* Set 'datadog.profiling.experimental_allocation_enabled' to 'On' in INI file: /opt/php/etc/conf.d/98-ddtrace.ini
* Set 'datadog.profiling.experimental_cpu_enabled' to 'On' in INI file: /opt/php/etc/conf.d/98-ddtrace.ini
*/
function config_set(array $options): void
{
$selectedBinaries = require_binaries_or_exit($options);
foreach ($selectedBinaries as $command => $fullPath) {
$binaryForLog = ($command === $fullPath) ? $fullPath : "$command ($fullPath)";
echo "Setting configuration for binary: $binaryForLog", PHP_EOL;

$phpProps = ini_values($fullPath);
$iniFilePaths = find_ini_files($phpProps);

foreach ($options['d'] as $cliIniSetting) {
// `trim()` should not be needed, but better safe than sorry
$iniSetting = array_map(
'trim',
explode('=', $cliIniSetting, 2)
);
if (count($iniSetting) !== 2) {
echo "The given INI setting '", $cliIniSetting, "' can't be parsed, skipping.", PHP_EOL;
continue;
}

// safety: try out if parsing the generated ini setting is actually
// possible
$newSetting = $iniSetting[0] . ' = ' . $iniSetting[1];
if (parse_ini_string($newSetting, false, INI_SCANNER_RAW) === false) {
echo "The given INI setting '", $cliIniSetting,
"' can't be converted to a valid INI setting, skipping.", PHP_EOL;
}

$found = false;

// search INI file with $iniSetting as it might already exists
// in some INI file
foreach ($iniFilePaths as $iniFile) {
if (!is_file($iniFile)) {
continue;
}
$iniFileContent = file_get_contents($iniFile);
if (preg_match("/^\s*" . preg_quote($iniSetting[0]) . "\s*=\s*/mi", $iniFileContent)) {
// in case we found the ini setting, we break the loop and
// leaf $iniFile and $iniFileContent to be used later
$found = true;
break;
}
}

if ($found) {
// $iniFile has the filename of the INI file and $iniFileContent
// has it's contents as a left over of the `foreach` above
$regex = '/^\s*' . preg_quote($iniSetting[0]) . '\s*=.*$/mi';
$iniFileContent = preg_replace($regex, $newSetting, $iniFileContent);
if ($iniFileContent === null) {
// something wrong with the regex, the user should see a warning
// in the form of "Warning: preg_replace(): Compilation failed ..."
echo "Could not update the given INI setting in file '", $iniFile, "', skipping", PHP_EOL;
}
} else {
// set filename
$iniFilePath = $phpProps[INI_SCANDIR] . '/98-ddtrace.ini';
$iniFileContent = '';
if (is_file($iniFilePath)) {
$iniFileContent = file_get_contents($iniFilePath);
}
// check for "End of Line" symbol at the end of the file and
// add in case it is missing
if (strlen($iniFileContent) > 0 && substr($iniFileContent, -1, 1) !== PHP_EOL) {
$iniFileContent .= PHP_EOL;
}
$iniFileContent .= $newSetting . PHP_EOL;
}
if (file_put_contents($iniFile, $iniFileContent) === false) {
echo "Could not set '", $iniSetting[0], "' to '", $iniSetting[1],
"' in INI file: ", $iniFile , PHP_EOL;
} else {
echo "Set '", $iniSetting[0], "' to '", $iniSetting[1], "' in INI file: ", $iniFile, PHP_EOL;
}
}
}
}

function install($options)
{
$architecture = get_architecture();
Expand Down Expand Up @@ -220,40 +396,7 @@ function install($options)
}
$appSecHelperPath = $installDir . '/bin/ddappsec-helper';

// Writing the ini file
if ($phpProperties[INI_SCANDIR]) {
$iniFileName = '98-ddtrace.ini';
// Search for pre-existing files with extension = ddtrace.so to avoid conflicts
// See issue https://github.com/DataDog/dd-trace-php/issues/1833
foreach (scandir($phpProperties[INI_SCANDIR]) as $ini) {
$path = "{$phpProperties[INI_SCANDIR]}/$ini";
if (is_file($path)) {
// match /path/to/ddtrace.so, plain extension = ddtrace or future extensions like ddtrace.dll
if (preg_match("(^\s*extension\s*=\s*(\S*ddtrace)\b)m", file_get_contents($path), $res)) {
if (basename($res[1]) == "ddtrace") {
$iniFileName = $ini;
}
}
}
}

$iniFilePaths = [$phpProperties[INI_SCANDIR] . '/' . $iniFileName];

if (strpos($phpProperties[INI_SCANDIR], '/cli/conf.d') !== false) {
/* debian based distros have INI folders split by SAPI, in a predefined way:
* - <...>/cli/conf.d <-- we know this from php -i
* - <...>/apache2/conf.d <-- we derive this from relative path
* - <...>/fpm/conf.d <-- we derive this from relative path
*/
$apacheConfd = str_replace('/cli/conf.d', '/apache2/conf.d', $phpProperties[INI_SCANDIR]);
if (is_dir($apacheConfd)) {
$iniFilePaths[] = "$apacheConfd/$iniFileName";
}
}
} else {
$iniFileName = $phpProperties[INI_MAIN];
$iniFilePaths = [$iniFileName];
}
$iniFilePaths = find_ini_files($phpProperties);

foreach ($iniFilePaths as $iniFilePath) {
if (!file_exists($iniFilePath)) {
Expand Down Expand Up @@ -386,6 +529,52 @@ function ($el) {
}
}

/**
* Finds the INI files for the given php properties. Those properties can be
* retrieved by calling `ini_values($pathToPHPBinary)`.
*
* @see ini_values
* @return string[]
*/
function find_ini_files(array $phpProperties): array
{
$iniFilePaths = [];
if ($phpProperties[INI_SCANDIR]) {
morrisonlevi marked this conversation as resolved.
Show resolved Hide resolved
$iniFileName = '98-ddtrace.ini';
// Search for pre-existing files with extension = ddtrace.so to avoid conflicts
// See issue https://github.com/DataDog/dd-trace-php/issues/1833
foreach (scandir($phpProperties[INI_SCANDIR]) as $ini) {
morrisonlevi marked this conversation as resolved.
Show resolved Hide resolved
$path = "{$phpProperties[INI_SCANDIR]}/$ini";
if (is_file($path)) {
// match /path/to/ddtrace.so, plain extension = ddtrace or future extensions like ddtrace.dll
if (preg_match("(^\s*extension\s*=\s*(\S*ddtrace)\b)m", file_get_contents($path), $res)) {
if (basename($res[1]) == "ddtrace") {
$iniFileName = $ini;
}
}
}
}

$iniFilePaths = [$phpProperties[INI_SCANDIR] . '/' . $iniFileName];

if (strpos($phpProperties[INI_SCANDIR], '/cli/conf.d') !== false) {
/* debian based distros have INI folders split by SAPI, in a predefined way:
* - <...>/cli/conf.d <-- we know this from php -i
* - <...>/apache2/conf.d <-- we derive this from relative path
* - <...>/fpm/conf.d <-- we derive this from relative path
*/
$apacheConfd = str_replace('/cli/conf.d', '/apache2/conf.d', $phpProperties[INI_SCANDIR]);
if (is_dir($apacheConfd)) {
$iniFilePaths[] = "$apacheConfd/$iniFileName";
}
}
} else {
$iniFileName = $phpProperties[INI_MAIN];
$iniFilePaths = [$iniFileName];
}
return $iniFilePaths;
}

/**
* Copies an extension's file to a destination using copy+rename to avoid segfault if the file is loaded by php.
*
Expand Down Expand Up @@ -639,12 +828,94 @@ function get_architecture()
);
}

function parse_cli_arguments(array $argv = NULL): array
{
if (is_null($argv)) {
$argv = $_SERVER['argv'];
}

// strip the application name
array_shift($argv);

$arguments = [
'cmd' => NULL,
'opts' => [],
];

while (NULL !== $token = array_shift($argv)) {
if (substr($token, 0, 2) === '--') {
// parse long option
$key = substr($token, 2);
$value = array_shift($argv);
if ($value === null) {
$value = false;
} elseif (substr($value, 0, 1) === '-') {
// another option, but it back onto $argv
array_unshift($argv, $value);
$value = false;
}
if (!isset($arguments['opts'][$key])) {
$arguments['opts'][$key] = $value;
} elseif(is_string($arguments['opts'][$key])) {
$arguments['opts'][$key] = [
$arguments['opts'][$key],
$value,
];
} else {
$arguments['opts'][$key][] = $value;
}
} elseif (substr($token, 0, 1) === '-') {
// parse short option
$key = $token[1];
if (strlen($token) === 2) {
// -d datadog.profiling.enabled or
// -h
$value = array_shift($argv);
if ($value === null) {
$value = false;
} elseif (substr($value, 0, 1) === '-') {
// another option, but it back onto $argv
array_unshift($argv, $value);
$value = false;
}
} else {
// -ddatadog.profiling.enabled
$value = substr($token, 2);
}
if (!isset($arguments['opts'][$key])) {
$arguments['opts'][$key] = $value;
} elseif(is_string($arguments['opts'][$key])) {
$arguments['opts'][$key] = [
$arguments['opts'][$key],
$value,
];
} else {
$arguments['opts'][$key][] = $value;
}
} else {
// parse command
if ($arguments['cmd'] === NULL) {
$arguments['cmd'] = $token;
} else {
$arguments['cmd'] .= ' '.$token;
}
}
}

if ($arguments['cmd'] === NULL) {
$arguments['cmd'] = 'install';
}

return $arguments;
}

/**
* Parses command line options provided by the user and generate a normalized $options array.
* @return array
*/
function parse_validate_user_options()
{
/*
$shortOptions = "h";
$longOptions = [
OPT_HELP,
Expand All @@ -656,6 +927,10 @@ function parse_validate_user_options()
OPT_ENABLE_PROFILING,
];
$options = getopt($shortOptions, $longOptions);
*/

$args = parse_cli_arguments();
$options = $args['opts'];

global $argc;
if ($options === false || (empty($options) && $argc > 1)) {
Expand Down
Loading