saltext.mysql 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ # pylint: disable=missing-module-docstring
2
+ import pathlib
3
+
4
+ PACKAGE_ROOT = pathlib.Path(__file__).resolve().parent
5
+ try:
6
+ from .version import __version__
7
+ except ImportError: # pragma: no cover
8
+ __version__: str = "0.0.0.not-installed"
9
+ try:
10
+ from importlib.metadata import version, PackageNotFoundError
11
+
12
+ try:
13
+ __version__ = version(__name__)
14
+ except PackageNotFoundError:
15
+ # package is not installed
16
+ pass
17
+ except ImportError:
18
+ try:
19
+ from pkg_resources import ( # type: ignore
20
+ get_distribution,
21
+ DistributionNotFound,
22
+ )
23
+
24
+ try:
25
+ __version__ = get_distribution(__name__).version
26
+ except DistributionNotFound:
27
+ # package is not installed
28
+ pass
29
+ except ImportError:
30
+ # pkg resources isn't even available?!
31
+ pass
File without changes
@@ -0,0 +1,131 @@
1
+ """
2
+ Provide authentication using MySQL.
3
+
4
+ When using MySQL as an authentication backend, you will need to create or
5
+ use an existing table that has a username and a password column.
6
+
7
+ To get started, create a simple table that holds just a username and
8
+ a password. The password field will hold a SHA256 checksum.
9
+
10
+ .. code-block:: sql
11
+
12
+ CREATE TABLE `users` (
13
+ `id` int(11) NOT NULL AUTO_INCREMENT,
14
+ `username` varchar(25) DEFAULT NULL,
15
+ `password` varchar(70) DEFAULT NULL,
16
+ PRIMARY KEY (`id`)
17
+ ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
18
+
19
+ To create a user within MySQL, execute the following statement.
20
+
21
+ .. code-block:: sql
22
+
23
+ INSERT INTO users VALUES (NULL, 'diana', SHA2('secret', 256))
24
+
25
+ .. code-block:: yaml
26
+
27
+ mysql_auth:
28
+ hostname: localhost
29
+ database: SaltStack
30
+ username: root
31
+ password: letmein
32
+ auth_sql: 'SELECT username FROM users WHERE username = "{0}" AND password = SHA2("{1}", 256)'
33
+
34
+ The `auth_sql` contains the SQL that will validate a user to ensure they are
35
+ correctly authenticated. This is where you can specify other SQL queries to
36
+ authenticate users.
37
+
38
+ Enable MySQL authentication.
39
+
40
+ .. code-block:: yaml
41
+
42
+ external_auth:
43
+ mysql:
44
+ damian:
45
+ - test.*
46
+
47
+ :depends: - MySQL-python Python module
48
+ """
49
+
50
+ import logging
51
+
52
+ log = logging.getLogger(__name__)
53
+
54
+ try:
55
+ # Trying to import MySQLdb
56
+ import MySQLdb
57
+ import MySQLdb.converters
58
+ import MySQLdb.cursors
59
+ from MySQLdb.connections import OperationalError
60
+ except ImportError:
61
+ try:
62
+ # MySQLdb import failed, try to import PyMySQL
63
+ import pymysql
64
+
65
+ pymysql.install_as_MySQLdb()
66
+ import MySQLdb
67
+ import MySQLdb.converters
68
+ import MySQLdb.cursors
69
+ from MySQLdb.err import OperationalError
70
+ except ImportError:
71
+ MySQLdb = None
72
+
73
+
74
+ def __virtual__():
75
+ """
76
+ Confirm that a python mysql client is installed.
77
+ """
78
+ if MySQLdb is None:
79
+ return (
80
+ False,
81
+ "No Python MySQL client could be imported: this module requires either "
82
+ "mysqlclient (MySQLdb) or PyMySQL. For onedir Salt installations, "
83
+ "install one via `salt-pip install pymysql`.",
84
+ )
85
+ return True
86
+
87
+
88
+ def __get_connection_info():
89
+ """
90
+ Grab MySQL Connection Details
91
+ """
92
+ conn_info = {}
93
+
94
+ try:
95
+ conn_info["hostname"] = __opts__["mysql_auth"]["hostname"]
96
+ conn_info["username"] = __opts__["mysql_auth"]["username"]
97
+ conn_info["password"] = __opts__["mysql_auth"]["password"]
98
+ conn_info["database"] = __opts__["mysql_auth"]["database"]
99
+
100
+ conn_info["auth_sql"] = __opts__["mysql_auth"]["auth_sql"]
101
+ except KeyError as e:
102
+ log.error("%s does not exist", e)
103
+ return None
104
+
105
+ return conn_info
106
+
107
+
108
+ def auth(username, password):
109
+ """
110
+ Authenticate using a MySQL user table
111
+ """
112
+ _info = __get_connection_info()
113
+
114
+ if _info is None:
115
+ return False
116
+
117
+ try:
118
+ conn = MySQLdb.connect(
119
+ _info["hostname"], _info["username"], _info["password"], _info["database"]
120
+ )
121
+ except OperationalError as e:
122
+ log.error(e)
123
+ return False
124
+
125
+ cur = conn.cursor()
126
+ cur.execute(_info["auth_sql"].format(username, password))
127
+
128
+ if cur.rowcount == 1:
129
+ return True
130
+
131
+ return False
File without changes
@@ -0,0 +1,381 @@
1
+ """
2
+ Minion data cache plugin for MySQL database.
3
+
4
+ .. versionadded:: 2018.3.0
5
+
6
+ It is up to the system administrator to set up and configure the MySQL
7
+ infrastructure. All is needed for this plugin is a working MySQL server.
8
+
9
+ .. warning::
10
+
11
+ The mysql.database and mysql.table_name will be directly added into certain
12
+ queries. Salt treats these as trusted input.
13
+
14
+ The module requires the database (default ``salt_cache``) to exist but creates
15
+ its own table if needed. The keys are indexed using the ``bank`` and
16
+ ``etcd_key`` columns.
17
+
18
+ To enable this cache plugin, the master will need the python client for
19
+ MySQL installed. This can be easily installed with pip:
20
+
21
+ .. code-block:: bash
22
+
23
+ pip install pymysql
24
+
25
+ Optionally, depending on the MySQL agent configuration, the following values
26
+ could be set in the master config. These are the defaults:
27
+
28
+ .. code-block:: yaml
29
+
30
+ mysql.host: 127.0.0.1
31
+ mysql.port: 2379
32
+ mysql.user: None
33
+ mysql.password: None
34
+ mysql.database: salt_cache
35
+ mysql.table_name: cache
36
+ # This may be enabled to create a fresh connection on every call
37
+ mysql.fresh_connection: false
38
+
39
+ To use the mysql as a minion data cache backend, set the master ``cache`` config
40
+ value to ``mysql``:
41
+
42
+ .. code-block:: yaml
43
+
44
+ cache: mysql
45
+
46
+
47
+ .. _`MySQL documentation`: https://github.com/coreos/mysql
48
+ """
49
+
50
+ import copy
51
+ import logging
52
+ import time
53
+
54
+ import salt.payload
55
+ import salt.utils.stringutils
56
+ from salt.exceptions import SaltCacheError
57
+
58
+ try:
59
+ # Trying to import MySQLdb
60
+ import MySQLdb
61
+ import MySQLdb.converters
62
+ import MySQLdb.cursors
63
+ from MySQLdb.connections import OperationalError
64
+
65
+ # Define the interface error as a subclass of exception
66
+ # It will never be thrown/used, it is defined to support the pymysql error below
67
+ class InterfaceError(Exception):
68
+ pass
69
+
70
+ except ImportError:
71
+ try:
72
+ # MySQLdb import failed, try to import PyMySQL
73
+ import pymysql
74
+ from pymysql.err import InterfaceError
75
+
76
+ pymysql.install_as_MySQLdb()
77
+ import MySQLdb
78
+ import MySQLdb.converters
79
+ import MySQLdb.cursors
80
+ from MySQLdb.err import OperationalError
81
+ except ImportError:
82
+ MySQLdb = None
83
+
84
+
85
+ _DEFAULT_DATABASE_NAME = "salt_cache"
86
+ _DEFAULT_CACHE_TABLE_NAME = "cache"
87
+ _RECONNECT_INTERVAL_SEC = 0.050
88
+
89
+ log = logging.getLogger(__name__)
90
+
91
+ # Module properties
92
+
93
+ __virtualname__ = "mysql"
94
+ __func_alias__ = {"ls": "list"}
95
+
96
+
97
+ def __virtual__():
98
+ """
99
+ Confirm that a python mysql client is installed.
100
+ """
101
+ if MySQLdb is None:
102
+ return (
103
+ False,
104
+ "No Python MySQL client could be imported: this module requires either "
105
+ "mysqlclient (MySQLdb) or PyMySQL. For onedir Salt installations, "
106
+ "install one via `salt-pip install pymysql`.",
107
+ )
108
+ return True
109
+
110
+
111
+ def force_reconnect():
112
+ """
113
+ Force a reconnection to the MySQL database, by removing the client from
114
+ Salt's __context__.
115
+ """
116
+ __context__.pop("mysql_client", None)
117
+
118
+
119
+ def run_query(conn, query, args=None, retries=3):
120
+ """
121
+ Get a cursor and run a query. Reconnect up to ``retries`` times if
122
+ needed.
123
+ Returns: cursor, affected rows counter
124
+ Raises: SaltCacheError, AttributeError, OperationalError, InterfaceError
125
+ """
126
+ if __context__.get("mysql_fresh_connection"):
127
+ # Create a new connection if configured
128
+ conn = MySQLdb.connect(**__context__["mysql_kwargs"])
129
+ __context__["mysql_client"] = conn
130
+ if conn is None:
131
+ conn = __context__.get("mysql_client")
132
+ try:
133
+ cur = conn.cursor()
134
+
135
+ if not args:
136
+ log.debug("Doing query: %s", query)
137
+ out = cur.execute(query)
138
+ else:
139
+ log.debug("Doing query: %s args: %s ", query, repr(args))
140
+ out = cur.execute(query, args)
141
+
142
+ return cur, out
143
+ except (AttributeError, OperationalError, InterfaceError) as e:
144
+ if retries == 0:
145
+ raise
146
+ # reconnect creating new client
147
+ time.sleep(_RECONNECT_INTERVAL_SEC)
148
+ if conn is None:
149
+ log.debug("mysql_cache: creating db connection")
150
+ else:
151
+ log.info("mysql_cache: recreating db connection due to: %r", e)
152
+ __context__["mysql_client"] = MySQLdb.connect(**__context__["mysql_kwargs"])
153
+ return run_query(
154
+ conn=__context__.get("mysql_client"),
155
+ query=query,
156
+ args=args,
157
+ retries=(retries - 1),
158
+ )
159
+ except Exception as e: # pylint: disable=broad-except
160
+ if len(query) > 150:
161
+ query = query[:150] + "<...>"
162
+ raise SaltCacheError(
163
+ "Error running {}{}: {}".format(query, f"- args: {args}" if args else "", e)
164
+ ) from e
165
+
166
+
167
+ def _create_table():
168
+ """
169
+ Create table if needed
170
+ """
171
+ # Explicitly check if the table already exists as the library logs a
172
+ # warning on CREATE TABLE
173
+ query = """SELECT COUNT(TABLE_NAME) FROM information_schema.tables
174
+ WHERE table_schema = %s AND table_name = %s"""
175
+ cur, _ = run_query(
176
+ __context__.get("mysql_client"),
177
+ query,
178
+ args=(__context__["mysql_kwargs"]["db"], __context__["mysql_table_name"]),
179
+ )
180
+ r = cur.fetchone()
181
+ cur.close()
182
+ if r[0] == 1:
183
+ query = """
184
+ SELECT COUNT(TABLE_NAME)
185
+ FROM
186
+ information_schema.columns
187
+ WHERE
188
+ table_schema = %s
189
+ AND table_name = %s
190
+ AND column_name = 'last_update'
191
+ """
192
+ cur, _ = run_query(
193
+ __context__["mysql_client"],
194
+ query,
195
+ args=(__context__["mysql_kwargs"]["db"], __context__["mysql_table_name"]),
196
+ )
197
+ r = cur.fetchone()
198
+ cur.close()
199
+ if r[0] == 1:
200
+ return
201
+ else:
202
+ query = """
203
+ ALTER TABLE {}.{}
204
+ ADD COLUMN last_update TIMESTAMP NOT NULL
205
+ DEFAULT CURRENT_TIMESTAMP
206
+ ON UPDATE CURRENT_TIMESTAMP
207
+ """.format(__context__["mysql_kwargs"]["db"], __context__["mysql_table_name"])
208
+ cur, _ = run_query(__context__["mysql_client"], query)
209
+ cur.close()
210
+ return
211
+
212
+ query = """CREATE TABLE IF NOT EXISTS {} (
213
+ bank CHAR(255),
214
+ etcd_key CHAR(255),
215
+ data MEDIUMBLOB,
216
+ last_update TIMESTAMP NOT NULL
217
+ DEFAULT CURRENT_TIMESTAMP
218
+ ON UPDATE CURRENT_TIMESTAMP,
219
+ PRIMARY KEY(bank, etcd_key)
220
+ );""".format(__context__["mysql_table_name"])
221
+ log.info("mysql_cache: creating table %s", __context__["mysql_table_name"])
222
+ cur, _ = run_query(__context__.get("mysql_client"), query)
223
+ cur.close()
224
+
225
+
226
+ def _init_client():
227
+ """Initialize connection and create table if needed"""
228
+ if __context__.get("mysql_client") is not None:
229
+ return
230
+
231
+ opts = copy.deepcopy(__opts__)
232
+ mysql_kwargs = {
233
+ "autocommit": True,
234
+ "host": opts.pop("mysql.host", "127.0.0.1"),
235
+ "user": opts.pop("mysql.user", None),
236
+ "passwd": opts.pop("mysql.password", None),
237
+ "db": opts.pop("mysql.database", _DEFAULT_DATABASE_NAME),
238
+ "port": opts.pop("mysql.port", 3306),
239
+ "unix_socket": opts.pop("mysql.unix_socket", None),
240
+ "connect_timeout": opts.pop("mysql.connect_timeout", None),
241
+ }
242
+ mysql_kwargs["autocommit"] = True
243
+
244
+ __context__["mysql_table_name"] = opts.pop("mysql.table_name", "salt")
245
+ __context__["mysql_fresh_connection"] = opts.pop("mysql.fresh_connection", False)
246
+
247
+ # Gather up any additional MySQL configuration options
248
+ for k in opts:
249
+ if k.startswith("mysql."):
250
+ _key = k.split(".")[1]
251
+ mysql_kwargs[_key] = opts.get(k)
252
+
253
+ # TODO: handle SSL connection parameters
254
+
255
+ for k, v in copy.deepcopy(mysql_kwargs).items():
256
+ if v is None:
257
+ mysql_kwargs.pop(k)
258
+ kwargs_copy = mysql_kwargs.copy()
259
+ kwargs_copy["passwd"] = "<hidden>"
260
+ log.info("mysql_cache: Setting up client with params: %r", kwargs_copy)
261
+ __context__["mysql_kwargs"] = mysql_kwargs
262
+ # The MySQL client is created later on by run_query
263
+ _create_table()
264
+
265
+
266
+ def store(bank, key, data):
267
+ """
268
+ Store a key value.
269
+ """
270
+ _init_client()
271
+ data = salt.payload.dumps(data)
272
+ query = "REPLACE INTO {} (bank, etcd_key, data) values(%s,%s,%s)".format(
273
+ __context__["mysql_table_name"]
274
+ )
275
+ args = (bank, key, data)
276
+
277
+ cur, cnt = run_query(__context__.get("mysql_client"), query, args=args)
278
+ cur.close()
279
+ if cnt not in (1, 2):
280
+ raise SaltCacheError(f"Error storing {bank} {key} returned {cnt}")
281
+
282
+
283
+ def fetch(bank, key):
284
+ """
285
+ Fetch a key value.
286
+ """
287
+ _init_client()
288
+ query = "SELECT data FROM {} WHERE bank=%s AND etcd_key=%s".format(
289
+ __context__["mysql_table_name"]
290
+ )
291
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=(bank, key))
292
+ r = cur.fetchone()
293
+ cur.close()
294
+ if r is None:
295
+ return {}
296
+ return salt.payload.loads(r[0])
297
+
298
+
299
+ def flush(bank, key=None):
300
+ """
301
+ Remove the key from the cache bank with all the key content.
302
+ """
303
+ _init_client()
304
+ query = "DELETE FROM {} WHERE bank=%s".format(__context__["mysql_table_name"])
305
+ if key is None:
306
+ data = (bank,)
307
+ else:
308
+ data = (bank, key)
309
+ query += " AND etcd_key=%s"
310
+
311
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=data)
312
+ cur.close()
313
+
314
+
315
+ def ls(bank):
316
+ """
317
+ Return an iterable object containing all entries stored in the specified bank.
318
+
319
+ For hierarchical banks like 'root/sub/dir', if listing 'root', it should return:
320
+ - Direct entries in 'root'
321
+ - First path segment of sub-banks (e.g. 'sub' from 'root/sub/dir')
322
+ """
323
+ bank = bank.rstrip("/")
324
+ bank_like = bank + "/%"
325
+ _init_client()
326
+ query = """SELECT bank, etcd_key FROM {} WHERE bank = %s OR bank LIKE %s""".format(
327
+ __context__["mysql_table_name"]
328
+ )
329
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=(bank, bank_like))
330
+
331
+ data = set()
332
+ bank_prefix_len = len(bank) + 1 # +1 for the trailing slash
333
+
334
+ for res_bank, res_key in cur.fetchall():
335
+ if res_bank == bank:
336
+ # Direct entries in the requested bank
337
+ data.add(res_key)
338
+ else:
339
+ # For sub-banks, get the first path segment after the requested bank
340
+ sub_path = res_bank[bank_prefix_len:]
341
+ first_segment = sub_path.split("/")[0]
342
+ if first_segment:
343
+ data.add(first_segment)
344
+
345
+ cur.close()
346
+ return list(data)
347
+
348
+
349
+ def contains(bank, key):
350
+ """
351
+ Checks if the specified bank contains the specified key.
352
+ """
353
+ _init_client()
354
+ if key is None:
355
+ data = (bank,)
356
+ query = "SELECT COUNT(data) FROM {} WHERE bank=%s".format(__context__["mysql_table_name"])
357
+ else:
358
+ data = (bank, key)
359
+ query = "SELECT COUNT(data) FROM {} WHERE bank=%s AND etcd_key=%s".format(
360
+ __context__["mysql_table_name"]
361
+ )
362
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=data)
363
+ r = cur.fetchone()
364
+ cur.close()
365
+ return r[0] == 1
366
+
367
+
368
+ def updated(bank, key):
369
+ """
370
+ Return the integer Unix epoch update timestamp of the specified bank and
371
+ key.
372
+ """
373
+ _init_client()
374
+ query = "SELECT UNIX_TIMESTAMP(last_update) FROM {} WHERE bank=%s " "AND etcd_key=%s".format(
375
+ __context__["mysql_table_name"]
376
+ )
377
+ data = (bank, key)
378
+ cur, _ = run_query(__context__.get("mysql_client"), query=query, args=data)
379
+ r = cur.fetchone()
380
+ cur.close()
381
+ return int(r[0]) if r else r
File without changes