Ivan Udovichenko | e25bced | 2018-08-07 12:52:46 +0300 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | ''' |
| 3 | PowerDNS MySQL interections |
| 4 | ========================== |
| 5 | |
| 6 | :depends: - MySQLdb Python module |
| 7 | :configuration: See :py:mod:`salt.modules.mysql` for setup instructions. |
| 8 | |
| 9 | The mysql_query module is used to execute queries on MySQL databases. |
| 10 | Its output may be stored in a file or in a grain. |
| 11 | |
| 12 | Sometimes you would need to store information about PowerDNS supermasters |
| 13 | in tables to work with. |
| 14 | Here is an example of making sure rows with specific data exist: |
| 15 | |
| 16 | .. code-block:: yaml |
| 17 | |
| 18 | use_supermaster_127.0.0.1: |
| 19 | powerdns_mysql.row_present: |
| 20 | - table: supermasters |
| 21 | - where_sql: ip="127.0.0.1" |
| 22 | - database: powerdns |
| 23 | - data: |
| 24 | ip: 127.0.0.1 |
| 25 | nameserver: ns.localhost.local |
| 26 | account: master |
| 27 | |
| 28 | Comprehensive example could be found in file: |
| 29 | powerdns/server/backends/mysql.sls |
| 30 | ''' |
| 31 | |
| 32 | import sys |
| 33 | import salt.utils |
| 34 | import salt.ext.six as six |
| 35 | |
| 36 | |
| 37 | def __virtual__(): |
| 38 | ''' |
| 39 | Only load if the mysql module is available in __salt__ |
| 40 | ''' |
| 41 | return 'mysql.query' in __salt__ |
| 42 | |
| 43 | |
| 44 | def _get_mysql_error(): |
| 45 | ''' |
| 46 | Look in module context for a MySQL error. Eventually we should make a less |
| 47 | ugly way of doing this. |
| 48 | ''' |
| 49 | return sys.modules[ |
| 50 | __salt__['test.ping'].__module__ |
| 51 | ].__context__.pop('mysql.error', None) |
| 52 | |
| 53 | |
| 54 | def row_present(name, |
| 55 | database, |
| 56 | table, |
| 57 | data, |
| 58 | where_sql, |
| 59 | update=False, |
| 60 | **connection_args): |
| 61 | ''' |
| 62 | Checks to make sure the given row exists. If row exists and update is True |
| 63 | then row will be updated with data. Otherwise it will leave existing |
| 64 | row unmodified and check it against data. If the existing data |
| 65 | doesn't match data check the state will fail. If the row doesn't |
| 66 | exist then it will insert data into the table. If more than one |
| 67 | row matches, then the state will fail. |
| 68 | |
| 69 | name |
| 70 | Used only as an ID |
| 71 | |
| 72 | database |
| 73 | The name of the database to execute the query on |
| 74 | |
| 75 | table |
| 76 | The table name to check the data |
| 77 | |
| 78 | data |
| 79 | The dictionary of key/value pairs to check against if |
| 80 | row exists, insert into the table if it doesn't |
| 81 | |
| 82 | where_sql |
| 83 | The SQL to select the row to check |
| 84 | |
| 85 | update |
| 86 | True will replace the existing row with data |
| 87 | When False and the row exists and data does not equal |
| 88 | the row data then the state will fail |
| 89 | |
| 90 | connection_args |
| 91 | MySQL connection arguments to connect to MySQL server with |
| 92 | ''' |
| 93 | |
| 94 | ret = {'name': name, |
| 95 | 'changes': {}, |
| 96 | 'result': True, |
| 97 | 'comment': 'Database {0} is already present'.format(database)} |
| 98 | |
| 99 | # check if database exists |
| 100 | if not __salt__['mysql.db_exists'](database, **connection_args): |
| 101 | err = _get_mysql_error() |
| 102 | if err is not None: |
| 103 | ret['comment'] = err |
| 104 | ret['result'] = False |
| 105 | return ret |
| 106 | |
| 107 | ret['result'] = None |
| 108 | ret['comment'] = ('Database {0} is not present' |
| 109 | ).format(name) |
| 110 | return ret |
| 111 | |
| 112 | try: |
| 113 | query = "SELECT * FROM `" + table + "` WHERE " + where_sql |
| 114 | select_result = __salt__['mysql.query'](database, query, **connection_args) |
| 115 | |
| 116 | if select_result['rows returned'] > 1: |
| 117 | ret['result'] = False |
| 118 | ret['comment'] = 'More than one row matched the specified query' |
| 119 | elif select_result['rows returned'] == 1: |
| 120 | # create ordered dict of values returned by mysql.query function |
| 121 | old_data = salt.utils.odict.OrderedDict() |
| 122 | for num in xrange(0, len(select_result['columns'])): |
| 123 | old_data[select_result['columns'][num]] = select_result['results'][0][num] |
| 124 | |
| 125 | for key, value in six.iteritems(data): |
| 126 | if key in old_data and old_data[key] != value: |
| 127 | if update: |
| 128 | if __opts__['test']: |
| 129 | ret['result'] = True |
| 130 | ret['comment'] = "Row will be update in " + table |
| 131 | else: |
| 132 | columns = [] |
| 133 | for key, value in six.iteritems(data): |
| 134 | columns.append('`' + key + '`="' + value + '"') |
| 135 | |
| 136 | query = "UPDATE `" + table + "` SET " |
| 137 | query += ",".join(columns) |
| 138 | query += " WHERE " |
| 139 | query += where_sql |
| 140 | update_result = __salt__['mysql.query'](database, query, **connection_args) |
| 141 | |
| 142 | if update_result['rows affected'] == 1: |
| 143 | ret['result'] = True |
| 144 | ret['comment'] = "Row updated" |
| 145 | ret['changes']['old'] = old_data |
| 146 | ret['changes']['new'] = data |
| 147 | else: |
| 148 | ret['result'] = None |
| 149 | ret['comment'] = "Row update failed" |
| 150 | else: |
| 151 | ret['result'] = False |
| 152 | ret['comment'] = "Existing data does" + \ |
| 153 | "not match desired state" |
| 154 | break |
| 155 | |
| 156 | if ret['result'] is None: |
| 157 | ret['result'] = True |
| 158 | ret['comment'] = "Row exists" |
| 159 | else: |
| 160 | if __opts__['test']: |
| 161 | ret['result'] = True |
| 162 | ret['changes']['new'] = data |
| 163 | ret['comment'] = "Row will be inserted into " + table |
| 164 | else: |
| 165 | columns = [] |
| 166 | values = [] |
| 167 | for key, value in six.iteritems(data): |
| 168 | values.append('"' + value + '"') |
| 169 | columns.append("`" + key + "`") |
| 170 | |
| 171 | query = "INSERT INTO `" + table + "` (" |
| 172 | query += ",".join(columns) |
| 173 | query += ") VALUES (" |
| 174 | query += ",".join(values) |
| 175 | query += ")" |
| 176 | insert_result = __salt__['mysql.query'](database, query, **connection_args) |
| 177 | |
| 178 | if insert_result['rows affected'] == 1: |
| 179 | ret['result'] = True |
| 180 | ret['changes']['new'] = data |
| 181 | ret['comment'] = 'Inserted row' |
| 182 | else: |
| 183 | ret['result'] = False |
| 184 | ret['comment'] = "Unable to insert data" |
| 185 | |
| 186 | except Exception as e: |
| 187 | ret['result'] = False |
| 188 | ret['comment'] = str(e) |
| 189 | |
| 190 | return ret |