Skip to content

Commit 93a779a

Browse files
[sqlserver] Fix odbc.ini config handling for Linux (SDBM-1171) (#18586)
* func for get embed dir * [sqlserver] Fix ODBC config handling for Linux * changelog * linter * test only for Linux * linter * import error * non-atomic getsize * refactoring file test * linter * fix os * fix tests * fix test * test case for odbc.inst creation * linter * linter * fix test_get_unixodbc_sysconfig * test_linux_connection * linter * unused imports * declare test as non windows test * is_non_empty_file always return boolean
1 parent ec07846 commit 93a779a

File tree

3 files changed

+67
-20
lines changed

3 files changed

+67
-20
lines changed

sqlserver/changelog.d/18586.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[sqlserver] Fix ODBC config handling for Linux

sqlserver/datadog_checks/sqlserver/utils.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Licensed under a 3-clause BSD style license (see LICENSE)
44
import os
55
import re
6+
import shutil
67
import sys
78
from typing import Dict
89

@@ -11,6 +12,7 @@
1112

1213
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
1314
DRIVER_CONFIG_DIR = os.path.join(CURRENT_DIR, 'data', 'driver_config')
15+
ODBC_INST_INI = 'odbcinst.ini'
1416

1517

1618
# Database is used to store both the name and physical_database_name
@@ -32,6 +34,22 @@ def __str__(self):
3234
return "name:{}, physical_db_name:{}".format(self.name, self.physical_db_name)
3335

3436

37+
def get_unixodbc_sysconfig(python_executable):
38+
return os.path.join(os.path.dirname(os.path.dirname(python_executable)), "etc")
39+
40+
41+
def is_non_empty_file(path):
42+
if not os.path.exists(path):
43+
return False
44+
try:
45+
if os.path.getsize(path) > 0:
46+
return True
47+
# exists and getsize aren't atomic
48+
except FileNotFoundError:
49+
return False
50+
return False
51+
52+
3553
def set_default_driver_conf():
3654
if Platform.is_containerized():
3755
# Use default `./driver_config/odbcinst.ini` when Agent is running in docker.
@@ -54,13 +72,16 @@ def set_default_driver_conf():
5472
# linux_unixodbc_sysconfig is set to the agent embedded /etc directory
5573
# this is a hacky way to get the path to the etc directory
5674
# by getting the path to the python executable and get the directory above /bin/python
57-
linux_unixodbc_sysconfig = os.path.dirname(os.path.dirname(sys.executable))
58-
if os.path.exists(os.path.join(linux_unixodbc_sysconfig, 'odbcinst.ini')) or os.path.exists(
59-
os.path.join(linux_unixodbc_sysconfig, 'odbc.ini')
60-
):
61-
# If there are already drivers or dataSources installed, don't override the ODBCSYSINI
62-
# This means user has copied odbcinst.ini and odbc.ini to the unixODBC sysconfig location
63-
return
75+
linux_unixodbc_sysconfig = get_unixodbc_sysconfig(sys.executable)
76+
odbc_ini = os.path.join(linux_unixodbc_sysconfig, 'odbc.ini')
77+
if is_non_empty_file(odbc_ini):
78+
os.environ.setdefault('ODBCSYSINI', linux_unixodbc_sysconfig)
79+
odbc_inst_ini_sysconfig = os.path.join(linux_unixodbc_sysconfig, ODBC_INST_INI)
80+
if not is_non_empty_file(odbc_inst_ini_sysconfig):
81+
shutil.copy(os.path.join(DRIVER_CONFIG_DIR, ODBC_INST_INI), odbc_inst_ini_sysconfig)
82+
# If there are already drivers or dataSources installed, don't override the ODBCSYSINI
83+
# This means user has copied odbcinst.ini and odbc.ini to the unixODBC sysconfig location
84+
return
6485

6586
# Use default `./driver_config/odbcinst.ini` to let the integration use agent embedded odbc driver.
6687
os.environ.setdefault('ODBCSYSINI', DRIVER_CONFIG_DIR)

sqlserver/tests/test_unit.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
from datadog_checks.sqlserver.utils import (
2121
Database,
2222
extract_sql_comments_and_procedure_name,
23+
get_unixodbc_sysconfig,
24+
is_non_empty_file,
2325
parse_sqlserver_major_version,
2426
set_default_driver_conf,
2527
)
2628

2729
from .common import CHECK_NAME, DOCKER_SERVER, assert_metrics
28-
from .utils import deep_compare, windows_ci
30+
from .utils import deep_compare, not_windows_ci, windows_ci
2931

3032
try:
3133
import pyodbc
@@ -435,6 +437,12 @@ def test_set_default_driver_conf():
435437
set_default_driver_conf()
436438
assert os.environ['ODBCSYSINI'].endswith(os.path.join('data', 'driver_config'))
437439

440+
with mock.patch("datadog_checks.base.utils.platform.Platform.is_linux", return_value=True):
441+
with EnvVars({}, ignore=['ODBCSYSINI']):
442+
set_default_driver_conf()
443+
assert 'ODBCSYSINI' in os.environ, "ODBCSYSINI should be set"
444+
assert os.environ['ODBCSYSINI'].endswith(os.path.join('data', 'driver_config'))
445+
438446
# `set_default_driver_conf` have no effect on the cases below
439447
with EnvVars({'ODBCSYSINI': 'ABC', 'DOCKER_DD_AGENT': 'true'}):
440448
set_default_driver_conf()
@@ -446,23 +454,27 @@ def test_set_default_driver_conf():
446454
assert 'ODBCSYSINI' in os.environ
447455
assert os.environ['ODBCSYSINI'].endswith(os.path.join('tests', 'odbc'))
448456

449-
with EnvVars({}, ignore=['ODBCSYSINI']):
450-
with mock.patch("os.path.exists", return_value=True):
451-
# odbcinst.ini or odbc.ini exists in agent embedded directory
452-
set_default_driver_conf()
453-
assert 'ODBCSYSINI' not in os.environ
454-
455-
with EnvVars({}, ignore=['ODBCSYSINI']):
456-
set_default_driver_conf()
457-
assert 'ODBCSYSINI' in os.environ # ODBCSYSINI is set by the integration
458-
if pyodbc is not None:
459-
assert pyodbc.drivers() is not None
460-
461457
with EnvVars({'ODBCSYSINI': 'ABC'}):
462458
set_default_driver_conf()
463459
assert os.environ['ODBCSYSINI'] == 'ABC'
464460

465461

462+
@not_windows_ci
463+
def test_set_default_driver_conf_linux():
464+
odbc_config_dir = os.path.expanduser('~')
465+
with mock.patch("datadog_checks.sqlserver.utils.get_unixodbc_sysconfig", return_value=odbc_config_dir):
466+
with EnvVars({}, ignore=['ODBCSYSINI']):
467+
odbc_inst = os.path.join(odbc_config_dir, "odbcinst.ini")
468+
odbc_ini = os.path.join(odbc_config_dir, "odbc.ini")
469+
for file in [odbc_inst, odbc_ini]:
470+
if os.path.exists(file):
471+
os.remove(file)
472+
with open(odbc_ini, "x") as file:
473+
file.write("dummy-content")
474+
set_default_driver_conf()
475+
assert is_non_empty_file(odbc_inst), "odbc_inst should have been created when a non empty odbc.ini exists"
476+
477+
466478
@windows_ci
467479
def test_check_local(aggregator, dd_run_check, init_config, instance_docker):
468480
sqlserver_check = SQLServer(CHECK_NAME, init_config, [instance_docker])
@@ -866,3 +878,16 @@ def test_exception_handling_by_do_for_dbs(instance_docker):
866878
'datadog_checks.sqlserver.utils.is_azure_sql_database', return_value={}
867879
):
868880
schemas._fetch_for_databases()
881+
882+
883+
def test_get_unixodbc_sysconfig():
884+
etc_dir = os.path.sep
885+
for dir in ["opt", "datadog-agent", "embedded", "bin", "python"]:
886+
etc_dir = os.path.join(etc_dir, dir)
887+
assert get_unixodbc_sysconfig(etc_dir).split(os.path.sep) == [
888+
"",
889+
"opt",
890+
"datadog-agent",
891+
"embedded",
892+
"etc",
893+
], "incorrect unix odbc config dir"

0 commit comments

Comments
 (0)