saltext.mysql 1.0.0__py2.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,28 @@
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__ = "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 get_distribution, DistributionNotFound
20
+
21
+ try:
22
+ __version__ = get_distribution(__name__).version
23
+ except DistributionNotFound:
24
+ # package is not installed
25
+ pass
26
+ except ImportError:
27
+ # pkg resources isn't even available?!
28
+ pass
File without changes
@@ -0,0 +1,124 @@
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
+ return bool(MySQLdb), "No python mysql client installed." if MySQLdb is None else ""
79
+
80
+
81
+ def __get_connection_info():
82
+ """
83
+ Grab MySQL Connection Details
84
+ """
85
+ conn_info = {}
86
+
87
+ try:
88
+ conn_info["hostname"] = __opts__["mysql_auth"]["hostname"]
89
+ conn_info["username"] = __opts__["mysql_auth"]["username"]
90
+ conn_info["password"] = __opts__["mysql_auth"]["password"]
91
+ conn_info["database"] = __opts__["mysql_auth"]["database"]
92
+
93
+ conn_info["auth_sql"] = __opts__["mysql_auth"]["auth_sql"]
94
+ except KeyError as e:
95
+ log.error("%s does not exist", e)
96
+ return None
97
+
98
+ return conn_info
99
+
100
+
101
+ def auth(username, password):
102
+ """
103
+ Authenticate using a MySQL user table
104
+ """
105
+ _info = __get_connection_info()
106
+
107
+ if _info is None:
108
+ return False
109
+
110
+ try:
111
+ conn = MySQLdb.connect(
112
+ _info["hostname"], _info["username"], _info["password"], _info["database"]
113
+ )
114
+ except OperationalError as e:
115
+ log.error(e)
116
+ return False
117
+
118
+ cur = conn.cursor()
119
+ cur.execute(_info["auth_sql"].format(username, password))
120
+
121
+ if cur.rowcount == 1:
122
+ return True
123
+
124
+ return False
File without changes
@@ -0,0 +1,357 @@
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
+ return bool(MySQLdb), "No python mysql client installed." if MySQLdb is None else ""
102
+
103
+
104
+ def force_reconnect():
105
+ """
106
+ Force a reconnection to the MySQL database, by removing the client from
107
+ Salt's __context__.
108
+ """
109
+ __context__.pop("mysql_client", None)
110
+
111
+
112
+ def run_query(conn, query, args=None, retries=3):
113
+ """
114
+ Get a cursor and run a query. Reconnect up to ``retries`` times if
115
+ needed.
116
+ Returns: cursor, affected rows counter
117
+ Raises: SaltCacheError, AttributeError, OperationalError, InterfaceError
118
+ """
119
+ if __context__.get("mysql_fresh_connection"):
120
+ # Create a new connection if configured
121
+ conn = MySQLdb.connect(**__context__["mysql_kwargs"])
122
+ __context__["mysql_client"] = conn
123
+ if conn is None:
124
+ conn = __context__.get("mysql_client")
125
+ try:
126
+ cur = conn.cursor()
127
+
128
+ if not args:
129
+ log.debug("Doing query: %s", query)
130
+ out = cur.execute(query)
131
+ else:
132
+ log.debug("Doing query: %s args: %s ", query, repr(args))
133
+ out = cur.execute(query, args)
134
+
135
+ return cur, out
136
+ except (AttributeError, OperationalError, InterfaceError) as e:
137
+ if retries == 0:
138
+ raise
139
+ # reconnect creating new client
140
+ time.sleep(_RECONNECT_INTERVAL_SEC)
141
+ if conn is None:
142
+ log.debug("mysql_cache: creating db connection")
143
+ else:
144
+ log.info("mysql_cache: recreating db connection due to: %r", e)
145
+ __context__["mysql_client"] = MySQLdb.connect(**__context__["mysql_kwargs"])
146
+ return run_query(
147
+ conn=__context__.get("mysql_client"),
148
+ query=query,
149
+ args=args,
150
+ retries=(retries - 1),
151
+ )
152
+ except Exception as e: # pylint: disable=broad-except
153
+ if len(query) > 150:
154
+ query = query[:150] + "<...>"
155
+ raise SaltCacheError(
156
+ "Error running {}{}: {}".format(query, f"- args: {args}" if args else "", e)
157
+ ) from e
158
+
159
+
160
+ def _create_table():
161
+ """
162
+ Create table if needed
163
+ """
164
+ # Explicitly check if the table already exists as the library logs a
165
+ # warning on CREATE TABLE
166
+ query = """SELECT COUNT(TABLE_NAME) FROM information_schema.tables
167
+ WHERE table_schema = %s AND table_name = %s"""
168
+ cur, _ = run_query(
169
+ __context__.get("mysql_client"),
170
+ query,
171
+ args=(__context__["mysql_kwargs"]["db"], __context__["mysql_table_name"]),
172
+ )
173
+ r = cur.fetchone()
174
+ cur.close()
175
+ if r[0] == 1:
176
+ query = """
177
+ SELECT COUNT(TABLE_NAME)
178
+ FROM
179
+ information_schema.columns
180
+ WHERE
181
+ table_schema = %s
182
+ AND table_name = %s
183
+ AND column_name = 'last_update'
184
+ """
185
+ cur, _ = run_query(
186
+ __context__["mysql_client"],
187
+ query,
188
+ args=(__context__["mysql_kwargs"]["db"], __context__["mysql_table_name"]),
189
+ )
190
+ r = cur.fetchone()
191
+ cur.close()
192
+ if r[0] == 1:
193
+ return
194
+ else:
195
+ query = """
196
+ ALTER TABLE {}.{}
197
+ ADD COLUMN last_update TIMESTAMP NOT NULL
198
+ DEFAULT CURRENT_TIMESTAMP
199
+ ON UPDATE CURRENT_TIMESTAMP
200
+ """.format(
201
+ __context__["mysql_kwargs"]["db"], __context__["mysql_table_name"]
202
+ )
203
+ cur, _ = run_query(__context__["mysql_client"], query)
204
+ cur.close()
205
+ return
206
+
207
+ query = """CREATE TABLE IF NOT EXISTS {} (
208
+ bank CHAR(255),
209
+ etcd_key CHAR(255),
210
+ data MEDIUMBLOB,
211
+ last_update TIMESTAMP NOT NULL
212
+ DEFAULT CURRENT_TIMESTAMP
213
+ ON UPDATE CURRENT_TIMESTAMP,
214
+ PRIMARY KEY(bank, etcd_key)
215
+ );""".format(
216
+ __context__["mysql_table_name"]
217
+ )
218
+ log.info("mysql_cache: creating table %s", __context__["mysql_table_name"])
219
+ cur, _ = run_query(__context__.get("mysql_client"), query)
220
+ cur.close()
221
+
222
+
223
+ def _init_client():
224
+ """Initialize connection and create table if needed"""
225
+ if __context__.get("mysql_client") is not None:
226
+ return
227
+
228
+ opts = copy.deepcopy(__opts__)
229
+ mysql_kwargs = {
230
+ "autocommit": True,
231
+ "host": opts.pop("mysql.host", "127.0.0.1"),
232
+ "user": opts.pop("mysql.user", None),
233
+ "passwd": opts.pop("mysql.password", None),
234
+ "db": opts.pop("mysql.database", _DEFAULT_DATABASE_NAME),
235
+ "port": opts.pop("mysql.port", 3306),
236
+ "unix_socket": opts.pop("mysql.unix_socket", None),
237
+ "connect_timeout": opts.pop("mysql.connect_timeout", None),
238
+ }
239
+ mysql_kwargs["autocommit"] = True
240
+
241
+ __context__["mysql_table_name"] = opts.pop("mysql.table_name", "salt")
242
+ __context__["mysql_fresh_connection"] = opts.pop("mysql.fresh_connection", False)
243
+
244
+ # Gather up any additional MySQL configuration options
245
+ for k in opts:
246
+ if k.startswith("mysql."):
247
+ _key = k.split(".")[1]
248
+ mysql_kwargs[_key] = opts.get(k)
249
+
250
+ # TODO: handle SSL connection parameters
251
+
252
+ for k, v in copy.deepcopy(mysql_kwargs).items():
253
+ if v is None:
254
+ mysql_kwargs.pop(k)
255
+ kwargs_copy = mysql_kwargs.copy()
256
+ kwargs_copy["passwd"] = "<hidden>"
257
+ log.info("mysql_cache: Setting up client with params: %r", kwargs_copy)
258
+ __context__["mysql_kwargs"] = mysql_kwargs
259
+ # The MySQL client is created later on by run_query
260
+ _create_table()
261
+
262
+
263
+ def store(bank, key, data):
264
+ """
265
+ Store a key value.
266
+ """
267
+ _init_client()
268
+ data = salt.payload.dumps(data)
269
+ query = "REPLACE INTO {} (bank, etcd_key, data) values(%s,%s,%s)".format(
270
+ __context__["mysql_table_name"]
271
+ )
272
+ args = (bank, key, data)
273
+
274
+ cur, cnt = run_query(__context__.get("mysql_client"), query, args=args)
275
+ cur.close()
276
+ if cnt not in (1, 2):
277
+ raise SaltCacheError(f"Error storing {bank} {key} returned {cnt}")
278
+
279
+
280
+ def fetch(bank, key):
281
+ """
282
+ Fetch a key value.
283
+ """
284
+ _init_client()
285
+ query = "SELECT data FROM {} WHERE bank=%s AND etcd_key=%s".format(
286
+ __context__["mysql_table_name"]
287
+ )
288
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=(bank, key))
289
+ r = cur.fetchone()
290
+ cur.close()
291
+ if r is None:
292
+ return {}
293
+ return salt.payload.loads(r[0])
294
+
295
+
296
+ def flush(bank, key=None):
297
+ """
298
+ Remove the key from the cache bank with all the key content.
299
+ """
300
+ _init_client()
301
+ query = "DELETE FROM {} WHERE bank=%s".format(__context__["mysql_table_name"])
302
+ if key is None:
303
+ data = (bank,)
304
+ else:
305
+ data = (bank, key)
306
+ query += " AND etcd_key=%s"
307
+
308
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=data)
309
+ cur.close()
310
+
311
+
312
+ def ls(bank):
313
+ """
314
+ Return an iterable object containing all entries stored in the specified
315
+ bank.
316
+ """
317
+ _init_client()
318
+ query = "SELECT etcd_key FROM {} WHERE bank=%s".format(__context__["mysql_table_name"])
319
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=(bank,))
320
+ out = [row[0] for row in cur.fetchall()]
321
+ cur.close()
322
+ return out
323
+
324
+
325
+ def contains(bank, key):
326
+ """
327
+ Checks if the specified bank contains the specified key.
328
+ """
329
+ _init_client()
330
+ if key is None:
331
+ data = (bank,)
332
+ query = "SELECT COUNT(data) FROM {} WHERE bank=%s".format(__context__["mysql_table_name"])
333
+ else:
334
+ data = (bank, key)
335
+ query = "SELECT COUNT(data) FROM {} WHERE bank=%s AND etcd_key=%s".format(
336
+ __context__["mysql_table_name"]
337
+ )
338
+ cur, _ = run_query(__context__.get("mysql_client"), query, args=data)
339
+ r = cur.fetchone()
340
+ cur.close()
341
+ return r[0] == 1
342
+
343
+
344
+ def updated(bank, key):
345
+ """
346
+ Return the integer Unix epoch update timestamp of the specified bank and
347
+ key.
348
+ """
349
+ _init_client()
350
+ query = "SELECT UNIX_TIMESTAMP(last_update) FROM {} WHERE bank=%s " "AND etcd_key=%s".format(
351
+ __context__["mysql_table_name"]
352
+ )
353
+ data = (bank, key)
354
+ cur, _ = run_query(__context__.get("mysql_client"), query=query, args=data)
355
+ r = cur.fetchone()
356
+ cur.close()
357
+ return int(r[0]) if r else r
File without changes