Skip to content

Commit 4aaa73d

Browse files
potiukgalak75
authored andcommitted
[AIRFLOW-3268] Better handling of extras field in MySQL connection (apache#4113)
1 parent 07050c0 commit 4aaa73d

File tree

3 files changed

+92
-5
lines changed

3 files changed

+92
-5
lines changed

airflow/hooks/mysql_hook.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import MySQLdb
2121
import MySQLdb.cursors
22+
import json
23+
import six
2224

2325
from airflow.hooks.dbapi_hook import DbApiHook
2426

@@ -87,7 +89,13 @@ def get_conn(self):
8789
conn_config["cursorclass"] = MySQLdb.cursors.SSDictCursor
8890
local_infile = conn.extra_dejson.get('local_infile', False)
8991
if conn.extra_dejson.get('ssl', False):
90-
conn_config['ssl'] = conn.extra_dejson['ssl']
92+
# SSL parameter for MySQL has to be a dictionary and in case
93+
# of extra/dejson we can get string if extra is passed via
94+
# URL parameters
95+
dejson_ssl = conn.extra_dejson['ssl']
96+
if isinstance(dejson_ssl, six.string_types):
97+
dejson_ssl = json.loads(dejson_ssl)
98+
conn_config['ssl'] = dejson_ssl
9199
if conn.extra_dejson.get('unix_socket'):
92100
conn_config['unix_socket'] = conn.extra_dejson['unix_socket']
93101
if local_infile:

docs/howto/manage-connections.rst

+59-4
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,9 @@ Scopes (comma separated)
133133
Scopes are ignored when using application default credentials. See
134134
issue `AIRFLOW-2522
135135
<https://issues.apache.org/jira/browse/AIRFLOW-2522>`_.
136+
136137
MySQL
137-
~~~~~~~~~~~~~~~~~~~~~
138+
~~~~~
138139
The MySQL connect type allows to connect with MySQL database.
139140

140141
Configuring the Connection
@@ -152,7 +153,61 @@ Password (required)
152153
Specify the password to connect.
153154

154155
Extra (optional)
155-
Specify the charset. Example: {"charset": "utf8"}
156-
156+
Specify the extra parameters (as json dictionary) that can be used in mysql
157+
connection. The following parameters are supported:
158+
159+
* **charset**: specify charset of the connection
160+
* **cursor**: one of "sscursor", "dictcursor, "ssdictcursor" - specifies cursor class to be
161+
used
162+
* **local_infile**: controls MySQL's LOCAL capability (permitting local data loading by
163+
clients). See `MySQLdb docs <https://mysqlclient.readthedocs.io/user_guide.html>`_
164+
for details.
165+
* **unix_socket**: UNIX socket used instead of the default socket
166+
* **ssl**: Dictionary of SSL parameters that control connecting using SSL (those
167+
parameters are server specific and should contain "ca", "cert", "key", "capath",
168+
"cipher" parameters. See
169+
`MySQLdb docs <https://mysqlclient.readthedocs.io/user_guide.html>`_ for details.
170+
Note that in order to be useful in URL notation, this parameter might also be
171+
a string where the SSL dictionary is a string-encoded JSON dictionary.
172+
173+
Example "extras" field:
174+
175+
.. code-block:: json
176+
177+
{
178+
"charset": "utf8",
179+
"cursorclass": "sscursor",
180+
"local_infile": true,
181+
"unix_socket": "/var/socket",
182+
"ssl": {
183+
"cert": "/tmp/client-cert.pem",
184+
"ca": "/tmp/server-ca.pem'",
185+
"key": "/tmp/client-key.pem"
186+
}
187+
}
188+
189+
or
190+
191+
.. code-block:: json
192+
193+
{
194+
"charset": "utf8",
195+
"cursorclass": "sscursor",
196+
"local_infile": true,
197+
"unix_socket": "/var/socket",
198+
"ssl": "{\"cert\": \"/tmp/client-cert.pem\", \"ca\": \"/tmp/server-ca.pem\", \"key\": \"/tmp/client-key.pem\"}"
199+
}
200+
201+
When specifying the connection as URI (in AIRFLOW_CONN_* variable) you should specify it
202+
following the standard syntax of DB connections, where extras as passed as parameters
203+
of the URI (note that all components of the URI should be URL-encoded).
204+
205+
For example:
206+
207+
.. code-block:: bash
208+
209+
mysql://mysql_user:XXXXXXXXXXXX@1.1.1.1:3306/mysqldb?ssl=%7B%22cert%22%3A+%22%2Ftmp%2Fclient-cert.pem%22%2C+%22ca%22%3A+%22%2Ftmp%2Fserver-ca.pem%22%2C+%22key%22%3A+%22%2Ftmp%2Fclient-key.pem%22%7D
210+
157211
.. note::
158-
If encounter UnicodeDecodeError while working with MySQL connection check the charset defined is matched to the database charset.
212+
If encounter UnicodeDecodeError while working with MySQL connection check
213+
the charset defined is matched to the database charset.

tests/hooks/test_mysql_hook.py

+24
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
from airflow import models
2828
from airflow.hooks.mysql_hook import MySqlHook
2929

30+
SSL_DICT = {
31+
'cert': '/tmp/client-cert.pem',
32+
'ca': '/tmp/server-ca.pem',
33+
'key': '/tmp/client-key.pem'
34+
}
35+
3036

3137
class TestMySqlHookConn(unittest.TestCase):
3238

@@ -101,6 +107,24 @@ def test_get_con_unix_socket(self, mock_connect):
101107
self.assertEqual(args, ())
102108
self.assertEqual(kwargs['unix_socket'], '/tmp/socket')
103109

110+
@mock.patch('airflow.hooks.mysql_hook.MySQLdb.connect')
111+
def test_get_conn_ssl_as_dictionary(self, mock_connect):
112+
self.connection.extra = json.dumps({'ssl': SSL_DICT})
113+
self.db_hook.get_conn()
114+
mock_connect.assert_called_once()
115+
args, kwargs = mock_connect.call_args
116+
self.assertEqual(args, ())
117+
self.assertEqual(kwargs['ssl'], SSL_DICT)
118+
119+
@mock.patch('airflow.hooks.mysql_hook.MySQLdb.connect')
120+
def test_get_conn_ssl_as_string(self, mock_connect):
121+
self.connection.extra = json.dumps({'ssl': json.dumps(SSL_DICT)})
122+
self.db_hook.get_conn()
123+
mock_connect.assert_called_once()
124+
args, kwargs = mock_connect.call_args
125+
self.assertEqual(args, ())
126+
self.assertEqual(kwargs['ssl'], SSL_DICT)
127+
104128

105129
class TestMySqlHook(unittest.TestCase):
106130

0 commit comments

Comments
 (0)