├── LICENSE ├── README.md ├── oracle_acfs ├── oracle_asmdg ├── oracle_asmvol ├── oracle_awr ├── oracle_datapatch ├── oracle_db ├── oracle_directory ├── oracle_facts ├── oracle_gi_facts ├── oracle_grants ├── oracle_job ├── oracle_jobclass ├── oracle_jobschedule ├── oracle_jobwindow ├── oracle_ldapuser ├── oracle_opatch ├── oracle_parameter ├── oracle_pdb ├── oracle_privs ├── oracle_profile ├── oracle_redo ├── oracle_role ├── oracle_rsrc_consgroup ├── oracle_services ├── oracle_sql ├── oracle_sqldba ├── oracle_stats_prefs ├── oracle_tablespace ├── oracle_user └── test-modules.yml /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mikael Sandström (oravirt@gmail.com, http://oravirt.wordpress.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-oracle-modules 2 | **Oracle modules for Ansible** 3 | 4 | - If you have any questions/requests just create an issue and I'll look into it 5 | - I've also included a playbook (test-modules.yml) that'll give you an idea on how the modules can be used. 6 | 7 | To use the modules, create a 'library' directory next to your top level playbooks and put the different modules in that directory. Then just reference them as you would any other module. 8 | For more information, check out: http://docs.ansible.com/developing_modules.html 9 | 10 | 11 | Most (if not all) requires `cx_Oracle` either on your controlmachine or on the managed node. 12 | 13 | The default behaviour for the modules using `cx_Oracle` is this: 14 | 15 | - If neither username or password is passed as input to the module(s), the use of an Oracle wallet is assumed. 16 | - In that case, the `cx_Oracle.makedsn` step is skipped, and the connection will use the `'/@'` format instead. 17 | - You then need to make sure that you're using the correct tns-entry (service_name) to match the credential stored in the wallet. 18 | 19 | 20 | These are the different modules: 21 | 22 | **oracle_user** 23 | 24 | pre-req: cx_Oracle 25 | 26 | - Creates & drops a user. 27 | - Grants privileges only (can not remove them with oracle_user, use oracle_grants for that) 28 | 29 | **oracle_tablespace** 30 | 31 | pre-req: cx_Oracle 32 | 33 | - Manages normal(permanent), temp & undo tablespaces (create, drop, make read only/read write, offline/online) 34 | - Tablespaces can be created as bigfile, autoextended 35 | 36 | 37 | **oracle_grants** 38 | 39 | pre-req: cx_Oracle 40 | 41 | - Manages privileges for a user 42 | - Grants/revokes privileges 43 | - Handles roles/sys privileges properly. Does NOT yet handle object privs. They can be added but they are not considered while revoking privileges 44 | - The grants can be added as a string (dba,'select any dictionary','create any table'), or in a list (ie.g for use with with_items) 45 | 46 | **oracle_role** 47 | 48 | pre-req: cx_Oracle 49 | 50 | - Manages roles in the database 51 | 52 | **oracle_parameter** 53 | 54 | pre-req: cx_Oracle 55 | 56 | - Manages init parameters in the database (i.e alter system set parameter...) 57 | - Also handles underscore parameters. That will require using mode=sysdba, to be able to read the X$ tables needed to verify the existence of the parameter. 58 | 59 | **Note:** 60 | When specifying sga-parameters the database requests memory based on granules which are variable in size depending on the size requested, 61 | and that means the database may round the requested value to the nearest multiple of a granule. 62 | e.g sga_max_size=1500M will be rounded up to 1504 (which is 94 granules of 16MB). That will cause the displayed value to be 1504M, which has 63 | the effect that the next time the module is is run with a desired value of 1500M it will be changed again. 64 | So that is something to consider when setting parameters that affects the SGA. 65 | 66 | **oracle_services** 67 | 68 | pre-req: cx_Oracle (if GI is not running) 69 | 70 | - Manages services in an Oracle database (RAC/Single instance) 71 | 72 | **Note:** 73 | At the moment, Idempotence only applies to the state (present,absent,started, stopped). No other options are considered. 74 | 75 | 76 | **oracle_pdb** 77 | 78 | pre-req: cx_Oracle 79 | 80 | - Manages pluggable databases in an Oracle container database 81 | - Creates/deletes/opens/closes the pdb 82 | - saves the state if you want it to. Default is yes 83 | - Can place the datafiles in a separate location 84 | 85 | 86 | **oracle_sql** 87 | 88 | pre-req: cx_Oracle 89 | 90 | - 2 modes: sql or script 91 | - Executes arbitrary sql or runs a script 92 | 93 | **Note:** 94 | Should be considered as experimental, or an alpha-release 95 | 96 | 97 | **oracle_asmdg** 98 | 99 | pre-req: cx_Oracle 100 | 101 | - Manages ASM diskgroup state. (absent/present) 102 | - Takes a list of disks and makes sure those disks are part of the DG. 103 | If the disk is removed from the disk it will be removed from the DG. 104 | - Also manages attributes 105 | 106 | **Note:** 107 | - Supports redundancy levels, but does not yet handle specifying failuregroups 108 | 109 | 110 | **oracle_asmvol** 111 | 112 | - Manages ASM volumes. (absent/present) 113 | 114 | **oracle_ldapuser** 115 | 116 | pre-req: cx_Oracle, ldap, re 117 | 118 | - Syncronises users/role grants from LDAP/Active Directory to the database 119 | 120 | **oracle_privs** 121 | 122 | pre-req: cx_Oracle, re 123 | 124 | - Manages system and object level grants 125 | - Object level grant support wildcards, so now it is possible to grant access to all tables in a schema and maintain it automatically! 126 | 127 | **oracle_jobclass** 128 | 129 | pre-req: cx_Oracle 130 | 131 | - Manages DBMS_SCHEDULER job classes 132 | 133 | **oracle_jobschedule** 134 | 135 | pre-req: cx_Oracle, re 136 | 137 | - Manages DBMS_SCHEDULER job schedules 138 | 139 | **oracle_jobwindow** 140 | 141 | pre-req: cx_Oracle, datetime 142 | 143 | - Manages DBMS_SCHEDULER windows 144 | 145 | **oracle_job** 146 | 147 | pre-req: cx_Oracle, re 148 | 149 | - Manages DBMS_SCHEDULER jobs 150 | 151 | **oracle_rsrc_consgroup** 152 | 153 | pre-req: cx_Oracle, re 154 | 155 | - Manages resource manager consumer groups including its mappings and grants 156 | 157 | **oracle_awr** 158 | 159 | pre-req: cx_Oracle, datetime 160 | 161 | - Manages AWR snapshot settings 162 | 163 | **oracle_facts** 164 | 165 | pre-req: cx_Oracle 166 | 167 | - Gathers facts about Oracle database 168 | 169 | **oracle_gi_facts** 170 | 171 | - Gathers facts about Grid Infrastructure cluster configuration 172 | 173 | **oracle_stats_prefs** 174 | 175 | pre-req: cx_Oracle 176 | 177 | - Managing DBMS_STATS global preferences 178 | 179 | 180 | **oracle_redo** 181 | 182 | pre-rec: cx_Oracle 183 | 184 | - Manage redo-groups and their size in RAC or single instance environments 185 | - NOTE: For RAC environments, the database needs to be in ARCHIVELOG mode. This is not required for SI environments. 186 | 187 | **oracle_db** 188 | 189 | pre-rec: cx_Oracle 190 | 191 | - Create/remove databases (cdb/non-cdb) 192 | - Can be created by passing in a responsefile or just by using parameters 193 | 194 | 195 | -------------------------------------------------------------------------------- /oracle_acfs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_acfs 7 | short_description: Manage diskgroups in an Oracle database 8 | description: 9 | - Manage ACFS filesystems 10 | version_added: "2.1.0.0" 11 | options: 12 | volume_name: 13 | description: 14 | - The name of volume 15 | required: true 16 | default: None 17 | aliases: ['volume','volume_name'] 18 | diskgroup: 19 | state: 20 | description: 21 | - The intended state of the diskgroup. 'status' will just show the status of the diskgroup 22 | default: present 23 | choices: ['present','absent','status'] 24 | username: 25 | description: 26 | - The ASM username 27 | required: false 28 | default: sys 29 | aliases: ['un'] 30 | password: 31 | description: 32 | - The password for the ASM user 33 | required: false 34 | default: None 35 | aliases: ['pw'] 36 | service_name: 37 | description: 38 | - The diskgroup_name to connect to the database if using dbms_diskgroup. 39 | required: false 40 | default: +ASM 41 | aliases: ['sn'] 42 | hostname: 43 | description: 44 | - The host of the database if using dbms_diskgroup 45 | required: false 46 | default: localhost 47 | aliases: ['host'] 48 | port: 49 | description: 50 | - The listener port to connect to the database if using dbms_diskgroup 51 | required: false 52 | default: 1521 53 | oracle_home: 54 | description: 55 | - The GI ORACLE_HOME 56 | required: false 57 | default: None 58 | aliases: ['oh'] 59 | 60 | 61 | 62 | notes: 63 | - cx_Oracle needs to be installed 64 | requirements: [ "cx_Oracle" ] 65 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 66 | ''' 67 | 68 | EXAMPLES = ''' 69 | 70 | ''' 71 | import os 72 | 73 | try: 74 | import cx_Oracle 75 | except ImportError: 76 | cx_oracle_exists = False 77 | else: 78 | cx_oracle_exists = True 79 | 80 | 81 | # Check if the diskgroup exists 82 | def check_volume_exists(cursor, module, msg, name, diskgroup): 83 | 84 | sql = ''' 85 | select count (*) from v$asm_volume v,v$asm_diskgroup g 86 | where v.group_number = g.group_number 87 | and lower (g.name) = \'%s\' 88 | and lower (v.volume_name) = \'%s\' 89 | ''' % (diskgroup.lower(),name.lower()) 90 | result = execute_sql_get(module, msg, cursor, sql) 91 | #msg = 'Normal Result is: %s, [0] is: %s, [0][0] is: %s, len is: %s, type is: %s' % (result,result[0],result[0][0],len(result), type(result)) 92 | #module.exit_json(msg=msg) 93 | if result[0][0] > 0: 94 | return True 95 | else: 96 | return False 97 | 98 | def add_filesystem(cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint, mountowner, mountgroup, mountperm, mountusers): 99 | 100 | 101 | device_sql = ''' 102 | select volume_device from v$asm_volume v,v$asm_diskgroup g 103 | where v.group_number = g.group_number 104 | and lower (g.name) = \'%s\' 105 | and lower (v.volume_name) = \'%s\' 106 | ''' % (diskgroup.lower(),volume_name.lower()) 107 | # module.exit_json(msg=device_sql, changed=False) 108 | 109 | _device_name = execute_sql_get(module,msg,cursor,device_sql) 110 | 111 | 112 | if not _check_filesystem_exist(cursor, module, msg, oracle_home, _device_name): 113 | if _format_filesystem(module,msg,_device_name[0][0]): 114 | command = ''' 115 | %s/bin/srvctl add filesystem -device %s -path %s 116 | -user %s -mountowner %s -mountgroup %s 117 | -mountperm %s -fstype ACFS 118 | ''' % (oracle_home,_device_name[0][0],mountpoint,mountusers, mountowner, mountgroup, mountperm) 119 | (rc, stdout, stderr) = module.run_command(command) 120 | if rc != 0: 121 | msg = 'Error, stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 122 | module.fail_json(msg=msg, changed=False) 123 | else: 124 | return True 125 | else: 126 | # msg = 'Filesystem: %s already exists on volume: %s (%s)' % (mountpoint,volume_name.upper(),_device_name[0][0]) 127 | # module.exit_json(msg=msg, changed=False) 128 | return True 129 | 130 | def ensure_filesystem (cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint, mountowner, mountgroup, mountperm, mountusers): 131 | if state == 'present': 132 | if _start_filesystem(cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint): 133 | msg = 'Filesystem %s successfully started' % (mountpoint) 134 | module.exit_json(msg=msg, changed=True) 135 | 136 | elif state == 'absent': 137 | if _stop_filesystem(cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint): 138 | msg = 'Filesystem %s successfully stopped' % (mountpoint) 139 | module.exit_json(msg=msg, changed=True) 140 | 141 | def _check_filesystem_exist(cursor, module, msg, oracle_home, _device_name): 142 | 143 | checkcommand = '%s/bin/srvctl status filesystem -device %s' % (oracle_home,_device_name[0][0]) 144 | (rc, stdout, stderr) = module.run_command(checkcommand) 145 | if rc != 0: 146 | if 'PRCA-1070' in stdout: 147 | return False 148 | else: 149 | msg = 'Error, stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 150 | module.fail_json(msg=msg, changed=False) 151 | 152 | else: 153 | return True 154 | 155 | 156 | 157 | def _start_filesystem(cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint): 158 | 159 | command = '%s/bin/srvctl start filesystem -diskgroup %s -volume %s' % (oracle_home,diskgroup,volume_name) 160 | (rc, stdout, stderr) = module.run_command(command) 161 | if rc != 0: 162 | if 'CRS-5702' in stdout: 163 | msg = 'Filesystem %s already running' % (mountpoint) 164 | module.exit_json(msg=msg, changed=False) 165 | 166 | else: 167 | msg = 'Error, stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 168 | module.fail_json(msg=msg, changed=False) 169 | else: 170 | return True 171 | 172 | def _stop_filesystem (cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint): 173 | command = '%s/bin/srvctl stop filesystem -device %s' % (oracle_home,diskgroup, volume_name) 174 | (rc, stdout, stderr) = module.run_command(command) 175 | if rc != 0: 176 | msg = 'Error, stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 177 | module.fail_json(msg=msg, changed=False) 178 | else: 179 | return True 180 | 181 | def _remove_filesystem(module, msg, _device_name): 182 | 183 | command = '%s/bin/srvctl remove filesystem -device %s' % (oracle_home,_device_name[0][0]) 184 | (rc, stdout, stderr) = module.run_command(command) 185 | if rc != 0: 186 | msg = 'Error, stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 187 | module.fail_json(msg=msg, changed=False) 188 | else: 189 | return True 190 | 191 | def _format_filesystem(module, msg, _device_name): 192 | 193 | command = '/usr/sbin/mkfs -t acfs %s' % (_device_name) 194 | (rc, stdout, stderr) = module.run_command(command) 195 | if rc != 0: 196 | if 'ACFS-01010' in stderr: # <-- Device already formatted 197 | return True 198 | else: 199 | msg = 'Error, stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 200 | module.fail_json(msg=msg, changed=False) 201 | else: 202 | return True 203 | 204 | 205 | 206 | def execute_sql_get(module, msg, cursor, sql): 207 | 208 | #module.exit_json(msg="In execute_sql_get", changed=False) 209 | try: 210 | cursor.execute(sql) 211 | result = (cursor.fetchall()) 212 | except cx_Oracle.DatabaseError as exc: 213 | error, = exc.args 214 | msg = 'Something went wrong while executing sql_get - %s sql: %s' % (error.message, sql) 215 | module.fail_json(msg=msg, changed=False) 216 | return False 217 | 218 | return result 219 | 220 | 221 | def execute_sql(module, msg, cursor, sql): 222 | 223 | try: 224 | cursor.execute(sql) 225 | except cx_Oracle.DatabaseError as exc: 226 | error, = exc.args 227 | msg = 'Something went wrong while executing sql - %s sql: %s' % (error.message, sql) 228 | module.fail_json(msg=msg, changed=False) 229 | return False 230 | return True 231 | 232 | 233 | 234 | def main(): 235 | 236 | msg = '' 237 | cursor = None 238 | mode = 'sysasm' 239 | global state 240 | 241 | module = AnsibleModule( 242 | argument_spec = dict( 243 | volume_name = dict(required=True, aliases = ['volume']), 244 | diskgroup = dict(required=False, aliases = ['dg']), 245 | mountpoint = dict(required=True, aliases = ['mntp']), 246 | mountowner = dict(required=False), 247 | mountgroup = dict(required=False), 248 | mountperm = dict(required=False), 249 | mountusers = dict(required=False), 250 | state = dict(default="present", choices = ["present", "absent", "stopped", "started"]), 251 | user = dict(required=False, aliases = ['un','username']), 252 | password = dict(required=False, no_log=True, aliases = ['pw']), 253 | hostname = dict(required=False, default = 'localhost', aliases = ['host']), 254 | port = dict(required=False, default = 1521), 255 | service_name = dict(required=False, default = '+ASM', aliases = ['sn']), 256 | oracle_home = dict(required=False, aliases = ['oh']), 257 | 258 | 259 | 260 | ), 261 | 262 | ) 263 | 264 | volume_name = module.params["volume_name"] 265 | diskgroup = module.params["diskgroup"] 266 | mountpoint = module.params["mountpoint"] 267 | mountowner = module.params["mountowner"] 268 | mountgroup = module.params["mountgroup"] 269 | mountperm = module.params["mountperm"] 270 | mountusers = module.params["mountusers"] 271 | state = module.params["state"] 272 | user = module.params["user"] 273 | password = module.params["password"] 274 | hostname = module.params["hostname"] 275 | port = module.params["port"] 276 | service_name = module.params["service_name"] 277 | oracle_home = module.params["oracle_home"] 278 | 279 | 280 | if not cx_oracle_exists: 281 | msg = "The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set" 282 | module.fail_json(msg=msg) 283 | 284 | wallet_connect = '/@%s' % service_name 285 | try: 286 | if (not user and not password) : # If neither user or password is supplied, the use of an oracle wallet is assumed 287 | connect = wallet_connect 288 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSASM) 289 | elif (user and password): 290 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 291 | connect = dsn 292 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSASM) 293 | elif (not(user) or not(password)): 294 | module.fail_json(msg='Missing username or password for cx_Oracle') 295 | 296 | except cx_Oracle.DatabaseError as exc: 297 | error, = exc.args 298 | msg = 'Could not connect to ASM: %s, connect descriptor: %s' % (error.message, connect) 299 | module.fail_json(msg=msg, changed=False) 300 | 301 | cursor = conn.cursor() 302 | 303 | if state == 'present': 304 | if check_volume_exists(cursor, module, msg, volume_name, diskgroup): 305 | if add_filesystem (cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint, mountowner, mountgroup, mountperm, mountusers): 306 | # msg = 'Successfully added filesystem %s in volume %s' % (mountpoint, volume_name.upper()) 307 | # module.exit_json(msg=msg, changed=True) 308 | ensure_filesystem (cursor, module, msg, oracle_home, volume_name, diskgroup, mountpoint, mountowner, mountgroup, mountperm, mountusers) 309 | else: 310 | msg = 'say what?!' 311 | module.fail_json(msg=msg, changed=False) 312 | # else: 313 | # ensure_diskgroup_state(cursor, module, msg, name, state, disks, attribute_name, attribute_value) 314 | 315 | elif state == 'absent' : 316 | if check_diskgroup_exists(cursor, module, msg, name): 317 | if remove_diskgroup(cursor, module, msg, oracle_home, name): 318 | msg = 'Diskgroup %s successfully removed' % (name) 319 | module.exit_json(msg=msg, changed=True) 320 | else: 321 | module.exit_json(msg=msg, changed=False) 322 | else: 323 | msg = 'Diskgroup %s doesn\'t exist' % (name) 324 | module.exit_json(msg=msg, changed=False) 325 | 326 | module.fail_json(msg="Unhandled exit", changed=False) 327 | 328 | 329 | 330 | 331 | from ansible.module_utils.basic import * 332 | if __name__ == '__main__': 333 | main() 334 | -------------------------------------------------------------------------------- /oracle_asmdg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_asmdg 7 | short_description: Manage diskgroups in an Oracle database 8 | description: 9 | - Manage diskgroups in an Oracle database 10 | version_added: "2.1.0.0" 11 | options: 12 | name: 13 | description: 14 | - The name of the diskgroup 15 | required: true 16 | default: None 17 | aliases: ['diskgroup','dg'] 18 | state: 19 | description: 20 | - The intended state of the diskgroup. 'status' will just show the status of the diskgroup 21 | default: present 22 | choices: ['present','absent','status'] 23 | disks: 24 | description: 25 | - A list of disks that should be part of the diskgroup. Only the listed disks will be part of the DG, meaning if the disk is removed from the list it will also be removed from the DG 26 | default: None 27 | redundancy: 28 | description: 29 | - The redundancy configuration for the diskgroup, It does not yet support putting disks in specific failure groups 30 | default: external 31 | choices: ['external','normal','high'] 32 | attribute_name: 33 | description: 34 | - The attribute name (e.g compatible.rdbms) 35 | default: None 36 | aliases: ['an'] 37 | attribute_value: 38 | description: 39 | - The attribute value (e.g 12.1.0.2) 40 | default: None 41 | aliases: ['av'] 42 | username: 43 | description: 44 | - The ASM username 45 | required: false 46 | default: sys 47 | aliases: ['un'] 48 | password: 49 | description: 50 | - The password for the ASM user 51 | required: false 52 | default: None 53 | aliases: ['pw'] 54 | service_name: 55 | description: 56 | - The diskgroup_name to connect to the database if using dbms_diskgroup. 57 | required: false 58 | default: +ASM 59 | aliases: ['sn'] 60 | hostname: 61 | description: 62 | - The host of the database if using dbms_diskgroup 63 | required: false 64 | default: localhost 65 | aliases: ['host'] 66 | port: 67 | description: 68 | - The listener port to connect to the database if using dbms_diskgroup 69 | required: false 70 | default: 1521 71 | oracle_home: 72 | description: 73 | - The GI ORACLE_HOME 74 | required: false 75 | default: None 76 | aliases: ['oh'] 77 | 78 | 79 | 80 | notes: 81 | - cx_Oracle needs to be installed 82 | requirements: [ "cx_Oracle" ] 83 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 84 | ''' 85 | 86 | EXAMPLES = ''' 87 | # Create a diskgroup 88 | oracle_asmdg: 89 | name: MYDG1 90 | disks: 91 | - ORCL:MYDG1 92 | - ORCL:MYDG2 93 | attribute_name: compatible.asm 94 | attribute_value: 12.1.0.2 95 | redundancy: external 96 | state: present 97 | un: sys 98 | pw: oracle123 99 | sn: '+ASM' 100 | host: localhost 101 | oh: /u01/app/oracle/12.1.2.0/grid 102 | 103 | oracle_asmdg: 104 | name: DATA 105 | disks: 106 | - /dev/oracle/data1 107 | - /dev/oracle/data2 108 | attributes: 109 | - {name: compatible.asm, value: 12.2.0.1.0 } 110 | - {name: compatible.rdbms, value: 12.2.0.1.0 } 111 | redundancy: external 112 | state: present 113 | un: sys 114 | pw: oracle123 115 | sn: '+ASM' 116 | host: localhost 117 | oh: /u01/app/oracle/12.2.0.1/grid 118 | 119 | ''' 120 | import os 121 | 122 | try: 123 | import cx_Oracle 124 | except ImportError: 125 | cx_oracle_exists = False 126 | else: 127 | cx_oracle_exists = True 128 | 129 | 130 | # Check if the diskgroup exists 131 | def check_diskgroup_exists(cursor, module, msg, name): 132 | 133 | sql = 'select count(*) from gv$asm_diskgroup where lower (name) = \'%s\'' % (name.lower()) 134 | result = execute_sql_get(module, msg, cursor, sql) 135 | #msg = 'Normal Result is: %s, [0] is: %s, [0][0] is: %s, len is: %s, type is: %s' % (result,result[0],result[0][0],len(result), type(result)) 136 | #module.exit_json(msg=msg) 137 | if result[0][0] > 0: 138 | return True 139 | else: 140 | return False 141 | 142 | def create_diskgroup(cursor, module, msg, oracle_home, name, disks, redundancy, attribute_name, attribute_value): 143 | 144 | add_attr = False 145 | if not any(x == 'None' for x in attribute_name): 146 | add_attr = True 147 | if not any(x == None for x in attribute_name): 148 | add_attr = True 149 | 150 | 151 | if add_attr: 152 | attributes =','.join(['\''+str(n)+'\'' +'='+'\''+ str(v) + '\'' for n,v in zip(attribute_name,attribute_value)]) 153 | 154 | disklist = "','".join(disks) 155 | sql = 'create diskgroup %s ' % (name) 156 | sql += '%s redundancy ' % (redundancy) 157 | sql += 'disk \'%s\' ' % (disklist) 158 | if add_attr: 159 | sql += ' attribute %s' % (attributes.lower()) 160 | 161 | if execute_sql(module, msg, cursor, sql): 162 | if rac: 163 | command = '%s/bin/srvctl start diskgroup -g %s' % (oracle_home, name.lower()) 164 | (rc, stdout, stderr) = module.run_command(command) 165 | if rc != 0: 166 | if 'CRS-5702' in stdout: #'Edge-case', where there is only one instance in the cluster. The diskgroup is already running after create statement so this command errors 167 | return True 168 | else: 169 | msg = 'Error, couldn\'t mount the dg on all nodes. stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 170 | module.fail_json(msg=msg, changed=False) 171 | else: 172 | return True 173 | else: 174 | return True 175 | else: 176 | msg = 'error in exec sql create' 177 | module.fail_json(msg=msg, changed=False) 178 | return False 179 | 180 | 181 | def remove_diskgroup(cursor, module, msg, oracle_home, name): 182 | 183 | 184 | mountsql = 'alter diskgroup %s mount' % (name.lower()) 185 | dropsql = 'drop diskgroup %s' % (name.lower()) 186 | 187 | # If in a rac config, we need to unmount the dg on all nodes, then mount 188 | if rac: 189 | command = '%s/bin/srvctl stop diskgroup -g %s' % (oracle_home, name.lower()) 190 | (rc, stdout, stderr) = module.run_command(command) 191 | if rc != 0: 192 | msg = 'Error, couldn\'t unmount the dg. stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 193 | return False 194 | 195 | if execute_sql(module, msg, cursor, mountsql): 196 | if execute_sql(module, msg, cursor, dropsql): 197 | return True 198 | else: 199 | return False 200 | else: 201 | return False 202 | else: 203 | if execute_sql(module, msg, cursor, dropsql): 204 | return True 205 | else: 206 | return False 207 | 208 | def ensure_diskgroup_state(cursor, module, msg, name, state, disks, attribute_name, attribute_value): 209 | 210 | total_sql = [] 211 | #disk_sql = [] 212 | disk_sql = 'alter diskgroup %s ' % (name.upper()) 213 | change_attr = False 214 | change_disk = False 215 | get_ro_attr_sql = 'select distinct(name) from v$asm_attribute where read_only = \'Y\'' 216 | read_only_attributes = [] 217 | 218 | # Deal with attribute differences 219 | if (attribute_name and attribute_value): 220 | # Get all read only attributes 221 | get_ro_attr = execute_sql_get(module, msg, cursor, get_ro_attr_sql) 222 | for a in get_ro_attr: 223 | read_only_attributes.append(a[0]) 224 | 225 | # Make sure properties are lower case 226 | attribute_name = [x.lower() for x in attribute_name] 227 | attribute_value = [y.lower() for y in attribute_value] 228 | wanted_attributes = zip(attribute_name,attribute_value) 229 | 230 | # Make sure we don't try to modify read only attributes. Removing them from the wanted_attributes list 231 | for a in wanted_attributes: 232 | if a[0] in read_only_attributes: 233 | wanted_attributes.remove(a) 234 | 235 | # Check the current attributes 236 | attribute_names_ =','.join(['\''+str(n[0])+'\'' for n in (wanted_attributes)]) 237 | # Only get current attributes if we still have attributes in the wanted list 238 | if len(attribute_names_) != 0: 239 | current_properties = get_current_properties (cursor, module, msg, name, attribute_names_) 240 | # Convert to dict and compare current with wanted 241 | if cmp(dict(current_properties),dict(wanted_attributes)) is not 0: 242 | change_attr = True 243 | for i in wanted_attributes: 244 | total_sql.append("alter diskgroup %s set attribute '%s'='%s'" % (name, i[0], i[1])) 245 | 246 | 247 | list_current_name = [] 248 | list_current_path = [] 249 | list_wanted = [x.upper() if ':' in x else x for x in disks] 250 | list_current = get_current_disks(cursor, module, msg, name) 251 | 252 | for p,n in list_current: 253 | list_current_name.append(n) 254 | list_current_path.append(p) 255 | 256 | # List of disks to add 257 | list_add=set(list_wanted).difference(list_current_path) 258 | # List of disks to remove 259 | list_remove=set(list_current_path).difference(list_wanted) 260 | # Pick out the v$asm_disk.name from the diskgroup 261 | remove_disks = [a[1] for a in list_current if a[0] in list_remove ] 262 | 263 | add_disk = "','".join(list_add) 264 | remove_disk= "','".join(remove_disks) 265 | if sorted(list_current_path) == sorted(list_wanted) and change_attr == False: 266 | msg = "Diskgroup %s is in the intended state" % (name) 267 | module.exit_json(msg=msg, changed=False) 268 | 269 | if len(list_add)>= 1: 270 | change_disk = True 271 | disk_sql += ' add disk ' 272 | disk_sql += "'%s'" % add_disk 273 | 274 | if len(list_remove) >= 1: 275 | #disk_sql = 'alter diskgroup %s ' % (name.upper()) 276 | change_disk = True 277 | disk_sql += ' drop disk ' 278 | disk_sql += "'%s'" % remove_disk 279 | if change_disk: 280 | total_sql.append(disk_sql) 281 | 282 | if ensure_diskgroup_state_sql(module,msg,cursor,total_sql): 283 | msg = 'Diskgroup %s has been put in the intended state' % (name) 284 | module.exit_json(msg=msg, changed=True) 285 | else: 286 | return False 287 | 288 | def ensure_diskgroup_state_sql(module,msg,cursor,total_sql): 289 | 290 | for a in total_sql: 291 | execute_sql(module, msg, cursor, a) 292 | return True 293 | 294 | def get_current_disks(cursor, module, msg, name): 295 | 296 | 297 | sql = 'select d.path,d.name from v$asm_disk d, v$asm_diskgroup dg ' 298 | sql += 'where dg.group_number = d.group_number ' 299 | sql += 'and upper(dg.name) = \'%s\'' % (name.upper()) 300 | 301 | result = execute_sql_get(module, msg, cursor, sql) 302 | return result 303 | 304 | def get_current_properties(cursor, module, msg, name,attribute_names_): 305 | 306 | 307 | sql = 'select lower(a.name),lower(a.value) from v$asm_attribute a, v$asm_diskgroup dg ' 308 | sql += 'where dg.group_number = a.group_number ' 309 | sql += 'and upper(dg.name) = \'%s\' ' % (name.upper()) 310 | sql += 'and a.name in (%s) ' % (attribute_names_.lower()) 311 | 312 | result = execute_sql_get(module, msg, cursor, sql) 313 | return result 314 | 315 | def execute_sql_get(module, msg, cursor, sql): 316 | 317 | #module.exit_json(msg="In execute_sql_get", changed=False) 318 | try: 319 | cursor.execute(sql) 320 | result = (cursor.fetchall()) 321 | except cx_Oracle.DatabaseError as exc: 322 | error, = exc.args 323 | msg = 'Something went wrong while executing sql_get - %s sql: %s' % (error.message, sql) 324 | module.fail_json(msg=msg, changed=False) 325 | return False 326 | 327 | return result 328 | 329 | 330 | def execute_sql(module, msg, cursor, sql): 331 | 332 | try: 333 | cursor.execute(sql) 334 | except cx_Oracle.DatabaseError as exc: 335 | error, = exc.args 336 | msg = 'Something went wrong while executing sql - %s sql: %s' % (error.message, sql) 337 | module.fail_json(msg=msg, changed=False) 338 | return False 339 | return True 340 | 341 | 342 | 343 | def main(): 344 | 345 | msg = [''] 346 | cursor = None 347 | mode = 'sysasm' 348 | global rac 349 | 350 | module = AnsibleModule( 351 | argument_spec = dict( 352 | name = dict(required=True, aliases = ['diskgroup','dg']), 353 | disks = dict(required=False, type='list'), 354 | redundancy = dict(default="external", choices = ["external","normal","high","flex"]), 355 | attribute_name = dict(required=False, type='list', aliases=['an']), 356 | attribute_value = dict(required=False, type='list', aliases=['av']), 357 | state = dict(default="present", choices = ["present", "absent", "status"]), 358 | user = dict(required=False, aliases = ['un','username']), 359 | password = dict(required=False, no_log=True, aliases = ['pw']), 360 | hostname = dict(required=False, default = 'localhost', aliases = ['host']), 361 | port = dict(required=False, default = 1521), 362 | service_name = dict(required=False, default = '+ASM', aliases = ['sn']), 363 | oracle_home = dict(required=False, aliases = ['oh']), 364 | 365 | 366 | 367 | ), 368 | 369 | ) 370 | 371 | name = module.params["name"] 372 | disks = module.params["disks"] 373 | redundancy = module.params["redundancy"] 374 | attribute_name = module.params["attribute_name"] 375 | attribute_value = module.params["attribute_value"] 376 | state = module.params["state"] 377 | user = module.params["user"] 378 | password = module.params["password"] 379 | hostname = module.params["hostname"] 380 | port = module.params["port"] 381 | service_name = module.params["service_name"] 382 | oracle_home = module.params["oracle_home"] 383 | 384 | 385 | if not cx_oracle_exists: 386 | msg = "The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set" 387 | module.fail_json(msg=msg) 388 | 389 | wallet_connect = '/@%s' % service_name 390 | try: 391 | if (not user and not password) : # If neither user or password is supplied, the use of an oracle wallet is assumed 392 | connect = wallet_connect 393 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSASM) 394 | elif (user and password): 395 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 396 | connect = dsn 397 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSASM) 398 | elif (not(user) or not(password)): 399 | module.fail_json(msg='Missing username or password for cx_Oracle') 400 | 401 | except cx_Oracle.DatabaseError as exc: 402 | error, = exc.args 403 | msg = 'Could not connect to ASM: %s, connect descriptor: %s, username: %s, pass: %s' % (error.message, connect,user,password) 404 | module.fail_json(msg=msg, changed=False) 405 | 406 | cursor = conn.cursor() 407 | 408 | checkifracsql = 'select parallel from v$instance' 409 | checkifrac = execute_sql_get(module, msg, cursor, checkifracsql) 410 | if checkifrac[0][0] == 'YES': 411 | rac = True 412 | if oracle_home is not None: 413 | os.environ['ORACLE_HOME'] = oracle_home 414 | elif 'ORACLE_HOME' in os.environ: 415 | oracle_home = os.environ['ORACLE_HOME'] 416 | else: 417 | msg = 'ORACLE_HOME variable not set. Please set it and re-run the command' 418 | module.fail_json(msg=msg, changed=False) 419 | else: 420 | rac = False 421 | 422 | 423 | if state == 'present': 424 | if not check_diskgroup_exists(cursor, module, msg, name): 425 | if create_diskgroup(cursor, module, msg, oracle_home, name, disks, redundancy, attribute_name, attribute_value): 426 | msg = 'Successfully created diskgroup %s ' % (name) 427 | module.exit_json(msg=msg, changed=True) 428 | else: 429 | msg = 'say what?!' 430 | module.fail_json(msg=msg, changed=False) 431 | else: 432 | ensure_diskgroup_state(cursor, module, msg, name, state, disks, attribute_name, attribute_value) 433 | 434 | elif state == 'absent' : 435 | if check_diskgroup_exists(cursor, module, msg, name): 436 | if remove_diskgroup(cursor, module, msg, oracle_home, name): 437 | msg = 'Diskgroup %s successfully removed' % (name) 438 | module.exit_json(msg=msg, changed=True) 439 | else: 440 | module.exit_json(msg=msg, changed=False) 441 | else: 442 | msg = 'Diskgroup %s doesn\'t exist' % (name) 443 | module.exit_json(msg=msg, changed=False) 444 | 445 | elif state == 'status' : 446 | if check_diskgroup_exists(cursor, module, msg, name): 447 | result = get_current_disks(cursor, module, msg, name) 448 | #msg = 'Diskgroup %s successfully removed' % (name) 449 | module.exit_json(msg=result, changed=False) 450 | else: 451 | module.exit_json(msg=msg, changed=False) 452 | 453 | 454 | module.exit_json(msg="Unhandled exit", changed=False) 455 | 456 | 457 | 458 | 459 | from ansible.module_utils.basic import * 460 | if __name__ == '__main__': 461 | main() 462 | -------------------------------------------------------------------------------- /oracle_asmvol: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_asmvol 7 | short_description: Manage Oracle ASMCMD Volumes 8 | description: 9 | - Manage Oracle advm Volumes 10 | version_added: "2.1.0.0" 11 | options: 12 | name: 13 | description: 14 | - The name of the volume 15 | required: True 16 | default: None 17 | size: 18 | description: 19 | - The size of the volume 20 | default: None 21 | column: 22 | description: 23 | - Number of columns in a stripe set 24 | required: False 25 | width: 26 | description: 27 | - Stripe width of a volume 28 | required: False 29 | diskgroup: 30 | description: 31 | - The diskgroup in which to create the volume 32 | required: True 33 | default: None 34 | aliases: ['dg'] 35 | state: 36 | description: 37 | - The state of the volume. 38 | default: present 39 | choices: ['present','absent', 'status'] 40 | # oracle_sid: 41 | # description: 42 | # - The name of the ASM instance 43 | # default: +ASM 44 | # aliases: ['sid'] 45 | oracle_home: 46 | description: 47 | - The GI ORACLE_HOME 48 | required: false 49 | default: None 50 | aliases: ['oh'] 51 | 52 | notes: 53 | 54 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 55 | ''' 56 | 57 | EXAMPLES = ''' 58 | # Create an ASM volume 59 | oracle_asmvol: name=acfsvol dg=acfsdg size=100G state=present oh=/u01/app/grid/12.1.0.2/grid 60 | 61 | # Delete an ASM volume 62 | oracle_asmvol: name=acfsvol dg=acfsdg state=absent oh=/u01/app/grid/12.1.0.2/grid 63 | 64 | ''' 65 | import os 66 | try: 67 | import cx_Oracle 68 | except ImportError: 69 | cx_oracle_exists = False 70 | else: 71 | cx_oracle_exists = True 72 | 73 | 74 | 75 | # Check if the volume exists 76 | def check_vol_exists(cursor, module, msg, diskgroup, name): 77 | 78 | sql = ''' 79 | select count (*) from v$asm_volume v,v$asm_diskgroup g 80 | where v.group_number = g.group_number 81 | and lower (g.name) = \'%s\' 82 | and lower (v.volume_name) = \'%s\' 83 | ''' % (diskgroup.lower(),name.lower()) 84 | result = execute_sql_get(module, msg, cursor, sql) 85 | # msg = 'Normal Result is: %s, [0] is: %s, [0][0] is: %s, len is: %s, type is: %s' % (result,result[0],result[0][0],len(result), type(result)) 86 | # module.exit_json(msg=msg) 87 | if result[0][0] > 0: 88 | return True 89 | else: 90 | return False 91 | # command = '%s/bin/asmcmd volinfo -G %s %s' % (oracle_home, diskgroup, name) 92 | # (rc, stdout, stderr) = module.run_command(command) 93 | # if rc != 0: 94 | # msg = 'Error, stdout: %s, stderr: %s, command is %s' % (stdout, stderr, command) 95 | # module.fail_json(msg=msg, changed=False) 96 | # 97 | # 98 | # if 'not found' in stdout: 99 | # return False 100 | # else: 101 | # return True 102 | 103 | 104 | def create_vol(cursor, module, msg, diskgroup, name, size): 105 | 106 | sql = 'alter diskgroup %s ' % (diskgroup) 107 | sql += 'add volume %s ' % (name) 108 | sql += 'size %s ' % (size) 109 | 110 | if execute_sql(module, msg, cursor, sql): 111 | return True 112 | else: 113 | msg = 'error in exec sql create' 114 | module.fail_json(msg=msg, changed=False) 115 | 116 | 117 | # command = '%s/bin/asmcmd volcreate %s -G %s -s %s ' % (oracle_home, name, diskgroup, size) 118 | # 119 | # if column is not None: 120 | # command += ' --column %s' % (column) 121 | # if width is not None: 122 | # command += ' --width %s' % (width) 123 | # if redundancy is not None: 124 | # command += ' --redundancy %s' % (redundancy) 125 | # 126 | # (rc, stdout, stderr) = module.run_command(command) 127 | # if rc != 0: 128 | # msg = 'Error, STDOUT: %s, STDERR: %s, command is: %s' % (stdout, stderr, command) 129 | # module.fail_json(msg=msg, changed=False) 130 | # else: 131 | # return True #<-- all is well 132 | 133 | def remove_vol(cursor, module, msg, diskgroup, name): 134 | 135 | sql = 'alter diskgroup %s ' % (diskgroup) 136 | sql += 'drop volume %s ' % (name) 137 | 138 | if execute_sql(module, msg, cursor, sql): 139 | return True 140 | else: 141 | msg = 'error in exec sql remove' 142 | module.fail_json(msg=msg, changed=False) 143 | 144 | def execute_sql_get(module, msg, cursor, sql): 145 | 146 | #module.exit_json(msg="In execute_sql_get", changed=False) 147 | try: 148 | cursor.execute(sql) 149 | result = (cursor.fetchall()) 150 | except cx_Oracle.DatabaseError as exc: 151 | error, = exc.args 152 | msg = 'Something went wrong while executing sql_get - %s sql: %s' % (error.message, sql) 153 | module.fail_json(msg=msg, changed=False) 154 | return False 155 | 156 | return result 157 | 158 | 159 | def execute_sql(module, msg, cursor, sql): 160 | 161 | try: 162 | cursor.execute(sql) 163 | except cx_Oracle.DatabaseError as exc: 164 | error, = exc.args 165 | msg = 'Something went wrong while executing sql - %s sql: %s' % (error.message, sql) 166 | module.fail_json(msg=msg, changed=False) 167 | return False 168 | return True 169 | 170 | def main(): 171 | 172 | msg = [''] 173 | 174 | module = AnsibleModule( 175 | argument_spec = dict( 176 | name = dict(required=True, aliases = ['volume_name']), 177 | diskgroup = dict(required=True, aliases = ['dg']), 178 | size = dict(required=False), 179 | column = dict(default=None), 180 | width = dict(default=None), 181 | redundancy = dict(default=None), 182 | state = dict(default="present", choices = ["present", "absent"]), 183 | user = dict(required=False, aliases = ['un','username']), 184 | password = dict(required=False, no_log=True, aliases = ['pw']), 185 | hostname = dict(required=False, default = 'localhost', aliases = ['host']), 186 | port = dict(required=False, default = 1521), 187 | service_name = dict(required=False, default = '+ASM', aliases = ['sn']), 188 | oracle_home = dict(required=False, aliases = ['oh']), 189 | 190 | ), 191 | 192 | ) 193 | 194 | name = module.params["name"] 195 | diskgroup = module.params["diskgroup"] 196 | size = module.params["size"] 197 | column = module.params["column"] 198 | width = module.params["width"] 199 | redundancy = module.params["redundancy"] 200 | state = module.params["state"] 201 | user = module.params["user"] 202 | password = module.params["password"] 203 | hostname = module.params["hostname"] 204 | port = module.params["port"] 205 | service_name = module.params["service_name"] 206 | oracle_home = module.params["oracle_home"] 207 | 208 | 209 | # if oracle_home is not None: 210 | # os.environ['ORACLE_HOME'] = oracle_home 211 | # elif 'ORACLE_HOME' in os.environ: 212 | # oracle_home = os.environ['ORACLE_HOME'] 213 | # else: 214 | # msg = 'ORACLE_HOME variable not set. Please set it and re-run the command' 215 | # module.fail_json(msg=msg, changed=False) 216 | 217 | # if oracle_sid != '+ASM': 218 | # os.environ['ORACLE_SID'] = oracle_sid 219 | # elif 'ORACLE_SID' in os.environ: 220 | # oracle_sid = os.environ['ORACLE_SID'] 221 | 222 | if not cx_oracle_exists: 223 | msg = "The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set" 224 | module.fail_json(msg=msg) 225 | 226 | wallet_connect = '/@%s' % service_name 227 | try: 228 | if (not user and not password) : # If neither user or password is supplied, the use of an oracle wallet is assumed 229 | connect = wallet_connect 230 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSASM) 231 | elif (user and password): 232 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 233 | connect = dsn 234 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSASM) 235 | elif (not(user) or not(password)): 236 | module.fail_json(msg='Missing username or password for cx_Oracle') 237 | 238 | except cx_Oracle.DatabaseError as exc: 239 | error, = exc.args 240 | msg = 'Could not connect to ASM: %s, connect descriptor: %s, username: %s, pass: %s' % (error.message, connect,user,password) 241 | module.fail_json(msg=msg, changed=False) 242 | 243 | cursor = conn.cursor() 244 | 245 | 246 | 247 | if state == 'present' and not size: 248 | msg = 'Missing argument: size. Please add and re-run the command' 249 | module.fail_json(msg=msg, changed=False) 250 | 251 | if state == 'present': 252 | if not check_vol_exists(cursor, module, msg, diskgroup, name): 253 | if create_vol(cursor, module, msg, diskgroup, name, size): 254 | msg = 'Volume %s successfully created. Size: %s ' % (name.upper(), size) 255 | module.exit_json(msg=msg, changed=True) 256 | else: 257 | msg = 'Volume %s already exists' % (name.upper()) 258 | module.exit_json(msgt=msg, changed=False) 259 | 260 | elif state == 'absent' : 261 | if check_vol_exists(cursor, module, msg, diskgroup, name): 262 | if remove_vol(cursor, module, msg, diskgroup, name): 263 | msg = 'Volume %s successfully removed' % (name.upper()) 264 | module.exit_json(msg=msg, changed=True) 265 | else: 266 | msg = 'Volume %s doesn\'t exist' % (name.upper()) 267 | module.exit_json(msg=msg, changed=False) 268 | 269 | 270 | 271 | module.exit_json(msg="Unhandled exit", changed=False) 272 | 273 | 274 | 275 | 276 | 277 | from ansible.module_utils.basic import * 278 | if __name__ == '__main__': 279 | main() 280 | -------------------------------------------------------------------------------- /oracle_awr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_awr 7 | short_description: Manage AWR configuration 8 | description: 9 | - Manage AWR configuration 10 | - Can be run locally on the controlmachine or on a remote host 11 | version_added: "2.2.1" 12 | options: 13 | hostname: 14 | description: 15 | - The Oracle database host 16 | required: false 17 | default: localhost 18 | port: 19 | description: 20 | - The listener port number on the host 21 | required: false 22 | default: 1521 23 | service_name: 24 | description: 25 | - The database service name to connect to 26 | required: true 27 | user: 28 | description: 29 | - The Oracle user name to connect to the database, must have DBA privilege 30 | required: False 31 | password: 32 | description: 33 | - The Oracle user password for 'user' 34 | required: False 35 | mode: 36 | description: 37 | - The mode with which to connect to the database 38 | required: true 39 | default: normal 40 | choices: ['normal','sysdba'] 41 | snapshot_interval_min: 42 | description: 43 | - AWR snapshot interval in minutes; 0 disables 44 | default: 60 45 | type: int 46 | aliases: 47 | - interval 48 | snapshot_retention_days: 49 | description: 50 | - AWR snapshot retention time in days 51 | default: 8 52 | type: int 53 | aliases: 54 | - retention 55 | notes: 56 | - cx_Oracle needs to be installed 57 | - Oracle RDBMS 10gR2 or later required 58 | requirements: [ "cx_Oracle" ] 59 | author: Ilmar Kerm, ilmar.kerm@gmail.com, @ilmarkerm 60 | ''' 61 | 62 | EXAMPLES = ''' 63 | --- 64 | - hosts: localhost 65 | vars: 66 | oraclehost: 192.168.56.101 67 | oracleport: 1521 68 | oracleservice: orcl12c 69 | oracleuser: system 70 | oraclepassword: oracle 71 | oracle_env: 72 | ORACLE_HOME: /usr/lib/oracle/12.1/client64 73 | LD_LIBRARY_PATH: /usr/lib/oracle/12.1/client64/lib 74 | tasks: 75 | - name: set AWR settings 76 | oracle_awr: 77 | hostname: "{{ oraclehost }}" 78 | port: "{{ oracleport }}" 79 | service_name: "{{ oracleservice }}" 80 | user: "{{ oracleuser }}" 81 | password: "{{ oraclepassword }}" 82 | interval: 30 83 | retention: 40 84 | environment: "{{ oracle_env }}" 85 | ''' 86 | 87 | from datetime import timedelta 88 | 89 | try: 90 | import cx_Oracle 91 | except ImportError: 92 | cx_oracle_exists = False 93 | else: 94 | cx_oracle_exists = True 95 | 96 | def query_existing(): 97 | c = conn.cursor() 98 | c.execute("select c.snap_interval, c.retention from dba_hist_wr_control c join v$database d on c.dbid = d.dbid") 99 | result = c.fetchone() 100 | if c.rowcount > 0: 101 | return {"exists": True, "snap_interval": result[0], "retention": result[1]} 102 | else: 103 | return {"exists": False} 104 | 105 | # Ansible code 106 | def main(): 107 | global lconn, conn, msg, module 108 | msg = [''] 109 | module = AnsibleModule( 110 | argument_spec = dict( 111 | hostname = dict(default='localhost'), 112 | port = dict(default=1521, type='int'), 113 | service_name = dict(required=True), 114 | user = dict(required=False), 115 | password = dict(required=False), 116 | mode = dict(default='normal', choices=["normal","sysdba"]), 117 | snapshot_interval_min = dict(default=60, type='int', aliases=['interval']), 118 | snapshot_retention_days = dict(default=8, type='int', aliases=['retention']) 119 | ), 120 | supports_check_mode=True 121 | ) 122 | # Check for required modules 123 | if not cx_oracle_exists: 124 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 125 | # Check input parameters 126 | if module.params['snapshot_interval_min'] < 10 and module.params['snapshot_interval_min'] != 0: 127 | module.fail_json(msg="Snapshot interval must be >= 10 or 0", changed=False) 128 | if module.params['snapshot_interval_min'] > 1000: 129 | module.fail_json(msg="You probably entered incorrect snapshot interval time", changed=False) 130 | if module.params['snapshot_retention_days'] <= 0: 131 | module.fail_json(msg="Snapshot retention must be > 0", changed=False) 132 | snap_interval = timedelta(minutes=module.params['snapshot_interval_min']) 133 | snap_retention = timedelta(days=module.params['snapshot_retention_days']) 134 | # Connect to database 135 | hostname = module.params["hostname"] 136 | port = module.params["port"] 137 | service_name = module.params["service_name"] 138 | user = module.params["user"] 139 | password = module.params["password"] 140 | mode = module.params["mode"] 141 | wallet_connect = '/@%s' % service_name 142 | try: 143 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 144 | if mode == 'sysdba': 145 | connect = wallet_connect 146 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 147 | else: 148 | connect = wallet_connect 149 | conn = cx_Oracle.connect(wallet_connect) 150 | 151 | elif (user and password ): 152 | if mode == 'sysdba': 153 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 154 | connect = dsn 155 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 156 | else: 157 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 158 | connect = dsn 159 | conn = cx_Oracle.connect(user, password, dsn) 160 | 161 | elif (not(user) or not(password)): 162 | module.fail_json(msg='Missing username or password for cx_Oracle') 163 | 164 | except cx_Oracle.DatabaseError as exc: 165 | error, = exc.args 166 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 167 | module.fail_json(msg=msg[0], changed=False) 168 | if conn.version < "10.2": 169 | module.fail_json(msg="Database version must be 10gR2 or greater", changed=False) 170 | # 171 | if module.check_mode: 172 | module.exit_json(changed=False) 173 | # 174 | result_changed = False 175 | result = query_existing() 176 | if result['exists']: 177 | if (snap_interval > timedelta(minutes=0) and snap_interval != result['snap_interval']) or (snap_interval == timedelta(minutes=0) and result['snap_interval'] != timedelta(days=40150)) or (snap_retention != result['retention']): 178 | c = conn.cursor() 179 | c.execute("CALL DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS(retention=>:retention, interval=>:interval)", 180 | {'retention': (module.params['snapshot_retention_days']*1440), 'interval': module.params['snapshot_interval_min']}) 181 | result_changed = True 182 | else: 183 | module.fail_json(msg="Should not be here, something went wrong", changed=False) 184 | 185 | conn.commit() 186 | module.exit_json(msg=", ".join(msg), changed=result_changed) 187 | 188 | 189 | from ansible.module_utils.basic import * 190 | if __name__ == '__main__': 191 | main() 192 | -------------------------------------------------------------------------------- /oracle_datapatch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_datapatch 7 | short_description: Manage datapatch functionality 8 | description: 9 | - Create/delete a database using dbca 10 | - If a responsefile is available, that will be used. If initparams is defined, those will be attached to the createDatabase command 11 | - If no responsefile is created, the database will be created based on all other parameters 12 | version_added: "2.4.0.0" 13 | options: 14 | oracle_home: 15 | description: 16 | - The home where the database will be created 17 | required: False 18 | aliases: ['oh'] 19 | db_name: 20 | description: 21 | - The name of the database 22 | required: True 23 | default: None 24 | aliases: ['db','database_name','name'] 25 | sid: 26 | description: 27 | - The instance name 28 | required: False 29 | default: None 30 | db_unique_name: 31 | description: 32 | - The database db_unique_name 33 | required: False 34 | default: None 35 | aliases: ['dbunqn','unique_name'] 36 | output: 37 | description: 38 | - The type of output you want. 39 | - Verbose: stdout of the command 40 | - short: Pre-defined message 41 | required: False 42 | default: short 43 | aliases: ['db','database_name','name'] 44 | fail_on_db_not_exist: 45 | description: 46 | - Fail the task if the db does not exist 47 | - If False, continues the play (changed=False) 48 | required: False 49 | default: True 50 | choices: ['True','False'] 51 | user: 52 | description: 53 | - Password for the DB user 54 | default: sys 55 | aliases: ['un'] 56 | password: 57 | description: 58 | - Password for the DB user 59 | required: True 60 | default: None 61 | aliases: ['pw','password'] 62 | hostname: 63 | description: 64 | - The host of the database 65 | required: false 66 | default: localhost 67 | aliases: ['host'] 68 | service_name: 69 | description: 70 | - The service_name to connect to (will default to db_name if empty) 71 | required: false 72 | aliases: ['sn'] 73 | port: 74 | description: 75 | - The listener port to connect to the database 76 | required: false 77 | default: 1521 78 | notes: 79 | - cx_Oracle needs to be installed 80 | requirements: [ "cx_Oracle" ] 81 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 82 | ''' 83 | 84 | EXAMPLES = ''' 85 | ''' 86 | import os 87 | 88 | try: 89 | import cx_Oracle 90 | except ImportError: 91 | cx_oracle_exists = False 92 | else: 93 | cx_oracle_exists = True 94 | 95 | 96 | def get_version(module, msg, oracle_home): 97 | command = '%s/bin/sqlplus -V' % (oracle_home) 98 | (rc, stdout, stderr) = module.run_command(command) 99 | if rc != 0: 100 | msg = 'Error - STDOUT: %s, STDERR: %s, COMMAND: %s' % (stdout, stderr, command) 101 | module.fail_json(msg=msg, changed=False) 102 | else: 103 | return stdout.split(' ')[2][0:4] 104 | 105 | # Check if the database exists 106 | def check_db_exists(module, msg, oracle_home, db_name, sid, db_unique_name ): 107 | 108 | if gimanaged: 109 | if db_unique_name != None: 110 | checkdb = db_unique_name 111 | else: 112 | checkdb = db_name 113 | command = "%s/bin/srvctl config database -d %s " % (oracle_home, checkdb) 114 | (rc, stdout, stderr) = module.run_command(command) 115 | if rc != 0: 116 | if '%s' % (db_name) in stdout: #<-- db doesn't exist 117 | return False 118 | else: 119 | msg = 'Error: command is %s. stdout is %s' % (command, stdout) 120 | return False 121 | elif 'Database name: %s' % (db_name) in stdout: #<-- Database already exist 122 | return True 123 | else: 124 | existingdbs = [] 125 | oratabfile = '/etc/oratab' 126 | if os.path.exists(oratabfile): 127 | with open(oratabfile) as oratab: 128 | for line in oratab: 129 | if line.startswith('#') or line.startswith(' '): 130 | continue 131 | elif re.search('^%s:' % db_name, line) or (sid is not None and re.search('^%s:' % sid, line)): 132 | existingdbs.append(line) 133 | 134 | if not existingdbs: #<-- db doesn't exist 135 | return False 136 | else: 137 | for dbs in existingdbs: 138 | if sid != '': 139 | if '%s:' % db_name in dbs or '%s:' % sid in dbs: 140 | if dbs.split(':')[1] != oracle_home.rstrip('/'): #<-- DB is created, but with a different ORACLE_HOME 141 | msg = 'Database %s already exists in a different ORACLE_HOME (%s)' % (db_name, dbs.split(':')[1]) 142 | module.fail_json(msg=msg, changed=False) 143 | elif dbs.split(':')[1] == oracle_home.rstrip('/'): #<-- Database already exist 144 | return True 145 | else: 146 | if '%s:' % db_name in dbs: 147 | if dbs.split(':')[1]!= oracle_home.rstrip('/'): #<-- DB is created, but with a different ORACLE_HOME 148 | msg = 'Database %s already exists in a different ORACLE_HOME (%s)' % (db_name, dbs.split(':')[1]) 149 | module.fail_json(msg=msg, changed=False) 150 | elif dbs.split(':')[1] == oracle_home.rstrip('/'): #<-- Database already exist 151 | return True 152 | 153 | 154 | def run_datapatch(module, msg, hostname, oracle_home, db_name, sid): 155 | 156 | if major_version > '11.2': 157 | if sid is not None: 158 | os.environ['ORACLE_SID'] = sid 159 | else: 160 | os.environ['ORACLE_SID'] = db_name 161 | 162 | command = '%s/OPatch/datapatch -verbose' % (oracle_home) 163 | (rc, stdout, stderr) = module.run_command(command) 164 | if rc != 0: 165 | msg = 'Error - STDOUT: %s, STDERR: %s, COMMAND: %s' % (stdout, stderr, command) 166 | module.fail_json(msg=msg, changed=False) 167 | else: 168 | checks = ['Patch installation complete' in stdout] 169 | if any(checks): 170 | if output == 'short': 171 | return True 172 | else: 173 | msg = 'STDOUT: %s, COMMAND: %s' % (stdout,command) 174 | module.exit_json(msg=msg, changed=True) 175 | else: 176 | msg = 'STDOUT: %s, COMMAND: %s' % (stdout,command) 177 | module.exit_json(msg=msg, changed=False) 178 | 179 | else: 180 | # check_outcome_sql = 'select count(*) from registry$history' 181 | # before = execute_sql_get(module,msg,cursor,check_outcome_sql) 182 | 183 | datapatch_sql = ''' 184 | connect / as sysdba 185 | @?/rdbms/admin/catbundle.sql psu apply 186 | exit 187 | ''' 188 | sqlplus_bin = '%s/bin/sqlplus' % (oracle_home) 189 | p = subprocess.Popen([sqlplus_bin,'/nolog'],stdin=subprocess.PIPE, 190 | stdout=subprocess.PIPE,stderr=subprocess.PIPE) 191 | (stdout,stderr) = p.communicate(datapatch_sql.encode('utf-8')) 192 | rc = p.returncode 193 | if rc != 0: 194 | msg = 'Error - STDOUT: %s, STDERR: %s, COMMAND: %s' % (stdout, stderr, datapatch_sql) 195 | module.fail_json(msg=msg, changed=False) 196 | else: 197 | return True 198 | # after = execute_sql_get(module,msg,cursor,check_outcome_sql) 199 | # if after[0][0] != before[0][0]: 200 | # if output == 'short': 201 | # return True 202 | # else: 203 | # msg = 'STDOUT: %s, sql: %s' % (stdout, datapatch_sql) 204 | # module.exit_json(msg=msg, changed=True) 205 | # else: 206 | # msg = 'No changes applied' 207 | # module.exit_json(msg=msg, changed=False) 208 | 209 | 210 | 211 | 212 | def execute_sql_get(module, msg, cursor, sql): 213 | 214 | try: 215 | cursor.execute(sql) 216 | result = (cursor.fetchall()) 217 | except cx_Oracle.DatabaseError as exc: 218 | error, = exc.args 219 | msg = 'Something went wrong while executing sql_get - %s sql: %s' % (error.message, sql) 220 | module.fail_json(msg=msg, changed=False) 221 | return False 222 | return result 223 | 224 | # def execute_sql(module, msg, cursor, sql): 225 | # 226 | # try: 227 | # cursor.execute(sql) 228 | # except cx_Oracle.DatabaseError as exc: 229 | # error, = exc.args 230 | # msg = 'Something went wrong while executing sql - %s sql: %s' % (error.message, sql) 231 | # module.fail_json(msg=msg, changed=False) 232 | # return False 233 | # return True 234 | 235 | def getconn(module,msg, hostname): 236 | 237 | if not hostname: 238 | hostname = os.uname()[1] 239 | wallet_connect = '/@%s' % service_name 240 | try: 241 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 242 | connect = wallet_connect 243 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 244 | elif (user and password ): 245 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name, ) 246 | connect = dsn 247 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 248 | elif (not(user) or not(password)): 249 | module.fail_json(msg='Missing username or password for cx_Oracle') 250 | 251 | except cx_Oracle.DatabaseError as exc: 252 | error, = exc.args 253 | msg = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 254 | module.fail_json(msg=msg, changed=False) 255 | 256 | cursor = conn.cursor() 257 | return cursor 258 | 259 | 260 | 261 | def main(): 262 | 263 | msg = [''] 264 | cursor = None 265 | global gimanaged 266 | global major_version 267 | global user 268 | global password 269 | global service_name 270 | global hostname 271 | global port 272 | global output 273 | 274 | module = AnsibleModule( 275 | argument_spec = dict( 276 | oracle_home = dict(default=None, aliases = ['oh']), 277 | db_name = dict(required=True, aliases = ['db','database_name','name']), 278 | sid = dict(required=False), 279 | db_unique_name = dict(required=False, aliases = ['dbunqn','unique_name']), 280 | fail_on_db_not_exist = dict(default=True, type='bool'), 281 | output = dict(default="short", choices = ["short","verbose"]), 282 | user = dict(default='sys', aliases = ['un']), 283 | password = dict(required=True, no_log=True, aliases = ['pw','password']), 284 | hostname = dict(required=False, default = 'localhost', aliases = ['host']), 285 | service_name = dict(required=False, aliases = ['sn']), 286 | port = dict(required=False, default = 1521), 287 | 288 | 289 | 290 | ), 291 | ) 292 | 293 | oracle_home = module.params["oracle_home"] 294 | db_name = module.params["db_name"] 295 | sid = module.params["sid"] 296 | db_unique_name = module.params["db_unique_name"] 297 | fail_on_db_not_exist = module.params["fail_on_db_not_exist"] 298 | output = module.params["output"] 299 | user = module.params["user"] 300 | password = module.params["password"] 301 | hostname = module.params["hostname"] 302 | service_name = module.params["service_name"] 303 | port = module.params["port"] 304 | 305 | 306 | #ld_library_path = '%s/lib' % (oracle_home) 307 | if oracle_home is not None: 308 | os.environ['ORACLE_HOME'] = oracle_home 309 | #os.environ['LD_LIBRARY_PATH'] = ld_library_path 310 | elif 'ORACLE_HOME' in os.environ: 311 | oracle_home = os.environ['ORACLE_HOME'] 312 | #ld_library_path = os.environ['LD_LIBRARY_PATH'] 313 | else: 314 | msg = 'ORACLE_HOME variable not set. Please set it and re-run the command' 315 | module.fail_json(msg=msg, changed=False) 316 | 317 | 318 | # Decide whether to use srvctl or sqlplus 319 | if os.path.exists('/etc/oracle/olr.loc'): 320 | gimanaged = True 321 | else: 322 | gimanaged = False 323 | 324 | if not cx_oracle_exists: 325 | msg = "The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set" 326 | module.fail_json(msg=msg) 327 | 328 | # Connection details for database 329 | if service_name is not None: 330 | service_name = service_name 331 | elif db_unique_name is not None: 332 | service_name = db_unique_name 333 | else: 334 | service_name = db_name 335 | # Get the Oracle version 336 | major_version = get_version(module,msg,oracle_home) 337 | if check_db_exists(module,msg,oracle_home,db_name,sid,db_unique_name): 338 | if run_datapatch(module,msg,hostname,oracle_home,db_name,sid): 339 | msg = 'Datapatch run successfully for database: %s' % (db_name) 340 | module.exit_json(msg=msg, changed=True) 341 | else: 342 | module.fail_json(msg='datapatch failed in a unhandled way') 343 | else: 344 | if fail_on_db_not_exist: 345 | msg = 'Database %s does not exist' % (db_name) 346 | module.fail_json(msg=msg) 347 | else: 348 | msg = 'Database %s does not exist (so datapatch can not run, obviously), but continuing anyway' % (db_name) 349 | module.exit_json(msg=msg, changed=False) 350 | 351 | from ansible.module_utils.basic import * 352 | if __name__ == '__main__': 353 | main() 354 | -------------------------------------------------------------------------------- /oracle_directory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_directory 7 | short_description: Manage users/schemas in an Oracle database 8 | description: 9 | - Manage grants/privileges in an Oracle database 10 | - Handles role/sys privileges at the moment. 11 | - It is possible to add object privileges as well, but they are not considered when removing privs at the moment. 12 | version_added: "1.9.1" 13 | options: 14 | hostname: 15 | description: 16 | - The Oracle database host 17 | required: false 18 | default: localhost 19 | port: 20 | description: 21 | - The listener port number on the host 22 | required: false 23 | default: 1521 24 | service_name: 25 | description: 26 | - The database service name to connect to 27 | required: true 28 | user: 29 | description: 30 | - The Oracle user name to connect to the database 31 | required: true 32 | password: 33 | description: 34 | - The Oracle user password for 'user' 35 | required: true 36 | mode: 37 | description: 38 | - The mode with which to connect to the database 39 | required: true 40 | default: normal 41 | choices: ['normal','sysdba'] 42 | directory_name: 43 | description: 44 | - The name of the directory 45 | required: True 46 | default: null 47 | path: 48 | description: 49 | - Where the directory should point 50 | required: false 51 | default: null 52 | 53 | notes: 54 | - cx_Oracle needs to be installed 55 | requirements: [ "cx_Oracle" ] 56 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 57 | ''' 58 | 59 | EXAMPLES = ''' 60 | 61 | 62 | ''' 63 | 64 | try: 65 | import cx_Oracle 66 | except ImportError: 67 | cx_oracle_exists = False 68 | else: 69 | cx_oracle_exists = True 70 | 71 | # Check if the directory exists 72 | def check_directory_exists(module, msg, cursor, directory_name): 73 | 74 | if not(directory_name): 75 | module.fail_json(msg='Error: Missing directory name', changed=False) 76 | return False 77 | 78 | sql = 'select count(*) from dba_directories where directory_name= upper(\'%s\')' % directory_name 79 | 80 | 81 | try: 82 | cursor.execute(sql) 83 | result = cursor.fetchone()[0] 84 | except cx_Oracle.DatabaseError as exc: 85 | error, = exc.args 86 | msg = error.message+ 'sql: ' + sql 87 | return False 88 | 89 | if result > 0: 90 | return True 91 | else: 92 | return False 93 | 94 | def ensure_directory(module, msg, cursor, directory_name, directory_path, directory_mode): 95 | 96 | if check_directory_exists(module, msg, cursor, directory_name): 97 | check_path_sql = 'select directory_path from dba_directories where directory_name = upper(\'%s\')' % directory_name 98 | _curr_path = execute_sql_get(module, msg, cursor, check_path_sql) 99 | 100 | if _curr_path[0][0] != directory_path: 101 | if directory_mode == 'enforce': 102 | directory_sql = 'create or replace directory %s as \'%s\'' % (directory_name, directory_path) 103 | if execute_sql(module,msg,cursor,directory_sql): 104 | module.exit_json(msg='Directory %s, changed to path: %s (old path: %s)' % (directory_name, directory_path,_curr_path[0][0]), changed=True) 105 | else: 106 | module.exit_json(msg='Directory %s already exists (%s)' % (directory_name, directory_path), changed=False) 107 | else: 108 | directory_sql = 'create directory %s as \'%s\'' % (directory_name, directory_path) 109 | 110 | if execute_sql(module,msg,cursor,directory_sql): 111 | msg = 'Directory: %s, created with path: %s' % (directory_name, directory_path) 112 | module.exit_json(msg=msg, changed=True) 113 | 114 | def drop_directory(module, msg, cursor, directory_name): 115 | drop_sql = 'drop directory %s' % (directory_name) 116 | 117 | if execute_sql(module,msg,cursor,drop_sql): 118 | module.exit_json(msg='Directory %s successfully dropped' % (directory_name), changed=True) 119 | 120 | 121 | def execute_sql(module, msg, cursor, sql): 122 | 123 | try: 124 | cursor.execute(sql) 125 | except cx_Oracle.DatabaseError as exc: 126 | error, = exc.args 127 | msg = 'Something went wrong while executing sql - %s sql: %s' % (error.message, sql) 128 | module.fail_json(msg=msg, changed=False) 129 | return False 130 | return True 131 | 132 | 133 | def execute_sql_get(module, msg, cursor, sql): 134 | 135 | try: 136 | cursor.execute(sql) 137 | result = (cursor.fetchall()) 138 | except cx_Oracle.DatabaseError as exc: 139 | error, = exc.args 140 | msg = 'Something went wrong while executing sql_get - %s sql: %s' % (error.message, sql) 141 | module.fail_json(msg=msg, changed=False) 142 | return False 143 | return result 144 | 145 | 146 | 147 | def main(): 148 | 149 | msg = [''] 150 | global state 151 | module = AnsibleModule( 152 | argument_spec = dict( 153 | hostname = dict(default='localhost'), 154 | port = dict(default=1521), 155 | service_name = dict(required=True), 156 | user = dict(required=False), 157 | password = dict(required=False, no_log=True), 158 | mode = dict(default='normal', choices=["normal","sysdba"]), 159 | directory_name = dict(default=None), 160 | directory_path = dict(default=None), 161 | directory_mode = dict(default="enforce", choices=["normal", "enforce"]), 162 | state = dict(default="present", choices=["present", "absent"]) 163 | 164 | ) 165 | ) 166 | 167 | hostname = module.params["hostname"] 168 | port = module.params["port"] 169 | service_name = module.params["service_name"] 170 | user = module.params["user"] 171 | password = module.params["password"] 172 | mode = module.params["mode"] 173 | directory_name = module.params["directory_name"] 174 | directory_path = module.params["directory_path"] 175 | directory_mode = module.params["directory_mode"] 176 | state = module.params["state"] 177 | 178 | 179 | 180 | if not cx_oracle_exists: 181 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 182 | 183 | wallet_connect = '/@%s' % service_name 184 | try: 185 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 186 | if mode == 'sysdba': 187 | connect = wallet_connect 188 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 189 | else: 190 | connect = wallet_connect 191 | conn = cx_Oracle.connect(wallet_connect) 192 | 193 | elif (user and password ): 194 | if mode == 'sysdba': 195 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 196 | connect = dsn 197 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 198 | else: 199 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 200 | connect = dsn 201 | conn = cx_Oracle.connect(user, password, dsn) 202 | 203 | elif (not(user) or not(password)): 204 | module.fail_json(msg='Missing username or password for cx_Oracle') 205 | 206 | except cx_Oracle.DatabaseError as exc: 207 | error, = exc.args 208 | msg = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 209 | module.fail_json(msg=msg, changed=False) 210 | 211 | cursor = conn.cursor() 212 | 213 | if state == 'present': 214 | ensure_directory(module, msg, cursor, directory_name, directory_path, directory_mode) 215 | elif state == 'absent': 216 | if check_directory_exists(module, msg, cursor, directory_name): 217 | drop_directory(module, msg, cursor, directory_name) 218 | 219 | else: 220 | msg = 'Directory %s doesn\'t exist' % (directory_name) 221 | module.exit_json(msg=msg, changed=False) 222 | 223 | else: 224 | module.fail_json(msg='Unhandled exit', changed=False) 225 | 226 | 227 | 228 | 229 | 230 | 231 | from ansible.module_utils.basic import * 232 | if __name__ == '__main__': 233 | main() 234 | -------------------------------------------------------------------------------- /oracle_facts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_facts 7 | short_description: Returns some facts about Oracle DB 8 | description: 9 | - Returns some facts about Oracle DB 10 | version_added: "2.2.1" 11 | options: 12 | hostname: 13 | description: 14 | - The Oracle database host 15 | required: false 16 | default: localhost 17 | port: 18 | description: 19 | - The listener port number on the host 20 | required: false 21 | default: 1521 22 | service_name: 23 | description: 24 | - The database service name to connect to 25 | required: true 26 | user: 27 | description: 28 | - The Oracle user name to connect to the database, must have DBA privilege 29 | required: False 30 | password: 31 | description: 32 | - The Oracle user password for 'user' 33 | required: False 34 | mode: 35 | description: 36 | - The mode with which to connect to the database 37 | required: false 38 | default: normal 39 | choices: 40 | - normal 41 | - sysdba 42 | notes: 43 | - cx_Oracle needs to be installed 44 | - Oracle RDBMS 10gR2 or later required 45 | requirements: [ "cx_Oracle" ] 46 | author: Ilmar Kerm, ilmar.kerm@gmail.com, @ilmarkerm 47 | ''' 48 | 49 | EXAMPLES = ''' 50 | - hosts: localhost 51 | vars: 52 | oraclehost: 192.168.56.101 53 | oracleport: 1521 54 | oracleservice: orcl 55 | oracleuser: system 56 | oraclepassword: oracle 57 | oracle_env: 58 | ORACLE_HOME: /usr/lib/oracle/12.1/client64 59 | LD_LIBRARY_PATH: /usr/lib/oracle/12.1/client64/lib 60 | tasks: 61 | - name: gather database facts 62 | oracle_facts: 63 | hostname: "{{ oraclehost }}" 64 | port: "{{ oracleport }}" 65 | service_name: "{{ oracleservice }}" 66 | user: "{{ oracleuser }}" 67 | password: "{{ oraclepassword }}" 68 | register: dbfacts 69 | - debug: 70 | var: dbfacts 71 | ''' 72 | 73 | try: 74 | import cx_Oracle 75 | except ImportError: 76 | cx_oracle_exists = False 77 | else: 78 | cx_oracle_exists = True 79 | 80 | def rows_to_dict_list(cursor): 81 | columns = [i[0] for i in cursor.description] 82 | return [dict(zip(columns, row)) for row in cursor] 83 | 84 | def query_result(query): 85 | c = conn.cursor() 86 | c.execute(query) 87 | res = rows_to_dict_list(c) 88 | c.close() 89 | return res 90 | 91 | def star_query(rowsource): 92 | return query_result("SELECT * FROM %s" % rowsource) 93 | 94 | # Ansible code 95 | def main(): 96 | global conn 97 | msg = [''] 98 | module = AnsibleModule( 99 | argument_spec = dict( 100 | hostname = dict(default='localhost'), 101 | port = dict(default=1521, type='int'), 102 | service_name = dict(required=True), 103 | user = dict(required=False), 104 | password = dict(required=False), 105 | mode = dict(default='normal', choices=["normal","sysdba"]) 106 | ), 107 | supports_check_mode=True 108 | ) 109 | # Check for required modules 110 | if not cx_oracle_exists: 111 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 112 | # Connect to database 113 | hostname = module.params["hostname"] 114 | port = module.params["port"] 115 | service_name = module.params["service_name"] 116 | user = module.params["user"] 117 | password = module.params["password"] 118 | mode = module.params["mode"] 119 | wallet_connect = '/@%s' % service_name 120 | try: 121 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 122 | if mode == 'sysdba': 123 | connect = wallet_connect 124 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 125 | else: 126 | connect = wallet_connect 127 | conn = cx_Oracle.connect(wallet_connect) 128 | 129 | elif (user and password ): 130 | if mode == 'sysdba': 131 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 132 | connect = dsn 133 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 134 | else: 135 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 136 | connect = dsn 137 | conn = cx_Oracle.connect(user, password, dsn) 138 | 139 | elif (not(user) or not(password)): 140 | module.fail_json(msg='Missing username or password for cx_Oracle') 141 | 142 | except cx_Oracle.DatabaseError as exc: 143 | error, = exc.args 144 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 145 | module.fail_json(msg=msg[0], changed=False) 146 | if conn.version < "10.2": 147 | module.fail_json(msg="Database version must be 10gR2 or greater", changed=False) 148 | # 149 | if module.check_mode: 150 | module.exit_json(changed=False) 151 | # 152 | facts = { 'version': conn.version } 153 | # Execute PL/SQL to return some additional facts 154 | database = star_query('v$database')[0] 155 | instance = star_query('v$instance')[0] 156 | if 'CDB' not in database: 157 | database.update({'CDB': 'NO'}) 158 | # 159 | try: 160 | rac = query_result("SELECT inst_id, instance_name, host_name, startup_time FROM gv$instance ORDER BY inst_id") 161 | except: 162 | rac = [] 163 | try: 164 | if database['CDB'] == 'YES': 165 | pdb = query_result("SELECT con_id, rawtohex(guid) guid_hex, name, open_mode FROM v$pdbs ORDER BY name") 166 | else: 167 | pdb = [] 168 | except: 169 | pdb = [] 170 | if conn.version >= '12.1': 171 | tablespace = query_result("select ts.con_id, ts.name, ts.bigfile, round(sum(bytes)/1024/1024) size_mb, count(*) datafiles# from v$tablespace ts join v$datafile df on df.ts#=ts.ts# and df.con_id=ts.con_id group by ts.name, ts.bigfile, ts.con_id order by 1,2") 172 | temp_tablespace = query_result("select ts.con_id, ts.name, ts.bigfile, round(sum(bytes)/1024/1024) size_mb, count(*) tempfiles# from v$tablespace ts join v$tempfile df on df.ts#=ts.ts# and df.con_id=ts.con_id group by ts.name, ts.bigfile, ts.con_id order by 1,2") 173 | else: 174 | tablespace = query_result("select 0 con_id, ts.name, ts.bigfile, round(sum(bytes)/1024/1024) size_mb, count(*) datafiles# from v$tablespace ts join v$datafile df on df.ts#=ts.ts# group by ts.name, ts.bigfile order by 1,2") 175 | temp_tablespace = query_result("select 0 con_id, ts.name, ts.bigfile, round(sum(bytes)/1024/1024) size_mb, count(*) tempfiles# from v$tablespace ts join v$tempfile df on df.ts#=ts.ts# group by ts.name, ts.bigfile order by 1,2") 176 | redolog = query_result("select group#, thread#, sequence#, round(bytes/1024/1024) mb, blocksize, archived, status from v$log order by thread#,group#") 177 | option = star_query("v$option") 178 | parameter = {} 179 | for param in query_result("select name, value, isdefault from v$parameter order by 1"): 180 | parameter[param['NAME']] = { 'isdefault': param['ISDEFAULT'], 'value': param['VALUE'] } 181 | # USERENV 182 | sql = "SELECT sys_context('USERENV','CURRENT_USER') current_user, sys_context('USERENV','DATABASE_ROLE') database_role, sys_context('USERENV','ISDBA') isdba, sys_context('USERENV','ORACLE_HOME') oracle_home" 183 | if conn.version >= '12.1': 184 | sql+= ", to_number(sys_context('USERENV','CON_ID')) con_id, sys_context('USERENV','CON_NAME') con_name" 185 | if conn.version >= '11.1': 186 | sql+= ", to_number(sys_context('USERENV','CURRENT_EDITION_ID')) CURRENT_EDITION_ID, sys_context('USERENV','CURRENT_EDITION_NAME') CURRENT_EDITION_NAME" 187 | sql+= " FROM DUAL" 188 | userenv = query_result(sql)[0] 189 | # 190 | facts.update({'database': database, 'instance': instance, 'rac': rac, 'pdb': pdb, 'tablespace': tablespace, 'temp_tablespace': temp_tablespace, 'userenv': userenv, 'redolog': redolog, 'option': option, 'parameter': parameter}) 191 | # 192 | module.exit_json(msg=msg[0], changed=False, ansible_facts=facts) 193 | 194 | 195 | from ansible.module_utils.basic import * 196 | if __name__ == '__main__': 197 | main() 198 | -------------------------------------------------------------------------------- /oracle_gi_facts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ANSIBLE_METADATA = { 5 | 'metadata_version': '1.1', 6 | 'status': ['preview'], 7 | 'supported_by': 'community' 8 | } 9 | 10 | DOCUMENTATION = ''' 11 | --- 12 | module: oracle_gi_facts 13 | short_description: Returns some facts about Grid Infrastructure environment 14 | description: 15 | - Returns some facts about Grid Infrastructure environment 16 | - Must be run on a remote host 17 | version_added: "2.4" 18 | options: 19 | oracle_home: 20 | description: 21 | - Grid Infrastructure home, can be absent if ORACLE_HOME environment variable is set 22 | required: false 23 | notes: 24 | - Oracle Grid Infrastructure 12cR1 or later required 25 | - Must be run as (become) GI owner 26 | author: Ilmar Kerm, ilmar.kerm@gmail.com, @ilmarkerm 27 | ''' 28 | 29 | EXAMPLES = ''' 30 | --- 31 | - hosts: localhost 32 | vars: 33 | oracle_env: 34 | ORACLE_HOME: /u01/app/grid/product/12.1.0.2/grid 35 | tasks: 36 | - name: Return GI facts 37 | oracle_gi_facts: 38 | environment: "{{ oracle_env }}" 39 | ''' 40 | 41 | import os, re 42 | from socket import gethostname, getfqdn 43 | 44 | # The following is to make the module usable in python 2.6 (RHEL6/OEL6) 45 | # Source: http://pydoc.net/pep8radius/0.9.0/pep8radius.shell/ 46 | try: 47 | from subprocess import check_output, CalledProcessError 48 | except ImportError: # pragma: no cover 49 | # python 2.6 doesn't include check_output 50 | # monkey patch it in! 51 | import subprocess 52 | STDOUT = subprocess.STDOUT 53 | 54 | def check_output(*popenargs, **kwargs): 55 | if 'stdout' in kwargs: # pragma: no cover 56 | raise ValueError('stdout argument not allowed, ' 57 | 'it will be overridden.') 58 | process = subprocess.Popen(stdout=subprocess.PIPE, 59 | *popenargs, **kwargs) 60 | output, _ = process.communicate() 61 | retcode = process.poll() 62 | if retcode: 63 | cmd = kwargs.get("args") 64 | if cmd is None: 65 | cmd = popenargs[0] 66 | raise subprocess.CalledProcessError(retcode, cmd, 67 | output=output) 68 | return output 69 | subprocess.check_output = check_output 70 | 71 | # overwrite CalledProcessError due to `output` 72 | # keyword not being available (in 2.6) 73 | class CalledProcessError(Exception): 74 | 75 | def __init__(self, returncode, cmd, output=None): 76 | self.returncode = returncode 77 | self.cmd = cmd 78 | self.output = output 79 | 80 | def __str__(self): 81 | return "Command '%s' returned non-zero exit status %d" % ( 82 | self.cmd, self.returncode) 83 | subprocess.CalledProcessError = CalledProcessError 84 | 85 | def is_executable(fpath): 86 | return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 87 | 88 | def exec_program_lines(arguments): 89 | try: 90 | output = check_output(arguments) 91 | return output.splitlines() 92 | except CalledProcessError: 93 | # Just ignore the error 94 | return [''] 95 | 96 | def exec_program(arguments): 97 | return exec_program_lines(arguments)[0] 98 | 99 | def hostname_to_fqdn(hostname): 100 | if "." not in hostname: 101 | return getfqdn(hostname) 102 | else: 103 | return hostname 104 | 105 | def local_listener(): 106 | global srvctl, shorthostname, iscrs, vips 107 | args = [srvctl, 'status', 'listener'] 108 | if iscrs: 109 | args += ['-n', shorthostname] 110 | listeners_out = exec_program_lines(args) 111 | re_listener_name = re.compile('Listener (.+) is enabled') 112 | listeners = [] 113 | out = [] 114 | for l in listeners_out: 115 | if "is enabled" in l: 116 | m = re_listener_name.search(l) 117 | listeners.append(m.group(1)) 118 | for l in listeners: 119 | config = {} 120 | output = exec_program_lines([srvctl, 'config', 'listener', '-l', l]) 121 | for line in output: 122 | if line.startswith('Name:'): 123 | config['name'] = line[6:] 124 | elif line.startswith('Type:'): 125 | config['type'] = line[6:] 126 | elif line.startswith('Network:'): 127 | config['network'] = line[9:line.find(',')] 128 | elif line.startswith('End points:'): 129 | config['endpoints'] = line[12:] 130 | for proto in config['endpoints'].split('/'): 131 | p = proto.split(':') 132 | config[p[0].lower()] = p[1] 133 | if "network" in config.keys(): 134 | config['address'] = vips[config['network']]['fqdn'] 135 | config['ipv4'] = vips[config['network']]['ipv4'] 136 | config['ipv6'] = vips[config['network']]['ipv6'] 137 | out.append(config) 138 | return out 139 | 140 | def scan_listener(): 141 | global srvctl, shorthostname, iscrs, networks, scans 142 | out = {} 143 | for n in networks.keys(): 144 | output = exec_program_lines([srvctl, 'config', 'scan_listener', '-k', n]) 145 | for line in output: 146 | endpoints = None 147 | # 19c 148 | m = re.search('Endpoints: (.+)', line) 149 | if m is not None: 150 | endpoints = m.group(1) 151 | else: 152 | # 18c, 12c 153 | m = re.search('SCAN Listener (.+) exists. Port: (.+)', line) 154 | if m is not None: 155 | endpoints = m.group(2) 156 | if endpoints: 157 | out[n] = {'network': n, 'scan_address': scans[n]['fqdn'], 'endpoints': endpoints, 'ipv4': scans[n]['ipv4'], 'ipv6': scans[n]['ipv6']} 158 | for proto in endpoints.split('/'): 159 | p = proto.split(':') 160 | out[n][p[0].lower()] = p[1] 161 | break 162 | return out 163 | 164 | def get_networks(): 165 | global srvctl, shorthostname, iscrs 166 | out = {} 167 | item = {} 168 | output = exec_program_lines([srvctl, 'config', 'network']) 169 | for line in output: 170 | m = re.search('Network ([0-9]+) exists', line) 171 | if m is not None: 172 | if "network" in item.keys(): 173 | out[item['network']] = item 174 | item = {'network': m.group(1)} 175 | elif line.startswith('Subnet IPv4:'): 176 | item['ipv4'] = line[13:] 177 | elif line.startswith('Subnet IPv6:'): 178 | item['ipv6'] = line[13:] 179 | if "network" in item.keys(): 180 | out[item['network']] = item 181 | return out 182 | 183 | def get_vips(): 184 | global srvctl, shorthostname, iscrs 185 | output = exec_program_lines([srvctl, 'config', 'vip', '-n', shorthostname]) 186 | vip = {} 187 | out = {} 188 | for line in output: 189 | if line.startswith('VIP exists:'): 190 | if "network" in vip.keys(): 191 | out[vip['network']] = vip 192 | vip = {} 193 | m = re.search('network number ([0-9]+),', line) 194 | vip['network'] = m.group(1) 195 | elif line.startswith('VIP Name:'): 196 | vip['name'] = line[10:] 197 | vip['fqdn'] = hostname_to_fqdn(vip['name']) 198 | elif line.startswith('VIP IPv4 Address:'): 199 | vip['ipv4'] = line[18:] 200 | elif line.startswith('VIP IPv6 Address:'): 201 | vip['ipv6'] = line[18:] 202 | if "network" in vip.keys(): 203 | out[vip['network']] = vip 204 | return out 205 | 206 | def get_scans(): 207 | global srvctl, shorthostname, iscrs 208 | out = {} 209 | item = {} 210 | output = exec_program_lines([srvctl, 'config', 'scan', '-all']) 211 | for line in output: 212 | if line.startswith('SCAN name:'): 213 | if "network" in item.keys(): 214 | out[item['network']] = item 215 | m = re.search('SCAN name: (.+), Network: ([0-9]+)', line) 216 | item = {'network': m.group(2), 'name': m.group(1), 'ipv4': [], 'ipv6': []} 217 | item['fqdn'] = hostname_to_fqdn(item['name']) 218 | else: 219 | m = re.search('SCAN [0-9]+ (IPv[46]) VIP: (.+)', line) 220 | if m is not None: 221 | item[m.group(1).lower()] += [m.group(2)] 222 | if "network" in item.keys(): 223 | out[item['network']] = item 224 | return out 225 | 226 | # Ansible code 227 | def main(): 228 | global module, shorthostname, hostname, srvctl, crsctl, cemutlo, iscrs, vips, networks, scans 229 | msg = [''] 230 | module = AnsibleModule( 231 | argument_spec = dict( 232 | oracle_home = dict(required=False) 233 | ), 234 | supports_check_mode=True 235 | ) 236 | # Preparation 237 | facts = {} 238 | if module.params["oracle_home"]: 239 | os.environ['ORACLE_HOME'] = module.params["oracle_home"] 240 | srvctl = os.path.join(os.environ['ORACLE_HOME'], 'bin', 'srvctl') 241 | crsctl = os.path.join(os.environ['ORACLE_HOME'], 'bin', 'crsctl') 242 | cemutlo = os.path.join(os.environ['ORACLE_HOME'], 'bin', 'cemutlo') 243 | if not is_executable(srvctl) or not is_executable(crsctl): 244 | module.fail_json(changed=False, msg="Are you sure ORACLE_HOME=%s points to GI home? I can't find executables srvctl or crsctl under bin/." % os.environ['ORACLE_HOME']) 245 | iscrs = True # This needs to be dynamically set if it is full clusterware or Oracle restart 246 | hostname = gethostname() 247 | shorthostname = hostname.split('.')[0] 248 | # 249 | if module.check_mode: 250 | module.exit_json(changed=False) 251 | # Cluster name 252 | if iscrs: 253 | facts.update({'clustername': exec_program([cemutlo, '-n'])}) 254 | else: 255 | facts.update({'clustername': 'ORACLE_RESTART'}) 256 | # Cluster version 257 | if iscrs: 258 | version = exec_program([crsctl, 'query','crs','activeversion']) 259 | else: 260 | version = exec_program([crsctl, 'query','has','releaseversion']) 261 | m = re.search('\[([0-9\.]+)\]$', version) 262 | facts.update({'version': m.group(1)}) 263 | # VIPS 264 | vips = get_vips() 265 | facts.update({'vip': vips.values()}) 266 | # Networks 267 | networks = get_networks() 268 | facts.update({'network': networks.values()}) 269 | # SCANs 270 | scans = get_scans() 271 | facts.update({'scan': scans.values()}) 272 | # Listener 273 | facts.update({'local_listener': local_listener()}) 274 | facts.update({'scan_listener': scan_listener().values() if iscrs else []}) 275 | # Databases 276 | facts.update({'database_list': exec_program_lines([srvctl, 'config', 'database'])}) 277 | # Output 278 | module.exit_json(msg=", ".join(msg), changed=False, ansible_facts=facts) 279 | 280 | 281 | from ansible.module_utils.basic import * 282 | if __name__ == '__main__': 283 | main() 284 | -------------------------------------------------------------------------------- /oracle_jobclass: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_jobclass 7 | short_description: Manage DBMS_SCHEDULER job classes in Oracle database 8 | description: 9 | - Manage DBMS_SCHEDULER job classes in Oracle database 10 | - Can be run locally on the controlmachine or on a remote host 11 | version_added: "2.2.1" 12 | options: 13 | hostname: 14 | description: 15 | - The Oracle database host 16 | required: false 17 | default: localhost 18 | port: 19 | description: 20 | - The listener port number on the host 21 | required: false 22 | default: 1521 23 | service_name: 24 | description: 25 | - The database service name to connect to 26 | required: true 27 | user: 28 | description: 29 | - The Oracle user name to connect to the database, must have DBA privilege 30 | required: False 31 | password: 32 | description: 33 | - The Oracle user password for 'user' 34 | required: False 35 | mode: 36 | description: 37 | - The mode with which to connect to the database 38 | required: true 39 | default: normal 40 | choices: ['normal','sysdba'] 41 | state: 42 | description: 43 | - If present, job class is created if absent then job class is removed 44 | required: true 45 | choices: ['present','absent'] 46 | name: 47 | description: 48 | - Job class name 49 | required: True 50 | resource_group: 51 | description: 52 | - Resource manager resource consumer group the class is associated with 53 | required: False 54 | service: 55 | description: 56 | - Database service under what jobs run as 57 | required: False 58 | logging: 59 | description: 60 | - How much information is logged 61 | default: failed runs 62 | choices: ["off","runs","failed runs","full"] 63 | history: 64 | description: 65 | - Number of days the logs for this job class are retained 66 | - If set to 0, no logs will be kept 67 | required: False 68 | type: int 69 | comments: 70 | description: 71 | - Comment about the class 72 | required: False 73 | 74 | notes: 75 | - cx_Oracle needs to be installed 76 | - Oracle RDBMS 10gR2 or later required 77 | requirements: [ "cx_Oracle" ] 78 | author: Ilmar Kerm, ilmar.kerm@gmail.com, @ilmarkerm 79 | ''' 80 | 81 | EXAMPLES = ''' 82 | --- 83 | - hosts: localhost 84 | vars: 85 | oraclehost: 192.168.56.101 86 | oracleport: 1521 87 | oracleservice: orcl 88 | oracleuser: system 89 | oraclepassword: oracle 90 | oracle_env: 91 | ORACLE_HOME: /usr/lib/oracle/12.1/client64 92 | LD_LIBRARY_PATH: /usr/lib/oracle/12.1/client64/lib 93 | tasks: 94 | - name: job class 95 | oracle_jobclass: 96 | hostname: "{{ oraclehost }}" 97 | port: "{{ oracleport }}" 98 | service_name: "{{ oracleservice }}" 99 | user: "{{ oracleuser }}" 100 | password: "{{ oraclepassword }}" 101 | state: present 102 | name: testclass 103 | logging: failed runs 104 | history: 14 105 | environment: "{{ oracle_env }}" 106 | ''' 107 | 108 | try: 109 | import cx_Oracle 110 | except ImportError: 111 | cx_oracle_exists = False 112 | else: 113 | cx_oracle_exists = True 114 | 115 | def query_existing(job_class_name): 116 | c = conn.cursor() 117 | c.execute("SELECT resource_consumer_group, service, logging_level, log_history, comments FROM all_scheduler_job_classes WHERE owner = 'SYS' AND job_class_name = :jobclass", 118 | {"jobclass": job_class_name.upper()}) 119 | result = c.fetchone() 120 | if c.rowcount > 0: 121 | return {"exists": True, "resource_group": result[0], "service": result[1], "logging": result[2], "history": result[3], "comments": result[4]} 122 | else: 123 | return {"exists": False} 124 | 125 | # Ansible code 126 | def main(): 127 | global lconn, conn, msg, module 128 | msg = [''] 129 | module = AnsibleModule( 130 | argument_spec = dict( 131 | hostname = dict(default='localhost'), 132 | port = dict(default=1521, type='int'), 133 | service_name = dict(required=True), 134 | user = dict(required=False), 135 | password = dict(required=False), 136 | mode = dict(default='normal', choices=["normal","sysdba"]), 137 | state = dict(default="present", choices=["present", "absent"]), 138 | name = dict(required=True), 139 | resource_group= dict(required=False), 140 | service = dict(required=False), 141 | logging = dict(default="failed runs", choices=["off","runs","failed runs","full"]), 142 | history = dict(required=False, type='int'), 143 | comments = dict(required=False) 144 | ), 145 | supports_check_mode=True 146 | ) 147 | # Check for required modules 148 | if not cx_oracle_exists: 149 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 150 | # Check input parameters 151 | # Connect to database 152 | hostname = module.params["hostname"] 153 | port = module.params["port"] 154 | service_name = module.params["service_name"] 155 | user = module.params["user"] 156 | password = module.params["password"] 157 | mode = module.params["mode"] 158 | wallet_connect = '/@%s' % service_name 159 | try: 160 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 161 | if mode == 'sysdba': 162 | connect = wallet_connect 163 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 164 | else: 165 | connect = wallet_connect 166 | conn = cx_Oracle.connect(wallet_connect) 167 | 168 | elif (user and password ): 169 | if mode == 'sysdba': 170 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 171 | connect = dsn 172 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 173 | else: 174 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 175 | connect = dsn 176 | conn = cx_Oracle.connect(user, password, dsn) 177 | 178 | elif (not(user) or not(password)): 179 | module.fail_json(msg='Missing username or password for cx_Oracle') 180 | 181 | except cx_Oracle.DatabaseError as exc: 182 | error, = exc.args 183 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 184 | module.fail_json(msg=msg[0], changed=False) 185 | if conn.version < "10.2": 186 | module.fail_json(msg="Database version must be 10gR2 or greater", changed=False) 187 | # 188 | if module.check_mode: 189 | module.exit_json(changed=False) 190 | # 191 | #c = conn.cursor() 192 | result_changed = False 193 | result = query_existing(module.params['name']) 194 | if result['exists'] and module.params['state'] == "present": 195 | # Check attributes and modify if needed 196 | if (result['comments'] != module.params['comments']) or (result['resource_group'] != module.params['resource_group']) or (result['service'] != module.params['service']) or (result['history'] != module.params['history']) or (result['logging'] != module.params['logging'].upper()): 197 | c = conn.cursor() 198 | c.execute(""" 199 | DECLARE 200 | v_name VARCHAR2(100); 201 | v_service VARCHAR2(100); 202 | v_logging PLS_INTEGER; 203 | v_history PLS_INTEGER; 204 | v_resource VARCHAR2(100); 205 | v_comments VARCHAR2(4000); 206 | BEGIN 207 | v_logging:= CASE :logging WHEN 'off' THEN DBMS_SCHEDULER.LOGGING_OFF 208 | WHEN 'runs' THEN DBMS_SCHEDULER.LOGGING_RUNS 209 | WHEN 'failed runs' THEN DBMS_SCHEDULER.LOGGING_FAILED_RUNS 210 | WHEN 'full' THEN DBMS_SCHEDULER.LOGGING_FULL 211 | END; 212 | v_name:= 'SYS.'||:name; 213 | v_resource:= :resource; 214 | v_service:= :service; 215 | v_history:= :history; 216 | v_comments:= :comments; 217 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'logging_level', v_logging); 218 | IF v_resource IS NOT NULL THEN 219 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'resource_consumer_group', v_resource); 220 | ELSE 221 | DBMS_SCHEDULER.SET_ATTRIBUTE_NULL(v_name, 'resource_consumer_group'); 222 | END IF; 223 | IF v_service IS NOT NULL THEN 224 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'service', v_service); 225 | ELSE 226 | DBMS_SCHEDULER.SET_ATTRIBUTE_NULL(v_name, 'service'); 227 | END IF; 228 | IF v_history IS NOT NULL THEN 229 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'log_history', v_history); 230 | ELSE 231 | DBMS_SCHEDULER.SET_ATTRIBUTE_NULL(v_name, 'log_history'); 232 | END IF; 233 | IF v_comments IS NOT NULL THEN 234 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'comments', v_comments); 235 | ELSE 236 | DBMS_SCHEDULER.SET_ATTRIBUTE_NULL(v_name, 'comments'); 237 | END IF; 238 | END; 239 | """, { 240 | "logging": module.params['logging'], 241 | "name": module.params['name'].upper(), 242 | "resource": module.params['resource_group'], 243 | "service": module.params['service'], 244 | "history": module.params['history'], 245 | "comments": module.params['comments'] 246 | }) 247 | result_changed = True 248 | elif result['exists'] and module.params['state'] == "absent": 249 | # Drop job class 250 | c = conn.cursor() 251 | c.execute("BEGIN DBMS_SCHEDULER.DROP_JOB_CLASS(:name); END;", {"name": module.params['name'].upper()}) 252 | result_changed = True 253 | elif not result['exists'] and module.params['state'] == "present": 254 | # Create job class 255 | c = conn.cursor() 256 | c.execute(""" 257 | DECLARE 258 | v_logging PLS_INTEGER; 259 | BEGIN 260 | v_logging:= CASE :logging WHEN 'off' THEN DBMS_SCHEDULER.LOGGING_OFF 261 | WHEN 'runs' THEN DBMS_SCHEDULER.LOGGING_RUNS 262 | WHEN 'failed runs' THEN DBMS_SCHEDULER.LOGGING_FAILED_RUNS 263 | WHEN 'full' THEN DBMS_SCHEDULER.LOGGING_FULL 264 | END; 265 | DBMS_SCHEDULER.CREATE_JOB_CLASS(job_class_name=>:name, resource_consumer_group=>:resource, service=>:service, 266 | logging_level=>v_logging, log_history=>:history, comments=>:comments); 267 | END;""", { 268 | "logging": module.params['logging'], 269 | "name": module.params['name'].upper(), 270 | "resource": module.params['resource_group'], 271 | "service": module.params['service'], 272 | "history": module.params['history'], 273 | "comments": module.params['comments'] 274 | }) 275 | result_changed = True 276 | 277 | conn.commit() 278 | module.exit_json(msg=", ".join(msg), changed=result_changed) 279 | 280 | 281 | from ansible.module_utils.basic import * 282 | if __name__ == '__main__': 283 | main() 284 | -------------------------------------------------------------------------------- /oracle_jobschedule: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_jobschedule 7 | short_description: Manage DBMS_SCHEDULER job schedules in Oracle database 8 | description: 9 | - Manage DBMS_SCHEDULER job schedules in Oracle database 10 | - Can be run locally on the controlmachine or on a remote host 11 | version_added: "2.2.1" 12 | options: 13 | hostname: 14 | description: 15 | - The Oracle database host 16 | required: false 17 | default: localhost 18 | port: 19 | description: 20 | - The listener port number on the host 21 | required: false 22 | default: 1521 23 | service_name: 24 | description: 25 | - The database service name to connect to 26 | required: true 27 | user: 28 | description: 29 | - The Oracle user name to connect to the database, must have DBA privilege 30 | required: False 31 | password: 32 | description: 33 | - The Oracle user password for 'user' 34 | required: False 35 | mode: 36 | description: 37 | - The mode with which to connect to the database 38 | required: true 39 | default: normal 40 | choices: ['normal','sysdba'] 41 | state: 42 | description: 43 | - If present, job schedule is created, if absent then schedule is dropped 44 | required: true 45 | choices: ['present','absent'] 46 | name: 47 | description: 48 | - Job schedule name 49 | required: True 50 | repeat_interval: 51 | description: 52 | - Schedule repeat interval using DBMS_SCHEDULER calendaring syntax 53 | required: True 54 | aliases: 55 | - interval 56 | comments: 57 | description: 58 | - Comment about the class 59 | required: False 60 | convert_to_upper: 61 | description: 62 | - Schedule name automatically converted to upper case 63 | required: false 64 | default: True 65 | type: bool 66 | 67 | notes: 68 | - cx_Oracle needs to be installed 69 | - Oracle RDBMS 10gR2 or later required 70 | requirements: [ "cx_Oracle", "re" ] 71 | author: Ilmar Kerm, ilmar.kerm@gmail.com, @ilmarkerm 72 | ''' 73 | 74 | EXAMPLES = ''' 75 | --- 76 | - hosts: localhost 77 | vars: 78 | oraclehost: 192.168.56.101 79 | oracleport: 1521 80 | oracleservice: orcl 81 | oracleuser: system 82 | oraclepassword: oracle 83 | oracle_env: 84 | ORACLE_HOME: /usr/lib/oracle/12.1/client64 85 | LD_LIBRARY_PATH: /usr/lib/oracle/12.1/client64/lib 86 | tasks: 87 | - name: job schedule 88 | oracle_jobschedule: 89 | hostname: "{{ oraclehost }}" 90 | port: "{{ oracleport }}" 91 | service_name: "{{ oracleservice }}" 92 | user: "{{ oracleuser }}" 93 | password: "{{ oraclepassword }}" 94 | state: present 95 | name: hr.hourly_schedule 96 | interval: FREQ=HOURLY; INTERVAL=1 97 | comments: Just for testing 98 | environment: "{{ oracle_env }}" 99 | ''' 100 | 101 | import re 102 | 103 | try: 104 | import cx_Oracle 105 | except ImportError: 106 | cx_oracle_exists = False 107 | else: 108 | cx_oracle_exists = True 109 | 110 | def query_existing(owner, name): 111 | c = conn.cursor() 112 | c.execute("SELECT repeat_interval, comments FROM all_scheduler_schedules WHERE owner = :owner AND schedule_name = :name", 113 | {"owner": owner, "name": name}) 114 | result = c.fetchone() 115 | if c.rowcount > 0: 116 | return {"exists": True, "repeat_interval": result[0], "comments": result[1]} 117 | else: 118 | return {"exists": False} 119 | 120 | # Ansible code 121 | def main(): 122 | global lconn, conn, msg, module 123 | msg = [''] 124 | module = AnsibleModule( 125 | argument_spec = dict( 126 | hostname = dict(default='localhost'), 127 | port = dict(default=1521, type='int'), 128 | service_name = dict(required=True), 129 | user = dict(required=False), 130 | password = dict(required=False), 131 | mode = dict(default='normal', choices=["normal","sysdba"]), 132 | state = dict(default="present", choices=["present", "absent"]), 133 | name = dict(required=True), 134 | repeat_interval = dict(required=True, aliases=['interval']), 135 | comments = dict(required=False), 136 | convert_to_upper = dict(default=True, type='bool') 137 | ), 138 | supports_check_mode=True 139 | ) 140 | # Check for required modules 141 | if not cx_oracle_exists: 142 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 143 | # Check input parameters 144 | re_name = re.compile("^[A-Za-z0-9_\$#]+\.[A-Za-z0-9_\$#]+$") 145 | if not re_name.match(module.params['name']): 146 | module.fail_json(msg="Invalid schedule name") 147 | job_fullname = module.params['name'].upper() if module.params['convert_to_upper'] else module.params['name'] 148 | job_parts = job_fullname.split(".") 149 | job_owner = job_parts[0] 150 | job_name = job_parts[1] 151 | job_fullname = "\"%s\".\"%s\"" % (job_owner, job_name) 152 | # Connect to database 153 | hostname = module.params["hostname"] 154 | port = module.params["port"] 155 | service_name = module.params["service_name"] 156 | user = module.params["user"] 157 | password = module.params["password"] 158 | mode = module.params["mode"] 159 | wallet_connect = '/@%s' % service_name 160 | try: 161 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 162 | if mode == 'sysdba': 163 | connect = wallet_connect 164 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 165 | else: 166 | connect = wallet_connect 167 | conn = cx_Oracle.connect(wallet_connect) 168 | 169 | elif (user and password ): 170 | if mode == 'sysdba': 171 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 172 | connect = dsn 173 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 174 | else: 175 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 176 | connect = dsn 177 | conn = cx_Oracle.connect(user, password, dsn) 178 | 179 | elif (not(user) or not(password)): 180 | module.fail_json(msg='Missing username or password for cx_Oracle') 181 | 182 | except cx_Oracle.DatabaseError as exc: 183 | error, = exc.args 184 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 185 | module.fail_json(msg=msg[0], changed=False) 186 | if conn.version < "10.2": 187 | module.fail_json(msg="Database version must be 10gR2 or greater", changed=False) 188 | # 189 | if module.check_mode: 190 | module.exit_json(changed=False) 191 | # 192 | #c = conn.cursor() 193 | result_changed = False 194 | result = query_existing(job_owner, job_name) 195 | if result['exists'] and module.params['state'] == "present": 196 | # Check attributes and modify if needed 197 | if (result['comments'] != module.params['comments']) or (result['repeat_interval'] != module.params['repeat_interval']): 198 | c = conn.cursor() 199 | c.execute(""" 200 | DECLARE 201 | v_name VARCHAR2(100); 202 | v_interval VARCHAR2(1000); 203 | v_comments VARCHAR2(4000); 204 | BEGIN 205 | v_name:= :name; 206 | v_interval:= :interval; 207 | v_comments:= :comments; 208 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'repeat_interval', v_interval); 209 | IF v_comments IS NOT NULL THEN 210 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'comments', v_comments); 211 | ELSE 212 | DBMS_SCHEDULER.SET_ATTRIBUTE_NULL(v_name, 'comments'); 213 | END IF; 214 | END; 215 | """, { 216 | "name": job_fullname, 217 | "interval": module.params['repeat_interval'], 218 | "comments": module.params['comments'] 219 | }) 220 | result_changed = True 221 | elif result['exists'] and module.params['state'] == "absent": 222 | # Drop job class 223 | c = conn.cursor() 224 | c.execute("BEGIN DBMS_SCHEDULER.DROP_SCHEDULE(:name); END;", {"name": job_fullname}) 225 | result_changed = True 226 | elif not result['exists'] and module.params['state'] == "present": 227 | # Create job class 228 | c = conn.cursor() 229 | c.execute(""" 230 | BEGIN 231 | DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name=>:name, repeat_interval=>:interval, comments=>:comments); 232 | END;""", { 233 | "name": job_fullname, 234 | "interval": module.params['repeat_interval'], 235 | "comments": module.params['comments'] 236 | }) 237 | result_changed = True 238 | 239 | conn.commit() 240 | module.exit_json(msg=", ".join(msg), changed=result_changed) 241 | 242 | 243 | from ansible.module_utils.basic import * 244 | if __name__ == '__main__': 245 | main() 246 | -------------------------------------------------------------------------------- /oracle_jobwindow: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_jobwindow 7 | short_description: Manage DBMS_SCHEDULER job windows in Oracle database 8 | description: 9 | - Manage DBMS_SCHEDULER job windows in Oracle database 10 | - Can be run locally on the controlmachine or on a remote host 11 | version_added: "2.2.1" 12 | options: 13 | hostname: 14 | description: 15 | - The Oracle database host 16 | required: false 17 | default: localhost 18 | port: 19 | description: 20 | - The listener port number on the host 21 | required: false 22 | default: 1521 23 | service_name: 24 | description: 25 | - The database service name to connect to 26 | required: true 27 | user: 28 | description: 29 | - The Oracle user name to connect to the database, must have DBA privilege 30 | required: False 31 | password: 32 | description: 33 | - The Oracle user password for 'user' 34 | required: False 35 | mode: 36 | description: 37 | - The mode with which to connect to the database 38 | required: true 39 | default: normal 40 | choices: ['normal','sysdba'] 41 | state: 42 | description: 43 | - If absent then window is dropped, if enabled or disabled then window is created at the requested state 44 | required: true 45 | choices: ['enabled','disabled','absent'] 46 | name: 47 | description: 48 | - Scheduler window name 49 | required: True 50 | repeat_interval: 51 | description: 52 | - Window repeat interval using DBMS_SCHEDULER calendaring syntax 53 | required: True 54 | aliases: 55 | - interval 56 | comments: 57 | description: 58 | - Comment about the window 59 | required: False 60 | resource_plan: 61 | description: 62 | - Comment about the window 63 | required: False 64 | window_priority: 65 | description: 66 | - Window priority 67 | default: low 68 | choices: 69 | - low 70 | - high 71 | aliases: 72 | - priority 73 | duration_min: 74 | description: 75 | - Total window duration in minutes 76 | required: False 77 | type: int 78 | duration_hour: 79 | description: 80 | - Total window duration in hours 81 | required: False 82 | type: int 83 | 84 | notes: 85 | - cx_Oracle needs to be installed 86 | - Oracle RDBMS 10gR2 or later required 87 | requirements: [ "cx_Oracle", "datetime" ] 88 | author: Ilmar Kerm, ilmar.kerm@gmail.com, @ilmarkerm 89 | ''' 90 | 91 | EXAMPLES = ''' 92 | - hosts: localhost 93 | vars: 94 | oraclehost: 192.168.56.101 95 | oracleport: 1521 96 | oracleservice: orcl 97 | oracleuser: system 98 | oraclepassword: oracle 99 | oracle_env: 100 | ORACLE_HOME: /usr/lib/oracle/12.1/client64 101 | LD_LIBRARY_PATH: /usr/lib/oracle/12.1/client64/lib 102 | tasks: 103 | - name: job window 104 | oracle_jobwindow: 105 | hostname: "{{ oraclehost }}" 106 | port: "{{ oracleport }}" 107 | service_name: "{{ oracleservice }}" 108 | user: "{{ oracleuser }}" 109 | password: "{{ oraclepassword }}" 110 | state: enabled 111 | name: SUNDAY_WINDOW 112 | interval: freq=daily;byday=SUN;byhour=6;byminute=0; bysecond=0 113 | comments: Sunday window for maintenance tasks 114 | duration_hour: 12 115 | resource_plan: DEFAULT_MAINTENANCE_PLAN 116 | environment: "{{ oracle_env }}" 117 | ''' 118 | 119 | from datetime import timedelta 120 | 121 | try: 122 | import cx_Oracle 123 | except ImportError: 124 | cx_oracle_exists = False 125 | else: 126 | cx_oracle_exists = True 127 | 128 | def query_existing(name): 129 | c = conn.cursor() 130 | c.execute("SELECT resource_plan, duration, window_priority, enabled, repeat_interval, comments FROM all_scheduler_windows WHERE owner = 'SYS' AND window_name = :name", 131 | {"name": name}) 132 | result = c.fetchone() 133 | if c.rowcount > 0: 134 | return {"exists": True, "resource_plan": result[0], "duration": result[1], "window_priority": result[2], "enabled": (result[3] == "TRUE"), 135 | "repeat_interval": result[4], "comments": result[5]} 136 | else: 137 | return {"exists": False} 138 | 139 | # Ansible code 140 | def main(): 141 | global lconn, conn, msg, module 142 | msg = [''] 143 | module = AnsibleModule( 144 | argument_spec = dict( 145 | hostname = dict(default='localhost'), 146 | port = dict(default=1521, type='int'), 147 | service_name = dict(required=True), 148 | user = dict(required=False), 149 | password = dict(required=False), 150 | mode = dict(default='normal', choices=["normal","sysdba"]), 151 | state = dict(default="enabled", choices=["absent","enabled","disabled"]), 152 | name = dict(required=True, aliases=["window_name"]), 153 | resource_plan = dict(required=False), 154 | repeat_interval = dict(required=True, aliases=['interval']), 155 | window_priority = dict(default="low", choices=["low","high"], aliases=['priority']), 156 | duration_min = dict(required=False, type='int'), 157 | duration_hour = dict(required=False, type='int'), 158 | comments = dict(required=False) 159 | ), 160 | supports_check_mode=True, 161 | mutually_exclusive=[['duration_min','duration_hour']] 162 | ) 163 | # Check for required modules 164 | if not cx_oracle_exists: 165 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 166 | # Check input parameters 167 | job_fullname = module.params['name'].upper() 168 | if module.params['duration_min'] is None and module.params['duration_hour'] is None: 169 | module.fail_json(msg='Either duration_min or duration_hour must be specified', changed=False) 170 | new_duration_min = module.params['duration_min'] if module.params['duration_min'] else (module.params['duration_hour']*60) 171 | new_duration = timedelta(minutes=new_duration_min) 172 | if new_duration_min < 1: 173 | module.fail_json(msg='Invalid window duration', changed=False) 174 | # Connect to database 175 | hostname = module.params["hostname"] 176 | port = module.params["port"] 177 | service_name = module.params["service_name"] 178 | user = module.params["user"] 179 | password = module.params["password"] 180 | mode = module.params["mode"] 181 | wallet_connect = '/@%s' % service_name 182 | try: 183 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 184 | if mode == 'sysdba': 185 | connect = wallet_connect 186 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 187 | else: 188 | connect = wallet_connect 189 | conn = cx_Oracle.connect(wallet_connect) 190 | 191 | elif (user and password ): 192 | if mode == 'sysdba': 193 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 194 | connect = dsn 195 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 196 | else: 197 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 198 | connect = dsn 199 | conn = cx_Oracle.connect(user, password, dsn) 200 | 201 | elif (not(user) or not(password)): 202 | module.fail_json(msg='Missing username or password for cx_Oracle') 203 | 204 | except cx_Oracle.DatabaseError as exc: 205 | error, = exc.args 206 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 207 | module.fail_json(msg=msg[0], changed=False) 208 | if conn.version < "10.2": 209 | module.fail_json(msg="Database version must be 10gR2 or greater", changed=False) 210 | # 211 | if module.check_mode: 212 | module.exit_json(changed=False) 213 | # 214 | #c = conn.cursor() 215 | result_changed = False 216 | result = query_existing(job_fullname) 217 | if (result['exists'] and module.params['state'] != "absent" and ( 218 | (result['comments'] != module.params['comments']) or 219 | (result['repeat_interval'] != module.params['repeat_interval']) or 220 | (result['resource_plan'] != module.params['resource_plan'].upper() if module.params['resource_plan'] else None) or 221 | (result['window_priority'] != module.params['window_priority'].upper()) or 222 | (result['duration'] != new_duration))): 223 | c = conn.cursor() 224 | c.execute(""" 225 | DECLARE 226 | v_name all_scheduler_windows.window_name%type; 227 | v_interval VARCHAR2(1000); 228 | v_comments VARCHAR2(1000); 229 | v_plan VARCHAR2(200); 230 | v_duration NUMBER; 231 | v_priority VARCHAR2(10); 232 | v_state VARCHAR2(10); 233 | BEGIN 234 | v_name:= 'SYS.'||:name; 235 | v_interval:= :interval; 236 | v_comments:= :comments; 237 | v_plan:= :plan; 238 | v_duration:= :duration; 239 | v_priority:= :priority; 240 | v_state:= :state; 241 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'repeat_interval', v_interval); 242 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'window_priority', v_priority); 243 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'duration', numtodsinterval(v_duration, 'MINUTE')); 244 | IF v_comments IS NOT NULL THEN 245 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'comments', v_comments); 246 | ELSE 247 | DBMS_SCHEDULER.SET_ATTRIBUTE_NULL(v_name, 'comments'); 248 | END IF; 249 | IF v_plan IS NOT NULL THEN 250 | DBMS_SCHEDULER.SET_ATTRIBUTE(v_name, 'resource_plan', v_plan); 251 | ELSE 252 | DBMS_SCHEDULER.SET_ATTRIBUTE_NULL(v_name, 'resource_plan'); 253 | END IF; 254 | IF v_state = 'enabled' THEN 255 | DBMS_SCHEDULER.ENABLE(v_name); 256 | ELSE 257 | DBMS_SCHEDULER.DISABLE(v_name); 258 | END IF; 259 | END; 260 | """, { 261 | "name": job_fullname, 262 | "interval": module.params['repeat_interval'], 263 | "comments": module.params['comments'], 264 | "plan": module.params['resource_plan'], 265 | "duration": new_duration_min, 266 | "priority": module.params['window_priority'].upper(), 267 | "state": module.params['state'] 268 | }) 269 | result_changed = True 270 | elif result['exists'] and result['enabled'] and module.params['state'] == "disabled": 271 | c = conn.cursor() 272 | c.execute("BEGIN DBMS_SCHEDULER.DISABLE('SYS.'||:name); END;", {"name": job_fullname}) 273 | result_changed = True 274 | elif result['exists'] and not result['enabled'] and module.params['state'] == "enabled": 275 | c = conn.cursor() 276 | c.execute("BEGIN DBMS_SCHEDULER.ENABLE('SYS.'||:name); END;", {"name": job_fullname}) 277 | result_changed = True 278 | elif result['exists'] and module.params['state'] == "absent": 279 | # Drop window 280 | c = conn.cursor() 281 | c.execute("BEGIN DBMS_SCHEDULER.DROP_WINDOW(window_name=>:name); END;", {"name": job_fullname}) 282 | result_changed = True 283 | elif not result['exists'] and module.params['state'] in ("enabled","disabled"): 284 | # Create window 285 | c = conn.cursor() 286 | c.execute(""" 287 | DECLARE 288 | v_name all_scheduler_windows.window_name%type; 289 | BEGIN 290 | v_name:= :name; 291 | DBMS_SCHEDULER.CREATE_WINDOW(window_name=>v_name, repeat_interval=>:interval, comments=>:comments, resource_plan=>:plan, 292 | duration=>numtodsinterval(:duration, 'MINUTE'), window_priority=>:priority); 293 | IF :state = 'enabled' THEN 294 | DBMS_SCHEDULER.ENABLE('SYS.'||v_name); 295 | ELSE 296 | DBMS_SCHEDULER.DISABLE('SYS.'||v_name); 297 | END IF; 298 | END;""", { 299 | "name": job_fullname, 300 | "interval": module.params['repeat_interval'], 301 | "comments": module.params['comments'], 302 | "plan": module.params['resource_plan'], 303 | "duration": new_duration_min, 304 | "priority": module.params['window_priority'].upper(), 305 | "state": module.params['state'] 306 | }) 307 | result_changed = True 308 | 309 | conn.commit() 310 | module.exit_json(msg=", ".join(msg), changed=result_changed) 311 | 312 | 313 | from ansible.module_utils.basic import * 314 | if __name__ == '__main__': 315 | main() 316 | -------------------------------------------------------------------------------- /oracle_parameter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_parameter 7 | short_description: Manage parameters in an Oracle database 8 | description: 9 | - Manage init parameters in an Oracle database 10 | 11 | version_added: "1.9.1" 12 | options: 13 | hostname: 14 | description: 15 | - The Oracle database host 16 | required: false 17 | default: localhost 18 | port: 19 | description: 20 | - The listener port number on the host 21 | required: false 22 | default: 1521 23 | service_name: 24 | description: 25 | - The database service name to connect to 26 | required: true 27 | user: 28 | description: 29 | - The Oracle user name to connect to the database 30 | required: true 31 | password: 32 | description: 33 | - The Oracle user password for 'user' 34 | required: true 35 | mode: 36 | description: 37 | - The mode with which to connect to the database 38 | required: true 39 | default: normal 40 | choices: ['normal','sysdba'] 41 | name: 42 | description: 43 | - The parameter that is being changed 44 | required: false 45 | default: null 46 | value: 47 | description: 48 | - The value of the parameter 49 | required: false 50 | default: null 51 | state: 52 | description: 53 | - The intended state of the parameter (present means set to value, absent/reset means the value is reset to its default value). 54 | default: present 55 | choices: ['present','absent','reset'] 56 | notes: 57 | - cx_Oracle needs to be installed 58 | requirements: [ "cx_Oracle","re" ] 59 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 60 | ''' 61 | 62 | EXAMPLES = ''' 63 | # Set the value of db_recovery_file_dest 64 | oracle_parameter: hostname=remote-db-server service_name=orcl user=system password=manager name=db_recovery_file_dest value='+FRA' state=present scope=both sid='*' 65 | 66 | # Set the value of db_recovery_file_dest_size 67 | oracle_parameter: hostname=remote-db-server service_name=orcl user=system password=manager name=db_recovery_file_dest_size value=100G state=present scope=both 68 | 69 | # Reset the value of open_cursors 70 | oracle_parameter: hostname=remote-db-server service_name=orcl user=system password=manager name=db_recovery_file_dest_size state=reset scope=spfile 71 | 72 | 73 | ''' 74 | import re 75 | try: 76 | import cx_Oracle 77 | except ImportError: 78 | cx_oracle_exists = False 79 | else: 80 | cx_oracle_exists = True 81 | 82 | 83 | # Check if the parameter exists 84 | def check_parameter_exists(module, mode, msg, cursor, name): 85 | 86 | 87 | if not(name): 88 | module.fail_json(msg='Error: Missing parameter name', changed=False) 89 | return False 90 | 91 | if name.startswith('_') and mode != 'sysdba': 92 | module.fail_json(msg='You need sysdba privs to verify underscore parameters (%s), mode: (%s)' % (name, mode), changed=False) 93 | 94 | elif name.startswith('_') and mode == 'sysdba': 95 | sql = 'select lower(ksppinm) from sys.x$ksppi where ksppinm = lower(\'%s\')' % name 96 | 97 | else: 98 | sql = 'select lower(name) from v$parameter where name = lower(\'%s\')' % name 99 | 100 | 101 | try: 102 | cursor.execute(sql) 103 | result = (cursor.fetchone()) 104 | except cx_Oracle.DatabaseError as exc: 105 | error, = exc.args 106 | msg = error.message+ 'sql: ' + sql 107 | return False 108 | 109 | if result: 110 | return True 111 | else: 112 | msg = 'The parameter (%s) doesn\'t exist' % name 113 | return False 114 | 115 | 116 | 117 | 118 | def modify_parameter(module, mode, msg, cursor, name, value, comment, scope, sid): 119 | 120 | contains = re.compile(r'[?*$%#()!\s,._/=+-]') 121 | starters = ('+','_','"','"_','/') 122 | 123 | if not(name) or not (value) or name is None or value is None: 124 | module.fail_json(msg='Error: Missing parameter name or value. (If value is supposed to be an empty string, make sure it\'s quoted)', changed=False) 125 | return False 126 | 127 | 128 | 129 | currval = get_curr_value(module, mode, msg, cursor, name, scope) 130 | 131 | if currval == value.lower() or not currval and value == "''": 132 | module.exit_json(msg='The parameter (%s) is already set to %s' % (name, value), changed=False) 133 | return True 134 | 135 | if module.check_mode: 136 | msg = '%s will be changed, new: %s, old: %s' % (name,value,currval.upper()) 137 | module.exit_json(msg=msg, changed=True) 138 | 139 | if name.startswith(starters): 140 | name = quote_name(name) 141 | 142 | if contains.search(value): 143 | value = quote_value(value) 144 | 145 | sql = 'alter system set %s=%s ' % (name, value) 146 | if comment is not None: 147 | sql += ' comment=\'%s\'' % (comment) 148 | # module.fail_json(msg=sql) 149 | sql += 'scope=%s sid=\'%s\'' % (scope,sid) 150 | try: 151 | cursor.execute(sql) 152 | except cx_Oracle.DatabaseError as exc: 153 | error, = exc.args 154 | msg = 'Blergh, something went wrong while changing the value - %s sql: %s' % (error.message, sql) 155 | module.fail_json(msg=msg, changed=False) 156 | 157 | name = clean_string(name) 158 | msg = 'The parameter (%s) has been changed successfully, new: %s, old: %s' % (name, value, currval) 159 | module.exit_json(msg=msg, changed=True) 160 | return True 161 | 162 | def quote_value(value): 163 | 164 | if len(value) > 0: 165 | return "'%s'" % (value) 166 | else: 167 | return value 168 | 169 | 170 | def quote_name(name): 171 | 172 | if len(name) > 0: 173 | return '"%s"' % (name) 174 | else: 175 | return name 176 | 177 | def clean_string(item): 178 | item = item.replace("""\"""","") 179 | 180 | return item 181 | 182 | def reset_parameter(module, mode, msg, cursor, name, value, comment, scope, sid): 183 | 184 | starters = ('+','_','"','"_') 185 | if not(name): 186 | module.fail_json(msg='Error: Missing parameter name', changed=False) 187 | return False 188 | 189 | if module.check_mode: 190 | module.exit_json(changed=True) 191 | 192 | if name.startswith(starters): 193 | name = quote_name(name) 194 | 195 | sql = 'alter system reset %s scope=%s sid=\'%s\'' % (name,scope,sid) 196 | 197 | try: 198 | cursor.execute(sql) 199 | except cx_Oracle.DatabaseError as exc: 200 | error, = exc.args 201 | if error.code == 32010: 202 | name = clean_string(name) 203 | msg = 'The parameter (%s) is already set to its default value' % (name) 204 | module.exit_json(msg=msg, changed=False) 205 | return True 206 | 207 | msg = 'Blergh, something went wrong while resetting the parameter - %s sql: %s' % (error.message, sql) 208 | return False 209 | 210 | name = clean_string(name) 211 | msg = 'The parameter (%s) has been reset to its default value' % (name) 212 | return True 213 | 214 | 215 | def get_curr_value(module, mode, msg, cursor, name, scope): 216 | 217 | name = clean_string(name) 218 | 219 | if scope == 'spfile': 220 | parameter_source = 'v$spparameter' 221 | else: 222 | parameter_source = 'v$parameter' 223 | 224 | if mode == 'sysdba': 225 | if scope == 'spfile': 226 | sql = 'select lower(KSPSPFFTCTXSPDVALUE) from x$kspspfile where lower(KSPSPFFTCTXSPNAME) = lower(\'%s\')' % (name) 227 | else: 228 | sql = 'select lower(y.ksppstdvl) from sys.x$ksppi x, sys.x$ksppcv y where x.indx = y.indx and x.ksppinm = lower(\'%s\')' % (name) 229 | else: 230 | sql = 'select lower(display_value) from %s where name = lower(\'%s\')' % (parameter_source, name) 231 | 232 | try: 233 | cursor.execute(sql) 234 | result = (cursor.fetchall()[0][0]) 235 | 236 | except cx_Oracle.DatabaseError as exc: 237 | error, = exc.args 238 | msg = 'Blergh, something went wrong while getting current value - %s sql: %s' % (error.message, sql) 239 | module.fail_json(msg=msg, changed=False) 240 | 241 | return result 242 | 243 | 244 | def main(): 245 | 246 | msg = [''] 247 | module = AnsibleModule( 248 | argument_spec = dict( 249 | hostname = dict(default='localhost'), 250 | port = dict(default=1521), 251 | service_name = dict(required=True), 252 | user = dict(required=False), 253 | password = dict(required=False, no_log=True), 254 | mode = dict(default='normal', choices=["normal","sysdba"]), 255 | name = dict(default=None, aliases = ['parameter']), 256 | value = dict(default=None), 257 | comment = dict(default=None), 258 | state = dict(default="present", choices=["present", "absent","reset"]), 259 | scope = dict(default="both", choices=["both", "spfile", "memory"]), 260 | sid = dict(default="*"), 261 | ), 262 | supports_check_mode=True 263 | ) 264 | 265 | hostname = module.params["hostname"] 266 | port = module.params["port"] 267 | service_name = module.params["service_name"] 268 | user = module.params["user"] 269 | password = module.params["password"] 270 | mode = module.params["mode"] 271 | name = module.params["name"] 272 | value = module.params["value"] 273 | comment = module.params["comment"] 274 | state = module.params["state"] 275 | scope = module.params["scope"] 276 | sid = module.params["sid"] 277 | 278 | 279 | 280 | 281 | if not cx_oracle_exists: 282 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 283 | 284 | wallet_connect = '/@%s' % service_name 285 | try: 286 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 287 | if mode == 'sysdba': 288 | connect = wallet_connect 289 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 290 | else: 291 | connect = wallet_connect 292 | conn = cx_Oracle.connect(wallet_connect) 293 | 294 | elif (user and password ): 295 | if mode == 'sysdba': 296 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 297 | connect = dsn 298 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 299 | else: 300 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 301 | connect = dsn 302 | conn = cx_Oracle.connect(user, password, dsn) 303 | 304 | elif (not(user) or not(password)): 305 | module.fail_json(msg='Missing username or password for cx_Oracle') 306 | 307 | except cx_Oracle.DatabaseError as exc: 308 | error, = exc.args 309 | msg = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 310 | module.fail_json(msg=msg, changed=False) 311 | 312 | cursor = conn.cursor() 313 | 314 | if state == 'present': 315 | if check_parameter_exists(module, mode, msg, cursor, name): 316 | if modify_parameter(module, mode, msg, cursor, name, value, comment, scope, sid): 317 | module.exit_json(msg=msg, changed=True) 318 | else: 319 | module.fail_json(msg=msg, changed=False) 320 | else: 321 | module.fail_json(msg=msg, changed=False) 322 | 323 | elif state == 'reset' or state == 'absent': 324 | if check_parameter_exists(module, mode, msg, cursor, name): 325 | if reset_parameter(module, mode, msg, cursor, name, value, comment, scope, sid): 326 | module.exit_json(msg=msg, changed=True) 327 | else: 328 | module.fail_json(msg=msg, changed=False) 329 | else: 330 | module.fail_json(msg=msg, changed=False) 331 | 332 | 333 | 334 | module.exit_json(msg=msg, changed=False) 335 | 336 | 337 | 338 | 339 | 340 | 341 | from ansible.module_utils.basic import * 342 | if __name__ == '__main__': 343 | main() 344 | -------------------------------------------------------------------------------- /oracle_profile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_profile 7 | short_description: Manage profiles in an Oracle database 8 | description: 9 | - Manage profiles in an Oracle database 10 | version_added: "2.4.1.0" 11 | options: 12 | name: 13 | description: 14 | - The name of the profile 15 | required: true 16 | default: None 17 | aliases: ['profile'] 18 | state: 19 | description: 20 | - The intended state of the profile. 21 | default: present 22 | choices: ['present','absent'] 23 | attribute_name: 24 | description: 25 | - The attribute name (e.g PASSWORD_REUSE_TIME) 26 | default: None 27 | aliases: ['an'] 28 | attribute_value: 29 | description: 30 | - The attribute value (e.g 10) 31 | default: None 32 | aliases: ['av'] 33 | username: 34 | description: 35 | - The DB username 36 | required: false 37 | default: sys 38 | aliases: ['un'] 39 | password: 40 | description: 41 | - The password for the DB user 42 | required: false 43 | default: None 44 | aliases: ['pw'] 45 | service_name: 46 | description: 47 | - The profile_name to connect to the database. 48 | required: false 49 | aliases: ['sn'] 50 | hostname: 51 | description: 52 | - The host of the database if using dbms_profile 53 | required: false 54 | default: localhost 55 | aliases: ['host'] 56 | port: 57 | description: 58 | - The listener port to connect to the database if using dbms_profile 59 | required: false 60 | default: 1521 61 | oracle_home: 62 | description: 63 | - The GI ORACLE_HOME 64 | required: false 65 | default: None 66 | aliases: ['oh'] 67 | 68 | 69 | 70 | notes: 71 | - cx_Oracle needs to be installed 72 | requirements: [ "cx_Oracle" ] 73 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 74 | ''' 75 | 76 | EXAMPLES = ''' 77 | # Create a profile 78 | - hosts: dbserver 79 | vars: 80 | oracle_home: /u01/app/oracle/12.2.0.1/db1 81 | hostname: "{{ inventory_hostname }}" 82 | service_name: orclpdb 83 | user: system 84 | password: Oracle_123 85 | oracle_env: 86 | ORACLE_HOME: "{{ oracle_home }}" 87 | LD_LIBRARY_PATH: "{{ oracle_home }}/lib" 88 | profiles: 89 | - name: profile1 90 | attribute_name: 91 | - password_reuse_max 92 | - password_reuse_time 93 | - sessions_per_user 94 | attribute_value: 95 | - 6 96 | - 20 97 | - 5 98 | state: present 99 | tasks: 100 | - name: Manage profiles 101 | oracle_profile: 102 | name={{ item.name }} 103 | attribute_name={{ item.attribute_name}} 104 | attribute_value={{ item.attribute_value}} 105 | state={{ item.state }} 106 | hostname={{ hostname }} 107 | service_name={{ service_name }} 108 | user={{ user }} 109 | password={{ password }} 110 | environment: "{{oracle_env}}" 111 | with_items: "{{ profiles }}" 112 | 113 | ''' 114 | import os 115 | 116 | try: 117 | import cx_Oracle 118 | except ImportError: 119 | cx_oracle_exists = False 120 | else: 121 | cx_oracle_exists = True 122 | 123 | 124 | # Check if the profile exists 125 | def check_profile_exists(cursor, module, msg, name): 126 | 127 | sql = 'select count(*) from dba_profiles where lower (profile) = \'%s\'' % (name.lower()) 128 | result = execute_sql_get(module, msg, cursor, sql) 129 | 130 | if result[0][0] > 0: 131 | return True 132 | else: 133 | return False 134 | 135 | def create_profile(cursor, module, msg, oracle_home, name, attribute_name, attribute_value): 136 | 137 | add_attr = False 138 | if not any(x == 'None' for x in attribute_name): 139 | add_attr = True 140 | if not any(x == None for x in attribute_name): 141 | add_attr = True 142 | 143 | if add_attr: 144 | attributes =' '.join([''+str(n)+' '+ str(v) + '' for n,v in zip(attribute_name,attribute_value)]) 145 | 146 | 147 | sql = 'create profile %s limit ' % (name) 148 | if add_attr: 149 | sql += ' %s' % (attributes.lower()) 150 | 151 | if execute_sql(module, msg, cursor, sql): 152 | return True 153 | else: 154 | return False 155 | 156 | 157 | def remove_profile(cursor, module, msg, oracle_home, name): 158 | 159 | dropsql = 'drop profile %s' % (name) 160 | if execute_sql(module, msg, cursor, dropsql): 161 | return True 162 | else: 163 | return False 164 | 165 | def ensure_profile_state(cursor, module, msg, name, state, attribute_name, attribute_value): 166 | #pass 167 | 168 | total_sql = [] 169 | profile_sql = 'alter profile %s ' % (name.upper()) 170 | 171 | # Deal with attribute differences 172 | if (attribute_name and attribute_value): 173 | # Make sure attributes are lower case 174 | attribute_name = [x.lower() for x in attribute_name] 175 | attribute_value = [str(y).lower() for y in attribute_value] 176 | wanted_attributes = list(zip(attribute_name,attribute_value)) 177 | 178 | # Check the current attributes 179 | attribute_names_ =','.join(['\''+n[0]+'\'' for n in wanted_attributes]) 180 | if len(attribute_names_) != 0: 181 | current_attributes = get_current_attributes (cursor, module, msg, name, attribute_names_) 182 | 183 | # Convert to dict and compare current with wanted 184 | if dict(current_attributes) != dict(wanted_attributes): 185 | for i in wanted_attributes: 186 | total_sql.append("alter profile %s limit %s %s " % (name, i[0], i[1])) 187 | 188 | # module.exit_json(msg=total_sql, changed=True) 189 | if len(total_sql) > 0: 190 | if ensure_profile_state_sql(module,msg,cursor,total_sql): 191 | msg = 'profile %s has been put in the intended state' % (name) 192 | module.exit_json(msg=msg, changed=True) 193 | else: 194 | return False 195 | else: 196 | msg = 'Nothing to do' 197 | module.exit_json(msg=msg, changed=False) 198 | 199 | def ensure_profile_state_sql(module,msg,cursor,total_sql): 200 | 201 | for sql in total_sql: 202 | execute_sql(module, msg, cursor, sql) 203 | return True 204 | 205 | 206 | def get_current_attributes(cursor, module, msg, name,attribute_names_): 207 | 208 | 209 | sql = 'select lower(resource_name),lower(limit) ' 210 | sql += 'from dba_profiles ' 211 | sql += 'where lower(profile) = \'%s\' ' % (name.lower()) 212 | sql += 'and lower(resource_name) in (%s) ' % (attribute_names_.lower()) 213 | 214 | result = execute_sql_get(module, msg, cursor, sql) 215 | 216 | return result 217 | 218 | def execute_sql_get(module, msg, cursor, sql): 219 | 220 | try: 221 | cursor.execute(sql) 222 | result = (cursor.fetchall()) 223 | except cx_Oracle.DatabaseError as exc: 224 | error, = exc.args 225 | msg = 'Something went wrong while executing sql_get - %s sql: %s' % (error.message, sql) 226 | module.fail_json(msg=msg, changed=False) 227 | return False 228 | 229 | 230 | return result 231 | 232 | 233 | def execute_sql(module, msg, cursor, sql): 234 | 235 | try: 236 | cursor.execute(sql) 237 | except cx_Oracle.DatabaseError as exc: 238 | error, = exc.args 239 | msg = 'Something went wrong while executing sql - %s sql: %s' % (error.message, sql) 240 | module.fail_json(msg=msg, changed=False) 241 | return False 242 | return True 243 | 244 | 245 | 246 | def main(): 247 | 248 | msg = [''] 249 | cursor = None 250 | 251 | module = AnsibleModule( 252 | argument_spec = dict( 253 | name = dict(required=True, aliases = ['profile']), 254 | attribute_name = dict(required=True, type='list', aliases=['an']), 255 | attribute_value = dict(required=True, type='list', aliases=['av']), 256 | state = dict(default="present", choices = ["present", "absent"]), 257 | user = dict(required=False, aliases = ['un','username']), 258 | password = dict(required=False, no_log=True, aliases = ['pw']), 259 | mode = dict(default='normal', choices=["normal","sysdba"]), 260 | hostname = dict(required=False, default = 'localhost', aliases = ['host']), 261 | port = dict(required=False, default = 1521), 262 | service_name = dict(required=False, aliases = ['sn']), 263 | oracle_home = dict(required=False, aliases = ['oh']), 264 | 265 | 266 | 267 | ), 268 | 269 | ) 270 | 271 | name = module.params["name"] 272 | attribute_name = module.params["attribute_name"] 273 | attribute_value = module.params["attribute_value"] 274 | state = module.params["state"] 275 | user = module.params["user"] 276 | password = module.params["password"] 277 | mode = module.params["mode"] 278 | hostname = module.params["hostname"] 279 | port = module.params["port"] 280 | service_name = module.params["service_name"] 281 | oracle_home = module.params["oracle_home"] 282 | 283 | 284 | if not cx_oracle_exists: 285 | msg = "The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set" 286 | module.fail_json(msg=msg) 287 | 288 | wallet_connect = '/@%s' % service_name 289 | try: 290 | if (not user and not password) : # If neither user or password is supplied, the use of an oracle wallet is assumed 291 | connect = wallet_connect 292 | if mode == 'sysdba': 293 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 294 | else: 295 | conn = cx_Oracle.connect(wallet_connect) 296 | elif (user and password): 297 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 298 | connect = dsn 299 | if mode == 'sysdba': 300 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 301 | else: 302 | conn = cx_Oracle.connect(user, password, dsn) 303 | elif (not(user) or not(password)): 304 | module.fail_json(msg='Missing username or password for cx_Oracle') 305 | 306 | except cx_Oracle.DatabaseError as exc: 307 | error, = exc.args 308 | msg = 'Could not connect to DB: %s, connect descriptor: %s, username: %s, pass: %s' % (error.message, connect,user,password) 309 | module.fail_json(msg=msg, changed=False) 310 | 311 | cursor = conn.cursor() 312 | 313 | 314 | if oracle_home is not None: 315 | os.environ['ORACLE_HOME'] = oracle_home 316 | elif 'ORACLE_HOME' in os.environ: 317 | oracle_home = os.environ['ORACLE_HOME'] 318 | else: 319 | msg = 'ORACLE_HOME variable not set. Please set it and re-run the command' 320 | module.fail_json(msg=msg, changed=False) 321 | 322 | 323 | if state == 'present': 324 | if not check_profile_exists(cursor, module, msg, name): 325 | if create_profile(cursor, module, msg, oracle_home, name, attribute_name, attribute_value): 326 | msg = 'Successfully created profile %s ' % (name) 327 | module.exit_json(msg=msg, changed=True) 328 | else: 329 | module.fail_json(msg=msg, changed=False) 330 | else: 331 | ensure_profile_state(cursor, module, msg, name, state, attribute_name, attribute_value) 332 | 333 | elif state == 'absent' : 334 | if check_profile_exists(cursor, module, msg, name): 335 | if remove_profile(cursor, module, msg, oracle_home, name): 336 | msg = 'Profile %s successfully removed' % (name) 337 | module.exit_json(msg=msg, changed=True) 338 | else: 339 | module.exit_json(msg=msg, changed=False) 340 | else: 341 | msg = 'Profile %s doesn\'t exist' % (name) 342 | module.exit_json(msg=msg, changed=False) 343 | 344 | 345 | module.exit_json(msg="Unhandled exit", changed=False) 346 | 347 | 348 | 349 | 350 | from ansible.module_utils.basic import * 351 | if __name__ == '__main__': 352 | main() 353 | -------------------------------------------------------------------------------- /oracle_redo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ANSIBLE_METADATA = { 5 | 'metadata_version': '1.1', 6 | 'status': ['preview'], 7 | 'supported_by': 'community' 8 | } 9 | 10 | DOCUMENTATION = ''' 11 | --- 12 | module: oracle_redo 13 | short_description: Manage Oracle redo related things 14 | description: 15 | - Manage redogroups 16 | - Can be run locally on the controlmachine or on a remote host 17 | version_added: "2.4" 18 | options: 19 | hostname: 20 | description: 21 | - The Oracle database host 22 | required: false 23 | default: localhost 24 | port: 25 | description: 26 | - The listener port number on the host 27 | required: false 28 | default: 1521 29 | service_name: 30 | description: 31 | - The database service name to connect to 32 | required: true 33 | user: 34 | description: 35 | - The Oracle user name to connect to the database, must have DBA privilege 36 | required: False 37 | password: 38 | description: 39 | - The Oracle user password for 'user' 40 | required: False 41 | mode: 42 | description: 43 | - The mode with which to connect to the database 44 | required: true 45 | default: normal 46 | choices: ['normal','sysdba'] 47 | size: 48 | description: 49 | - size of redologs 50 | required: 51 | - True 52 | default: 53 | - 50MB 54 | groups: 55 | description: 56 | - The number of redolog groups 57 | required: 58 | - False 59 | members: 60 | description: 61 | - Either to set the preference (present) or reset it to default (absent) 62 | required: true 63 | default: 1 64 | 65 | 66 | notes: 67 | - cx_Oracle needs to be installed 68 | requirements: [ "cx_Oracle" ] 69 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 70 | ''' 71 | 72 | EXAMPLES = ''' 73 | - hosts: all 74 | gather_facts: true 75 | vars: 76 | oracle_home: /u01/app/oracle/12.2.0.1/db1 77 | hostname: "{{ ansible_hostname }}" 78 | service_name: orclcdb 79 | user: system 80 | password: Oracle_123 81 | oracle_env: 82 | ORACLE_HOME: "{{ oracle_home }}" 83 | LD_LIBRARY_PATH: "{{ oracle_home }}/lib" 84 | 85 | redosize: 15M 86 | numgroups: 3 87 | tasks: 88 | - name: Manage redologs 89 | oracle_redo: 90 | service_name={{ service_name }} 91 | hostname={{ hostname}} 92 | user={{ user }} 93 | password={{ password }} 94 | groups={{ numgroups |default(omit) }} 95 | size={{ redosize |default(omit)}} 96 | environment: "{{ oracle_env }}" 97 | run_once: True 98 | ''' 99 | 100 | try: 101 | import cx_Oracle 102 | except ImportError: 103 | cx_oracle_exists = False 104 | else: 105 | cx_oracle_exists = True 106 | 107 | # Ansible code 108 | def main(): 109 | global lconn, conn, msg, module 110 | msg = [''] 111 | module = AnsibleModule( 112 | argument_spec = dict( 113 | hostname = dict(default='localhost'), 114 | port = dict(default=1521, type='int'), 115 | service_name = dict(required=True), 116 | user = dict(required=False), 117 | password = dict(required=False, no_log=True), 118 | mode = dict(default='normal', choices=["normal","sysdba"]), 119 | size = dict(required=True), 120 | groups = dict(required=True) 121 | # threads = dict(default=1) 122 | ), 123 | 124 | ) 125 | 126 | 127 | hostname = module.params["hostname"] 128 | port = module.params["port"] 129 | service_name = module.params["service_name"] 130 | user = module.params["user"] 131 | password = module.params["password"] 132 | mode = module.params["mode"] 133 | size = module.params["size"] 134 | groups = module.params["groups"] 135 | # threads = module.params["threads"] 136 | 137 | # Check for required modules 138 | if not cx_oracle_exists: 139 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 140 | 141 | 142 | wallet_connect = '/@%s' % service_name 143 | try: 144 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 145 | if mode == 'sysdba': 146 | connect = wallet_connect 147 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 148 | else: 149 | connect = wallet_connect 150 | conn = cx_Oracle.connect(wallet_connect) 151 | 152 | elif (user and password ): 153 | if mode == 'sysdba': 154 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 155 | connect = dsn 156 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 157 | else: 158 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 159 | connect = dsn 160 | conn = cx_Oracle.connect(user, password, dsn) 161 | 162 | elif (not(user) or not(password)): 163 | module.fail_json(msg='Missing username or password for cx_Oracle') 164 | 165 | except cx_Oracle.DatabaseError as exc: 166 | error, = exc.args 167 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 168 | module.fail_json(msg=msg[0], changed=False) 169 | # 170 | # if module.check_mode: 171 | # module.exit_json(changed=False) 172 | # 173 | 174 | if not (size.endswith('M') or size.endswith('G') or size.endswith('T')): 175 | msg = 'You need to suffix the size with (M,G or T), i.e: %sM/%sG/%sT' % (size,size,size) 176 | module.fail_json(msg=msg, changed=False) 177 | 178 | redosql = """ 179 | DECLARE 180 | 181 | -- output 182 | v_redo_size_changes number := 0; 183 | v_redo_group_changes number := 0; 184 | v_msg varchar2(100); 185 | -- input 186 | v_maxbytes varchar2(20) ; 187 | v_groups number ; 188 | -- runtime 189 | v_maxbytes_actual number(10,1); 190 | v_maxbytes_suffix varchar2(1); 191 | v_divisor number(20); 192 | v_sleep number := 5; 193 | v_israc VARCHAR2(3); 194 | v_existing_redogroups number ; 195 | v_max_groupnum number; 196 | v_group_diff number; 197 | v_sql_sw_lf varchar2 (50); 198 | v_sql_cp varchar2 (50); 199 | 200 | -- exceptions 201 | missing_suffix exception; 202 | 203 | BEGIN 204 | 205 | v_maxbytes:= :redosize; 206 | v_groups:= :redogroups; 207 | 208 | 209 | 210 | -- Check if it is a RAC database or not (parallel = True -> RAC) 211 | select parallel into v_israc from v$instance; 212 | IF v_israc = 'YES' THEN 213 | v_sql_sw_lf := 'alter system switch all logfile'; 214 | v_sql_cp := 'alter system checkpoint global'; 215 | ELSE 216 | v_sql_sw_lf := 'alter system switch logfile'; 217 | v_sql_cp := 'alter system checkpoint'; 218 | END IF; 219 | 220 | 221 | -- Get the suffix to decide the divisor 222 | select substr(v_maxbytes,-1) into v_maxbytes_suffix from dual; 223 | IF upper(v_maxbytes_suffix) = 'M' THEN 224 | v_divisor := 1024*1024; 225 | ELSIF upper(v_maxbytes_suffix) = 'G' THEN 226 | v_divisor := 1024*1024*1024; 227 | ELSIF upper(v_maxbytes_suffix) = 'T' THEN 228 | v_divisor := 1024*1024*1024*1024; 229 | ELSE 230 | raise missing_suffix; 231 | END IF; 232 | 233 | -- Strip the suffix (M/G/T) from the input string 234 | IF upper(v_maxbytes_suffix) in ('M','G','T') THEN 235 | select substr (v_maxbytes, 0, (length (v_maxbytes)-1)) into v_maxbytes_actual from dual; 236 | END IF; 237 | 238 | 239 | FOR rec in (select thread#, group#, status, bytes from v$log order by thread#) 240 | LOOP 241 | IF rec.bytes/v_divisor != v_maxbytes_actual THEN 242 | v_redo_size_changes := v_redo_size_changes+1; 243 | for chloop in (select thread#, group#, status from v$log where thread# = rec.thread# and group# = rec.group#) 244 | loop 245 | IF chloop.status in ('CURRENT','ACTIVE') THEN 246 | --dbms_output.put_line('Current'); 247 | execute immediate v_sql_sw_lf; 248 | execute immediate v_sql_cp; 249 | dbms_lock.sleep(v_sleep); 250 | execute immediate 'alter database add logfile thread '||chloop.thread# ||' size '||v_maxbytes ; 251 | execute immediate 'alter database drop logfile group '||chloop.group# ; 252 | ELSE 253 | execute immediate 'alter database add logfile thread '||chloop.thread# ||' size '||v_maxbytes ; 254 | execute immediate 'alter database drop logfile group '||chloop.group# ; 255 | 256 | END IF; 257 | END LOOP; 258 | END IF; 259 | END LOOP; 260 | dbms_output.put_line(v_redo_size_changes); 261 | 262 | -- Get number of groups 263 | for t in (select * from v$thread where enabled != 'DISABLED') 264 | loop 265 | dbms_output.put_line('Thread: '||t.thread#); 266 | select count(*) into v_existing_redogroups from v$log where thread# = t.thread#; 267 | dbms_output.put_line('Thread: '||t.thread# ||' numgroups: '||v_existing_redogroups); 268 | IF v_existing_redogroups < v_groups THEN 269 | select v_groups-v_existing_redogroups into v_group_diff from dual; 270 | dbms_output.put_line('Adding '||v_group_diff ||' groups to thread: '||t.thread#); 271 | for grloop in 1..v_group_diff 272 | loop 273 | v_redo_group_changes := v_redo_group_changes+1; 274 | --v_max_groupnum := 0; 275 | select max(group#) into v_max_groupnum from v$log; 276 | v_max_groupnum := v_max_groupnum + 1; 277 | execute immediate 'alter database add logfile thread '|| t.thread# ||' group '||v_max_groupnum ||' size '||v_maxbytes; 278 | dbms_output.put_line('adding group '||v_max_groupnum ||' to thread '||t.thread#); 279 | end loop; 280 | 281 | ELSIF v_existing_redogroups > v_groups THEN 282 | --select count(*) into v_existing_redogroups from v$log where thread# = t.thread#; 283 | select v_existing_redogroups-v_groups into v_group_diff from dual; 284 | dbms_output.put_line('Removing ' ||v_group_diff ||' groups from thread: '||t.thread#); 285 | 286 | for grloop in 1..v_group_diff 287 | loop 288 | v_redo_group_changes := v_redo_group_changes+1; 289 | select max(group#) into v_max_groupnum from v$log where upper(status) not in ('ACTIVE','CURRENT') and thread# = t.thread#; 290 | execute immediate 'alter database drop logfile group '||v_max_groupnum ; 291 | dbms_output.put_line('dropping group '||v_max_groupnum ||' thread#: '||t.thread#); 292 | end loop; 293 | 294 | ELSE 295 | dbms_output.put_line('Nothing to do'); 296 | END IF; 297 | end loop; 298 | 299 | 300 | 301 | :o_size_changed := v_redo_size_changes; 302 | :o_group_changed := v_redo_group_changes; 303 | IF v_redo_size_changes > 0 THEN 304 | :o_size_msg := 'All redologs have been changed to '||v_maxbytes; 305 | ELSE 306 | :o_size_msg := 'No size changes'; 307 | END IF; 308 | 309 | IF v_redo_group_changes > 0 THEN 310 | :o_group_msg := 'Groups have been adjusted to '||v_groups ||' groups'; 311 | ELSE 312 | :o_group_msg := 'No group changes'; 313 | END IF; 314 | 315 | EXCEPTION 316 | WHEN missing_suffix THEN 317 | dbms_output.put_line('--------'); 318 | dbms_output.put_line('You need to suffix the size with (M,G or T), i.e: '||v_maxbytes ||'M/'||v_maxbytes ||'G/' ||v_maxbytes ||'T'); 319 | dbms_output.put_line('--------'); 320 | 321 | END; 322 | """ 323 | 324 | 325 | cur = conn.cursor() 326 | 327 | try: 328 | v_size_changed = cur.var(cx_Oracle.NUMBER) 329 | v_group_changed = cur.var(cx_Oracle.NUMBER) 330 | v_size_msg = cur.var(cx_Oracle.STRING) 331 | v_group_msg = cur.var(cx_Oracle.STRING) 332 | cur.execute(redosql, 333 | {'redosize': size, 'redogroups': groups , 'o_size_changed': v_size_changed, 'o_group_changed': v_group_changed, 'o_size_msg': v_size_msg, 'o_group_msg': v_group_msg}) 334 | except cx_Oracle.DatabaseError as exc: 335 | error, = exc.args 336 | msg = '%s' % (error.message) 337 | module.fail_json(msg=msg, changed=False) 338 | 339 | 340 | 341 | size_result_changed = v_size_changed.getvalue() 342 | group_result_changed = v_group_changed.getvalue() 343 | #module.exit_json(msg=result_changed, changed=False) 344 | size_msg = v_size_msg.getvalue() 345 | group_msg = v_group_msg.getvalue() 346 | msg = "%s. %s." % (size_msg, group_msg) 347 | if (size_result_changed > 0 or group_result_changed > 0): 348 | changed = True 349 | else: 350 | changed = False 351 | #module.exit_json(msg=result_changed, changed=False) 352 | 353 | 354 | module.exit_json(msg=msg, changed=changed) 355 | 356 | 357 | from ansible.module_utils.basic import * 358 | if __name__ == '__main__': 359 | main() 360 | -------------------------------------------------------------------------------- /oracle_role: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_role 7 | short_description: Manage users/roles in an Oracle database 8 | description: 9 | - Manage grants/privileges in an Oracle database 10 | - Handles role/sys privileges at the moment. 11 | - It is possible to add object privileges as well, but they are not considered when removing privs at the moment. 12 | version_added: "1.9.1" 13 | options: 14 | hostname: 15 | description: 16 | - The Oracle database host 17 | required: false 18 | default: localhost 19 | port: 20 | description: 21 | - The listener port number on the host 22 | required: false 23 | default: 1521 24 | service_name: 25 | description: 26 | - The database service name to connect to 27 | required: true 28 | user: 29 | description: 30 | - The Oracle user name to connect to the database 31 | required: true 32 | password: 33 | description: 34 | - The Oracle user password for 'user' 35 | required: true 36 | mode: 37 | description: 38 | - The mode with which to connect to the database 39 | required: true 40 | default: normal 41 | choices: ['normal','sysdba'] 42 | role: 43 | description: 44 | - The role that should get grants added/removed 45 | required: false 46 | default: null 47 | grants: 48 | description: 49 | - The privileges granted to the new role. Can be a string or a list 50 | required: false 51 | default: null 52 | state: 53 | description: 54 | - The intended state of the priv (present=added to the user, absent=removed from the user). REMOVEALL will remove ALL role/sys privileges 55 | default: present 56 | choices: ['present','absent','REMOVEALL'] 57 | notes: 58 | - cx_Oracle needs to be installed 59 | requirements: [ "cx_Oracle" ] 60 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 61 | ''' 62 | 63 | EXAMPLES = ''' 64 | # Add grants to the user 65 | oracle_role: hostname=remote-db-server service_name=orcl user=system password=manager role=myrole state=present grants='create session','create any table',connect,resource 66 | 67 | # Revoke the 'create any table' grant 68 | oracle_role: hostname=localhost service_name=orcl user=system password=manager role=myrole state=absent grants='create any table' 69 | 70 | # Remove all grants from a user 71 | oracle_role: hostname=localhost service_name=orcl user=system password=manager role=myrole state=REMOVEALL grants= 72 | 73 | 74 | ''' 75 | 76 | try: 77 | import cx_Oracle 78 | except ImportError: 79 | cx_oracle_exists = False 80 | else: 81 | cx_oracle_exists = True 82 | 83 | 84 | def clean_string(item): 85 | item = item.replace("'","").replace(", ",",").lstrip(" ").rstrip(",").replace("[","").replace("]","") 86 | 87 | return item 88 | 89 | def clean_list(item): 90 | item = [p.replace("'","").replace(", ",",").lstrip(" ").rstrip(",").replace("[","").replace("]","") for p in item] 91 | 92 | return item 93 | 94 | 95 | 96 | # Check if the user/role exists 97 | def check_role_exists(module, msg, cursor, role, auth): 98 | 99 | if not(role): 100 | module.fail_json(msg='Error: Missing role name', changed=False) 101 | return False 102 | 103 | role = clean_string(role) 104 | #sql = 'select count(*) from dba_roles where role = upper(\'%s\')' % role 105 | sql = 'select lower(role), lower(authentication_type) from dba_roles where role = upper(\'%s\')' % role 106 | 107 | 108 | try: 109 | cursor.execute(sql) 110 | result = (cursor.fetchone()) 111 | except cx_Oracle.DatabaseError as exc: 112 | error, = exc.args 113 | msg[0] = error.message+ 'sql: ' + sql 114 | return False 115 | 116 | if result > 0: 117 | 118 | msg[0] = 'The role (%s) already exists' % role 119 | return True 120 | 121 | 122 | # Create the role 123 | def create_role(module, msg, cursor, role, auth, auth_conf): 124 | 125 | if not(role) or not (auth): 126 | module.fail_json(msg='Error: Missing role name', changed=False) 127 | return False 128 | 129 | 130 | # This is the default role creation 131 | sql = 'create role %s ' % role 132 | 133 | 134 | if auth == 'password': 135 | if not auth_conf: 136 | module.fail_json(msg='Missing password', changed=False) 137 | return False 138 | else: 139 | sql += 'identified by %s' % auth_conf 140 | 141 | if auth == 'application': 142 | if not (auth_conf): 143 | module.fail_json(msg='Missing authentication package (schema.name)', changed=False) 144 | return False 145 | else: 146 | sql += 'identified using %s' % auth_conf 147 | 148 | if auth == 'external': 149 | sql += 'identified externally ' 150 | 151 | if auth == 'global': 152 | sql += 'identified globally' 153 | 154 | 155 | 156 | 157 | try: 158 | cursor.execute(sql) 159 | except cx_Oracle.DatabaseError as exc: 160 | error, = exc.args 161 | msg[0] = 'Blergh, something went wrong while creating the role - %s sql: %s' % (error.message, sql) 162 | return False 163 | 164 | msg[0] = 'The role (%s) has been created successfully, authentication: %s' % (role, auth) 165 | return True 166 | 167 | 168 | def modify_role(module, msg, cursor, role, auth, auth_conf): 169 | 170 | if not(role) or not (auth): 171 | module.fail_json(msg='Error: Missing role name', changed=False) 172 | return False 173 | 174 | sql = 'alter role %s ' % (role) 175 | 176 | currauth = get_role_specs(module, msg, cursor, role) 177 | 178 | if currauth.lower() == auth.lower(): 179 | module.exit_json(msg='The role (%s) already exists' % role, changed=False) 180 | 181 | else: 182 | if auth == 'none': 183 | sql += ' not identified ' 184 | 185 | if auth == 'password': 186 | if not auth_conf: 187 | module.fail_json(msg='Missing password for authentication_type %s' % (auth), changed=False) 188 | return False 189 | else: 190 | sql += ' identified by %s' % auth_conf 191 | 192 | if auth == 'application': 193 | if not (auth_conf): 194 | module.fail_json(msg='Missing authentication package (schema.name)', changed=False) 195 | return False 196 | else: 197 | sql += 'identified using %s' % auth_conf 198 | 199 | if auth == 'external': 200 | sql += 'identified externally ' 201 | 202 | if auth == 'global': 203 | sql += 'identified globally' 204 | 205 | 206 | try: 207 | cursor.execute(sql) 208 | except cx_Oracle.DatabaseError as exc: 209 | error, = exc.args 210 | msg[0] = 'Blergh, something went wrong while altering the role - %s sql: %s' % (error.message, sql) 211 | return False 212 | 213 | msg[0] = 'The role (%s) has been changed successfully, authentication: %s, previous: %s' % (role, auth, currauth) 214 | return True 215 | 216 | 217 | 218 | def get_role_specs(module, msg, cursor, role): 219 | 220 | sql = 'select lower(authentication_type) from dba_roles where role = upper(\'%s\')' % role 221 | 222 | 223 | try: 224 | cursor.execute(sql) 225 | result = (cursor.fetchall()[0][0]) 226 | except cx_Oracle.DatabaseError as exc: 227 | error, = exc.args 228 | msg[0] = 'Blergh, something went wrong while getting the role auth scheme - %s sql: %s' % (error.message, sql) 229 | module.fail_json(msg=msg[0], changed=False) 230 | return False 231 | 232 | #module.exit_json(msg='Result: %s, sql: %s' % (result, sql), changed=False) 233 | return result 234 | 235 | 236 | # Create the role 237 | def drop_role(module, msg, cursor, role): 238 | 239 | if not(role): 240 | module.fail_json(msg='Error: Missing role name', changed=False) 241 | return False 242 | 243 | 244 | sql = 'drop role %s' % role 245 | 246 | try: 247 | cursor.execute(sql) 248 | except cx_Oracle.DatabaseError as exc: 249 | error, = exc.args 250 | msg[0] = 'Blergh, something went wrong while dropping the role - %s sql: %s' % (error.message, sql) 251 | return False 252 | 253 | msg[0] = 'The role (%s) has been successfully dropped' % role 254 | return True 255 | 256 | 257 | def main(): 258 | 259 | msg = [''] 260 | module = AnsibleModule( 261 | argument_spec = dict( 262 | hostname = dict(default='localhost'), 263 | port = dict(default=1521), 264 | service_name = dict(required=True), 265 | user = dict(required=False), 266 | password = dict(required=False, no_log=True), 267 | mode = dict(default='normal', choices=["normal","sysdba"]), 268 | role = dict(default=None), 269 | state = dict(default="present", choices=["present", "absent"]), 270 | auth = dict(default='none', choices=["none", "password", "external", "global", "application"]), 271 | auth_conf = dict(default=None) 272 | 273 | ), 274 | 275 | ) 276 | 277 | hostname = module.params["hostname"] 278 | port = module.params["port"] 279 | service_name = module.params["service_name"] 280 | user = module.params["user"] 281 | password = module.params["password"] 282 | mode = module.params["mode"] 283 | role = module.params["role"] 284 | state = module.params["state"] 285 | auth = module.params["auth"] 286 | auth_conf = module.params["auth_conf"] 287 | 288 | 289 | 290 | if not cx_oracle_exists: 291 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 292 | 293 | wallet_connect = '/@%s' % service_name 294 | try: 295 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 296 | if mode == 'sysdba': 297 | connect = wallet_connect 298 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 299 | else: 300 | connect = wallet_connect 301 | conn = cx_Oracle.connect(wallet_connect) 302 | 303 | elif (user and password ): 304 | if mode == 'sysdba': 305 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 306 | connect = dsn 307 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 308 | else: 309 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 310 | connect = dsn 311 | conn = cx_Oracle.connect(user, password, dsn) 312 | 313 | elif (not(user) or not(password)): 314 | module.fail_json(msg='Missing username or password for cx_Oracle') 315 | 316 | except cx_Oracle.DatabaseError as exc: 317 | error, = exc.args 318 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 319 | module.fail_json(msg=msg[0], changed=False) 320 | 321 | cursor = conn.cursor() 322 | 323 | if state == 'present': 324 | if not check_role_exists(module, msg, cursor, role, auth): 325 | if create_role(module, msg, cursor, role, auth, auth_conf): 326 | module.exit_json(msg=msg[0], changed=True) 327 | else: 328 | module.fail_json(msg=msg[0], changed=False) 329 | 330 | elif modify_role(module, msg, cursor, role, auth, auth_conf): 331 | module.exit_json(msg=msg[0], changed=True) 332 | 333 | else: 334 | module.fail_json(msg=msg[0], changed=False) 335 | 336 | 337 | elif state == 'absent': 338 | if check_role_exists(module, msg, cursor, role, auth): 339 | if drop_role(module, msg, cursor, role): 340 | module.exit_json(msg=msg[0], changed=True) 341 | else: 342 | module.exit_json(msg='The role (%s) doesn\'t exist' % role, changed=False) 343 | 344 | 345 | 346 | 347 | module.exit_json(msg=msg[0], changed=False) 348 | 349 | 350 | 351 | 352 | 353 | 354 | from ansible.module_utils.basic import * 355 | if __name__ == '__main__': 356 | main() 357 | -------------------------------------------------------------------------------- /oracle_sql: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_sql 7 | short_description: Execute arbitrary sql 8 | description: 9 | - Execute arbitrary sql against an Oracle database 10 | version_added: "2.1.0.0" 11 | options: 12 | username: 13 | description: 14 | - The database username to connect to the database 15 | required: false 16 | default: None 17 | aliases: ['un'] 18 | password: 19 | description: 20 | - The password to connect to the database 21 | required: false 22 | default: None 23 | aliases: ['pw'] 24 | service_name: 25 | description: 26 | - The service_name to connect to the database 27 | required: false 28 | default: database_name 29 | aliases: ['sn'] 30 | hostname: 31 | description: 32 | - The host of the database 33 | required: false 34 | default: localhost 35 | aliases: ['host'] 36 | port: 37 | description: 38 | - The listener port to connect to the database 39 | required: false 40 | default: 1521 41 | sql: 42 | description: 43 | - The sql you want to execute 44 | required: false 45 | script: 46 | description: 47 | - The script you want to execute. Doesn't handle selects 48 | required: false 49 | notes: 50 | - cx_Oracle needs to be installed 51 | - Oracle client libraries need to be installed along with ORACLE_HOME and LD_LIBRARY_PATH settings. 52 | requirements: [ "cx_Oracle" ] 53 | author: Mikael Sandström, oravirt@gmail.com, @oravirt 54 | ''' 55 | 56 | EXAMPLES = ''' 57 | # Execute arbitrary sql 58 | - oracle_sql: 59 | username: "{{ user }}" 60 | password: "{{ password }}" 61 | service_name: one.world 62 | sql: 'select username from dba_users' 63 | # Execute arbitrary script1 64 | - oracle_sql: 65 | username: "{{ user }}" 66 | password: "{{ password }}" 67 | service_name: one.world 68 | script: /u01/scripts/create-all-the-procedures.sql 69 | # Execute arbitrary script2 70 | - oracle_sql: 71 | username: "{{ user }}" 72 | password: "{{ password }}" 73 | service_name: one.world 74 | script: /u01/scripts/create-tables-and-insert-default-values.sql 75 | ''' 76 | import os 77 | from ansible.module_utils.basic import AnsibleModule 78 | 79 | try: 80 | import cx_Oracle 81 | except ImportError: 82 | cx_oracle_exists = False 83 | else: 84 | cx_oracle_exists = True 85 | 86 | 87 | def execute_sql_get(module, cursor, sql): 88 | try: 89 | cursor.execute(sql) 90 | result = (cursor.fetchall()) 91 | except cx_Oracle.DatabaseError as exc: 92 | error, = exc.args 93 | msg = 'Something went wrong while executing sql_get - %s sql: %s' % (error.message, sql) 94 | module.fail_json(msg=msg, changed=False) 95 | return False 96 | return result 97 | 98 | 99 | def execute_sql(module, cursor, conn, sql): 100 | if 'insert' or 'delete' or 'update' in sql.lower(): 101 | docommit = True 102 | else: 103 | docommit = False 104 | 105 | try: 106 | # module.exit_json(msg=sql.strip()) 107 | cursor.execute(sql) 108 | except cx_Oracle.DatabaseError as exc: 109 | error, = exc.args 110 | msg = 'Something went wrong while executing sql - %s sql: %s' % (error.message, sql) 111 | module.fail_json(msg=msg, changed=False) 112 | return False 113 | if docommit: 114 | conn.commit() 115 | return True 116 | 117 | 118 | def read_file(module, script): 119 | try: 120 | f = open(script, 'r') 121 | sqlfile = f.read() 122 | f.close() 123 | return sqlfile 124 | except IOError as e: 125 | msg = 'Couldn\'t open/read file: %s' % (e) 126 | module.fail_json(msg=msg, changed=False) 127 | return 128 | 129 | 130 | def clean_sqlfile(sqlfile): 131 | sqlfile = sqlfile.strip() 132 | sqlfile = sqlfile.lstrip() 133 | sqlfile = sqlfile.lstrip() 134 | sqlfile = os.linesep.join([s for s in sqlfile.splitlines() if s]) 135 | return sqlfile 136 | 137 | 138 | def main(): 139 | 140 | module = AnsibleModule( 141 | argument_spec=dict( 142 | user=dict(required=False, aliases=['un', 'username']), 143 | password=dict(required=False, no_log=True, aliases=['pw']), 144 | mode=dict(default="normal", choices=["sysasm", "sysdba", "normal"]), 145 | service_name=dict(required=False, aliases=['sn']), 146 | hostname=dict(required=False, default='localhost', aliases=['host']), 147 | port=dict(required=False, default=1521), 148 | sql=dict(required=False), 149 | script=dict(required=False), 150 | 151 | ), 152 | mutually_exclusive=[['sql', 'script']] 153 | ) 154 | 155 | user = module.params["user"] 156 | password = module.params["password"] 157 | mode = module.params["mode"] 158 | service_name = module.params["service_name"] 159 | hostname = module.params["hostname"] 160 | port = module.params["port"] 161 | sql = module.params["sql"] 162 | script = module.params["script"] 163 | 164 | if not cx_oracle_exists: 165 | msg = "The cx_Oracle module is required. Also set LD_LIBRARY_PATH & ORACLE_HOME" 166 | module.fail_json(msg=msg) 167 | 168 | wallet_connect = '/@%s' % service_name 169 | 170 | try: 171 | if not user and not password: # If neither user or password is supplied, the use of an oracle wallet is assumed 172 | if mode == 'sysdba': 173 | connect = wallet_connect 174 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 175 | elif mode == 'sysasm': 176 | connect = wallet_connect 177 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSASM) 178 | else: 179 | connect = wallet_connect 180 | conn = cx_Oracle.connect(wallet_connect) 181 | 182 | elif user and password: 183 | if mode == 'sysdba': 184 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 185 | connect = dsn 186 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 187 | elif mode == 'sysasm': 188 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 189 | connect = dsn 190 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSASM) 191 | else: 192 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 193 | connect = dsn 194 | conn = cx_Oracle.connect(user, password, dsn) 195 | 196 | elif not user or not password: 197 | module.fail_json(msg='Missing username or password for cx_Oracle') 198 | 199 | except cx_Oracle.DatabaseError as exc: 200 | error, = exc.args 201 | msg = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 202 | module.fail_json(msg=msg, changed=False) 203 | 204 | cursor = conn.cursor() 205 | 206 | if sql: 207 | if sql.lower().startswith('begin '): 208 | execute_sql(module, cursor, conn, sql) 209 | msg = 'SQL executed: %s' % (sql) 210 | module.exit_json(msg=msg, changed=True) 211 | 212 | else: 213 | sql = sql.rstrip(';') 214 | if sql.lower().startswith('select '): 215 | result = execute_sql_get(module, cursor, sql) 216 | module.exit_json(msg=result, changed=False) 217 | else: 218 | execute_sql(module, cursor, conn, sql) 219 | msg = 'SQL executed: %s' % (sql) 220 | module.exit_json(msg=msg, changed=True) 221 | else: 222 | sqlfile = read_file(module, script) 223 | if len(sqlfile) > 0: 224 | sqlfile = clean_sqlfile(sqlfile) 225 | 226 | if sqlfile.endswith('/') or ('create or replace') in sqlfile.lower(): 227 | sqldelim = '/' 228 | else: 229 | sqldelim = ';' 230 | 231 | sqlfile = sqlfile.strip(sqldelim) 232 | sql = sqlfile.split(sqldelim) 233 | 234 | for q in sql: 235 | execute_sql(module, cursor, conn, q) 236 | msg = 'Finished running script %s \nContents: \n%s' % (script, sqlfile) 237 | module.exit_json(msg=msg, changed=True) 238 | else: 239 | module.fail_json(msg='SQL file seems to be empty') 240 | 241 | module.exit_json(msg="Unhandled exit", changed=False) 242 | 243 | 244 | if __name__ == '__main__': 245 | main() 246 | -------------------------------------------------------------------------------- /oracle_sqldba: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: oracle_sqldba 7 | short_description: Execute sql (scripts) using sqlplus (BEQ) or catcon.pl 8 | description: 9 | - Needed for post-installation tasks not covered by other modules 10 | - Uses sqlplus (BEQ connect, e.g. / as sysdba) or $OH//perl catcon.pl 11 | options: 12 | sql: 13 | description: 14 | - Single SQL statement 15 | - Will be executed by sqlplus 16 | - Used for DDL and DML 17 | required: false 18 | default: None 19 | sqlscript: 20 | description: 21 | - Script name, optionally followed by parameters 22 | - Will be executed by sqlplus 23 | required: false 24 | default: None 25 | catcon_pl: 26 | description: 27 | - Script name, optionally followed by parameters 28 | - Will be executed by $OH//perl catcon.pl 29 | required: false 30 | default: None 31 | sqlselect: 32 | description: 33 | - Single SQL statement 34 | - Will be executed by sqlplus using dbms_xmlgen.getxml 35 | - Used for select only, returns dict in .state 36 | - To access the column "value" of the first row write: "<>.state.ROW[0].VALUE" (use uppercase) 37 | required: false 38 | default: None 39 | creates_sql: 40 | description: 41 | - This is the check query to ensure idempotence. 42 | - Must be a single SQL select that results to no rows or a plain 0 if the catcon_pl/sqlscript/sql has to be executed. Any other result prevent the execution of catcon_pl/sqlscript/sql. 43 | - The catcon_pl/sqlscript/sql will be executed unconditionally if creates_sql is omitted. 44 | - Creates_sql must be omitted when sqlselect is used. 45 | - Creates_sql is executed with sqlplus / as sysdba in the root container. Write the sql query according to this fact. 46 | - If pdb_list is given (implicitely whith all_pdbs) creates_sql is executed in every PDB incl. CDB$ROOT. The pdb_list will be shortened according to the results of creates_sql in the PDBs. 47 | required: false 48 | default: None 49 | username: 50 | description: 51 | - Database username, defaults to "/ as sysdba" 52 | required: false 53 | default: None 54 | password: 55 | description: 56 | - Password of database user 57 | required: false 58 | default: None 59 | scope: 60 | description: 61 | - Shall the SQL be applied to CDB, PDBs, or both? 62 | values: 63 | - default: if catcon_pl is filled then all_pdbs else cdb 64 | - db: alias for cdb, allows for better readability for non-cdb 65 | - cdb: apply to root container or whole db 66 | - pdbs: apply to specified PDB's only (requires pdb_list) 67 | - all_pdbs: apply to all PDB's except PDB$SEED 68 | required: false 69 | default: cdb 70 | pdb_list: 71 | description: 72 | - Optional list of PDB names 73 | - Space separated, as catcon.pl wants 74 | - Gets used only if scope is "pdbs" 75 | - Will be automatically filled and used when scope = all_pdbs and action like sql% 76 | required: false 77 | default: None 78 | oracle_home: 79 | description: 80 | - content of $ORACLE_HOME 81 | oracle_db_name: 82 | description: 83 | - SID or DB_NAME, needed for BEQ connect 84 | nls_lang: 85 | description: 86 | - set NLS_LANG to the given value 87 | chdir: 88 | description: 89 | - Working directory for SQL/script execution 90 | 91 | author: Dietmar Uhlig, Robotron (www.robotron.de) 92 | ''' 93 | 94 | EXAMPLES = ''' 95 | # Example 1, mixed post installation tasks 96 | # from inventory: 97 | 98 | oracle_databases: 99 | - oracle_db_name: eek17ec 100 | home: 12.2.0.1-ee 101 | state: present 102 | init_parameters: "{{ init_parameters['12.2.0.1-EMS'] }}" 103 | profiles: "{{ db_profiles['12.2.0.1-EMS'] }}" 104 | postinstall: "{{ db_postinstall['12.2.0.1-EMS'] }}" 105 | 106 | oracle_pdbs: 107 | - cdb: eek17ec 108 | pdb_name: eckpdb 109 | - cdb: eek17ec 110 | pdb_name: sckpdb 111 | 112 | db_postinstall: 113 | 12.2.0.1-EMS: 114 | - catcon_pl: "$ORACLE_HOME/ctx/admin/catctx.sql context SYSAUX TEMP NOLOCK" 115 | creates_sql: "select 1 from dba_registry where comp_id = 'CONTEXT'" 116 | - sqlscript: "?/rdbms/admin/initsqlj.sql" 117 | scope: pdbs 118 | creates_sql: "select count(*) from dba_tab_privs where table_name = 'SQLJUTL' and grantee = 'PUBLIC'" 119 | - sqlscript: "?/rdbms/admin/utlrp.sql" 120 | - sql: "alter pluggable database {{ pdb.pdb_name | default(omit) }} save state" 121 | scope: pdbs 122 | 123 | # see role oradb-postinstall, loops over {{ oracle_databases }} = loop_var oradb 124 | 125 | - name: Conditionally execute post installation tasks 126 | oracle_sqldba: 127 | sql: "{{ pitask.sql | default(omit) }}" 128 | sqlscript: "{{ pitask.sqlscript | default(omit) }}" 129 | catcon_pl: "{{ pitask.catcon_pl | default(omit) }}" 130 | creates_sql: "{{ pitask.creates_sql | default(omit) }}" 131 | username: "{{ pitask.username | default(omit) }}" 132 | password: "{%if pitask.username is defined%}{{ dbpasswords[oradb.oracle_db_name][pitask.username] }}{%endif%}" 133 | scope: "{{ pitask.scope | default(omit) }}" 134 | pdb_list: "{{ oracle_pdbs | default([]) | json_query('[?cdb==`' + oradb.oracle_db_name + '`].pdb_name') | join(' ') }}" 135 | oracle_home: "{{ db_homes_config[oradb.home].oracle_home }}" 136 | oracle_db_name: "{{ oradb.oracle_db_name }}" 137 | loop: "{{ oradb.postinstall }}" 138 | loop_control: 139 | loop_var: pitask 140 | 141 | # Example 2, read sql result 142 | 143 | - name: Read job_queue_processes 144 | oracle_sqldba: 145 | sqlselect: "select value from gv$parameter where name = 'job_queue_processes'" 146 | oracle_home: "{{ oracle_db_home }}" 147 | oracle_db_name: "{{ oracle_db_name }}" 148 | register: jqpresult 149 | 150 | - name: Store job_queue_processes 151 | set_fact: 152 | job_queue_processes: "{{ jqpresult.state.ROW[0].VALUE }}" 153 | # Use all uppercase for "ROW" and for column names! 154 | 155 | ''' 156 | 157 | import errno 158 | import os 159 | import re 160 | import shlex 161 | import shutil 162 | import tempfile 163 | from subprocess import Popen, PIPE 164 | from threading import Timer 165 | from ansible.module_utils.basic import AnsibleModule 166 | from ansible.module_utils._text import to_native, to_text 167 | import xml.etree.ElementTree as ET 168 | from copy import copy 169 | 170 | changed = False 171 | result = "" 172 | err_msg = None 173 | oracle_home = "" 174 | pdb_list = "" 175 | sql_process = None 176 | 177 | # Maximum runtime for sqlplus and catcon.pl in seconds. 0 means no timeout. 178 | timeout = 0 179 | 180 | # dictify is based on https://stackoverflow.com/questions/2148119/how-to-convert-an-xml-string-to-a-dictionary/10077069#10077069 181 | def dictify(r,root=True): 182 | if root: 183 | #return {r.tag : dictify(r, False)} # no, but... 184 | return dictify(r, False) # skip root node "ROWSET" 185 | d=copy(r.attrib) 186 | if (r.text).strip(): 187 | d["_text"]=r.text 188 | for x in r.findall("./*"): 189 | if x.tag not in d: 190 | d[x.tag]=[] 191 | if (x.text).strip(): # assume scalar 192 | d[x.tag] = x.text 193 | else: 194 | d[x.tag].append(dictify(x,False)) 195 | return d 196 | 197 | def sqlplus(): 198 | global oracle_home 199 | 200 | sql_bin = os.path.join(oracle_home, "bin", "sqlplus") 201 | return [sql_bin, "-l", "-s", "/nolog"] 202 | 203 | def conn(username, password): 204 | if username == None: 205 | return "conn / as sysdba\n" 206 | else: 207 | return "conn " + "/".join([username, password]) + "\n" 208 | 209 | def sql_input(sql, username, password, pdb): 210 | sql_scr = "set heading off echo off feedback off termout on\n" 211 | sql_scr += "set long 1000000 pagesize 0 linesize 1000 trimspool on\n" 212 | sql_scr += conn(username, password) 213 | 214 | if pdb is not None: 215 | sql_scr += "alter session set container = " + pdb + ";\n" 216 | sql_scr += sql + "\n" 217 | sql_scr += "exit;\n" 218 | return sql_scr 219 | 220 | def kill_process(): 221 | global err_msg, sql_process 222 | 223 | sql_process.kill() 224 | err_msg = "Timeout occured after %d seconds. " % timeout 225 | 226 | def run_sql_p(sql, username, password, scope, pdb_list): 227 | global changed, err_msg, sql_process 228 | 229 | err_msg = "" 230 | result = "" 231 | if scope == 'pdbs': 232 | for pdb in pdb_list.split(): 233 | result += run_sql(sql, username, password, pdb) 234 | else: 235 | result = run_sql(sql, username, password, None) 236 | return result 237 | 238 | def run_sql(sql, username, password, pdb): 239 | global changed, err_msg, sql_process 240 | 241 | t = None 242 | try: 243 | sql_cmd = sql_input(sql, username, password, pdb) 244 | sql_process = Popen(sqlplus(), stdin = PIPE, stdout = PIPE, stderr = PIPE) 245 | if timeout > 0: 246 | t = Timer(timeout, kill_process) 247 | t.start() 248 | [sout, serr] = sql_process.communicate(input = sql_cmd) 249 | except Exception as e: 250 | err_msg += 'Could not call sqlplus. %s. called: %s.' % (to_native(e), " ".join(sqlplus())) 251 | return "[ERR]" 252 | finally: 253 | if timeout > 0 and t is not None: 254 | t.cancel() 255 | if sql_process.returncode != 0: 256 | err_msg += "called: %s\nreturncode: %d\nresult: %s. stderr = %s." % (sql_cmd, sql_process.returncode, sout, serr) 257 | return "[ERR]" 258 | sqlerr_pat = re.compile("^(ORA|TNS|SP2)-[0-9]+", re.MULTILINE) 259 | sqlplus_err = sqlerr_pat.search(sout) 260 | if sqlplus_err: 261 | err_msg += "[ERR] sqlplus: %s\nERR Code: %s.\n" % (sql_cmd, sqlplus_err.group()) 262 | return "[ERR]\n%s\n" % sout.strip() 263 | 264 | changed = True 265 | return sout.strip() 266 | 267 | 268 | def check_creates_sql(sql, scope): 269 | global pdb_list 270 | 271 | if not sql.endswith(";"): 272 | sql += ";" 273 | if scope == 'cdb': 274 | res = run_sql(sql, None, None, None) 275 | # error handling see call of check_creates_sql 276 | return False if not res or res == "0" else True 277 | else: 278 | checked_pdb_list = "" 279 | for pdb in pdb_list.split(): 280 | res = run_sql(sql, None, None, pdb) 281 | # error handling see call of check_creates_sql 282 | if not res or res == "0": 283 | checked_pdb_list += " " + pdb 284 | pdb_list = checked_pdb_list.lstrip() 285 | return True if pdb_list == "" else False 286 | 287 | 288 | def is_container(): 289 | return run_sql("select cdb from gv$database;", None, None, None) == 'YES' 290 | 291 | def get_all_pdbs(): 292 | global result, pdb_list 293 | 294 | sql = "select listagg(pdb_name, ' ') within group (order by pdb_name) from dba_pdbs where status = 'NORMAL' and pdb_name <> 'PDB$SEED';" 295 | pdb_list = 'CDB$ROOT ' + run_sql(sql, None, None, None) 296 | 297 | 298 | def run_catcon_pl(catcon_pl): 299 | # after pre-processing in main() the parameter scope is not necessary any more 300 | global oracle_home, changed, result, err_msg, pdb_list, sql_process 301 | 302 | err_msg = "" 303 | catcon_pl = re.sub("^(\$ORACLE_HOME|\?)", oracle_home, catcon_pl) 304 | logdir = tempfile.mkdtemp() 305 | catcon_cmd = [ os.path.join(oracle_home, "perl", "bin", "perl"), 306 | os.path.join(oracle_home, "rdbms", "admin", "catcon.pl"), 307 | "-l", logdir, "-b", "catcon" ] 308 | if pdb_list is not None: 309 | catcon_cmd += [ "-c", pdb_list ] 310 | cc_script = shlex.split(catcon_pl) 311 | if len(cc_script) > 1: 312 | for i in range(1, len(cc_script)): 313 | cc_script[i] = "1" + cc_script[i] 314 | catcon_cmd += [ "-a", "1" ] 315 | catcon_cmd += [ "--" ] + cc_script 316 | try: 317 | sql_process = Popen(catcon_cmd, stdout = PIPE, stderr = PIPE) 318 | if timeout > 0: 319 | t = Timer(timeout, kill_process) 320 | t.start() 321 | [sout, serr] = sql_process.communicate() 322 | except Exception as e: 323 | err_msg += 'Could not call perl. %s. called: %s.' % (to_native(e), " ".join(catcon_cmd)) 324 | return 325 | finally: 326 | if timeout > 0: 327 | t.cancel() 328 | try: 329 | shutil.rmtree(logdir) 330 | except OSError as exc: 331 | if exc.errno != errno.ENOENT: 332 | raise 333 | if sql_process.returncode != 0: 334 | err_msg += "called: %s\nreturncode: %d\nresult: %s\nstderr = %s." % (" ".join(catcon_cmd), sql_process.returncode, sout, serr) 335 | return 336 | result += sout 337 | changed = True 338 | 339 | 340 | 341 | def main(): 342 | global oracle_home, changed, result, err_msg, pdb_list 343 | 344 | module = AnsibleModule( 345 | argument_spec = dict( 346 | sql = dict(required = False), 347 | sqlscript = dict(required = False), 348 | catcon_pl = dict(required = False), 349 | sqlselect = dict(required = False), 350 | creates_sql = dict(required = False), 351 | username = dict(required = False), 352 | password = dict(required = False, no_log = True), 353 | scope = dict(required = False, choices = ["default", "db", "cdb", "pdbs", "all_pdbs"], default = 'default'), 354 | pdb_list = dict(required = False), 355 | oracle_home = dict(required = True), 356 | oracle_db_name = dict(required = True), 357 | nls_lang = dict(required = False), 358 | chdir = dict(required = False) 359 | ), 360 | mutually_exclusive=[['sql', 'sqlscript', 'catcon_pl', 'sqlselect'], ['sqlselect', 'creates_sql']] 361 | ) 362 | 363 | sql = module.params["sql"] 364 | sqlscript = module.params["sqlscript"] 365 | catcon_pl = module.params["catcon_pl"] 366 | sqlselect = module.params["sqlselect"] 367 | creates_sql = module.params["creates_sql"] 368 | username = module.params["username"] 369 | password = module.params["password"] 370 | scope = module.params["scope"] 371 | pdb_list = module.params["pdb_list"] 372 | oracle_home = module.params["oracle_home"] 373 | oracle_db_name = module.params["oracle_db_name"] 374 | nls_lang = module.params["nls_lang"] 375 | workdir = module.params["chdir"] 376 | 377 | os.environ["ORACLE_HOME"] = oracle_home 378 | os.environ["ORACLE_SID"] = oracle_db_name 379 | os.environ["PATH"] += os.pathsep + os.path.join(oracle_home, "bin") 380 | if nls_lang is not None: 381 | os.environ["NLS_LANG"] = nls_lang 382 | 383 | if scope == 'db': 384 | scope = 'cdb' 385 | if scope == 'default': 386 | scope = "all_pdbs" if catcon_pl is not None else "cdb" 387 | if scope == 'pdbs' and (pdb_list is None or pdb_list.strip() == ""): 388 | module.exit_json(msg = "scope = pdbs, but pdb_list is empty", changed = False) 389 | if scope == 'cdb' and catcon_pl is not None: 390 | scope = 'pdbs' 391 | pdb_list = 'CDB$ROOT' 392 | if scope == 'all_pdbs' and (catcon_pl is None or creates_sql is not None): 393 | if is_container(): 394 | scope = 'pdbs' 395 | get_all_pdbs() 396 | else: 397 | scope = 'cdb' 398 | 399 | if workdir is not None: 400 | try: 401 | os.chdir(workdir) 402 | except Exception as e: 403 | module.fail_json(msg = 'Could not chdir to %s: %s.' % (workdir, to_native(e)), changed = False) 404 | 405 | if creates_sql is not None: 406 | already_done = check_creates_sql(creates_sql, scope) 407 | if err_msg: 408 | module.fail_json(msg = "%s\n%s" % (result, err_msg), changed = False) 409 | else: 410 | if already_done: 411 | module.exit_json(msg = result, changed = False) 412 | 413 | if pdb_list is not None: 414 | result = "Run on these PDBs: %s\n" % pdb_list 415 | 416 | if sqlselect is not None: 417 | if sqlselect.endswith(";"): 418 | sqlselect.rstrip(";") 419 | sqlselect = "select dbms_xmlgen.getxml('" + sqlselect.replace("'", "''") + "') from dual;" 420 | result = run_sql_p(sqlselect, username, password, scope, pdb_list) 421 | elif sql is not None: 422 | sql = os.linesep.join([s for s in sql.splitlines() if s.strip()]) 423 | if not sql.endswith(";") and not sql.endswith("/"): 424 | sql += ";" 425 | result += run_sql_p(sql, username, password, scope, pdb_list) 426 | elif sqlscript is not None: 427 | if not sqlscript.startswith("@"): 428 | sqlscript = "@" + sqlscript 429 | result += run_sql_p(sqlscript, username, password, scope, pdb_list) 430 | elif catcon_pl is not None: 431 | run_catcon_pl(catcon_pl) 432 | 433 | if not err_msg: 434 | if sqlselect is not None: 435 | res_dict = dictify(ET.fromstring(result)) if result else {"ROW": []} 436 | module.exit_json(msg = result, changed = False, state = res_dict) 437 | else: 438 | module.exit_json(msg = result, changed = changed) 439 | else: 440 | module.fail_json(msg = "%s\n%s" % (result, err_msg), changed = changed) 441 | 442 | 443 | if __name__ == '__main__': 444 | main() 445 | -------------------------------------------------------------------------------- /oracle_stats_prefs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ANSIBLE_METADATA = { 5 | 'metadata_version': '1.1', 6 | 'status': ['preview'], 7 | 'supported_by': 'community' 8 | } 9 | 10 | DOCUMENTATION = ''' 11 | --- 12 | module: oracle_stats_prefs 13 | short_description: Manage DBMS_STATS global preferences 14 | description: 15 | - Manage DBMS_STATS global preferences 16 | - Can be run locally on the controlmachine or on a remote host 17 | version_added: "2.4" 18 | options: 19 | hostname: 20 | description: 21 | - The Oracle database host 22 | required: false 23 | default: localhost 24 | port: 25 | description: 26 | - The listener port number on the host 27 | required: false 28 | default: 1521 29 | service_name: 30 | description: 31 | - The database service name to connect to 32 | required: true 33 | user: 34 | description: 35 | - The Oracle user name to connect to the database, must have DBA privilege 36 | required: False 37 | password: 38 | description: 39 | - The Oracle user password for 'user' 40 | required: False 41 | mode: 42 | description: 43 | - The mode with which to connect to the database 44 | required: true 45 | default: normal 46 | choices: ['normal','sysdba'] 47 | preference_name: 48 | description: 49 | - DBMS_STATS preference name 50 | aliases: 51 | - pname 52 | preference_value: 53 | description: 54 | - Preference value 55 | aliases: 56 | - pvalue 57 | state: 58 | description: 59 | - Either to set the preference (present) or reset it to default (absent) 60 | required: true 61 | default: present 62 | choices: ['present','absent'] 63 | notes: 64 | - cx_Oracle needs to be installed 65 | - Oracle RDBMS 10gR2 or later required 66 | requirements: [ "cx_Oracle" ] 67 | author: Ilmar Kerm, ilmar.kerm@gmail.com, @ilmarkerm 68 | ''' 69 | 70 | EXAMPLES = ''' 71 | --- 72 | - hosts: localhost 73 | vars: 74 | oraclehost: 192.168.56.102 75 | oracleport: 1521 76 | oracleservice: orcl 77 | oracleuser: system 78 | oraclepassword: oracle 79 | oracle_env: 80 | ORACLE_HOME: /usr/lib/oracle/12.1/client64 81 | LD_LIBRARY_PATH: /usr/lib/oracle/12.1/client64/lib 82 | tasks: 83 | - name: set dbms_stats settings 84 | oracle_stats_prefs: 85 | hostname: "{{ oraclehost }}" 86 | port: "{{ oracleport }}" 87 | service_name: "{{ oracleservice }}" 88 | user: "{{ oracleuser }}" 89 | password: "{{ oraclepassword }}" 90 | pname: TABLE_CACHED_BLOCKS 91 | pvalue: 16 92 | environment: "{{ oracle_env }}" 93 | ''' 94 | 95 | try: 96 | import cx_Oracle 97 | except ImportError: 98 | cx_oracle_exists = False 99 | else: 100 | cx_oracle_exists = True 101 | 102 | # Ansible code 103 | def main(): 104 | global lconn, conn, msg, module 105 | msg = [''] 106 | module = AnsibleModule( 107 | argument_spec = dict( 108 | hostname = dict(default='localhost'), 109 | port = dict(default=1521, type='int'), 110 | service_name = dict(required=True), 111 | user = dict(required=False), 112 | password = dict(required=False, no_log=True), 113 | mode = dict(default='normal', choices=["normal","sysdba"]), 114 | preference_name = dict(required=True, aliases=['pname']), 115 | preference_value = dict(aliases=['pvalue']), 116 | state = dict(default='present', choices=["present","absent"]) 117 | ), 118 | supports_check_mode=True 119 | ) 120 | # Check for required modules 121 | if not cx_oracle_exists: 122 | module.fail_json(msg="The cx_Oracle module is required. 'pip install cx_Oracle' should do the trick. If cx_Oracle is installed, make sure ORACLE_HOME & LD_LIBRARY_PATH is set") 123 | # Connect to database 124 | hostname = module.params["hostname"] 125 | port = module.params["port"] 126 | service_name = module.params["service_name"] 127 | user = module.params["user"] 128 | password = module.params["password"] 129 | mode = module.params["mode"] 130 | wallet_connect = '/@%s' % service_name 131 | try: 132 | if (not user and not password ): # If neither user or password is supplied, the use of an oracle wallet is assumed 133 | if mode == 'sysdba': 134 | connect = wallet_connect 135 | conn = cx_Oracle.connect(wallet_connect, mode=cx_Oracle.SYSDBA) 136 | else: 137 | connect = wallet_connect 138 | conn = cx_Oracle.connect(wallet_connect) 139 | 140 | elif (user and password ): 141 | if mode == 'sysdba': 142 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 143 | connect = dsn 144 | conn = cx_Oracle.connect(user, password, dsn, mode=cx_Oracle.SYSDBA) 145 | else: 146 | dsn = cx_Oracle.makedsn(host=hostname, port=port, service_name=service_name) 147 | connect = dsn 148 | conn = cx_Oracle.connect(user, password, dsn) 149 | 150 | elif (not(user) or not(password)): 151 | module.fail_json(msg='Missing username or password for cx_Oracle') 152 | 153 | except cx_Oracle.DatabaseError as exc: 154 | error, = exc.args 155 | msg[0] = 'Could not connect to database - %s, connect descriptor: %s' % (error.message, connect) 156 | module.fail_json(msg=msg[0], changed=False) 157 | if conn.version < "10.2": 158 | module.fail_json(msg="Database version must be 10gR2 or greater", changed=False) 159 | # 160 | if module.check_mode: 161 | module.exit_json(changed=False) 162 | # 163 | c = conn.cursor() 164 | var_changed = c.var(cx_Oracle.NUMBER) 165 | var_msg = c.var(cx_Oracle.STRING) 166 | c.execute(""" 167 | DECLARE 168 | v_param_name VARCHAR2(100); 169 | v_param_value VARCHAR2(100); 170 | v_current_v VARCHAR2(100); 171 | v_state VARCHAR2(10); 172 | v_msg VARCHAR2(200):= 'Not changed'; 173 | v_changed NUMBER:= 0; 174 | BEGIN 175 | v_param_name:= :pname; 176 | v_param_value:= :pvalue; 177 | v_state:= :state; 178 | v_current_v:= DBMS_STATS.GET_PREFS(v_param_name); 179 | IF v_state = 'present' AND upper(v_param_value) != upper(v_current_v) THEN 180 | DBMS_STATS.SET_GLOBAL_PREFS(v_param_name, v_param_value); 181 | v_changed:= 1; 182 | v_msg:= 'Old value '||v_current_v||' changed to '||v_param_value; 183 | ELSIF v_state = 'absent' THEN 184 | DBMS_STATS.SET_GLOBAL_PREFS(v_param_name, NULL); 185 | v_param_value:= DBMS_STATS.GET_PREFS(v_param_name); 186 | IF v_param_value != v_current_v THEN 187 | v_msg:= 'Value reset to default '||v_param_value; 188 | v_changed:= 1; 189 | END IF; 190 | END IF; 191 | :changed:= v_changed; 192 | :msg:= v_msg; 193 | END; 194 | """, {'pname': module.params['preference_name'], 'pvalue': module.params['preference_value'], 'state': module.params['state'], 195 | 'changed': var_changed, 'msg': var_msg}) 196 | result_changed = var_changed.getvalue() > 0 197 | msg[0] = var_msg.getvalue() 198 | module.exit_json(msg=", ".join(msg), changed=result_changed) 199 | 200 | 201 | from ansible.module_utils.basic import * 202 | if __name__ == '__main__': 203 | main() 204 | -------------------------------------------------------------------------------- /test-modules.yml: -------------------------------------------------------------------------------- 1 | # Test playbook to test all oracle modules 2 | --- 3 | - hosts: moddev 4 | user: vagrant 5 | sudo: yes 6 | sudo_user: oracle 7 | gather_facts: false 8 | 9 | 10 | vars: 11 | oracle_home: /u01/app/oracle/11.2.0.4/home1 12 | hostname: moddev1 13 | service_name: asmdb 14 | set_init_mode: sysdba 15 | set_init_user: sys 16 | user: system 17 | password: Oracle123 18 | oracle_env: 19 | ORACLE_HOME: "{{ oracle_home }}" 20 | LD_LIBRARY_PATH: "{{ oracle_home }}/lib" 21 | 22 | set_init: False 23 | restart_db: False 24 | init_parameters: 25 | - { name: db_recovery_file_dest_size, value: 4G, state: present, scope: both } 26 | - { name: db_recovery_file_dest, value: '+FRA', state: present, scope: both } 27 | - { name: pga_aggregate_target, value: 300M, state: present, scope: both } 28 | - { name: _parallel_statement_queuing, value: true, state: present, scope: both } 29 | - { name: sga_target, value: 900M, state: present, scope: spfile } 30 | - { name: sga_max_size, value: 900M, state: present, scope: spfile } 31 | - { name: open_cursors, value: 400, state: present, scope: both } 32 | - { name: processes, value: 700, state: present, scope: spfile } 33 | - { name: log_archive_dest_2, value: 'service=hippie2, LGWR ASYNC NOAFFIRM db_unique_name=hippie2 valid_for=(online_logfile,primary_role)', state: present, scope: both } 34 | 35 | 36 | tablespaces: 37 | - { name: newtbs, datafile: '+DATA', size: 5M, bigfile: True, autoextend: false , next: 5M, maxsize: 500M, content: permanent, state: present } 38 | - { name: newundo, datafile: '+DATA', size: 5M, bigfile: false, autoextend: true, next: 5M, maxsize: 500M, content: undo, state: present } 39 | - { name: newtemp, datafile: '+DATA', size: 5M, bigfile: false, autoextend: on, next: 5M, maxsize: 500M, content: temp, state: present } 40 | 41 | dba_user: 42 | - schema: dbauser 43 | schema_password_hash: B7C930D09B3AF263 # passwd = dbauser 44 | state: present 45 | default_tablespace: users 46 | grants: 47 | - "dba" 48 | 49 | passw_user: 50 | - schema: userpw 51 | schema_password: userpw 52 | state: present 53 | default_tablespace: newtbs 54 | grants: 55 | - "approle1" 56 | 57 | roles: 58 | - name: approle1 59 | role_grants: 60 | - 'create session' 61 | - 'select any table' 62 | state: present 63 | 64 | 65 | 66 | tasks: 67 | 68 | 69 | - name: Create tablespace 70 | oracle_tablespace: hostname={{ hostname }} service_name={{ service_name }} user={{ user }} password={{ password }} tablespace={{ item.name }} datafile={{ item.datafile }} size={{ item.size }} bigfile={{ item.bigfile }} autoextend={{ item.autoextend }} next={{ item.next }} maxsize={{ item.maxsize }} content={{ item.content }} state={{ item.state }} 71 | environment: oracle_env 72 | with_items: tablespaces 73 | 74 | - name: Create application role 75 | oracle_role: hostname={{ hostname }} service_name={{ service_name }} user={{ user }} password={{ password }} role={{ item.name }} state={{ item.state }} 76 | environment: oracle_env 77 | with_items: roles 78 | 79 | 80 | - name: Add grants to role 81 | oracle_grants: hostname={{ hostname }} service_name={{ service_name }} user={{ user }} password={{ password }} role={{ item.0.name }} grants="{{ item.1 }}" state={{ item.0.state }} 82 | environment: oracle_env 83 | with_subelements: 84 | - roles 85 | - role_grants 86 | 87 | - name: Create DBA user 88 | oracle_user: hostname={{ hostname }} service_name={{ service_name }} mode=normal user={{ user }} password={{ password }} schema={{ item.0.schema }} schema_password_hash={{ item.0.schema_password_hash }} state={{ item.0.state }} default_tablespace={{ item.0.default_tablespace }} grants={{ item.1 }} 89 | environment: oracle_env 90 | with_subelements: 91 | - dba_user 92 | - grants 93 | 94 | 95 | - name: Create Application user 96 | oracle_user: hostname={{ hostname }} service_name={{ service_name }} mode=normal user={{ user }} password={{ password }} schema={{ item.0.schema }} schema_password={{ item.0.schema_password }} state={{ item.0.state }} default_tablespace={{ item.0.default_tablespace }} grants={{ item.1 }} 97 | environment: oracle_env 98 | with_subelements: 99 | - passw_user 100 | - grants 101 | 102 | - name: Set parameters 103 | oracle_parameter: hostname={{ hostname }} service_name={{ service_name }} user={{ set_init_user }} password={{ password }} mode={{ set_init_mode }} name={{ item.name }} value="{{ item.value }}" state={{ item.state }} scope={{ item.scope }} 104 | environment: oracle_env 105 | with_items: init_parameters 106 | when: set_init 107 | register: param_change 108 | 109 | - name: Restart database (stop) 110 | shell: "{{ oracle_home }}/bin/srvctl stop database -d {{ service_name }}" 111 | environment: oracle_env 112 | when: param_change.changed and set_init and restart_db 113 | 114 | - name: Restart database (start) 115 | shell: "{{ oracle_home }}/bin/srvctl start database -d {{ service_name }}" 116 | environment: oracle_env 117 | when: param_change.changed and set_init and restart_db 118 | 119 | - name: Check if database is running 120 | shell: "{{ oracle_home }}/bin/srvctl status database -d {{ service_name }}" 121 | environment: oracle_env 122 | register: check_db_up 123 | when: restart_db 124 | tags: check 125 | 126 | - name: Check if database is running 127 | debug: msg={{ item }} 128 | with_items: check_db_up.stdout_lines 129 | when: restart_db 130 | tags: check 131 | 132 | 133 | 134 | 135 | --------------------------------------------------------------------------------