mirror of https://github.com/ansible/ansible.git
Core/Extras splitting WIP.
parent
dc8540c755
commit
7e94a05dd6
@ -1,242 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# (c) 2012, Elliott Foster <elliott@fourkitchens.com>
|
||||
# Sponsored by Four Kitchens http://fourkitchens.com.
|
||||
# (c) 2014, Epic Games, Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: mongodb_user
|
||||
short_description: Adds or removes a user from a MongoDB database.
|
||||
description:
|
||||
- Adds or removes a user from a MongoDB database.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- The host running the database
|
||||
required: false
|
||||
default: localhost
|
||||
login_port:
|
||||
description:
|
||||
- The port to connect to
|
||||
required: false
|
||||
default: 27017
|
||||
replica_set:
|
||||
version_added: "1.6"
|
||||
description:
|
||||
- Replica set to connect to (automatically connects to primary for writes)
|
||||
required: false
|
||||
default: null
|
||||
database:
|
||||
description:
|
||||
- The name of the database to add/remove the user from
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- The name of the user to add or remove
|
||||
required: true
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- The password to use for the user
|
||||
required: false
|
||||
default: null
|
||||
roles:
|
||||
version_added: "1.3"
|
||||
description:
|
||||
- "The database user roles valid values are one or more of the following: read, 'readWrite', 'dbAdmin', 'userAdmin', 'clusterAdmin', 'readAnyDatabase', 'readWriteAnyDatabase', 'userAdminAnyDatabase', 'dbAdminAnyDatabase'"
|
||||
- This param requires mongodb 2.4+ and pymongo 2.5+
|
||||
required: false
|
||||
default: "readWrite"
|
||||
state:
|
||||
state:
|
||||
description:
|
||||
- The database user state
|
||||
required: false
|
||||
default: present
|
||||
choices: [ "present", "absent" ]
|
||||
notes:
|
||||
- Requires the pymongo Python package on the remote host, version 2.4.2+. This
|
||||
can be installed using pip or the OS package manager. @see http://api.mongodb.org/python/current/installation.html
|
||||
requirements: [ "pymongo" ]
|
||||
author: Elliott Foster
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create 'burgers' database user with name 'bob' and password '12345'.
|
||||
- mongodb_user: database=burgers name=bob password=12345 state=present
|
||||
|
||||
# Delete 'burgers' database user with name 'bob'.
|
||||
- mongodb_user: database=burgers name=bob state=absent
|
||||
|
||||
# Define more users with various specific roles (if not defined, no roles is assigned, and the user will be added via pre mongo 2.2 style)
|
||||
- mongodb_user: database=burgers name=ben password=12345 roles='read' state=present
|
||||
- mongodb_user: database=burgers name=jim password=12345 roles='readWrite,dbAdmin,userAdmin' state=present
|
||||
- mongodb_user: database=burgers name=joe password=12345 roles='readWriteAnyDatabase' state=present
|
||||
|
||||
# add a user to database in a replica set, the primary server is automatically discovered and written to
|
||||
- mongodb_user: database=burgers name=bob replica_set=blecher password=12345 roles='readWriteAnyDatabase' state=present
|
||||
'''
|
||||
|
||||
import ConfigParser
|
||||
from distutils.version import LooseVersion
|
||||
try:
|
||||
from pymongo.errors import ConnectionFailure
|
||||
from pymongo.errors import OperationFailure
|
||||
from pymongo import version as PyMongoVersion
|
||||
from pymongo import MongoClient
|
||||
except ImportError:
|
||||
try: # for older PyMongo 2.2
|
||||
from pymongo import Connection as MongoClient
|
||||
except ImportError:
|
||||
pymongo_found = False
|
||||
else:
|
||||
pymongo_found = True
|
||||
else:
|
||||
pymongo_found = True
|
||||
|
||||
# =========================================
|
||||
# MongoDB module specific support methods.
|
||||
#
|
||||
|
||||
def user_add(module, client, db_name, user, password, roles):
|
||||
db = client[db_name]
|
||||
if roles is None:
|
||||
db.add_user(user, password, False)
|
||||
else:
|
||||
try:
|
||||
db.add_user(user, password, None, roles=roles)
|
||||
except OperationFailure, e:
|
||||
err_msg = str(e)
|
||||
if LooseVersion(PyMongoVersion) <= LooseVersion('2.5'):
|
||||
err_msg = err_msg + ' (Note: you must be on mongodb 2.4+ and pymongo 2.5+ to use the roles param)'
|
||||
module.fail_json(msg=err_msg)
|
||||
|
||||
def user_remove(client, db_name, user):
|
||||
db = client[db_name]
|
||||
db.remove_user(user)
|
||||
|
||||
def load_mongocnf():
|
||||
config = ConfigParser.RawConfigParser()
|
||||
mongocnf = os.path.expanduser('~/.mongodb.cnf')
|
||||
|
||||
try:
|
||||
config.readfp(open(mongocnf))
|
||||
creds = dict(
|
||||
user=config.get('client', 'user'),
|
||||
password=config.get('client', 'pass')
|
||||
)
|
||||
except (ConfigParser.NoOptionError, IOError):
|
||||
return False
|
||||
|
||||
return creds
|
||||
|
||||
# =========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
login_user=dict(default=None),
|
||||
login_password=dict(default=None),
|
||||
login_host=dict(default='localhost'),
|
||||
login_port=dict(default='27017'),
|
||||
replica_set=dict(default=None),
|
||||
database=dict(required=True, aliases=['db']),
|
||||
user=dict(required=True, aliases=['name']),
|
||||
password=dict(aliases=['pass']),
|
||||
roles=dict(default=None, type='list'),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
)
|
||||
)
|
||||
|
||||
if not pymongo_found:
|
||||
module.fail_json(msg='the python pymongo module is required')
|
||||
|
||||
login_user = module.params['login_user']
|
||||
login_password = module.params['login_password']
|
||||
login_host = module.params['login_host']
|
||||
login_port = module.params['login_port']
|
||||
replica_set = module.params['replica_set']
|
||||
db_name = module.params['database']
|
||||
user = module.params['user']
|
||||
password = module.params['password']
|
||||
roles = module.params['roles']
|
||||
state = module.params['state']
|
||||
|
||||
try:
|
||||
if replica_set:
|
||||
client = MongoClient(login_host, int(login_port), replicaset=replica_set)
|
||||
else:
|
||||
client = MongoClient(login_host, int(login_port))
|
||||
|
||||
# try to authenticate as a target user to check if it already exists
|
||||
try:
|
||||
client[db_name].authenticate(user, password)
|
||||
if state == 'present':
|
||||
module.exit_json(changed=False, user=user)
|
||||
except OperationFailure:
|
||||
if state == 'absent':
|
||||
module.exit_json(changed=False, user=user)
|
||||
|
||||
if login_user is None and login_password is None:
|
||||
mongocnf_creds = load_mongocnf()
|
||||
if mongocnf_creds is not False:
|
||||
login_user = mongocnf_creds['user']
|
||||
login_password = mongocnf_creds['password']
|
||||
elif login_password is None and login_user is not None:
|
||||
module.fail_json(msg='when supplying login arguments, both login_user and login_password must be provided')
|
||||
|
||||
if login_user is not None and login_password is not None:
|
||||
client.admin.authenticate(login_user, login_password)
|
||||
|
||||
except ConnectionFailure, e:
|
||||
module.fail_json(msg='unable to connect to database: %s' % str(e))
|
||||
|
||||
if state == 'present':
|
||||
if password is None:
|
||||
module.fail_json(msg='password parameter required when adding a user')
|
||||
|
||||
try:
|
||||
user_add(module, client, db_name, user, password, roles)
|
||||
except OperationFailure, e:
|
||||
module.fail_json(msg='Unable to add or update user: %s' % str(e))
|
||||
|
||||
elif state == 'absent':
|
||||
try:
|
||||
user_remove(client, db_name, user)
|
||||
except OperationFailure, e:
|
||||
module.fail_json(msg='Unable to remove user: %s' % str(e))
|
||||
|
||||
module.exit_json(changed=True, user=user)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,369 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
||||
Ansible module to manage mysql replication
|
||||
(c) 2013, Balazs Pocze <banyek@gawker.com>
|
||||
Certain parts are taken from Mark Theunissen's mysqldb module
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: mysql_replication
|
||||
|
||||
short_description: Manage MySQL replication
|
||||
description:
|
||||
- Manages MySQL server replication, slave, master status get and change master host.
|
||||
version_added: "1.3"
|
||||
options:
|
||||
mode:
|
||||
description:
|
||||
- module operating mode. Could be getslave (SHOW SLAVE STATUS), getmaster (SHOW MASTER STATUS), changemaster (CHANGE MASTER TO), startslave (START SLAVE), stopslave (STOP SLAVE)
|
||||
required: False
|
||||
choices:
|
||||
- getslave
|
||||
- getmaster
|
||||
- changemaster
|
||||
- stopslave
|
||||
- startslave
|
||||
default: getslave
|
||||
login_user:
|
||||
description:
|
||||
- username to connect mysql host, if defined login_password also needed.
|
||||
required: False
|
||||
login_password:
|
||||
description:
|
||||
- password to connect mysql host, if defined login_user also needed.
|
||||
required: False
|
||||
login_host:
|
||||
description:
|
||||
- mysql host to connect
|
||||
required: False
|
||||
login_unix_socket:
|
||||
description:
|
||||
- unix socket to connect mysql server
|
||||
master_host:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_user:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_password:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_port:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_connect_retry:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_log_file:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_log_pos:
|
||||
description:
|
||||
- same as mysql variable
|
||||
relay_log_file:
|
||||
description:
|
||||
- same as mysql variable
|
||||
relay_log_pos:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl:
|
||||
description:
|
||||
- same as mysql variable
|
||||
possible values: 0,1
|
||||
master_ssl_ca:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_capath:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_cert:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_key:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_cipher:
|
||||
description:
|
||||
- same as mysql variable
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Stop mysql slave thread
|
||||
- mysql_replication: mode=stopslave
|
||||
|
||||
# Get master binlog file name and binlog position
|
||||
- mysql_replication: mode=getmaster
|
||||
|
||||
# Change master to master server 192.168.1.1 and use binary log 'mysql-bin.000009' with position 4578
|
||||
- mysql_replication: mode=changemaster master_host=192.168.1.1 master_log_file=mysql-bin.000009 master_log_pos=4578
|
||||
'''
|
||||
|
||||
import ConfigParser
|
||||
import os
|
||||
import warnings
|
||||
|
||||
try:
|
||||
import MySQLdb
|
||||
except ImportError:
|
||||
mysqldb_found = False
|
||||
else:
|
||||
mysqldb_found = True
|
||||
|
||||
|
||||
def get_master_status(cursor):
|
||||
cursor.execute("SHOW MASTER STATUS")
|
||||
masterstatus = cursor.fetchone()
|
||||
return masterstatus
|
||||
|
||||
|
||||
def get_slave_status(cursor):
|
||||
cursor.execute("SHOW SLAVE STATUS")
|
||||
slavestatus = cursor.fetchone()
|
||||
return slavestatus
|
||||
|
||||
|
||||
def stop_slave(cursor):
|
||||
try:
|
||||
cursor.execute("STOP SLAVE")
|
||||
stopped = True
|
||||
except:
|
||||
stopped = False
|
||||
return stopped
|
||||
|
||||
|
||||
def start_slave(cursor):
|
||||
try:
|
||||
cursor.execute("START SLAVE")
|
||||
started = True
|
||||
except:
|
||||
started = False
|
||||
return started
|
||||
|
||||
|
||||
def changemaster(cursor, chm):
|
||||
SQLPARAM = ",".join(chm)
|
||||
cursor.execute("CHANGE MASTER TO " + SQLPARAM)
|
||||
|
||||
|
||||
def strip_quotes(s):
|
||||
""" Remove surrounding single or double quotes
|
||||
|
||||
>>> print strip_quotes('hello')
|
||||
hello
|
||||
>>> print strip_quotes('"hello"')
|
||||
hello
|
||||
>>> print strip_quotes("'hello'")
|
||||
hello
|
||||
>>> print strip_quotes("'hello")
|
||||
'hello
|
||||
|
||||
"""
|
||||
single_quote = "'"
|
||||
double_quote = '"'
|
||||
|
||||
if s.startswith(single_quote) and s.endswith(single_quote):
|
||||
s = s.strip(single_quote)
|
||||
elif s.startswith(double_quote) and s.endswith(double_quote):
|
||||
s = s.strip(double_quote)
|
||||
return s
|
||||
|
||||
|
||||
def config_get(config, section, option):
|
||||
""" Calls ConfigParser.get and strips quotes
|
||||
|
||||
See: http://dev.mysql.com/doc/refman/5.0/en/option-files.html
|
||||
"""
|
||||
return strip_quotes(config.get(section, option))
|
||||
|
||||
|
||||
def load_mycnf():
|
||||
config = ConfigParser.RawConfigParser()
|
||||
mycnf = os.path.expanduser('~/.my.cnf')
|
||||
if not os.path.exists(mycnf):
|
||||
return False
|
||||
try:
|
||||
config.readfp(open(mycnf))
|
||||
except (IOError):
|
||||
return False
|
||||
# We support two forms of passwords in .my.cnf, both pass= and password=,
|
||||
# as these are both supported by MySQL.
|
||||
try:
|
||||
passwd = config_get(config, 'client', 'password')
|
||||
except (ConfigParser.NoOptionError):
|
||||
try:
|
||||
passwd = config_get(config, 'client', 'pass')
|
||||
except (ConfigParser.NoOptionError):
|
||||
return False
|
||||
|
||||
# If .my.cnf doesn't specify a user, default to user login name
|
||||
try:
|
||||
user = config_get(config, 'client', 'user')
|
||||
except (ConfigParser.NoOptionError):
|
||||
user = getpass.getuser()
|
||||
creds = dict(user=user, passwd=passwd)
|
||||
return creds
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
login_user=dict(default=None),
|
||||
login_password=dict(default=None),
|
||||
login_host=dict(default="localhost"),
|
||||
login_unix_socket=dict(default=None),
|
||||
mode=dict(default="getslave", choices=["getmaster", "getslave", "changemaster", "stopslave", "startslave"]),
|
||||
master_host=dict(default=None),
|
||||
master_user=dict(default=None),
|
||||
master_password=dict(default=None),
|
||||
master_port=dict(default=None),
|
||||
master_connect_retry=dict(default=None),
|
||||
master_log_file=dict(default=None),
|
||||
master_log_pos=dict(default=None),
|
||||
relay_log_file=dict(default=None),
|
||||
relay_log_pos=dict(default=None),
|
||||
master_ssl=dict(default=False, type='bool'),
|
||||
master_ssl_ca=dict(default=None),
|
||||
master_ssl_capath=dict(default=None),
|
||||
master_ssl_cert=dict(default=None),
|
||||
master_ssl_key=dict(default=None),
|
||||
master_ssl_cipher=dict(default=None),
|
||||
)
|
||||
)
|
||||
user = module.params["login_user"]
|
||||
password = module.params["login_password"]
|
||||
host = module.params["login_host"]
|
||||
mode = module.params["mode"]
|
||||
master_host = module.params["master_host"]
|
||||
master_user = module.params["master_user"]
|
||||
master_password = module.params["master_password"]
|
||||
master_port = module.params["master_port"]
|
||||
master_connect_retry = module.params["master_connect_retry"]
|
||||
master_log_file = module.params["master_log_file"]
|
||||
master_log_pos = module.params["master_log_pos"]
|
||||
relay_log_file = module.params["relay_log_file"]
|
||||
relay_log_pos = module.params["relay_log_pos"]
|
||||
master_ssl = module.params["master_ssl"]
|
||||
master_ssl_ca = module.params["master_ssl_ca"]
|
||||
master_ssl_capath = module.params["master_ssl_capath"]
|
||||
master_ssl_cert = module.params["master_ssl_cert"]
|
||||
master_ssl_key = module.params["master_ssl_key"]
|
||||
master_ssl_cipher = module.params["master_ssl_cipher"]
|
||||
|
||||
if not mysqldb_found:
|
||||
module.fail_json(msg="the python mysqldb module is required")
|
||||
else:
|
||||
warnings.filterwarnings('error', category=MySQLdb.Warning)
|
||||
|
||||
# Either the caller passes both a username and password with which to connect to
|
||||
# mysql, or they pass neither and allow this module to read the credentials from
|
||||
# ~/.my.cnf.
|
||||
login_password = module.params["login_password"]
|
||||
login_user = module.params["login_user"]
|
||||
if login_user is None and login_password is None:
|
||||
mycnf_creds = load_mycnf()
|
||||
if mycnf_creds is False:
|
||||
login_user = "root"
|
||||
login_password = ""
|
||||
else:
|
||||
login_user = mycnf_creds["user"]
|
||||
login_password = mycnf_creds["passwd"]
|
||||
elif login_password is None or login_user is None:
|
||||
module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
|
||||
|
||||
try:
|
||||
if module.params["login_unix_socket"]:
|
||||
db_connection = MySQLdb.connect(host=module.params["login_host"], unix_socket=module.params["login_unix_socket"], user=login_user, passwd=login_password, db="mysql")
|
||||
else:
|
||||
db_connection = MySQLdb.connect(host=module.params["login_host"], user=login_user, passwd=login_password, db="mysql")
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials")
|
||||
try:
|
||||
cursor = db_connection.cursor(cursorclass=MySQLdb.cursors.DictCursor)
|
||||
except Exception, e:
|
||||
module.fail_json(msg="Trouble getting DictCursor from db_connection: %s" % e)
|
||||
|
||||
if mode in "getmaster":
|
||||
masterstatus = get_master_status(cursor)
|
||||
try:
|
||||
module.exit_json( **masterstatus )
|
||||
except TypeError:
|
||||
module.fail_json(msg="Server is not configured as mysql master")
|
||||
|
||||
elif mode in "getslave":
|
||||
slavestatus = get_slave_status(cursor)
|
||||
try:
|
||||
module.exit_json( **slavestatus )
|
||||
except TypeError:
|
||||
module.fail_json(msg="Server is not configured as mysql slave")
|
||||
|
||||
elif mode in "changemaster":
|
||||
print "Change master"
|
||||
chm=[]
|
||||
if master_host:
|
||||
chm.append("MASTER_HOST='" + master_host + "'")
|
||||
if master_user:
|
||||
chm.append("MASTER_USER='" + master_user + "'")
|
||||
if master_password:
|
||||
chm.append("MASTER_PASSWORD='" + master_password + "'")
|
||||
if master_port:
|
||||
chm.append("MASTER_PORT=" + master_port)
|
||||
if master_connect_retry:
|
||||
chm.append("MASTER_CONNECT_RETRY='" + master_connect_retry + "'")
|
||||
if master_log_file:
|
||||
chm.append("MASTER_LOG_FILE='" + master_log_file + "'")
|
||||
if master_log_pos:
|
||||
chm.append("MASTER_LOG_POS=" + master_log_pos)
|
||||
if relay_log_file:
|
||||
chm.append("RELAY_LOG_FILE='" + relay_log_file + "'")
|
||||
if relay_log_pos:
|
||||
chm.append("RELAY_LOG_POS=" + relay_log_pos)
|
||||
if master_ssl:
|
||||
chm.append("MASTER_SSL=1")
|
||||
if master_ssl_ca:
|
||||
chm.append("MASTER_SSL_CA='" + master_ssl_ca + "'")
|
||||
if master_ssl_capath:
|
||||
chm.append("MASTER_SSL_CAPATH='" + master_ssl_capath + "'")
|
||||
if master_ssl_cert:
|
||||
chm.append("MASTER_SSL_CERT='" + master_ssl_cert + "'")
|
||||
if master_ssl_key:
|
||||
chm.append("MASTER_SSL_KEY='" + master_ssl_key + "'")
|
||||
if master_ssl_cipher:
|
||||
chm.append("MASTER_SSL_CIPHER='" + master_ssl_cipher + "'")
|
||||
changemaster(cursor,chm)
|
||||
module.exit_json(changed=True)
|
||||
elif mode in "startslave":
|
||||
started = start_slave(cursor)
|
||||
if started is True:
|
||||
module.exit_json(msg="Slave started ", changed=True)
|
||||
else:
|
||||
module.exit_json(msg="Slave already started (Or cannot be started)", changed=False)
|
||||
elif mode in "stopslave":
|
||||
stopped = stop_slave(cursor)
|
||||
if stopped is True:
|
||||
module.exit_json(msg="Slave stopped", changed=True)
|
||||
else:
|
||||
module.exit_json(msg="Slave already stopped", changed=False)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
warnings.simplefilter("ignore")
|
@ -1,329 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: redis
|
||||
short_description: Various redis commands, slave and flush
|
||||
description:
|
||||
- Unified utility to interact with redis instances.
|
||||
'slave' sets a redis instance in slave or master mode.
|
||||
'flush' flushes all the instance or a specified db.
|
||||
'config' (new in 1.6), ensures a configuration setting on an instance.
|
||||
version_added: "1.3"
|
||||
options:
|
||||
command:
|
||||
description:
|
||||
- The selected redis command
|
||||
required: true
|
||||
default: null
|
||||
choices: [ "slave", "flush", "config" ]
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with (usually not used)
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- The host running the database
|
||||
required: false
|
||||
default: localhost
|
||||
login_port:
|
||||
description:
|
||||
- The port to connect to
|
||||
required: false
|
||||
default: 6379
|
||||
master_host:
|
||||
description:
|
||||
- The host of the master instance [slave command]
|
||||
required: false
|
||||
default: null
|
||||
master_port:
|
||||
description:
|
||||
- The port of the master instance [slave command]
|
||||
required: false
|
||||
default: null
|
||||
slave_mode:
|
||||
description:
|
||||
- the mode of the redis instance [slave command]
|
||||
required: false
|
||||
default: slave
|
||||
choices: [ "master", "slave" ]
|
||||
db:
|
||||
description:
|
||||
- The database to flush (used in db mode) [flush command]
|
||||
required: false
|
||||
default: null
|
||||
flush_mode:
|
||||
description:
|
||||
- Type of flush (all the dbs in a redis instance or a specific one)
|
||||
[flush command]
|
||||
required: false
|
||||
default: all
|
||||
choices: [ "all", "db" ]
|
||||
name:
|
||||
version_added: 1.6
|
||||
description:
|
||||
- A redis config key.
|
||||
required: false
|
||||
default: null
|
||||
value:
|
||||
version_added: 1.6
|
||||
description:
|
||||
- A redis config value.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
|
||||
notes:
|
||||
- Requires the redis-py Python package on the remote host. You can
|
||||
install it with pip (pip install redis) or with a package manager.
|
||||
https://github.com/andymccurdy/redis-py
|
||||
- If the redis master instance we are making slave of is password protected
|
||||
this needs to be in the redis.conf in the masterauth variable
|
||||
|
||||
requirements: [ redis ]
|
||||
author: Xabier Larrakoetxea
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set local redis instance to be slave of melee.island on port 6377
|
||||
- redis: command=slave master_host=melee.island master_port=6377
|
||||
|
||||
# Deactivate slave mode
|
||||
- redis: command=slave slave_mode=master
|
||||
|
||||
# Flush all the redis db
|
||||
- redis: command=flush flush_mode=all
|
||||
|
||||
# Flush only one db in a redis instance
|
||||
- redis: command=flush db=1 flush_mode=db
|
||||
|
||||
# Configure local redis to have 10000 max clients
|
||||
- redis: command=config name=maxclients value=10000
|
||||
|
||||
# Configure local redis to have lua time limit of 100 ms
|
||||
- redis: command=config name=lua-time-limit value=100
|
||||
'''
|
||||
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
redis_found = False
|
||||
else:
|
||||
redis_found = True
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Redis module specific support methods.
|
||||
#
|
||||
|
||||
def set_slave_mode(client, master_host, master_port):
|
||||
try:
|
||||
return client.slaveof(master_host, master_port)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def set_master_mode(client):
|
||||
try:
|
||||
return client.slaveof()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def flush(client, db=None):
|
||||
try:
|
||||
if type(db) != int:
|
||||
return client.flushall()
|
||||
else:
|
||||
# The passed client has been connected to the database already
|
||||
return client.flushdb()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
command=dict(default=None, choices=['slave', 'flush', 'config']),
|
||||
login_password=dict(default=None),
|
||||
login_host=dict(default='localhost'),
|
||||
login_port=dict(default='6379'),
|
||||
master_host=dict(default=None),
|
||||
master_port=dict(default=None),
|
||||
slave_mode=dict(default='slave', choices=['master', 'slave']),
|
||||
db=dict(default=None),
|
||||
flush_mode=dict(default='all', choices=['all', 'db']),
|
||||
name=dict(default=None),
|
||||
value=dict(default=None)
|
||||
),
|
||||
supports_check_mode = True
|
||||
)
|
||||
|
||||
if not redis_found:
|
||||
module.fail_json(msg="python redis module is required")
|
||||
|
||||
login_password = module.params['login_password']
|
||||
login_host = module.params['login_host']
|
||||
login_port = int(module.params['login_port'])
|
||||
command = module.params['command']
|
||||
|
||||
# Slave Command section -----------
|
||||
if command == "slave":
|
||||
master_host = module.params['master_host']
|
||||
master_port = module.params['master_port']
|
||||
try:
|
||||
master_port = int(module.params['master_port'])
|
||||
except Exception:
|
||||
pass
|
||||
mode = module.params['slave_mode']
|
||||
|
||||
#Check if we have all the data
|
||||
if mode == "slave": # Only need data if we want to be slave
|
||||
if not master_host:
|
||||
module.fail_json(
|
||||
msg='In slave mode master host must be provided')
|
||||
|
||||
if not master_port:
|
||||
module.fail_json(
|
||||
msg='In slave mode master port must be provided')
|
||||
|
||||
#Connect and check
|
||||
r = redis.StrictRedis(host=login_host,
|
||||
port=login_port,
|
||||
password=login_password)
|
||||
try:
|
||||
r.ping()
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
|
||||
#Check if we are already in the mode that we want
|
||||
info = r.info()
|
||||
if mode == "master" and info["role"] == "master":
|
||||
module.exit_json(changed=False, mode=mode)
|
||||
|
||||
elif mode == "slave" and\
|
||||
info["role"] == "slave" and\
|
||||
info["master_host"] == master_host and\
|
||||
info["master_port"] == master_port:
|
||||
status = {
|
||||
'status': mode,
|
||||
'master_host': master_host,
|
||||
'master_port': master_port,
|
||||
}
|
||||
module.exit_json(changed=False, mode=status)
|
||||
else:
|
||||
# Do the stuff
|
||||
# (Check Check_mode before commands so the commands aren't evaluated
|
||||
# if not necessary)
|
||||
if mode == "slave":
|
||||
if module.check_mode or\
|
||||
set_slave_mode(r, master_host, master_port):
|
||||
info = r.info()
|
||||
status = {
|
||||
'status': mode,
|
||||
'master_host': master_host,
|
||||
'master_port': master_port,
|
||||
}
|
||||
module.exit_json(changed=True, mode=status)
|
||||
else:
|
||||
module.fail_json(msg='Unable to set slave mode')
|
||||
|
||||
else:
|
||||
if module.check_mode or set_master_mode(r):
|
||||
module.exit_json(changed=True, mode=mode)
|
||||
else:
|
||||
module.fail_json(msg='Unable to set master mode')
|
||||
|
||||
# flush Command section -----------
|
||||
elif command == "flush":
|
||||
try:
|
||||
db = int(module.params['db'])
|
||||
except Exception:
|
||||
db = 0
|
||||
mode = module.params['flush_mode']
|
||||
|
||||
#Check if we have all the data
|
||||
if mode == "db":
|
||||
if type(db) != int:
|
||||
module.fail_json(
|
||||
msg="In db mode the db number must be provided")
|
||||
|
||||
#Connect and check
|
||||
r = redis.StrictRedis(host=login_host,
|
||||
port=login_port,
|
||||
password=login_password,
|
||||
db=db)
|
||||
try:
|
||||
r.ping()
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
|
||||
# Do the stuff
|
||||
# (Check Check_mode before commands so the commands aren't evaluated
|
||||
# if not necessary)
|
||||
if mode == "all":
|
||||
if module.check_mode or flush(r):
|
||||
module.exit_json(changed=True, flushed=True)
|
||||
else: # Flush never fails :)
|
||||
module.fail_json(msg="Unable to flush all databases")
|
||||
|
||||
else:
|
||||
if module.check_mode or flush(r, db):
|
||||
module.exit_json(changed=True, flushed=True, db=db)
|
||||
else: # Flush never fails :)
|
||||
module.fail_json(msg="Unable to flush '%d' database" % db)
|
||||
elif command == 'config':
|
||||
name = module.params['name']
|
||||
value = module.params['value']
|
||||
|
||||
r = redis.StrictRedis(host=login_host,
|
||||
port=login_port,
|
||||
password=login_password)
|
||||
|
||||
try:
|
||||
r.ping()
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
|
||||
|
||||
try:
|
||||
old_value = r.config_get(name)[name]
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to read config: %s" % e)
|
||||
changed = old_value != value
|
||||
|
||||
if module.check_mode or not changed:
|
||||
module.exit_json(changed=changed, name=name, value=value)
|
||||
else:
|
||||
try:
|
||||
r.config_set(name, value)
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to write config: %s" % e)
|
||||
module.exit_json(changed=changed, name=name, value=value)
|
||||
else:
|
||||
module.fail_json(msg='A valid command must be provided')
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,255 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, James Martin <jmartin@basho.com>, Drew Kerrigan <dkerrigan@basho.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: riak
|
||||
short_description: This module handles some common Riak operations
|
||||
description:
|
||||
- This module can be used to join nodes to a cluster, check
|
||||
the status of the cluster.
|
||||
version_added: "1.2"
|
||||
options:
|
||||
command:
|
||||
description:
|
||||
- The command you would like to perform against the cluster.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
choices: ['ping', 'kv_test', 'join', 'plan', 'commit']
|
||||
config_dir:
|
||||
description:
|
||||
- The path to the riak configuration directory
|
||||
required: false
|
||||
default: /etc/riak
|
||||
aliases: []
|
||||
http_conn:
|
||||
description:
|
||||
- The ip address and port that is listening for Riak HTTP queries
|
||||
required: false
|
||||
default: 127.0.0.1:8098
|
||||
aliases: []
|
||||
target_node:
|
||||
description:
|
||||
- The target node for certain operations (join, ping)
|
||||
required: false
|
||||
default: riak@127.0.0.1
|
||||
aliases: []
|
||||
wait_for_handoffs:
|
||||
description:
|
||||
- Number of seconds to wait for handoffs to complete.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
type: 'int'
|
||||
wait_for_ring:
|
||||
description:
|
||||
- Number of seconds to wait for all nodes to agree on the ring.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
type: 'int'
|
||||
wait_for_service:
|
||||
description:
|
||||
- Waits for a riak service to come online before continuing.
|
||||
required: false
|
||||
default: None
|
||||
aliases: []
|
||||
choices: ['kv']
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Join's a Riak node to another node
|
||||
- riak: command=join target_node=riak@10.1.1.1
|
||||
|
||||
# Wait for handoffs to finish. Use with async and poll.
|
||||
- riak: wait_for_handoffs=yes
|
||||
|
||||
# Wait for riak_kv service to startup
|
||||
- riak: wait_for_service=kv
|
||||
'''
|
||||
|
||||
import urllib2
|
||||
import time
|
||||
import socket
|
||||
import sys
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
|
||||
def ring_check(module, riak_admin_bin):
|
||||
cmd = '%s ringready' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0 and 'TRUE All nodes agree on the ring' in out:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
command=dict(required=False, default=None, choices=[
|
||||
'ping', 'kv_test', 'join', 'plan', 'commit']),
|
||||
config_dir=dict(default='/etc/riak'),
|
||||
http_conn=dict(required=False, default='127.0.0.1:8098'),
|
||||
target_node=dict(default='riak@127.0.0.1', required=False),
|
||||
wait_for_handoffs=dict(default=False, type='int'),
|
||||
wait_for_ring=dict(default=False, type='int'),
|
||||
wait_for_service=dict(
|
||||
required=False, default=None, choices=['kv']),
|
||||
validate_certs = dict(default='yes', type='bool'))
|
||||
)
|
||||
|
||||
|
||||
command = module.params.get('command')
|
||||
config_dir = module.params.get('config_dir')
|
||||
http_conn = module.params.get('http_conn')
|
||||
target_node = module.params.get('target_node')
|
||||
wait_for_handoffs = module.params.get('wait_for_handoffs')
|
||||
wait_for_ring = module.params.get('wait_for_ring')
|
||||
wait_for_service = module.params.get('wait_for_service')
|
||||
validate_certs = module.params.get('validate_certs')
|
||||
|
||||
|
||||
#make sure riak commands are on the path
|
||||
riak_bin = module.get_bin_path('riak')
|
||||
riak_admin_bin = module.get_bin_path('riak-admin')
|
||||
|
||||
timeout = time.time() + 120
|
||||
while True:
|
||||
if time.time() > timeout:
|
||||
module.fail_json(msg='Timeout, could not fetch Riak stats.')
|
||||
(response, info) = fetch_url(module, 'http://%s/stats' % (http_conn), force=True, timeout=5)
|
||||
if info['status'] == 200:
|
||||
stats_raw = response.read()
|
||||
break
|
||||
time.sleep(5)
|
||||
|
||||
# here we attempt to load those stats,
|
||||
try:
|
||||
stats = json.loads(stats_raw)
|
||||
except:
|
||||
module.fail_json(msg='Could not parse Riak stats.')
|
||||
|
||||
node_name = stats['nodename']
|
||||
nodes = stats['ring_members']
|
||||
ring_size = stats['ring_creation_size']
|
||||
rc, out, err = module.run_command([riak_bin, 'version'] )
|
||||
version = out.strip()
|
||||
|
||||
result = dict(node_name=node_name,
|
||||
nodes=nodes,
|
||||
ring_size=ring_size,
|
||||
version=version)
|
||||
|
||||
if command == 'ping':
|
||||
cmd = '%s ping %s' % ( riak_bin, target_node )
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['ping'] = out
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'kv_test':
|
||||
cmd = '%s test' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['kv_test'] = out
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'join':
|
||||
if nodes.count(node_name) == 1 and len(nodes) > 1:
|
||||
result['join'] = 'Node is already in cluster or staged to be in cluster.'
|
||||
else:
|
||||
cmd = '%s cluster join %s' % (riak_admin_bin, target_node)
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['join'] = out
|
||||
result['changed'] = True
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'plan':
|
||||
cmd = '%s cluster plan' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['plan'] = out
|
||||
if 'Staged Changes' in out:
|
||||
result['changed'] = True
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'commit':
|
||||
cmd = '%s cluster commit' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['commit'] = out
|
||||
result['changed'] = True
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
# this could take a while, recommend to run in async mode
|
||||
if wait_for_handoffs:
|
||||
timeout = time.time() + wait_for_handoffs
|
||||
while True:
|
||||
cmd = '%s transfers' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if 'No transfers active' in out:
|
||||
result['handoffs'] = 'No transfers active.'
|
||||
break
|
||||
time.sleep(10)
|
||||
if time.time() > timeout:
|
||||
module.fail_json(msg='Timeout waiting for handoffs.')
|
||||
|
||||
if wait_for_service:
|
||||
cmd = [riak_admin_bin, 'wait_for_service', 'riak_%s' % wait_for_service, node_name ]
|
||||
rc, out, err = module.run_command(cmd)
|
||||
result['service'] = out
|
||||
|
||||
if wait_for_ring:
|
||||
timeout = time.time() + wait_for_ring
|
||||
while True:
|
||||
if ring_check(module, riak_admin_bin):
|
||||
break
|
||||
time.sleep(10)
|
||||
if time.time() > timeout:
|
||||
module.fail_json(msg='Timeout waiting for nodes to agree on ring.')
|
||||
|
||||
result['ring_ready'] = ring_check(module, riak_admin_bin)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,152 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Chatham Financial <oss@chathamfinancial.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rabbitmq_parameter
|
||||
short_description: Adds or removes parameters to RabbitMQ
|
||||
description:
|
||||
- Manage dynamic, cluster-wide parameters for RabbitMQ
|
||||
version_added: "1.1"
|
||||
author: Chris Hoffman
|
||||
options:
|
||||
component:
|
||||
description:
|
||||
- Name of the component of which the parameter is being set
|
||||
required: true
|
||||
default: null
|
||||
name:
|
||||
description:
|
||||
- Name of the parameter being set
|
||||
required: true
|
||||
default: null
|
||||
value:
|
||||
description:
|
||||
- Value of the parameter, as a JSON term
|
||||
required: false
|
||||
default: null
|
||||
vhost:
|
||||
description:
|
||||
- vhost to apply access privileges.
|
||||
required: false
|
||||
default: /
|
||||
node:
|
||||
description:
|
||||
- erlang node name of the rabbit we wish to configure
|
||||
required: false
|
||||
default: rabbit
|
||||
version_added: "1.2"
|
||||
state:
|
||||
description:
|
||||
- Specify if user is to be added or removed
|
||||
required: false
|
||||
default: present
|
||||
choices: [ 'present', 'absent']
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
# Set the federation parameter 'local_username' to a value of 'guest' (in quotes)
|
||||
- rabbitmq_parameter: component=federation
|
||||
name=local-username
|
||||
value='"guest"'
|
||||
state=present
|
||||
"""
|
||||
|
||||
class RabbitMqParameter(object):
|
||||
def __init__(self, module, component, name, value, vhost, node):
|
||||
self.module = module
|
||||
self.component = component
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.vhost = vhost
|
||||
self.node = node
|
||||
|
||||
self._value = None
|
||||
|
||||
self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True)
|
||||
|
||||
def _exec(self, args, run_in_check_mode=False):
|
||||
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
|
||||
cmd = [self._rabbitmqctl, '-q', '-n', self.node]
|
||||
rc, out, err = self.module.run_command(cmd + args, check_rc=True)
|
||||
return out.splitlines()
|
||||
return list()
|
||||
|
||||
def get(self):
|
||||
parameters = self._exec(['list_parameters', '-p', self.vhost], True)
|
||||
|
||||
for param_item in parameters:
|
||||
component, name, value = param_item.split('\t')
|
||||
|
||||
if component == self.component and name == self.name:
|
||||
self._value = value
|
||||
return True
|
||||
return False
|
||||
|
||||
def set(self):
|
||||
self._exec(['set_parameter', '-p', self.vhost, self.component, self.name, self.value])
|
||||
|
||||
def delete(self):
|
||||
self._exec(['clear_parameter', '-p', self.vhost, self.component, self.name])
|
||||
|
||||
def has_modifications(self):
|
||||
return self.value != self._value
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
component=dict(required=True),
|
||||
name=dict(required=True),
|
||||
value=dict(default=None),
|
||||
vhost=dict(default='/'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
node=dict(default='rabbit')
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
component = module.params['component']
|
||||
name = module.params['name']
|
||||
value = module.params['value']
|
||||
vhost = module.params['vhost']
|
||||
state = module.params['state']
|
||||
node = module.params['node']
|
||||
|
||||
rabbitmq_parameter = RabbitMqParameter(module, component, name, value, vhost, node)
|
||||
|
||||
changed = False
|
||||
if rabbitmq_parameter.get():
|
||||
if state == 'absent':
|
||||
rabbitmq_parameter.delete()
|
||||
changed = True
|
||||
else:
|
||||
if rabbitmq_parameter.has_modifications():
|
||||
rabbitmq_parameter.set()
|
||||
changed = True
|
||||
elif state == 'present':
|
||||
rabbitmq_parameter.set()
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, component=component, name=name, vhost=vhost, state=state)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,130 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Chatham Financial <oss@chathamfinancial.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rabbitmq_plugin
|
||||
short_description: Adds or removes plugins to RabbitMQ
|
||||
description:
|
||||
- Enables or disables RabbitMQ plugins
|
||||
version_added: "1.1"
|
||||
author: Chris Hoffman
|
||||
options:
|
||||
names:
|
||||
description:
|
||||
- Comma-separated list of plugin names
|
||||
required: true
|
||||
default: null
|
||||
aliases: [name]
|
||||
new_only:
|
||||
description:
|
||||
- Only enable missing plugins
|
||||
- Does not disable plugins that are not in the names list
|
||||
required: false
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
state:
|
||||
description:
|
||||
- Specify if plugins are to be enabled or disabled
|
||||
required: false
|
||||
default: enabled
|
||||
choices: [enabled, disabled]
|
||||
prefix:
|
||||
description:
|
||||
- Specify a custom install prefix to a Rabbit
|
||||
required: false
|
||||
version_added: "1.3"
|
||||
default: null
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Enables the rabbitmq_management plugin
|
||||
- rabbitmq_plugin: names=rabbitmq_management state=enabled
|
||||
'''
|
||||
|
||||
class RabbitMqPlugins(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
if module.params['prefix']:
|
||||
self._rabbitmq_plugins = module.params['prefix'] + "/sbin/rabbitmq-plugins"
|
||||
else:
|
||||
self._rabbitmq_plugins = module.get_bin_path('rabbitmq-plugins', True)
|
||||
|
||||
def _exec(self, args, run_in_check_mode=False):
|
||||
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
|
||||
cmd = [self._rabbitmq_plugins]
|
||||
rc, out, err = self.module.run_command(cmd + args, check_rc=True)
|
||||
return out.splitlines()
|
||||
return list()
|
||||
|
||||
def get_all(self):
|
||||
return self._exec(['list', '-E', '-m'], True)
|
||||
|
||||
def enable(self, name):
|
||||
self._exec(['enable', name])
|
||||
|
||||
def disable(self, name):
|
||||
self._exec(['disable', name])
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
names=dict(required=True, aliases=['name']),
|
||||
new_only=dict(default='no', type='bool'),
|
||||
state=dict(default='enabled', choices=['enabled', 'disabled']),
|
||||
prefix=dict(required=False, default=None)
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
names = module.params['names'].split(',')
|
||||
new_only = module.params['new_only']
|
||||
state = module.params['state']
|
||||
|
||||
rabbitmq_plugins = RabbitMqPlugins(module)
|
||||
enabled_plugins = rabbitmq_plugins.get_all()
|
||||
|
||||
enabled = []
|
||||
disabled = []
|
||||
if state == 'enabled':
|
||||
if not new_only:
|
||||
for plugin in enabled_plugins:
|
||||
if plugin not in names:
|
||||
rabbitmq_plugins.disable(plugin)
|
||||
disabled.append(plugin)
|
||||
|
||||
for name in names:
|
||||
if name not in enabled_plugins:
|
||||
rabbitmq_plugins.enable(name)
|
||||
enabled.append(name)
|
||||
else:
|
||||
for plugin in enabled_plugins:
|
||||
if plugin in names:
|
||||
rabbitmq_plugins.disable(plugin)
|
||||
disabled.append(plugin)
|
||||
|
||||
changed = len(enabled) > 0 or len(disabled) > 0
|
||||
module.exit_json(changed=changed, enabled=enabled, disabled=disabled)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,156 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, John Dewey <john@dewey.ws>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rabbitmq_policy
|
||||
short_description: Manage the state of policies in RabbitMQ.
|
||||
description:
|
||||
- Manage the state of a virtual host in RabbitMQ.
|
||||
version_added: "1.5"
|
||||
author: John Dewey
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the policy to manage.
|
||||
required: true
|
||||
default: null
|
||||
vhost:
|
||||
description:
|
||||
- The name of the vhost to apply to.
|
||||
required: false
|
||||
default: /
|
||||
pattern:
|
||||
description:
|
||||
- A regex of queues to apply the policy to.
|
||||
required: true
|
||||
default: null
|
||||
tags:
|
||||
description:
|
||||
- A dict or string describing the policy.
|
||||
required: true
|
||||
default: null
|
||||
priority:
|
||||
description:
|
||||
- The priority of the policy.
|
||||
required: false
|
||||
default: 0
|
||||
node:
|
||||
description:
|
||||
- Erlang node name of the rabbit we wish to configure.
|
||||
required: false
|
||||
default: rabbit
|
||||
state:
|
||||
description:
|
||||
- The state of the policy.
|
||||
default: present
|
||||
choices: [present, absent]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: ensure the default vhost contains the HA policy via a dict
|
||||
rabbitmq_policy: name=HA pattern='.*'
|
||||
args:
|
||||
tags:
|
||||
"ha-mode": all
|
||||
|
||||
- name: ensure the default vhost contains the HA policy
|
||||
rabbitmq_policy: name=HA pattern='.*' tags="ha-mode=all"
|
||||
'''
|
||||
class RabbitMqPolicy(object):
|
||||
def __init__(self, module, name):
|
||||
self._module = module
|
||||
self._name = name
|
||||
self._vhost = module.params['vhost']
|
||||
self._pattern = module.params['pattern']
|
||||
self._tags = module.params['tags']
|
||||
self._priority = module.params['priority']
|
||||
self._node = module.params['node']
|
||||
self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True)
|
||||
|
||||
def _exec(self, args, run_in_check_mode=False):
|
||||
if not self._module.check_mode or (self._module.check_mode and run_in_check_mode):
|
||||
cmd = [self._rabbitmqctl, '-q', '-n', self._node]
|
||||
args.insert(1, '-p')
|
||||
args.insert(2, self._vhost)
|
||||
rc, out, err = self._module.run_command(cmd + args, check_rc=True)
|
||||
return out.splitlines()
|
||||
return list()
|
||||
|
||||
def list(self):
|
||||
policies = self._exec(['list_policies'], True)
|
||||
|
||||
for policy in policies:
|
||||
policy_name = policy.split('\t')[1]
|
||||
if policy_name == self._name:
|
||||
return True
|
||||
return False
|
||||
|
||||
def set(self):
|
||||
import json
|
||||
args = ['set_policy']
|
||||
args.append(self._name)
|
||||
args.append(self._pattern)
|
||||
args.append(json.dumps(self._tags))
|
||||
args.append('--priority')
|
||||
args.append(self._priority)
|
||||
return self._exec(args)
|
||||
|
||||
def clear(self):
|
||||
return self._exec(['clear_policy', self._name])
|
||||
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
name=dict(required=True),
|
||||
vhost=dict(default='/'),
|
||||
pattern=dict(required=True),
|
||||
tags=dict(type='dict', required=True),
|
||||
priority=dict(default='0'),
|
||||
node=dict(default='rabbit'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
name = module.params['name']
|
||||
state = module.params['state']
|
||||
rabbitmq_policy = RabbitMqPolicy(module, name)
|
||||
|
||||
changed = False
|
||||
if rabbitmq_policy.list():
|
||||
if state == 'absent':
|
||||
rabbitmq_policy.clear()
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
elif state == 'present':
|
||||
rabbitmq_policy.set()
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, name=name, state=state)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,249 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Chatham Financial <oss@chathamfinancial.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rabbitmq_user
|
||||
short_description: Adds or removes users to RabbitMQ
|
||||
description:
|
||||
- Add or remove users to RabbitMQ and assign permissions
|
||||
version_added: "1.1"
|
||||
author: Chris Hoffman
|
||||
options:
|
||||
user:
|
||||
description:
|
||||
- Name of user to add
|
||||
required: true
|
||||
default: null
|
||||
aliases: [username, name]
|
||||
password:
|
||||
description:
|
||||
- Password of user to add.
|
||||
- To change the password of an existing user, you must also specify
|
||||
C(force=yes).
|
||||
required: false
|
||||
default: null
|
||||
tags:
|
||||
description:
|
||||
- User tags specified as comma delimited
|
||||
required: false
|
||||
default: null
|
||||
vhost:
|
||||
description:
|
||||
- vhost to apply access privileges.
|
||||
required: false
|
||||
default: /
|
||||
node:
|
||||
description:
|
||||
- erlang node name of the rabbit we wish to configure
|
||||
required: false
|
||||
default: rabbit
|
||||
version_added: "1.2"
|
||||
configure_priv:
|
||||
description:
|
||||
- Regular expression to restrict configure actions on a resource
|
||||
for the specified vhost.
|
||||
- By default all actions are restricted.
|
||||
required: false
|
||||
default: ^$
|
||||
write_priv:
|
||||
description:
|
||||
- Regular expression to restrict configure actions on a resource
|
||||
for the specified vhost.
|
||||
- By default all actions are restricted.
|
||||
required: false
|
||||
default: ^$
|
||||
read_priv:
|
||||
description:
|
||||
- Regular expression to restrict configure actions on a resource
|
||||
for the specified vhost.
|
||||
- By default all actions are restricted.
|
||||
required: false
|
||||
default: ^$
|
||||
force:
|
||||
description:
|
||||
- Deletes and recreates the user.
|
||||
required: false
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
state:
|
||||
description:
|
||||
- Specify if user is to be added or removed
|
||||
required: false
|
||||
default: present
|
||||
choices: [present, absent]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Add user to server and assign full access control
|
||||
- rabbitmq_user: user=joe
|
||||
password=changeme
|
||||
vhost=/
|
||||
configure_priv=.*
|
||||
read_priv=.*
|
||||
write_priv=.*
|
||||
state=present
|
||||
'''
|
||||
|
||||
class RabbitMqUser(object):
|
||||
def __init__(self, module, username, password, tags, vhost, configure_priv, write_priv, read_priv, node):
|
||||
self.module = module
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.node = node
|
||||
if tags is None:
|
||||
self.tags = list()
|
||||
else:
|
||||
self.tags = tags.split(',')
|
||||
|
||||
permissions = dict(
|
||||
vhost=vhost,
|
||||
configure_priv=configure_priv,
|
||||
write_priv=write_priv,
|
||||
read_priv=read_priv
|
||||
)
|
||||
self.permissions = permissions
|
||||
|
||||
self._tags = None
|
||||
self._permissions = None
|
||||
self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True)
|
||||
|
||||
def _exec(self, args, run_in_check_mode=False):
|
||||
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
|
||||
cmd = [self._rabbitmqctl, '-q', '-n', self.node]
|
||||
rc, out, err = self.module.run_command(cmd + args, check_rc=True)
|
||||
return out.splitlines()
|
||||
return list()
|
||||
|
||||
def get(self):
|
||||
users = self._exec(['list_users'], True)
|
||||
|
||||
for user_tag in users:
|
||||
user, tags = user_tag.split('\t')
|
||||
|
||||
if user == self.username:
|
||||
for c in ['[',']',' ']:
|
||||
tags = tags.replace(c, '')
|
||||
|
||||
if tags != '':
|
||||
self._tags = tags.split(',')
|
||||
else:
|
||||
self._tags = list()
|
||||
|
||||
self._permissions = self._get_permissions()
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_permissions(self):
|
||||
perms_out = self._exec(['list_user_permissions', self.username], True)
|
||||
|
||||
for perm in perms_out:
|
||||
vhost, configure_priv, write_priv, read_priv = perm.split('\t')
|
||||
if vhost == self.permissions['vhost']:
|
||||
return dict(vhost=vhost, configure_priv=configure_priv, write_priv=write_priv, read_priv=read_priv)
|
||||
|
||||
return dict()
|
||||
|
||||
def add(self):
|
||||
self._exec(['add_user', self.username, self.password])
|
||||
|
||||
def delete(self):
|
||||
self._exec(['delete_user', self.username])
|
||||
|
||||
def set_tags(self):
|
||||
self._exec(['set_user_tags', self.username] + self.tags)
|
||||
|
||||
def set_permissions(self):
|
||||
cmd = ['set_permissions']
|
||||
cmd.append('-p')
|
||||
cmd.append(self.permissions['vhost'])
|
||||
cmd.append(self.username)
|
||||
cmd.append(self.permissions['configure_priv'])
|
||||
cmd.append(self.permissions['write_priv'])
|
||||
cmd.append(self.permissions['read_priv'])
|
||||
self._exec(cmd)
|
||||
|
||||
def has_tags_modifications(self):
|
||||
return set(self.tags) != set(self._tags)
|
||||
|
||||
def has_permissions_modifications(self):
|
||||
return self._permissions != self.permissions
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
user=dict(required=True, aliases=['username', 'name']),
|
||||
password=dict(default=None),
|
||||
tags=dict(default=None),
|
||||
vhost=dict(default='/'),
|
||||
configure_priv=dict(default='^$'),
|
||||
write_priv=dict(default='^$'),
|
||||
read_priv=dict(default='^$'),
|
||||
force=dict(default='no', type='bool'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
node=dict(default='rabbit')
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
username = module.params['user']
|
||||
password = module.params['password']
|
||||
tags = module.params['tags']
|
||||
vhost = module.params['vhost']
|
||||
configure_priv = module.params['configure_priv']
|
||||
write_priv = module.params['write_priv']
|
||||
read_priv = module.params['read_priv']
|
||||
force = module.params['force']
|
||||
state = module.params['state']
|
||||
node = module.params['node']
|
||||
|
||||
rabbitmq_user = RabbitMqUser(module, username, password, tags, vhost, configure_priv, write_priv, read_priv, node)
|
||||
|
||||
changed = False
|
||||
if rabbitmq_user.get():
|
||||
if state == 'absent':
|
||||
rabbitmq_user.delete()
|
||||
changed = True
|
||||
else:
|
||||
if force:
|
||||
rabbitmq_user.delete()
|
||||
rabbitmq_user.add()
|
||||
rabbitmq_user.get()
|
||||
changed = True
|
||||
|
||||
if rabbitmq_user.has_tags_modifications():
|
||||
rabbitmq_user.set_tags()
|
||||
changed = True
|
||||
|
||||
if rabbitmq_user.has_permissions_modifications():
|
||||
rabbitmq_user.set_permissions()
|
||||
changed = True
|
||||
elif state == 'present':
|
||||
rabbitmq_user.add()
|
||||
rabbitmq_user.set_tags()
|
||||
rabbitmq_user.set_permissions()
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, user=username, state=state)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,147 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Chatham Financial <oss@chathamfinancial.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rabbitmq_vhost
|
||||
short_description: Manage the state of a virtual host in RabbitMQ
|
||||
description:
|
||||
- Manage the state of a virtual host in RabbitMQ
|
||||
version_added: "1.1"
|
||||
author: Chris Hoffman
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the vhost to manage
|
||||
required: true
|
||||
default: null
|
||||
aliases: [vhost]
|
||||
node:
|
||||
description:
|
||||
- erlang node name of the rabbit we wish to configure
|
||||
required: false
|
||||
default: rabbit
|
||||
version_added: "1.2"
|
||||
tracing:
|
||||
description:
|
||||
- Enable/disable tracing for a vhost
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
aliases: [trace]
|
||||
state:
|
||||
description:
|
||||
- The state of vhost
|
||||
default: present
|
||||
choices: [present, absent]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure that the vhost /test exists.
|
||||
- rabbitmq_vhost: name=/test state=present
|
||||
'''
|
||||
|
||||
class RabbitMqVhost(object):
|
||||
def __init__(self, module, name, tracing, node):
|
||||
self.module = module
|
||||
self.name = name
|
||||
self.tracing = tracing
|
||||
self.node = node
|
||||
|
||||
self._tracing = False
|
||||
self._rabbitmqctl = module.get_bin_path('rabbitmqctl', True)
|
||||
|
||||
def _exec(self, args, run_in_check_mode=False):
|
||||
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
|
||||
cmd = [self._rabbitmqctl, '-q', '-n', self.node]
|
||||
rc, out, err = self.module.run_command(cmd + args, check_rc=True)
|
||||
return out.splitlines()
|
||||
return list()
|
||||
|
||||
def get(self):
|
||||
vhosts = self._exec(['list_vhosts', 'name', 'tracing'], True)
|
||||
|
||||
for vhost in vhosts:
|
||||
name, tracing = vhost.split('\t')
|
||||
if name == self.name:
|
||||
self._tracing = self.module.boolean(tracing)
|
||||
return True
|
||||
return False
|
||||
|
||||
def add(self):
|
||||
return self._exec(['add_vhost', self.name])
|
||||
|
||||
def delete(self):
|
||||
return self._exec(['delete_vhost', self.name])
|
||||
|
||||
def set_tracing(self):
|
||||
if self.tracing != self._tracing:
|
||||
if self.tracing:
|
||||
self._enable_tracing()
|
||||
else:
|
||||
self._disable_tracing()
|
||||
return True
|
||||
return False
|
||||
|
||||
def _enable_tracing(self):
|
||||
return self._exec(['trace_on', '-p', self.name])
|
||||
|
||||
def _disable_tracing(self):
|
||||
return self._exec(['trace_off', '-p', self.name])
|
||||
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
name=dict(required=True, aliases=['vhost']),
|
||||
tracing=dict(default='off', aliases=['trace'], type='bool'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
node=dict(default='rabbit'),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
name = module.params['name']
|
||||
tracing = module.params['tracing']
|
||||
state = module.params['state']
|
||||
node = module.params['node']
|
||||
|
||||
rabbitmq_vhost = RabbitMqVhost(module, name, tracing, node)
|
||||
|
||||
changed = False
|
||||
if rabbitmq_vhost.get():
|
||||
if state == 'absent':
|
||||
rabbitmq_vhost.delete()
|
||||
changed = True
|
||||
else:
|
||||
if rabbitmq_vhost.set_tracing():
|
||||
changed = True
|
||||
elif state == 'present':
|
||||
rabbitmq_vhost.add()
|
||||
rabbitmq_vhost.set_tracing()
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, name=name, state=state)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,130 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Bruce Pennypacker <bruce@pennypacker.org>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: airbrake_deployment
|
||||
version_added: "1.2"
|
||||
author: Bruce Pennypacker
|
||||
short_description: Notify airbrake about app deployments
|
||||
description:
|
||||
- Notify airbrake about app deployments (see http://help.airbrake.io/kb/api-2/deploy-tracking)
|
||||
options:
|
||||
token:
|
||||
description:
|
||||
- API token.
|
||||
required: true
|
||||
environment:
|
||||
description:
|
||||
- The airbrake environment name, typically 'production', 'staging', etc.
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- The username of the person doing the deployment
|
||||
required: false
|
||||
repo:
|
||||
description:
|
||||
- URL of the project repository
|
||||
required: false
|
||||
revision:
|
||||
description:
|
||||
- A hash, number, tag, or other identifier showing what revision was deployed
|
||||
required: false
|
||||
url:
|
||||
description:
|
||||
- Optional URL to submit the notification to. Use to send notifications to Airbrake-compliant tools like Errbit.
|
||||
required: false
|
||||
default: "https://airbrake.io/deploys"
|
||||
version_added: "1.5"
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates for the target url will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ urllib, urllib2 ]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- airbrake_deployment: token=AAAAAA
|
||||
environment='staging'
|
||||
user='ansible'
|
||||
revision=4.2
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
token=dict(required=True),
|
||||
environment=dict(required=True),
|
||||
user=dict(required=False),
|
||||
repo=dict(required=False),
|
||||
revision=dict(required=False),
|
||||
url=dict(required=False, default='https://api.airbrake.io/deploys.txt'),
|
||||
validate_certs=dict(default='yes', type='bool'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# build list of params
|
||||
params = {}
|
||||
|
||||
if module.params["environment"]:
|
||||
params["deploy[rails_env]"] = module.params["environment"]
|
||||
|
||||
if module.params["user"]:
|
||||
params["deploy[local_username]"] = module.params["user"]
|
||||
|
||||
if module.params["repo"]:
|
||||
params["deploy[scm_repository]"] = module.params["repo"]
|
||||
|
||||
if module.params["revision"]:
|
||||
params["deploy[scm_revision]"] = module.params["revision"]
|
||||
|
||||
params["api_key"] = module.params["token"]
|
||||
|
||||
url = module.params.get('url')
|
||||
|
||||
# If we're in check mode, just exit pretending like we succeeded
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
# Send the data to airbrake
|
||||
data = urllib.urlencode(params)
|
||||
response, info = fetch_url(module, url, data=data)
|
||||
if info['status'] == 200:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.fail_json(msg="HTTP result code: %d connecting to %s" % (info['status'], url))
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
||||
|
@ -1,172 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: bigpanda
|
||||
author: BigPanda
|
||||
short_description: Notify BigPanda about deployments
|
||||
version_added: "1.8"
|
||||
description:
|
||||
- Notify BigPanda when deployments start and end (successfully or not). Returns a deployment object containing all the parameters for future module calls.
|
||||
options:
|
||||
component:
|
||||
description:
|
||||
- "The name of the component being deployed. Ex: billing"
|
||||
required: true
|
||||
alias: name
|
||||
version:
|
||||
description:
|
||||
- The deployment version.
|
||||
required: true
|
||||
token:
|
||||
description:
|
||||
- API token.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- State of the deployment.
|
||||
required: true
|
||||
choices: ['started', 'finished', 'failed']
|
||||
hosts:
|
||||
description:
|
||||
- Name of affected host name. Can be a list.
|
||||
required: false
|
||||
default: machine's hostname
|
||||
alias: host
|
||||
env:
|
||||
description:
|
||||
- The environment name, typically 'production', 'staging', etc.
|
||||
required: false
|
||||
owner:
|
||||
description:
|
||||
- The person responsible for the deployment.
|
||||
required: false
|
||||
description:
|
||||
description:
|
||||
- Free text description of the deployment.
|
||||
required: false
|
||||
url:
|
||||
description:
|
||||
- Base URL of the API server.
|
||||
required: False
|
||||
default: https://api.bigpanda.io
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates for the target url will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ urllib, urllib2 ]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- bigpanda: component=myapp version=1.3 token={{ bigpanda_token }} state=started
|
||||
...
|
||||
- bigpanda: component=myapp version=1.3 token={{ bigpanda_token }} state=finished
|
||||
|
||||
or using a deployment object:
|
||||
- bigpanda: component=myapp version=1.3 token={{ bigpanda_token }} state=started
|
||||
register: deployment
|
||||
|
||||
- bigpanda: state=finished
|
||||
args: deployment
|
||||
|
||||
If outside servers aren't reachable from your machine, use local_action and pass the hostname:
|
||||
- local_action: bigpanda component=myapp version=1.3 hosts={{ansible_hostname}} token={{ bigpanda_token }} state=started
|
||||
register: deployment
|
||||
...
|
||||
- local_action: bigpanda state=finished
|
||||
args: deployment
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
import socket
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
component=dict(required=True, aliases=['name']),
|
||||
version=dict(required=True),
|
||||
token=dict(required=True),
|
||||
state=dict(required=True, choices=['started', 'finished', 'failed']),
|
||||
hosts=dict(required=False, default=[socket.gethostname()], aliases=['host']),
|
||||
env=dict(required=False),
|
||||
owner=dict(required=False),
|
||||
description=dict(required=False),
|
||||
message=dict(required=False),
|
||||
source_system=dict(required=False, default='ansible'),
|
||||
validate_certs=dict(default='yes', type='bool'),
|
||||
url=dict(required=False, default='https://api.bigpanda.io'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
check_invalid_arguments=False,
|
||||
)
|
||||
|
||||
token = module.params['token']
|
||||
state = module.params['state']
|
||||
url = module.params['url']
|
||||
|
||||
# Build the common request body
|
||||
body = dict()
|
||||
for k in ('component', 'version', 'hosts'):
|
||||
v = module.params[k]
|
||||
if v is not None:
|
||||
body[k] = v
|
||||
|
||||
if not isinstance(body['hosts'], list):
|
||||
body['hosts'] = [body['hosts']]
|
||||
|
||||
# Insert state-specific attributes to body
|
||||
if state == 'started':
|
||||
for k in ('source_system', 'env', 'owner', 'description'):
|
||||
v = module.params[k]
|
||||
if v is not None:
|
||||
body[k] = v
|
||||
|
||||
request_url = url + '/data/events/deployments/start'
|
||||
else:
|
||||
message = module.params['message']
|
||||
if message is not None:
|
||||
body['errorMessage'] = message
|
||||
|
||||
if state == 'finished':
|
||||
body['status'] = 'success'
|
||||
else:
|
||||
body['status'] = 'failure'
|
||||
|
||||
request_url = url + '/data/events/deployments/end'
|
||||
|
||||
# Build the deployment object we return
|
||||
deployment = dict(token=token, url=url)
|
||||
deployment.update(body)
|
||||
if 'errorMessage' in deployment:
|
||||
message = deployment.pop('errorMessage')
|
||||
deployment['message'] = message
|
||||
|
||||
# If we're in check mode, just exit pretending like we succeeded
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, **deployment)
|
||||
|
||||
# Send the data to bigpanda
|
||||
data = json.dumps(body)
|
||||
headers = {'Authorization':'Bearer %s' % token, 'Content-Type':'application/json'}
|
||||
try:
|
||||
response, info = fetch_url(module, request_url, data=data, headers=headers)
|
||||
if info['status'] == 200:
|
||||
module.exit_json(changed=True, **deployment)
|
||||
else:
|
||||
module.fail_json(msg=json.dumps(info))
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,256 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Ansible module to add boundary meters.
|
||||
|
||||
(c) 2013, curtis <curtis@serverascode.com>
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import json
|
||||
import datetime
|
||||
import base64
|
||||
import os
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: boundary_meter
|
||||
short_description: Manage boundary meters
|
||||
description:
|
||||
- This module manages boundary meters
|
||||
version_added: "1.3"
|
||||
author: curtis@serverascode.com
|
||||
requirements:
|
||||
- Boundary API access
|
||||
- bprobe is required to send data, but not to register a meter
|
||||
- Python urllib2
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- meter name
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether to create or remove the client from boundary
|
||||
required: false
|
||||
default: true
|
||||
choices: ["present", "absent"]
|
||||
apiid:
|
||||
description:
|
||||
- Organizations boundary API ID
|
||||
required: true
|
||||
apikey:
|
||||
description:
|
||||
- Organizations boundary API KEY
|
||||
required: true
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
|
||||
notes:
|
||||
- This module does not yet support boundary tags.
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES='''
|
||||
- name: Create meter
|
||||
boundary_meter: apiid=AAAAAA api_key=BBBBBB state=present name={{ inventory_hostname }}"
|
||||
|
||||
- name: Delete meter
|
||||
boundary_meter: apiid=AAAAAA api_key=BBBBBB state=absent name={{ inventory_hostname }}"
|
||||
|
||||
'''
|
||||
|
||||
api_host = "api.boundary.com"
|
||||
config_directory = "/etc/bprobe"
|
||||
|
||||
# "resource" like thing or apikey?
|
||||
def auth_encode(apikey):
|
||||
auth = base64.standard_b64encode(apikey)
|
||||
auth.replace("\n", "")
|
||||
return auth
|
||||
|
||||
def build_url(name, apiid, action, meter_id=None, cert_type=None):
|
||||
if action == "create":
|
||||
return 'https://%s/%s/meters' % (api_host, apiid)
|
||||
elif action == "search":
|
||||
return "https://%s/%s/meters?name=%s" % (api_host, apiid, name)
|
||||
elif action == "certificates":
|
||||
return "https://%s/%s/meters/%s/%s.pem" % (api_host, apiid, meter_id, cert_type)
|
||||
elif action == "tags":
|
||||
return "https://%s/%s/meters/%s/tags" % (api_host, apiid, meter_id)
|
||||
elif action == "delete":
|
||||
return "https://%s/%s/meters/%s" % (api_host, apiid, meter_id)
|
||||
|
||||
def http_request(module, name, apiid, apikey, action, data=None, meter_id=None, cert_type=None):
|
||||
|
||||
if meter_id is None:
|
||||
url = build_url(name, apiid, action)
|
||||
else:
|
||||
if cert_type is None:
|
||||
url = build_url(name, apiid, action, meter_id)
|
||||
else:
|
||||
url = build_url(name, apiid, action, meter_id, cert_type)
|
||||
|
||||
headers = dict()
|
||||
headers["Authorization"] = "Basic %s" % auth_encode(apikey)
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
return fetch_url(module, url, data=data, headers=headers)
|
||||
|
||||
def create_meter(module, name, apiid, apikey):
|
||||
|
||||
meters = search_meter(module, name, apiid, apikey)
|
||||
|
||||
if len(meters) > 0:
|
||||
# If the meter already exists, do nothing
|
||||
module.exit_json(status="Meter " + name + " already exists",changed=False)
|
||||
else:
|
||||
# If it doesn't exist, create it
|
||||
body = '{"name":"' + name + '"}'
|
||||
response, info = http_request(module, name, apiid, apikey, data=body, action="create")
|
||||
|
||||
if info['status'] != 200:
|
||||
module.fail_json(msg="Failed to connect to api host to create meter")
|
||||
|
||||
# If the config directory doesn't exist, create it
|
||||
if not os.path.exists(config_directory):
|
||||
try:
|
||||
os.makedirs(config_directory)
|
||||
except:
|
||||
module.fail_json("Could not create " + config_directory)
|
||||
|
||||
|
||||
# Download both cert files from the api host
|
||||
types = ['key', 'cert']
|
||||
for cert_type in types:
|
||||
try:
|
||||
# If we can't open the file it's not there, so we should download it
|
||||
cert_file = open('%s/%s.pem' % (config_directory,cert_type))
|
||||
except IOError:
|
||||
# Now download the file...
|
||||
rc = download_request(module, name, apiid, apikey, cert_type)
|
||||
if rc == False:
|
||||
module.fail_json("Download request for " + cert_type + ".pem failed")
|
||||
|
||||
return 0, "Meter " + name + " created"
|
||||
|
||||
def search_meter(module, name, apiid, apikey):
|
||||
|
||||
response, info = http_request(module, name, apiid, apikey, action="search")
|
||||
|
||||
if info['status'] != 200:
|
||||
module.fail_json("Failed to connect to api host to search for meter")
|
||||
|
||||
# Return meters
|
||||
return json.loads(response.read())
|
||||
|
||||
def get_meter_id(module, name, apiid, apikey):
|
||||
# In order to delete the meter we need its id
|
||||
meters = search_meter(module, name, apiid, apikey)
|
||||
|
||||
if len(meters) > 0:
|
||||
return meters[0]['id']
|
||||
else:
|
||||
return None
|
||||
|
||||
def delete_meter(module, name, apiid, apikey):
|
||||
|
||||
meter_id = get_meter_id(module, name, apiid, apikey)
|
||||
|
||||
if meter_id is None:
|
||||
return 1, "Meter does not exist, so can't delete it"
|
||||
else:
|
||||
response, info = http_request(module, name, apiid, apikey, action, meter_id)
|
||||
if info['status'] != 200:
|
||||
module.fail_json("Failed to delete meter")
|
||||
|
||||
# Each new meter gets a new key.pem and ca.pem file, so they should be deleted
|
||||
types = ['cert', 'key']
|
||||
for cert_type in types:
|
||||
try:
|
||||
cert_file = '%s/%s.pem' % (config_directory,cert_type)
|
||||
os.remove(cert_file)
|
||||
except OSError, e:
|
||||
module.fail_json("Failed to remove " + cert_type + ".pem file")
|
||||
|
||||
return 0, "Meter " + name + " deleted"
|
||||
|
||||
def download_request(module, name, apiid, apikey, cert_type):
|
||||
|
||||
meter_id = get_meter_id(module, name, apiid, apikey)
|
||||
|
||||
if meter_id is not None:
|
||||
action = "certificates"
|
||||
response, info = http_request(module, name, apiid, apikey, action, meter_id, cert_type)
|
||||
if info['status'] != 200:
|
||||
module.fail_json("Failed to connect to api host to download certificate")
|
||||
|
||||
if result:
|
||||
try:
|
||||
cert_file_path = '%s/%s.pem' % (config_directory,cert_type)
|
||||
body = response.read()
|
||||
cert_file = open(cert_file_path, 'w')
|
||||
cert_file.write(body)
|
||||
cert_file.close
|
||||
os.chmod(cert_file_path, 0o600)
|
||||
except:
|
||||
module.fail_json("Could not write to certificate file")
|
||||
|
||||
return True
|
||||
else:
|
||||
module.fail_json("Could not get meter id")
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(required=True, choices=['present', 'absent']),
|
||||
name=dict(required=False),
|
||||
apikey=dict(required=True),
|
||||
apiid=dict(required=True),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
)
|
||||
)
|
||||
|
||||
state = module.params['state']
|
||||
name= module.params['name']
|
||||
apikey = module.params['api_key']
|
||||
apiid = module.params['api_id']
|
||||
|
||||
if state == "present":
|
||||
(rc, result) = create_meter(module, name, apiid, apikey)
|
||||
|
||||
if state == "absent":
|
||||
(rc, result) = delete_meter(module, name, apiid, apikey)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg=result)
|
||||
|
||||
module.exit_json(status=result,changed=True)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
main()
|
||||
|
@ -1,143 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Author: Artūras 'arturaz' Šlajus <x11@arturaz.net>
|
||||
#
|
||||
# This module is proudly sponsored by iGeolise (www.igeolise.com) and
|
||||
# Tiny Lab Productions (www.tinylabproductions.com).
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: datadog_event
|
||||
short_description: Posts events to DataDog service
|
||||
description:
|
||||
- "Allows to post events to DataDog (www.datadoghq.com) service."
|
||||
- "Uses http://docs.datadoghq.com/api/#events API."
|
||||
version_added: "1.3"
|
||||
author: Artūras 'arturaz' Šlajus <x11@arturaz.net>
|
||||
notes: []
|
||||
requirements: [urllib2]
|
||||
options:
|
||||
api_key:
|
||||
description: ["Your DataDog API key."]
|
||||
required: true
|
||||
default: null
|
||||
title:
|
||||
description: ["The event title."]
|
||||
required: true
|
||||
default: null
|
||||
text:
|
||||
description: ["The body of the event."]
|
||||
required: true
|
||||
default: null
|
||||
date_happened:
|
||||
description:
|
||||
- POSIX timestamp of the event.
|
||||
- Default value is now.
|
||||
required: false
|
||||
default: now
|
||||
priority:
|
||||
description: ["The priority of the event."]
|
||||
required: false
|
||||
default: normal
|
||||
choices: [normal, low]
|
||||
tags:
|
||||
description: ["Comma separated list of tags to apply to the event."]
|
||||
required: false
|
||||
default: null
|
||||
alert_type:
|
||||
description: ["Type of alert."]
|
||||
required: false
|
||||
default: info
|
||||
choices: ['error', 'warning', 'info', 'success']
|
||||
aggregation_key:
|
||||
description: ["An arbitrary string to use for aggregation."]
|
||||
required: false
|
||||
default: null
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Post an event with low priority
|
||||
datadog_event: title="Testing from ansible" text="Test!" priority="low"
|
||||
api_key="6873258723457823548234234234"
|
||||
# Post an event with several tags
|
||||
datadog_event: title="Testing from ansible" text="Test!"
|
||||
api_key="6873258723457823548234234234"
|
||||
tags=aa,bb,cc
|
||||
'''
|
||||
|
||||
import socket
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
api_key=dict(required=True),
|
||||
title=dict(required=True),
|
||||
text=dict(required=True),
|
||||
date_happened=dict(required=False, default=None, type='int'),
|
||||
priority=dict(
|
||||
required=False, default='normal', choices=['normal', 'low']
|
||||
),
|
||||
tags=dict(required=False, default=None),
|
||||
alert_type=dict(
|
||||
required=False, default='info',
|
||||
choices=['error', 'warning', 'info', 'success']
|
||||
),
|
||||
aggregation_key=dict(required=False, default=None),
|
||||
source_type_name=dict(
|
||||
required=False, default='my apps',
|
||||
choices=['nagios', 'hudson', 'jenkins', 'user', 'my apps',
|
||||
'feed', 'chef', 'puppet', 'git', 'bitbucket', 'fabric',
|
||||
'capistrano']
|
||||
),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
)
|
||||
)
|
||||
|
||||
post_event(module)
|
||||
|
||||
def post_event(module):
|
||||
uri = "https://app.datadoghq.com/api/v1/events?api_key=%s" % module.params['api_key']
|
||||
|
||||
body = dict(
|
||||
title=module.params['title'],
|
||||
text=module.params['text'],
|
||||
priority=module.params['priority'],
|
||||
alert_type=module.params['alert_type']
|
||||
)
|
||||
if module.params['date_happened'] != None:
|
||||
body['date_happened'] = module.params['date_happened']
|
||||
if module.params['tags'] != None:
|
||||
body['tags'] = module.params['tags'].split(",")
|
||||
if module.params['aggregation_key'] != None:
|
||||
body['aggregation_key'] = module.params['aggregation_key']
|
||||
if module.params['source_type_name'] != None:
|
||||
body['source_type_name'] = module.params['source_type_name']
|
||||
|
||||
json_body = module.jsonify(body)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
(response, info) = fetch_url(module, uri, data=json_body, headers=headers)
|
||||
if info['status'] == 200:
|
||||
response_body = response.read()
|
||||
response_json = module.from_json(response_body)
|
||||
if response_json['status'] == 'ok':
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.fail_json(msg=response)
|
||||
else:
|
||||
module.fail_json(**info)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,169 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (C) Seth Edwards, 2014
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
import base64
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: librato_annotation
|
||||
short_description: create an annotation in librato
|
||||
description:
|
||||
- Create an annotation event on the given annotation stream :name. If the annotation stream does not exist, it will be created automatically
|
||||
version_added: "1.6"
|
||||
author: Seth Edwards
|
||||
requirements:
|
||||
- urllib2
|
||||
- base64
|
||||
options:
|
||||
user:
|
||||
description:
|
||||
- Librato account username
|
||||
required: true
|
||||
api_key:
|
||||
description:
|
||||
- Librato account api key
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- The annotation stream name
|
||||
- If the annotation stream does not exist, it will be created automatically
|
||||
required: false
|
||||
title:
|
||||
description:
|
||||
- The title of an annotation is a string and may contain spaces
|
||||
- The title should be a short, high-level summary of the annotation e.g. v45 Deployment
|
||||
required: true
|
||||
source:
|
||||
description:
|
||||
- A string which describes the originating source of an annotation when that annotation is tracked across multiple members of a population
|
||||
required: false
|
||||
description:
|
||||
description:
|
||||
- The description contains extra meta-data about a particular annotation
|
||||
- The description should contain specifics on the individual annotation e.g. Deployed 9b562b2 shipped new feature foo!
|
||||
required: false
|
||||
start_time:
|
||||
description:
|
||||
- The unix timestamp indicating the the time at which the event referenced by this annotation started
|
||||
required: false
|
||||
end_time:
|
||||
description:
|
||||
- The unix timestamp indicating the the time at which the event referenced by this annotation ended
|
||||
- For events that have a duration, this is a useful way to annotate the duration of the event
|
||||
required: false
|
||||
links:
|
||||
description:
|
||||
- See examples
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a simple annotation event with a source
|
||||
- librato_annotation:
|
||||
user: user@example.com
|
||||
api_key: XXXXXXXXXXXXXXXXX
|
||||
title: 'App Config Change'
|
||||
source: 'foo.bar'
|
||||
description: 'This is a detailed description of the config change'
|
||||
|
||||
# Create an annotation that includes a link
|
||||
- librato_annotation:
|
||||
user: user@example.com
|
||||
api_key: XXXXXXXXXXXXXXXXXX
|
||||
name: 'code.deploy'
|
||||
title: 'app code deploy'
|
||||
description: 'this is a detailed description of a deployment'
|
||||
links:
|
||||
- { rel: 'example', href: 'http://www.example.com/deploy' }
|
||||
|
||||
# Create an annotation with a start_time and end_time
|
||||
- librato_annotation:
|
||||
user: user@example.com
|
||||
api_key: XXXXXXXXXXXXXXXXXX
|
||||
name: 'maintenance'
|
||||
title: 'Maintenance window'
|
||||
description: 'This is a detailed description of maintenance'
|
||||
start_time: 1395940006
|
||||
end_time: 1395954406
|
||||
'''
|
||||
|
||||
|
||||
try:
|
||||
import urllib2
|
||||
HAS_URLLIB2 = True
|
||||
except ImportError:
|
||||
HAS_URLLIB2 = False
|
||||
|
||||
def post_annotation(module):
|
||||
user = module.params['user']
|
||||
api_key = module.params['api_key']
|
||||
name = module.params['name']
|
||||
title = module.params['title']
|
||||
|
||||
url = 'https://metrics-api.librato.com/v1/annotations/%s' % name
|
||||
params = {}
|
||||
params['title'] = title
|
||||
|
||||
if module.params['source'] != None:
|
||||
params['source'] = module.params['source']
|
||||
if module.params['description'] != None:
|
||||
params['description'] = module.params['description']
|
||||
if module.params['start_time'] != None:
|
||||
params['start_time'] = module.params['start_time']
|
||||
if module.params['end_time'] != None:
|
||||
params['end_time'] = module.params['end_time']
|
||||
if module.params['links'] != None:
|
||||
params['links'] = module.params['links']
|
||||
|
||||
json_body = module.jsonify(params)
|
||||
|
||||
headers = {}
|
||||
headers['Content-Type'] = 'application/json'
|
||||
headers['Authorization'] = b"Basic " + base64.b64encode(user + b":" + api_key).strip()
|
||||
req = urllib2.Request(url, json_body, headers)
|
||||
try:
|
||||
response = urllib2.urlopen(req)
|
||||
except urllib2.HTTPError as e:
|
||||
module.fail_json(msg="Request Failed", reason=e.reason)
|
||||
response = response.read()
|
||||
module.exit_json(changed=True, annotation=response)
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
user = dict(required=True),
|
||||
api_key = dict(required=True),
|
||||
name = dict(required=False),
|
||||
title = dict(required=True),
|
||||
source = dict(required=False),
|
||||
description = dict(required=False),
|
||||
start_time = dict(required=False, default=None, type='int'),
|
||||
end_time = dict(require=False, default=None, type='int'),
|
||||
links = dict(type='list')
|
||||
)
|
||||
)
|
||||
|
||||
post_annotation(module)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,130 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Ivan Vanderbyl <ivan@app.io>
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: logentries
|
||||
author: Ivan Vanderbyl
|
||||
short_description: Module for tracking logs via logentries.com
|
||||
description:
|
||||
- Sends logs to LogEntries in realtime
|
||||
version_added: "1.6"
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- path to a log file
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- following state of the log
|
||||
choices: [ 'present', 'absent' ]
|
||||
required: false
|
||||
default: present
|
||||
notes:
|
||||
- Requires the LogEntries agent which can be installed following the instructions at logentries.com
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
- logentries: path=/var/log/nginx/access.log state=present
|
||||
- logentries: path=/var/log/nginx/error.log state=absent
|
||||
'''
|
||||
|
||||
def query_log_status(module, le_path, path, state="present"):
|
||||
""" Returns whether a log is followed or not. """
|
||||
|
||||
if state == "present":
|
||||
rc, out, err = module.run_command("%s followed %s" % (le_path, path))
|
||||
if rc == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def follow_log(module, le_path, logs):
|
||||
""" Follows one or more logs if not already followed. """
|
||||
|
||||
followed_count = 0
|
||||
|
||||
for log in logs:
|
||||
if query_log_status(module, le_path, log):
|
||||
continue
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
rc, out, err = module.run_command([le_path, 'follow', log])
|
||||
|
||||
if not query_log_status(module, le_path, log):
|
||||
module.fail_json(msg="failed to follow '%s': %s" % (log, err.strip()))
|
||||
|
||||
followed_count += 1
|
||||
|
||||
if followed_count > 0:
|
||||
module.exit_json(changed=True, msg="followed %d log(s)" % (followed_count,))
|
||||
|
||||
module.exit_json(changed=False, msg="logs(s) already followed")
|
||||
|
||||
def unfollow_log(module, le_path, logs):
|
||||
""" Unfollows one or more logs if followed. """
|
||||
|
||||
removed_count = 0
|
||||
|
||||
# Using a for loop incase of error, we can report the package that failed
|
||||
for log in logs:
|
||||
# Query the log first, to see if we even need to remove.
|
||||
if not query_log_status(module, le_path, log):
|
||||
continue
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
rc, out, err = module.run_command([le_path, 'rm', log])
|
||||
|
||||
if query_log_status(module, le_path, log):
|
||||
module.fail_json(msg="failed to remove '%s': %s" % (log, err.strip()))
|
||||
|
||||
removed_count += 1
|
||||
|
||||
if removed_count > 0:
|
||||
module.exit_json(changed=True, msg="removed %d package(s)" % removed_count)
|
||||
|
||||
module.exit_json(changed=False, msg="logs(s) already unfollowed")
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
path = dict(aliases=["name"], required=True),
|
||||
state = dict(default="present", choices=["present", "followed", "absent", "unfollowed"])
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
le_path = module.get_bin_path('le', True, ['/usr/local/bin'])
|
||||
|
||||
p = module.params
|
||||
|
||||
# Handle multiple log files
|
||||
logs = p["path"].split(",")
|
||||
logs = filter(None, logs)
|
||||
|
||||
if p["state"] in ["present", "followed"]:
|
||||
follow_log(module, le_path, logs)
|
||||
|
||||
elif p["state"] in ["absent", "unfollowed"]:
|
||||
unfollow_log(module, le_path, logs)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -1,155 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Darryl Stoflet <stoflet@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: monit
|
||||
short_description: Manage the state of a program monitored via Monit
|
||||
description:
|
||||
- Manage the state of a program monitored via I(Monit)
|
||||
version_added: "1.2"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the I(monit) program/process to manage
|
||||
required: true
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- The state of service
|
||||
required: true
|
||||
default: null
|
||||
choices: [ "present", "started", "stopped", "restarted", "monitored", "unmonitored", "reloaded" ]
|
||||
requirements: [ ]
|
||||
author: Darryl Stoflet
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Manage the state of program "httpd" to be in "started" state.
|
||||
- monit: name=httpd state=started
|
||||
'''
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
name=dict(required=True),
|
||||
state=dict(required=True, choices=['present', 'started', 'restarted', 'stopped', 'monitored', 'unmonitored', 'reloaded'])
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
|
||||
|
||||
name = module.params['name']
|
||||
state = module.params['state']
|
||||
|
||||
MONIT = module.get_bin_path('monit', True)
|
||||
|
||||
if state == 'reloaded':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
rc, out, err = module.run_command('%s reload' % MONIT)
|
||||
if rc != 0:
|
||||
module.fail_json(msg='monit reload failed', stdout=out, stderr=err)
|
||||
module.exit_json(changed=True, name=name, state=state)
|
||||
|
||||
def status():
|
||||
"""Return the status of the process in monit, or the empty string if not present."""
|
||||
rc, out, err = module.run_command('%s summary' % MONIT, check_rc=True)
|
||||
for line in out.split('\n'):
|
||||
# Sample output lines:
|
||||
# Process 'name' Running
|
||||
# Process 'name' Running - restart pending
|
||||
parts = line.lower().split()
|
||||
if len(parts) > 2 and parts[0] == 'process' and parts[1] == "'%s'" % name:
|
||||
return ' '.join(parts[2:])
|
||||
else:
|
||||
return ''
|
||||
|
||||
def run_command(command):
|
||||
"""Runs a monit command, and returns the new status."""
|
||||
module.run_command('%s %s %s' % (MONIT, command, name), check_rc=True)
|
||||
return status()
|
||||
|
||||
present = status() != ''
|
||||
|
||||
if not present and not state == 'present':
|
||||
module.fail_json(msg='%s process not presently configured with monit' % name, name=name, state=state)
|
||||
|
||||
if state == 'present':
|
||||
if not present:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
status = run_command('reload')
|
||||
if status == '':
|
||||
module.fail_json(msg='%s process not configured with monit' % name, name=name, state=state)
|
||||
else:
|
||||
module.exit_json(changed=True, name=name, state=state)
|
||||
module.exit_json(changed=False, name=name, state=state)
|
||||
|
||||
running = 'running' in status()
|
||||
|
||||
if running and state in ['started', 'monitored']:
|
||||
module.exit_json(changed=False, name=name, state=state)
|
||||
|
||||
if running and state == 'stopped':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
status = run_command('stop')
|
||||
if status in ['not monitored'] or 'stop pending' in status:
|
||||
module.exit_json(changed=True, name=name, state=state)
|
||||
module.fail_json(msg='%s process not stopped' % name, status=status)
|
||||
|
||||
if running and state == 'unmonitored':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
status = run_command('unmonitor')
|
||||
if status in ['not monitored']:
|
||||
module.exit_json(changed=True, name=name, state=state)
|
||||
module.fail_json(msg='%s process not unmonitored' % name, status=status)
|
||||
|
||||
elif state == 'restarted':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
status = run_command('restart')
|
||||
if status in ['initializing', 'running'] or 'restart pending' in status:
|
||||
module.exit_json(changed=True, name=name, state=state)
|
||||
module.fail_json(msg='%s process not restarted' % name, status=status)
|
||||
|
||||
elif not running and state == 'started':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
status = run_command('start')
|
||||
if status in ['initializing', 'running'] or 'start pending' in status:
|
||||
module.exit_json(changed=True, name=name, state=state)
|
||||
module.fail_json(msg='%s process not started' % name, status=status)
|
||||
|
||||
elif not running and state == 'monitored':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
status = run_command('monitor')
|
||||
if status not in ['not monitored']:
|
||||
module.exit_json(changed=True, name=name, state=state)
|
||||
module.fail_json(msg='%s process not monitored' % name, status=status)
|
||||
|
||||
module.exit_json(changed=False, name=name, state=state)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -1,880 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is largely copied from the Nagios module included in the
|
||||
# Func project. Original copyright follows:
|
||||
#
|
||||
# func-nagios - Schedule downtime and enables/disable notifications
|
||||
# Copyright 2011, Red Hat, Inc.
|
||||
# Tim Bielawa <tbielawa@redhat.com>
|
||||
#
|
||||
# This software may be freely redistributed under the terms of the GNU
|
||||
# general public license version 2.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: nagios
|
||||
short_description: Perform common tasks in Nagios related to downtime and notifications.
|
||||
description:
|
||||
- "The M(nagios) module has two basic functions: scheduling downtime and toggling alerts for services or hosts."
|
||||
- All actions require the I(host) parameter to be given explicitly. In playbooks you can use the C({{inventory_hostname}}) variable to refer to the host the playbook is currently running on.
|
||||
- You can specify multiple services at once by separating them with commas, .e.g., C(services=httpd,nfs,puppet).
|
||||
- When specifying what service to handle there is a special service value, I(host), which will handle alerts/downtime for the I(host itself), e.g., C(service=host). This keyword may not be given with other services at the same time. I(Setting alerts/downtime for a host does not affect alerts/downtime for any of the services running on it.) To schedule downtime for all services on particular host use keyword "all", e.g., C(service=all).
|
||||
- When using the M(nagios) module you will need to specify your Nagios server using the C(delegate_to) parameter.
|
||||
version_added: "0.7"
|
||||
options:
|
||||
action:
|
||||
description:
|
||||
- Action to take.
|
||||
required: true
|
||||
default: null
|
||||
choices: [ "downtime", "enable_alerts", "disable_alerts", "silence", "unsilence",
|
||||
"silence_nagios", "unsilence_nagios", "command" ]
|
||||
host:
|
||||
description:
|
||||
- Host to operate on in Nagios.
|
||||
required: false
|
||||
default: null
|
||||
cmdfile:
|
||||
description:
|
||||
- Path to the nagios I(command file) (FIFO pipe).
|
||||
Only required if auto-detection fails.
|
||||
required: false
|
||||
default: auto-detected
|
||||
author:
|
||||
description:
|
||||
- Author to leave downtime comments as.
|
||||
Only usable with the C(downtime) action.
|
||||
required: false
|
||||
default: Ansible
|
||||
minutes:
|
||||
description:
|
||||
- Minutes to schedule downtime for.
|
||||
- Only usable with the C(downtime) action.
|
||||
required: false
|
||||
default: 30
|
||||
services:
|
||||
description:
|
||||
- What to manage downtime/alerts for. Separate multiple services with commas.
|
||||
C(service) is an alias for C(services).
|
||||
B(Required) option when using the C(downtime), C(enable_alerts), and C(disable_alerts) actions.
|
||||
aliases: [ "service" ]
|
||||
required: true
|
||||
default: null
|
||||
command:
|
||||
description:
|
||||
- The raw command to send to nagios, which
|
||||
should not include the submitted time header or the line-feed
|
||||
B(Required) option when using the C(command) action.
|
||||
required: true
|
||||
default: null
|
||||
|
||||
author: Tim Bielawa
|
||||
requirements: [ "Nagios" ]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# set 30 minutes of apache downtime
|
||||
- nagios: action=downtime minutes=30 service=httpd host={{ inventory_hostname }}
|
||||
|
||||
# schedule an hour of HOST downtime
|
||||
- nagios: action=downtime minutes=60 service=host host={{ inventory_hostname }}
|
||||
|
||||
# schedule downtime for ALL services on HOST
|
||||
- nagios: action=downtime minutes=45 service=all host={{ inventory_hostname }}
|
||||
|
||||
# schedule downtime for a few services
|
||||
- nagios: action=downtime services=frob,foobar,qeuz host={{ inventory_hostname }}
|
||||
|
||||
# enable SMART disk alerts
|
||||
- nagios: action=enable_alerts service=smart host={{ inventory_hostname }}
|
||||
|
||||
# "two services at once: disable httpd and nfs alerts"
|
||||
- nagios: action=disable_alerts service=httpd,nfs host={{ inventory_hostname }}
|
||||
|
||||
# disable HOST alerts
|
||||
- nagios: action=disable_alerts service=host host={{ inventory_hostname }}
|
||||
|
||||
# silence ALL alerts
|
||||
- nagios: action=silence host={{ inventory_hostname }}
|
||||
|
||||
# unsilence all alerts
|
||||
- nagios: action=unsilence host={{ inventory_hostname }}
|
||||
|
||||
# SHUT UP NAGIOS
|
||||
- nagios: action=silence_nagios
|
||||
|
||||
# ANNOY ME NAGIOS
|
||||
- nagios: action=unsilence_nagios
|
||||
|
||||
# command something
|
||||
- nagios: action=command command='DISABLE_FAILURE_PREDICTION'
|
||||
'''
|
||||
|
||||
import ConfigParser
|
||||
import types
|
||||
import time
|
||||
import os.path
|
||||
|
||||
######################################################################
|
||||
|
||||
|
||||
def which_cmdfile():
|
||||
locations = [
|
||||
# rhel
|
||||
'/etc/nagios/nagios.cfg',
|
||||
# debian
|
||||
'/etc/nagios3/nagios.cfg',
|
||||
# older debian
|
||||
'/etc/nagios2/nagios.cfg',
|
||||
# bsd, solaris
|
||||
'/usr/local/etc/nagios/nagios.cfg',
|
||||
# groundwork it monitoring
|
||||
'/usr/local/groundwork/nagios/etc/nagios.cfg',
|
||||
# open monitoring distribution
|
||||
'/omd/sites/oppy/tmp/nagios/nagios.cfg',
|
||||
# ???
|
||||
'/usr/local/nagios/etc/nagios.cfg',
|
||||
'/usr/local/nagios/nagios.cfg',
|
||||
'/opt/nagios/etc/nagios.cfg',
|
||||
'/opt/nagios/nagios.cfg',
|
||||
# icinga on debian/ubuntu
|
||||
'/etc/icinga/icinga.cfg',
|
||||
# icinga installed from source (default location)
|
||||
'/usr/local/icinga/etc/icinga.cfg',
|
||||
]
|
||||
|
||||
for path in locations:
|
||||
if os.path.exists(path):
|
||||
for line in open(path):
|
||||
if line.startswith('command_file'):
|
||||
return line.split('=')[1].strip()
|
||||
|
||||
return None
|
||||
|
||||
######################################################################
|
||||
|
||||
|
||||
def main():
|
||||
ACTION_CHOICES = [
|
||||
'downtime',
|
||||
'silence',
|
||||
'unsilence',
|
||||
'enable_alerts',
|
||||
'disable_alerts',
|
||||
'silence_nagios',
|
||||
'unsilence_nagios',
|
||||
'command',
|
||||
]
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
action=dict(required=True, default=None, choices=ACTION_CHOICES),
|
||||
author=dict(default='Ansible'),
|
||||
host=dict(required=False, default=None),
|
||||
minutes=dict(default=30),
|
||||
cmdfile=dict(default=which_cmdfile()),
|
||||
services=dict(default=None, aliases=['service']),
|
||||
command=dict(required=False, default=None),
|
||||
)
|
||||
)
|
||||
|
||||
action = module.params['action']
|
||||
host = module.params['host']
|
||||
minutes = module.params['minutes']
|
||||
services = module.params['services']
|
||||
cmdfile = module.params['cmdfile']
|
||||
command = module.params['command']
|
||||
|
||||
##################################################################
|
||||
# Required args per action:
|
||||
# downtime = (minutes, service, host)
|
||||
# (un)silence = (host)
|
||||
# (enable/disable)_alerts = (service, host)
|
||||
# command = command
|
||||
#
|
||||
# AnsibleModule will verify most stuff, we need to verify
|
||||
# 'minutes' and 'service' manually.
|
||||
|
||||
##################################################################
|
||||
if action not in ['command', 'silence_nagios', 'unsilence_nagios']:
|
||||
if not host:
|
||||
module.fail_json(msg='no host specified for action requiring one')
|
||||
######################################################################
|
||||
if action == 'downtime':
|
||||
# Make sure there's an actual service selected
|
||||
if not services:
|
||||
module.fail_json(msg='no service selected to set downtime for')
|
||||
# Make sure minutes is a number
|
||||
try:
|
||||
m = int(minutes)
|
||||
if not isinstance(m, types.IntType):
|
||||
module.fail_json(msg='minutes must be a number')
|
||||
except Exception:
|
||||
module.fail_json(msg='invalid entry for minutes')
|
||||
|
||||
##################################################################
|
||||
if action in ['enable_alerts', 'disable_alerts']:
|
||||
if not services:
|
||||
module.fail_json(msg='a service is required when setting alerts')
|
||||
|
||||
if action in ['command']:
|
||||
if not command:
|
||||
module.fail_json(msg='no command passed for command action')
|
||||
##################################################################
|
||||
if not cmdfile:
|
||||
module.fail_json('unable to locate nagios.cfg')
|
||||
|
||||
##################################################################
|
||||
ansible_nagios = Nagios(module, **module.params)
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
ansible_nagios.act()
|
||||
##################################################################
|
||||
|
||||
|
||||
######################################################################
|
||||
class Nagios(object):
|
||||
"""
|
||||
Perform common tasks in Nagios related to downtime and
|
||||
notifications.
|
||||
|
||||
The complete set of external commands Nagios handles is documented
|
||||
on their website:
|
||||
|
||||
http://old.nagios.org/developerinfo/externalcommands/commandlist.php
|
||||
|
||||
Note that in the case of `schedule_svc_downtime`,
|
||||
`enable_svc_notifications`, and `disable_svc_notifications`, the
|
||||
service argument should be passed as a list.
|
||||
"""
|
||||
|
||||
def __init__(self, module, **kwargs):
|
||||
self.module = module
|
||||
self.action = kwargs['action']
|
||||
self.author = kwargs['author']
|
||||
self.host = kwargs['host']
|
||||
self.minutes = int(kwargs['minutes'])
|
||||
self.cmdfile = kwargs['cmdfile']
|
||||
self.command = kwargs['command']
|
||||
|
||||
if (kwargs['services'] is None) or (kwargs['services'] == 'host') or (kwargs['services'] == 'all'):
|
||||
self.services = kwargs['services']
|
||||
else:
|
||||
self.services = kwargs['services'].split(',')
|
||||
|
||||
self.command_results = []
|
||||
|
||||
def _now(self):
|
||||
"""
|
||||
The time in seconds since 12:00:00AM Jan 1, 1970
|
||||
"""
|
||||
|
||||
return int(time.time())
|
||||
|
||||
def _write_command(self, cmd):
|
||||
"""
|
||||
Write the given command to the Nagios command file
|
||||
"""
|
||||
|
||||
try:
|
||||
fp = open(self.cmdfile, 'w')
|
||||
fp.write(cmd)
|
||||
fp.flush()
|
||||
fp.close()
|
||||
self.command_results.append(cmd.strip())
|
||||
except IOError:
|
||||
self.module.fail_json(msg='unable to write to nagios command file',
|
||||
cmdfile=self.cmdfile)
|
||||
|
||||
def _fmt_dt_str(self, cmd, host, duration, author=None,
|
||||
comment="Scheduling downtime", start=None,
|
||||
svc=None, fixed=1, trigger=0):
|
||||
"""
|
||||
Format an external-command downtime string.
|
||||
|
||||
cmd - Nagios command ID
|
||||
host - Host schedule downtime on
|
||||
duration - Minutes to schedule downtime for
|
||||
author - Name to file the downtime as
|
||||
comment - Reason for running this command (upgrade, reboot, etc)
|
||||
start - Start of downtime in seconds since 12:00AM Jan 1 1970
|
||||
Default is to use the entry time (now)
|
||||
svc - Service to schedule downtime for, omit when for host downtime
|
||||
fixed - Start now if 1, start when a problem is detected if 0
|
||||
trigger - Optional ID of event to start downtime from. Leave as 0 for
|
||||
fixed downtime.
|
||||
|
||||
Syntax: [submitted] COMMAND;<host_name>;[<service_description>]
|
||||
<start_time>;<end_time>;<fixed>;<trigger_id>;<duration>;<author>;
|
||||
<comment>
|
||||
"""
|
||||
|
||||
entry_time = self._now()
|
||||
if start is None:
|
||||
start = entry_time
|
||||
|
||||
hdr = "[%s] %s;%s;" % (entry_time, cmd, host)
|
||||
duration_s = (duration * 60)
|
||||
end = start + duration_s
|
||||
|
||||
if not author:
|
||||
author = self.author
|
||||
|
||||
if svc is not None:
|
||||
dt_args = [svc, str(start), str(end), str(fixed), str(trigger),
|
||||
str(duration_s), author, comment]
|
||||
else:
|
||||
# Downtime for a host if no svc specified
|
||||
dt_args = [str(start), str(end), str(fixed), str(trigger),
|
||||
str(duration_s), author, comment]
|
||||
|
||||
dt_arg_str = ";".join(dt_args)
|
||||
dt_str = hdr + dt_arg_str + "\n"
|
||||
|
||||
return dt_str
|
||||
|
||||
def _fmt_notif_str(self, cmd, host=None, svc=None):
|
||||
"""
|
||||
Format an external-command notification string.
|
||||
|
||||
cmd - Nagios command ID.
|
||||
host - Host to en/disable notifications on.. A value is not required
|
||||
for global downtime
|
||||
svc - Service to schedule downtime for. A value is not required
|
||||
for host downtime.
|
||||
|
||||
Syntax: [submitted] COMMAND;<host_name>[;<service_description>]
|
||||
"""
|
||||
|
||||
entry_time = self._now()
|
||||
notif_str = "[%s] %s" % (entry_time, cmd)
|
||||
if host is not None:
|
||||
notif_str += ";%s" % host
|
||||
|
||||
if svc is not None:
|
||||
notif_str += ";%s" % svc
|
||||
|
||||
notif_str += "\n"
|
||||
|
||||
return notif_str
|
||||
|
||||
def schedule_svc_downtime(self, host, services=[], minutes=30):
|
||||
"""
|
||||
This command is used to schedule downtime for a particular
|
||||
service.
|
||||
|
||||
During the specified downtime, Nagios will not send
|
||||
notifications out about the service.
|
||||
|
||||
Syntax: SCHEDULE_SVC_DOWNTIME;<host_name>;<service_description>
|
||||
<start_time>;<end_time>;<fixed>;<trigger_id>;<duration>;<author>;
|
||||
<comment>
|
||||
"""
|
||||
|
||||
cmd = "SCHEDULE_SVC_DOWNTIME"
|
||||
for service in services:
|
||||
dt_cmd_str = self._fmt_dt_str(cmd, host, minutes, svc=service)
|
||||
self._write_command(dt_cmd_str)
|
||||
|
||||
def schedule_host_downtime(self, host, minutes=30):
|
||||
"""
|
||||
This command is used to schedule downtime for a particular
|
||||
host.
|
||||
|
||||
During the specified downtime, Nagios will not send
|
||||
notifications out about the host.
|
||||
|
||||
Syntax: SCHEDULE_HOST_DOWNTIME;<host_name>;<start_time>;<end_time>;
|
||||
<fixed>;<trigger_id>;<duration>;<author>;<comment>
|
||||
"""
|
||||
|
||||
cmd = "SCHEDULE_HOST_DOWNTIME"
|
||||
dt_cmd_str = self._fmt_dt_str(cmd, host, minutes)
|
||||
self._write_command(dt_cmd_str)
|
||||
|
||||
def schedule_host_svc_downtime(self, host, minutes=30):
|
||||
"""
|
||||
This command is used to schedule downtime for
|
||||
all services associated with a particular host.
|
||||
|
||||
During the specified downtime, Nagios will not send
|
||||
notifications out about the host.
|
||||
|
||||
SCHEDULE_HOST_SVC_DOWNTIME;<host_name>;<start_time>;<end_time>;
|
||||
<fixed>;<trigger_id>;<duration>;<author>;<comment>
|
||||
"""
|
||||
|
||||
cmd = "SCHEDULE_HOST_SVC_DOWNTIME"
|
||||
dt_cmd_str = self._fmt_dt_str(cmd, host, minutes)
|
||||
self._write_command(dt_cmd_str)
|
||||
|
||||
def schedule_hostgroup_host_downtime(self, hostgroup, minutes=30):
|
||||
"""
|
||||
This command is used to schedule downtime for all hosts in a
|
||||
particular hostgroup.
|
||||
|
||||
During the specified downtime, Nagios will not send
|
||||
notifications out about the hosts.
|
||||
|
||||
Syntax: SCHEDULE_HOSTGROUP_HOST_DOWNTIME;<hostgroup_name>;<start_time>;
|
||||
<end_time>;<fixed>;<trigger_id>;<duration>;<author>;<comment>
|
||||
"""
|
||||
|
||||
cmd = "SCHEDULE_HOSTGROUP_HOST_DOWNTIME"
|
||||
dt_cmd_str = self._fmt_dt_str(cmd, hostgroup, minutes)
|
||||
self._write_command(dt_cmd_str)
|
||||
|
||||
def schedule_hostgroup_svc_downtime(self, hostgroup, minutes=30):
|
||||
"""
|
||||
This command is used to schedule downtime for all services in
|
||||
a particular hostgroup.
|
||||
|
||||
During the specified downtime, Nagios will not send
|
||||
notifications out about the services.
|
||||
|
||||
Note that scheduling downtime for services does not
|
||||
automatically schedule downtime for the hosts those services
|
||||
are associated with.
|
||||
|
||||
Syntax: SCHEDULE_HOSTGROUP_SVC_DOWNTIME;<hostgroup_name>;<start_time>;
|
||||
<end_time>;<fixed>;<trigger_id>;<duration>;<author>;<comment>
|
||||
"""
|
||||
|
||||
cmd = "SCHEDULE_HOSTGROUP_SVC_DOWNTIME"
|
||||
dt_cmd_str = self._fmt_dt_str(cmd, hostgroup, minutes)
|
||||
self._write_command(dt_cmd_str)
|
||||
|
||||
def schedule_servicegroup_host_downtime(self, servicegroup, minutes=30):
|
||||
"""
|
||||
This command is used to schedule downtime for all hosts in a
|
||||
particular servicegroup.
|
||||
|
||||
During the specified downtime, Nagios will not send
|
||||
notifications out about the hosts.
|
||||
|
||||
Syntax: SCHEDULE_SERVICEGROUP_HOST_DOWNTIME;<servicegroup_name>;
|
||||
<start_time>;<end_time>;<fixed>;<trigger_id>;<duration>;<author>;
|
||||
<comment>
|
||||
"""
|
||||
|
||||
cmd = "SCHEDULE_SERVICEGROUP_HOST_DOWNTIME"
|
||||
dt_cmd_str = self._fmt_dt_str(cmd, servicegroup, minutes)
|
||||
self._write_command(dt_cmd_str)
|
||||
|
||||
def schedule_servicegroup_svc_downtime(self, servicegroup, minutes=30):
|
||||
"""
|
||||
This command is used to schedule downtime for all services in
|
||||
a particular servicegroup.
|
||||
|
||||
During the specified downtime, Nagios will not send
|
||||
notifications out about the services.
|
||||
|
||||
Note that scheduling downtime for services does not
|
||||
automatically schedule downtime for the hosts those services
|
||||
are associated with.
|
||||
|
||||
Syntax: SCHEDULE_SERVICEGROUP_SVC_DOWNTIME;<servicegroup_name>;
|
||||
<start_time>;<end_time>;<fixed>;<trigger_id>;<duration>;<author>;
|
||||
<comment>
|
||||
"""
|
||||
|
||||
cmd = "SCHEDULE_SERVICEGROUP_SVC_DOWNTIME"
|
||||
dt_cmd_str = self._fmt_dt_str(cmd, servicegroup, minutes)
|
||||
self._write_command(dt_cmd_str)
|
||||
|
||||
def disable_host_svc_notifications(self, host):
|
||||
"""
|
||||
This command is used to prevent notifications from being sent
|
||||
out for all services on the specified host.
|
||||
|
||||
Note that this command does not disable notifications from
|
||||
being sent out about the host.
|
||||
|
||||
Syntax: DISABLE_HOST_SVC_NOTIFICATIONS;<host_name>
|
||||
"""
|
||||
|
||||
cmd = "DISABLE_HOST_SVC_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, host)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def disable_host_notifications(self, host):
|
||||
"""
|
||||
This command is used to prevent notifications from being sent
|
||||
out for the specified host.
|
||||
|
||||
Note that this command does not disable notifications for
|
||||
services associated with this host.
|
||||
|
||||
Syntax: DISABLE_HOST_NOTIFICATIONS;<host_name>
|
||||
"""
|
||||
|
||||
cmd = "DISABLE_HOST_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, host)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def disable_svc_notifications(self, host, services=[]):
|
||||
"""
|
||||
This command is used to prevent notifications from being sent
|
||||
out for the specified service.
|
||||
|
||||
Note that this command does not disable notifications from
|
||||
being sent out about the host.
|
||||
|
||||
Syntax: DISABLE_SVC_NOTIFICATIONS;<host_name>;<service_description>
|
||||
"""
|
||||
|
||||
cmd = "DISABLE_SVC_NOTIFICATIONS"
|
||||
for service in services:
|
||||
notif_str = self._fmt_notif_str(cmd, host, svc=service)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def disable_servicegroup_host_notifications(self, servicegroup):
|
||||
"""
|
||||
This command is used to prevent notifications from being sent
|
||||
out for all hosts in the specified servicegroup.
|
||||
|
||||
Note that this command does not disable notifications for
|
||||
services associated with hosts in this service group.
|
||||
|
||||
Syntax: DISABLE_SERVICEGROUP_HOST_NOTIFICATIONS;<servicegroup_name>
|
||||
"""
|
||||
|
||||
cmd = "DISABLE_SERVICEGROUP_HOST_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, servicegroup)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def disable_servicegroup_svc_notifications(self, servicegroup):
|
||||
"""
|
||||
This command is used to prevent notifications from being sent
|
||||
out for all services in the specified servicegroup.
|
||||
|
||||
Note that this does not prevent notifications from being sent
|
||||
out about the hosts in this servicegroup.
|
||||
|
||||
Syntax: DISABLE_SERVICEGROUP_SVC_NOTIFICATIONS;<servicegroup_name>
|
||||
"""
|
||||
|
||||
cmd = "DISABLE_SERVICEGROUP_SVC_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, servicegroup)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def disable_hostgroup_host_notifications(self, hostgroup):
|
||||
"""
|
||||
Disables notifications for all hosts in a particular
|
||||
hostgroup.
|
||||
|
||||
Note that this does not disable notifications for the services
|
||||
associated with the hosts in the hostgroup - see the
|
||||
DISABLE_HOSTGROUP_SVC_NOTIFICATIONS command for that.
|
||||
|
||||
Syntax: DISABLE_HOSTGROUP_HOST_NOTIFICATIONS;<hostgroup_name>
|
||||
"""
|
||||
|
||||
cmd = "DISABLE_HOSTGROUP_HOST_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, hostgroup)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def disable_hostgroup_svc_notifications(self, hostgroup):
|
||||
"""
|
||||
Disables notifications for all services associated with hosts
|
||||
in a particular hostgroup.
|
||||
|
||||
Note that this does not disable notifications for the hosts in
|
||||
the hostgroup - see the DISABLE_HOSTGROUP_HOST_NOTIFICATIONS
|
||||
command for that.
|
||||
|
||||
Syntax: DISABLE_HOSTGROUP_SVC_NOTIFICATIONS;<hostgroup_name>
|
||||
"""
|
||||
|
||||
cmd = "DISABLE_HOSTGROUP_SVC_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, hostgroup)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def enable_host_notifications(self, host):
|
||||
"""
|
||||
Enables notifications for a particular host.
|
||||
|
||||
Note that this command does not enable notifications for
|
||||
services associated with this host.
|
||||
|
||||
Syntax: ENABLE_HOST_NOTIFICATIONS;<host_name>
|
||||
"""
|
||||
|
||||
cmd = "ENABLE_HOST_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, host)
|
||||
self._write_command(notif_str)
|
||||
|
||||
def enable_host_svc_notifications(self, host):
|
||||
"""
|
||||
Enables notifications for all services on the specified host.
|
||||
|
||||
Note that this does not enable notifications for the host.
|
||||
|
||||
Syntax: ENABLE_HOST_SVC_NOTIFICATIONS;<host_name>
|
||||
"""
|
||||
|
||||
cmd = "ENABLE_HOST_SVC_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, host)
|
||||
nagios_return = self._write_command(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return notif_str
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def enable_svc_notifications(self, host, services=[]):
|
||||
"""
|
||||
Enables notifications for a particular service.
|
||||
|
||||
Note that this does not enable notifications for the host.
|
||||
|
||||
Syntax: ENABLE_SVC_NOTIFICATIONS;<host_name>;<service_description>
|
||||
"""
|
||||
|
||||
cmd = "ENABLE_SVC_NOTIFICATIONS"
|
||||
nagios_return = True
|
||||
return_str_list = []
|
||||
for service in services:
|
||||
notif_str = self._fmt_notif_str(cmd, host, svc=service)
|
||||
nagios_return = self._write_command(notif_str) and nagios_return
|
||||
return_str_list.append(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return return_str_list
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def enable_hostgroup_host_notifications(self, hostgroup):
|
||||
"""
|
||||
Enables notifications for all hosts in a particular hostgroup.
|
||||
|
||||
Note that this command does not enable notifications for
|
||||
services associated with the hosts in this hostgroup.
|
||||
|
||||
Syntax: ENABLE_HOSTGROUP_HOST_NOTIFICATIONS;<hostgroup_name>
|
||||
"""
|
||||
|
||||
cmd = "ENABLE_HOSTGROUP_HOST_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, hostgroup)
|
||||
nagios_return = self._write_command(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return notif_str
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def enable_hostgroup_svc_notifications(self, hostgroup):
|
||||
"""
|
||||
Enables notifications for all services that are associated
|
||||
with hosts in a particular hostgroup.
|
||||
|
||||
Note that this does not enable notifications for the hosts in
|
||||
this hostgroup.
|
||||
|
||||
Syntax: ENABLE_HOSTGROUP_SVC_NOTIFICATIONS;<hostgroup_name>
|
||||
"""
|
||||
|
||||
cmd = "ENABLE_HOSTGROUP_SVC_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, hostgroup)
|
||||
nagios_return = self._write_command(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return notif_str
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def enable_servicegroup_host_notifications(self, servicegroup):
|
||||
"""
|
||||
Enables notifications for all hosts that have services that
|
||||
are members of a particular servicegroup.
|
||||
|
||||
Note that this command does not enable notifications for
|
||||
services associated with the hosts in this servicegroup.
|
||||
|
||||
Syntax: ENABLE_SERVICEGROUP_HOST_NOTIFICATIONS;<servicegroup_name>
|
||||
"""
|
||||
|
||||
cmd = "ENABLE_SERVICEGROUP_HOST_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, servicegroup)
|
||||
nagios_return = self._write_command(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return notif_str
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def enable_servicegroup_svc_notifications(self, servicegroup):
|
||||
"""
|
||||
Enables notifications for all services that are members of a
|
||||
particular servicegroup.
|
||||
|
||||
Note that this does not enable notifications for the hosts in
|
||||
this servicegroup.
|
||||
|
||||
Syntax: ENABLE_SERVICEGROUP_SVC_NOTIFICATIONS;<servicegroup_name>
|
||||
"""
|
||||
|
||||
cmd = "ENABLE_SERVICEGROUP_SVC_NOTIFICATIONS"
|
||||
notif_str = self._fmt_notif_str(cmd, servicegroup)
|
||||
nagios_return = self._write_command(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return notif_str
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def silence_host(self, host):
|
||||
"""
|
||||
This command is used to prevent notifications from being sent
|
||||
out for the host and all services on the specified host.
|
||||
|
||||
This is equivalent to calling disable_host_svc_notifications
|
||||
and disable_host_notifications.
|
||||
|
||||
Syntax: DISABLE_HOST_SVC_NOTIFICATIONS;<host_name>
|
||||
Syntax: DISABLE_HOST_NOTIFICATIONS;<host_name>
|
||||
"""
|
||||
|
||||
cmd = [
|
||||
"DISABLE_HOST_SVC_NOTIFICATIONS",
|
||||
"DISABLE_HOST_NOTIFICATIONS"
|
||||
]
|
||||
nagios_return = True
|
||||
return_str_list = []
|
||||
for c in cmd:
|
||||
notif_str = self._fmt_notif_str(c, host)
|
||||
nagios_return = self._write_command(notif_str) and nagios_return
|
||||
return_str_list.append(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return return_str_list
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def unsilence_host(self, host):
|
||||
"""
|
||||
This command is used to enable notifications for the host and
|
||||
all services on the specified host.
|
||||
|
||||
This is equivalent to calling enable_host_svc_notifications
|
||||
and enable_host_notifications.
|
||||
|
||||
Syntax: ENABLE_HOST_SVC_NOTIFICATIONS;<host_name>
|
||||
Syntax: ENABLE_HOST_NOTIFICATIONS;<host_name>
|
||||
"""
|
||||
|
||||
cmd = [
|
||||
"ENABLE_HOST_SVC_NOTIFICATIONS",
|
||||
"ENABLE_HOST_NOTIFICATIONS"
|
||||
]
|
||||
nagios_return = True
|
||||
return_str_list = []
|
||||
for c in cmd:
|
||||
notif_str = self._fmt_notif_str(c, host)
|
||||
nagios_return = self._write_command(notif_str) and nagios_return
|
||||
return_str_list.append(notif_str)
|
||||
|
||||
if nagios_return:
|
||||
return return_str_list
|
||||
else:
|
||||
return "Fail: could not write to the command file"
|
||||
|
||||
def silence_nagios(self):
|
||||
"""
|
||||
This command is used to disable notifications for all hosts and services
|
||||
in nagios.
|
||||
|
||||
This is a 'SHUT UP, NAGIOS' command
|
||||
"""
|
||||
cmd = 'DISABLE_NOTIFICATIONS'
|
||||
self._write_command(self._fmt_notif_str(cmd))
|
||||
|
||||
def unsilence_nagios(self):
|
||||
"""
|
||||
This command is used to enable notifications for all hosts and services
|
||||
in nagios.
|
||||
|
||||
This is a 'OK, NAGIOS, GO'' command
|
||||
"""
|
||||
cmd = 'ENABLE_NOTIFICATIONS'
|
||||
self._write_command(self._fmt_notif_str(cmd))
|
||||
|
||||
def nagios_cmd(self, cmd):
|
||||
"""
|
||||
This sends an arbitrary command to nagios
|
||||
|
||||
It prepends the submitted time and appends a \n
|
||||
|
||||
You just have to provide the properly formatted command
|
||||
"""
|
||||
|
||||
pre = '[%s]' % int(time.time())
|
||||
|
||||
post = '\n'
|
||||
cmdstr = '%s %s %s' % (pre, cmd, post)
|
||||
self._write_command(cmdstr)
|
||||
|
||||
def act(self):
|
||||
"""
|
||||
Figure out what you want to do from ansible, and then do the
|
||||
needful (at the earliest).
|
||||
"""
|
||||
# host or service downtime?
|
||||
if self.action == 'downtime':
|
||||
if self.services == 'host':
|
||||
self.schedule_host_downtime(self.host, self.minutes)
|
||||
elif self.services == 'all':
|
||||
self.schedule_host_svc_downtime(self.host, self.minutes)
|
||||
else:
|
||||
self.schedule_svc_downtime(self.host,
|
||||
services=self.services,
|
||||
minutes=self.minutes)
|
||||
|
||||
# toggle the host AND service alerts
|
||||
elif self.action == 'silence':
|
||||
self.silence_host(self.host)
|
||||
|
||||
elif self.action == 'unsilence':
|
||||
self.unsilence_host(self.host)
|
||||
|
||||
# toggle host/svc alerts
|
||||
elif self.action == 'enable_alerts':
|
||||
if self.services == 'host':
|
||||
self.enable_host_notifications(self.host)
|
||||
else:
|
||||
self.enable_svc_notifications(self.host,
|
||||
services=self.services)
|
||||
|
||||
elif self.action == 'disable_alerts':
|
||||
if self.services == 'host':
|
||||
self.disable_host_notifications(self.host)
|
||||
else:
|
||||
self.disable_svc_notifications(self.host,
|
||||
services=self.services)
|
||||
elif self.action == 'silence_nagios':
|
||||
self.silence_nagios()
|
||||
|
||||
elif self.action == 'unsilence_nagios':
|
||||
self.unsilence_nagios()
|
||||
|
||||
elif self.action == 'command':
|
||||
self.nagios_cmd(self.command)
|
||||
|
||||
# wtf?
|
||||
else:
|
||||
self.module.fail_json(msg="unknown action specified: '%s'" % \
|
||||
self.action)
|
||||
|
||||
self.module.exit_json(nagios_commands=self.command_results,
|
||||
changed=True)
|
||||
|
||||
######################################################################
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,145 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Matt Coddington <coddington@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: newrelic_deployment
|
||||
version_added: "1.2"
|
||||
author: Matt Coddington
|
||||
short_description: Notify newrelic about app deployments
|
||||
description:
|
||||
- Notify newrelic about app deployments (see http://newrelic.github.io/newrelic_api/NewRelicApi/Deployment.html)
|
||||
options:
|
||||
token:
|
||||
description:
|
||||
- API token.
|
||||
required: true
|
||||
app_name:
|
||||
description:
|
||||
- (one of app_name or application_id are required) The value of app_name in the newrelic.yml file used by the application
|
||||
required: false
|
||||
application_id:
|
||||
description:
|
||||
- (one of app_name or application_id are required) The application id, found in the URL when viewing the application in RPM
|
||||
required: false
|
||||
changelog:
|
||||
description:
|
||||
- A list of changes for this deployment
|
||||
required: false
|
||||
description:
|
||||
description:
|
||||
- Text annotation for the deployment - notes for you
|
||||
required: false
|
||||
revision:
|
||||
description:
|
||||
- A revision number (e.g., git commit SHA)
|
||||
required: false
|
||||
user:
|
||||
description:
|
||||
- The name of the user/process that triggered this deployment
|
||||
required: false
|
||||
appname:
|
||||
description:
|
||||
- Name of the application
|
||||
required: false
|
||||
environment:
|
||||
description:
|
||||
- The environment for this deployment
|
||||
required: false
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ urllib, urllib2 ]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- newrelic_deployment: token=AAAAAA
|
||||
app_name=myapp
|
||||
user='ansible deployment'
|
||||
revision=1.0
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
token=dict(required=True),
|
||||
app_name=dict(required=False),
|
||||
application_id=dict(required=False),
|
||||
changelog=dict(required=False),
|
||||
description=dict(required=False),
|
||||
revision=dict(required=False),
|
||||
user=dict(required=False),
|
||||
appname=dict(required=False),
|
||||
environment=dict(required=False),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# build list of params
|
||||
params = {}
|
||||
if module.params["app_name"] and module.params["application_id"]:
|
||||
module.fail_json(msg="only one of 'app_name' or 'application_id' can be set")
|
||||
|
||||
if module.params["app_name"]:
|
||||
params["app_name"] = module.params["app_name"]
|
||||
elif module.params["application_id"]:
|
||||
params["application_id"] = module.params["application_id"]
|
||||
else:
|
||||
module.fail_json(msg="you must set one of 'app_name' or 'application_id'")
|
||||
|
||||
for item in [ "changelog", "description", "revision", "user", "appname", "environment" ]:
|
||||
if module.params[item]:
|
||||
params[item] = module.params[item]
|
||||
|
||||
# If we're in check mode, just exit pretending like we succeeded
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
# Send the data to NewRelic
|
||||
url = "https://rpm.newrelic.com/deployments.xml"
|
||||
data = urllib.urlencode(params)
|
||||
headers = {
|
||||
'x-api-key': module.params["token"],
|
||||
}
|
||||
response, info = fetch_url(module, url, data=data, headers=headers)
|
||||
if info['status'] in (200, 201):
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.fail_json(msg="unable to update newrelic: %s" % info['msg'])
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
||||
|
@ -1,232 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: pagerduty
|
||||
short_description: Create PagerDuty maintenance windows
|
||||
description:
|
||||
- This module will let you create PagerDuty maintenance windows
|
||||
version_added: "1.2"
|
||||
author: Justin Johns
|
||||
requirements:
|
||||
- PagerDuty API access
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Create a maintenance window or get a list of ongoing windows.
|
||||
required: true
|
||||
default: null
|
||||
choices: [ "running", "started", "ongoing" ]
|
||||
aliases: []
|
||||
name:
|
||||
description:
|
||||
- PagerDuty unique subdomain.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
user:
|
||||
description:
|
||||
- PagerDuty user ID.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
passwd:
|
||||
description:
|
||||
- PagerDuty user password.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
token:
|
||||
description:
|
||||
- A pagerduty token, generated on the pagerduty site. Can be used instead of
|
||||
user/passwd combination.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
version_added: '1.8'
|
||||
requester_id:
|
||||
description:
|
||||
- ID of user making the request. Only needed when using a token and creating a maintenance_window.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
version_added: '1.8'
|
||||
service:
|
||||
description:
|
||||
- PagerDuty service ID.
|
||||
required: false
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
hours:
|
||||
description:
|
||||
- Length of maintenance window in hours.
|
||||
required: false
|
||||
default: 1
|
||||
choices: []
|
||||
aliases: []
|
||||
minutes:
|
||||
description:
|
||||
- Maintenance window in minutes (this is added to the hours).
|
||||
required: false
|
||||
default: 0
|
||||
choices: []
|
||||
aliases: []
|
||||
version_added: '1.8'
|
||||
desc:
|
||||
description:
|
||||
- Short description of maintenance window.
|
||||
required: false
|
||||
default: Created by Ansible
|
||||
choices: []
|
||||
aliases: []
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
|
||||
notes:
|
||||
- This module does not yet have support to end maintenance windows.
|
||||
'''
|
||||
|
||||
EXAMPLES='''
|
||||
# List ongoing maintenance windows using a user/passwd
|
||||
- pagerduty: name=companyabc user=example@example.com passwd=password123 state=ongoing
|
||||
|
||||
# List ongoing maintenance windows using a token
|
||||
- pagerduty: name=companyabc token=xxxxxxxxxxxxxx state=ongoing
|
||||
|
||||
# Create a 1 hour maintenance window for service FOO123, using a user/passwd
|
||||
- pagerduty: name=companyabc
|
||||
user=example@example.com
|
||||
passwd=password123
|
||||
state=running
|
||||
service=FOO123
|
||||
|
||||
# Create a 5 minute maintenance window for service FOO123, using a token
|
||||
- pagerduty: name=companyabc
|
||||
token=xxxxxxxxxxxxxx
|
||||
hours=0
|
||||
minutes=5
|
||||
state=running
|
||||
service=FOO123
|
||||
|
||||
|
||||
# Create a 4 hour maintenance window for service FOO123 with the description "deployment".
|
||||
- pagerduty: name=companyabc
|
||||
user=example@example.com
|
||||
passwd=password123
|
||||
state=running
|
||||
service=FOO123
|
||||
hours=4
|
||||
desc=deployment
|
||||
'''
|
||||
|
||||
import json
|
||||
import datetime
|
||||
import base64
|
||||
|
||||
def auth_header(user, passwd, token):
|
||||
if token:
|
||||
return "Token token=%s" % token
|
||||
|
||||
auth = base64.encodestring('%s:%s' % (user, passwd)).replace('\n', '')
|
||||
return "Basic %s" % auth
|
||||
|
||||
def ongoing(module, name, user, passwd, token):
|
||||
url = "https://" + name + ".pagerduty.com/api/v1/maintenance_windows/ongoing"
|
||||
headers = {"Authorization": auth_header(user, passwd, token)}
|
||||
|
||||
response, info = fetch_url(module, url, headers=headers)
|
||||
if info['status'] != 200:
|
||||
module.fail_json(msg="failed to lookup the ongoing window: %s" % info['msg'])
|
||||
|
||||
return False, response.read()
|
||||
|
||||
|
||||
def create(module, name, user, passwd, token, requester_id, service, hours, minutes, desc):
|
||||
now = datetime.datetime.utcnow()
|
||||
later = now + datetime.timedelta(hours=int(hours), minutes=int(minutes))
|
||||
start = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
end = later.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
url = "https://" + name + ".pagerduty.com/api/v1/maintenance_windows"
|
||||
headers = {
|
||||
'Authorization': auth_header(user, passwd, token),
|
||||
'Content-Type' : 'application/json',
|
||||
}
|
||||
request_data = {'maintenance_window': {'start_time': start, 'end_time': end, 'description': desc, 'service_ids': [service]}}
|
||||
if requester_id:
|
||||
request_data['requester_id'] = requester_id
|
||||
else:
|
||||
if token:
|
||||
module.fail_json(msg="requester_id is required when using a token")
|
||||
|
||||
data = json.dumps(request_data)
|
||||
response, info = fetch_url(module, url, data=data, headers=headers, method='POST')
|
||||
if info['status'] != 200:
|
||||
module.fail_json(msg="failed to create the window: %s" % info['msg'])
|
||||
|
||||
return False, response.read()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(required=True, choices=['running', 'started', 'ongoing']),
|
||||
name=dict(required=True),
|
||||
user=dict(required=False),
|
||||
passwd=dict(required=False),
|
||||
token=dict(required=False),
|
||||
service=dict(required=False),
|
||||
requester_id=dict(required=False),
|
||||
hours=dict(default='1', required=False),
|
||||
minutes=dict(default='0', required=False),
|
||||
desc=dict(default='Created by Ansible', required=False),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
)
|
||||
)
|
||||
|
||||
state = module.params['state']
|
||||
name = module.params['name']
|
||||
user = module.params['user']
|
||||
passwd = module.params['passwd']
|
||||
token = module.params['token']
|
||||
service = module.params['service']
|
||||
hours = module.params['hours']
|
||||
minutes = module.params['minutes']
|
||||
token = module.params['token']
|
||||
desc = module.params['desc']
|
||||
requester_id = module.params['requester_id']
|
||||
|
||||
if not token and not (user or passwd):
|
||||
module.fail_json(msg="neither user and passwd nor token specified")
|
||||
|
||||
if state == "running" or state == "started":
|
||||
if not service:
|
||||
module.fail_json(msg="service not specified")
|
||||
(rc, out) = create(module, name, user, passwd, token, requester_id, service, hours, minutes, desc)
|
||||
|
||||
if state == "ongoing":
|
||||
(rc, out) = ongoing(module, name, user, passwd, token)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg="failed", result=out)
|
||||
|
||||
module.exit_json(msg="success", result=out)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,135 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: pingdom
|
||||
short_description: Pause/unpause Pingdom alerts
|
||||
description:
|
||||
- This module will let you pause/unpause Pingdom alerts
|
||||
version_added: "1.2"
|
||||
author: Justin Johns
|
||||
requirements:
|
||||
- "This pingdom python library: https://github.com/mbabineau/pingdom-python"
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Define whether or not the check should be running or paused.
|
||||
required: true
|
||||
default: null
|
||||
choices: [ "running", "paused" ]
|
||||
aliases: []
|
||||
checkid:
|
||||
description:
|
||||
- Pingdom ID of the check.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
uid:
|
||||
description:
|
||||
- Pingdom user ID.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
passwd:
|
||||
description:
|
||||
- Pingdom user password.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
key:
|
||||
description:
|
||||
- Pingdom API key.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
notes:
|
||||
- This module does not yet have support to add/remove checks.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Pause the check with the ID of 12345.
|
||||
- pingdom: uid=example@example.com
|
||||
passwd=password123
|
||||
key=apipassword123
|
||||
checkid=12345
|
||||
state=paused
|
||||
|
||||
# Unpause the check with the ID of 12345.
|
||||
- pingdom: uid=example@example.com
|
||||
passwd=password123
|
||||
key=apipassword123
|
||||
checkid=12345
|
||||
state=running
|
||||
'''
|
||||
|
||||
try:
|
||||
import pingdom
|
||||
HAS_PINGDOM = True
|
||||
except:
|
||||
HAS_PINGDOM = False
|
||||
|
||||
|
||||
|
||||
def pause(checkid, uid, passwd, key):
|
||||
|
||||
c = pingdom.PingdomConnection(uid, passwd, key)
|
||||
c.modify_check(checkid, paused=True)
|
||||
check = c.get_check(checkid)
|
||||
name = check.name
|
||||
result = check.status
|
||||
#if result != "paused": # api output buggy - accept raw exception for now
|
||||
# return (True, name, result)
|
||||
return (False, name, result)
|
||||
|
||||
|
||||
def unpause(checkid, uid, passwd, key):
|
||||
|
||||
c = pingdom.PingdomConnection(uid, passwd, key)
|
||||
c.modify_check(checkid, paused=False)
|
||||
check = c.get_check(checkid)
|
||||
name = check.name
|
||||
result = check.status
|
||||
#if result != "up": # api output buggy - accept raw exception for now
|
||||
# return (True, name, result)
|
||||
return (False, name, result)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(required=True, choices=['running', 'paused', 'started', 'stopped']),
|
||||
checkid=dict(required=True),
|
||||
uid=dict(required=True),
|
||||
passwd=dict(required=True),
|
||||
key=dict(required=True)
|
||||
)
|
||||
)
|
||||
|
||||
if not HAS_PINGDOM:
|
||||
module.fail_json(msg="Missing requried pingdom module (check docs)")
|
||||
|
||||
checkid = module.params['checkid']
|
||||
state = module.params['state']
|
||||
uid = module.params['uid']
|
||||
passwd = module.params['passwd']
|
||||
key = module.params['key']
|
||||
|
||||
if (state == "paused" or state == "stopped"):
|
||||
(rc, name, result) = pause(checkid, uid, passwd, key)
|
||||
|
||||
if (state == "running" or state == "started"):
|
||||
(rc, name, result) = unpause(checkid, uid, passwd, key)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(checkid=checkid, name=name, status=result)
|
||||
|
||||
module.exit_json(checkid=checkid, name=name, status=result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,133 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014, Max Riveiro, <kavu13@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rollbar_deployment
|
||||
version_added: 1.6
|
||||
author: Max Riveiro
|
||||
short_description: Notify Rollbar about app deployments
|
||||
description:
|
||||
- Notify Rollbar about app deployments
|
||||
(see https://rollbar.com/docs/deploys_other/)
|
||||
options:
|
||||
token:
|
||||
description:
|
||||
- Your project access token.
|
||||
required: true
|
||||
environment:
|
||||
description:
|
||||
- Name of the environment being deployed, e.g. 'production'.
|
||||
required: true
|
||||
revision:
|
||||
description:
|
||||
- Revision number/sha being deployed.
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- User who deployed.
|
||||
required: false
|
||||
rollbar_user:
|
||||
description:
|
||||
- Rollbar username of the user who deployed.
|
||||
required: false
|
||||
comment:
|
||||
description:
|
||||
- Deploy comment (e.g. what is being deployed).
|
||||
required: false
|
||||
url:
|
||||
description:
|
||||
- Optional URL to submit the notification to.
|
||||
required: false
|
||||
default: 'https://api.rollbar.com/api/1/deploy/'
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates for the target url will not be validated.
|
||||
This should only be used on personally controlled sites using
|
||||
self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- rollbar_deployment: token=AAAAAA
|
||||
environment='staging'
|
||||
user='ansible'
|
||||
revision=4.2,
|
||||
rollbar_user='admin',
|
||||
comment='Test Deploy'
|
||||
'''
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
token=dict(required=True),
|
||||
environment=dict(required=True),
|
||||
revision=dict(required=True),
|
||||
user=dict(required=False),
|
||||
rollbar_user=dict(required=False),
|
||||
comment=dict(required=False),
|
||||
url=dict(
|
||||
required=False,
|
||||
default='https://api.rollbar.com/api/1/deploy/'
|
||||
),
|
||||
validate_certs=dict(default='yes', type='bool'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
params = dict(
|
||||
access_token=module.params['token'],
|
||||
environment=module.params['environment'],
|
||||
revision=module.params['revision']
|
||||
)
|
||||
|
||||
if module.params['user']:
|
||||
params['local_username'] = module.params['user']
|
||||
|
||||
if module.params['rollbar_user']:
|
||||
params['rollbar_username'] = module.params['rollbar_user']
|
||||
|
||||
if module.params['comment']:
|
||||
params['comment'] = module.params['comment']
|
||||
|
||||
url = module.params.get('url')
|
||||
|
||||
try:
|
||||
data = urllib.urlencode(params)
|
||||
response, info = fetch_url(module, url, data=data)
|
||||
except Exception, e:
|
||||
module.fail_json(msg='Unable to notify Rollbar: %s' % e)
|
||||
else:
|
||||
if info['status'] == 200:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.fail_json(msg='HTTP result code: %d connecting to %s' % (info['status'], url))
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,371 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: zabbix_maintenance
|
||||
short_description: Create Zabbix maintenance windows
|
||||
description:
|
||||
- This module will let you create Zabbix maintenance windows.
|
||||
version_added: "1.8"
|
||||
author: Alexander Bulimov
|
||||
requirements:
|
||||
- zabbix-api python module
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Create or remove a maintenance window.
|
||||
required: true
|
||||
default: null
|
||||
choices: [ "present", "absent" ]
|
||||
server_url:
|
||||
description:
|
||||
- Url of Zabbix server, with protocol (http or https).
|
||||
C(url) is an alias for C(server_url).
|
||||
required: true
|
||||
default: null
|
||||
aliases: [ "url" ]
|
||||
login_user:
|
||||
description:
|
||||
- Zabbix user name.
|
||||
required: true
|
||||
default: null
|
||||
login_password:
|
||||
description:
|
||||
- Zabbix user password.
|
||||
required: true
|
||||
default: null
|
||||
host_names:
|
||||
description:
|
||||
- Hosts to manage maintenance window for.
|
||||
Separate multiple hosts with commas.
|
||||
C(host_name) is an alias for C(host_names).
|
||||
B(Required) option when C(state) is I(present)
|
||||
and no C(host_groups) specified.
|
||||
required: false
|
||||
default: null
|
||||
aliases: [ "host_name" ]
|
||||
host_groups:
|
||||
description:
|
||||
- Host groups to manage maintenance window for.
|
||||
Separate multiple groups with commas.
|
||||
C(host_group) is an alias for C(host_groups).
|
||||
B(Required) option when C(state) is I(present)
|
||||
and no C(host_names) specified.
|
||||
required: false
|
||||
default: null
|
||||
aliases: [ "host_group" ]
|
||||
minutes:
|
||||
description:
|
||||
- Length of maintenance window in minutes.
|
||||
required: false
|
||||
default: 10
|
||||
name:
|
||||
description:
|
||||
- Unique name of maintenance window.
|
||||
required: true
|
||||
default: null
|
||||
desc:
|
||||
description:
|
||||
- Short description of maintenance window.
|
||||
required: true
|
||||
default: Created by Ansible
|
||||
collect_data:
|
||||
description:
|
||||
- Type of maintenance. With data collection, or without.
|
||||
required: false
|
||||
default: "true"
|
||||
notes:
|
||||
- Useful for setting hosts in maintenance mode before big update,
|
||||
and removing maintenance window after update.
|
||||
- Module creates maintenance window from now() to now() + minutes,
|
||||
so if Zabbix server's time and host's time are not synchronized,
|
||||
you will get strange results.
|
||||
- Install required module with 'pip install zabbix-api' command.
|
||||
- Checks existance only by maintenance name.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create maintenance window named "Update of www1"
|
||||
# for host www1.example.com for 90 minutes
|
||||
- zabbix_maintenance: name="Update of www1"
|
||||
host_name=www1.example.com
|
||||
state=present
|
||||
minutes=90
|
||||
server_url=https://monitoring.example.com
|
||||
login_user=ansible
|
||||
login_password=pAsSwOrD
|
||||
|
||||
# Create maintenance window named "Mass update"
|
||||
# for host www1.example.com and host groups Office and Dev
|
||||
- zabbix_maintenance: name="Update of www1"
|
||||
host_name=www1.example.com
|
||||
host_groups=Office,Dev
|
||||
state=present
|
||||
server_url=https://monitoring.example.com
|
||||
login_user=ansible
|
||||
login_password=pAsSwOrD
|
||||
|
||||
# Create maintenance window named "update"
|
||||
# for hosts www1.example.com and db1.example.com and without data collection.
|
||||
- zabbix_maintenance: name=update
|
||||
host_names=www1.example.com,db1.example.com
|
||||
state=present
|
||||
collect_data=false
|
||||
server_url=https://monitoring.example.com
|
||||
login_user=ansible
|
||||
login_password=pAsSwOrD
|
||||
|
||||
# Remove maintenance window named "Test1"
|
||||
- zabbix_maintenance: name=Test1
|
||||
state=absent
|
||||
server_url=https://monitoring.example.com
|
||||
login_user=ansible
|
||||
login_password=pAsSwOrD
|
||||
'''
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
try:
|
||||
from zabbix_api import ZabbixAPI
|
||||
HAS_ZABBIX_API = True
|
||||
except ImportError:
|
||||
HAS_ZABBIX_API = False
|
||||
|
||||
|
||||
def create_maintenance(zbx, group_ids, host_ids, start_time, maintenance_type, period, name, desc):
|
||||
end_time = start_time + period
|
||||
try:
|
||||
zbx.maintenance.create(
|
||||
{
|
||||
"groupids": group_ids,
|
||||
"hostids": host_ids,
|
||||
"name": name,
|
||||
"maintenance_type": maintenance_type,
|
||||
"active_since": str(start_time),
|
||||
"active_till": str(end_time),
|
||||
"description": desc,
|
||||
"timeperiods": [{
|
||||
"timeperiod_type": "0",
|
||||
"start_date": str(start_time),
|
||||
"period": str(period),
|
||||
}]
|
||||
}
|
||||
)
|
||||
except BaseException as e:
|
||||
return 1, None, str(e)
|
||||
return 0, None, None
|
||||
|
||||
|
||||
def get_maintenance_id(zbx, name):
|
||||
try:
|
||||
result = zbx.maintenance.get(
|
||||
{
|
||||
"filter":
|
||||
{
|
||||
"name": name,
|
||||
}
|
||||
}
|
||||
)
|
||||
except BaseException as e:
|
||||
return 1, None, str(e)
|
||||
|
||||
maintenance_ids = []
|
||||
for res in result:
|
||||
maintenance_ids.append(res["maintenanceid"])
|
||||
|
||||
return 0, maintenance_ids, None
|
||||
|
||||
|
||||
def delete_maintenance(zbx, maintenance_id):
|
||||
try:
|
||||
zbx.maintenance.delete(maintenance_id)
|
||||
except BaseException as e:
|
||||
return 1, None, str(e)
|
||||
return 0, None, None
|
||||
|
||||
|
||||
def check_maintenance(zbx, name):
|
||||
try:
|
||||
result = zbx.maintenance.exists(
|
||||
{
|
||||
"name": name
|
||||
}
|
||||
)
|
||||
except BaseException as e:
|
||||
return 1, None, str(e)
|
||||
return 0, result, None
|
||||
|
||||
|
||||
def get_group_ids(zbx, host_groups):
|
||||
group_ids = []
|
||||
for group in host_groups:
|
||||
try:
|
||||
result = zbx.hostgroup.get(
|
||||
{
|
||||
"output": "extend",
|
||||
"filter":
|
||||
{
|
||||
"name": group
|
||||
}
|
||||
}
|
||||
)
|
||||
except BaseException as e:
|
||||
return 1, None, str(e)
|
||||
|
||||
if not result:
|
||||
return 1, None, "Group id for group %s not found" % group
|
||||
|
||||
group_ids.append(result[0]["groupid"])
|
||||
|
||||
return 0, group_ids, None
|
||||
|
||||
|
||||
def get_host_ids(zbx, host_names):
|
||||
host_ids = []
|
||||
for host in host_names:
|
||||
try:
|
||||
result = zbx.host.get(
|
||||
{
|
||||
"output": "extend",
|
||||
"filter":
|
||||
{
|
||||
"name": host
|
||||
}
|
||||
}
|
||||
)
|
||||
except BaseException as e:
|
||||
return 1, None, str(e)
|
||||
|
||||
if not result:
|
||||
return 1, None, "Host id for host %s not found" % host
|
||||
|
||||
host_ids.append(result[0]["hostid"])
|
||||
|
||||
return 0, host_ids, None
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(required=True, default=None, choices=['present', 'absent']),
|
||||
server_url=dict(required=True, default=None, aliases=['url']),
|
||||
host_names=dict(type='list', required=False, default=None, aliases=['host_name']),
|
||||
minutes=dict(type='int', required=False, default=10),
|
||||
host_groups=dict(type='list', required=False, default=None, aliases=['host_group']),
|
||||
login_user=dict(required=True, default=None),
|
||||
login_password=dict(required=True, default=None),
|
||||
name=dict(required=True, default=None),
|
||||
desc=dict(required=False, default="Created by Ansible"),
|
||||
collect_data=dict(type='bool', required=False, default=True),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_ZABBIX_API:
|
||||
module.fail_json(msg="Missing requried zabbix-api module (check docs or install with: pip install zabbix-api)")
|
||||
|
||||
host_names = module.params['host_names']
|
||||
host_groups = module.params['host_groups']
|
||||
state = module.params['state']
|
||||
login_user = module.params['login_user']
|
||||
login_password = module.params['login_password']
|
||||
minutes = module.params['minutes']
|
||||
name = module.params['name']
|
||||
desc = module.params['desc']
|
||||
server_url = module.params['server_url']
|
||||
collect_data = module.params['collect_data']
|
||||
if collect_data:
|
||||
maintenance_type = 0
|
||||
else:
|
||||
maintenance_type = 1
|
||||
|
||||
try:
|
||||
zbx = ZabbixAPI(server_url)
|
||||
zbx.login(login_user, login_password)
|
||||
except BaseException as e:
|
||||
module.fail_json(msg="Failed to connect to Zabbix server: %s" % e)
|
||||
|
||||
changed = False
|
||||
|
||||
if state == "present":
|
||||
|
||||
now = datetime.datetime.now()
|
||||
start_time = time.mktime(now.timetuple())
|
||||
period = 60 * int(minutes) # N * 60 seconds
|
||||
|
||||
if host_groups:
|
||||
(rc, group_ids, error) = get_group_ids(zbx, host_groups)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to get group_ids: %s" % error)
|
||||
else:
|
||||
group_ids = []
|
||||
|
||||
if host_names:
|
||||
(rc, host_ids, error) = get_host_ids(zbx, host_names)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to get host_ids: %s" % error)
|
||||
else:
|
||||
host_ids = []
|
||||
|
||||
(rc, exists, error) = check_maintenance(zbx, name)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to check maintenance %s existance: %s" % (name, error))
|
||||
|
||||
if not exists:
|
||||
if not host_names and not host_groups:
|
||||
module.fail_json(msg="At least one host_name or host_group must be defined for each created maintenance.")
|
||||
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
(rc, _, error) = create_maintenance(zbx, group_ids, host_ids, start_time, maintenance_type, period, name, desc)
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Failed to create maintenance: %s" % error)
|
||||
|
||||
if state == "absent":
|
||||
|
||||
(rc, exists, error) = check_maintenance(zbx, name)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to check maintenance %s existance: %s" % (name, error))
|
||||
|
||||
if exists:
|
||||
(rc, maintenance, error) = get_maintenance_id(zbx, name)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to get maintenance id: %s" % error)
|
||||
|
||||
if maintenance:
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
(rc, _, error) = delete_maintenance(zbx, maintenance)
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Failed to remove maintenance: %s" % error)
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,269 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Ansible module to manage A10 Networks slb server objects
|
||||
(c) 2014, Mischa Peters <mpeters@a10networks.com>
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: a10_server
|
||||
version_added: 1.8
|
||||
short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices
|
||||
description:
|
||||
- Manage slb server objects on A10 Networks devices via aXAPI
|
||||
author: Mischa Peters
|
||||
notes:
|
||||
- Requires A10 Networks aXAPI 2.1
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- hostname or ip of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
choices: []
|
||||
username:
|
||||
description:
|
||||
- admin account of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['user', 'admin']
|
||||
choices: []
|
||||
password:
|
||||
description:
|
||||
- admin password of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['pass', 'pwd']
|
||||
choices: []
|
||||
server_name:
|
||||
description:
|
||||
- slb server name
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['server']
|
||||
choices: []
|
||||
server_ip:
|
||||
description:
|
||||
- slb server IP address
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['ip', 'address']
|
||||
choices: []
|
||||
server_status:
|
||||
description:
|
||||
- slb virtual server status
|
||||
required: false
|
||||
default: enable
|
||||
aliases: ['status']
|
||||
choices: ['enabled', 'disabled']
|
||||
server_ports:
|
||||
description:
|
||||
- A list of ports to create for the server. Each list item should be a
|
||||
dictionary which specifies the C(port:) and C(protocol:), but can also optionally
|
||||
specify the C(status:). See the examples below for details. This parameter is
|
||||
required when C(state) is C(present).
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
choices: []
|
||||
state:
|
||||
description:
|
||||
- create, update or remove slb server
|
||||
required: false
|
||||
default: present
|
||||
aliases: []
|
||||
choices: ['present', 'absent']
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a new server
|
||||
- a10_server:
|
||||
host: a10.mydomain.com
|
||||
username: myadmin
|
||||
password: mypassword
|
||||
server: test
|
||||
server_ip: 1.1.1.100
|
||||
server_ports:
|
||||
- port_num: 8080
|
||||
protocol: tcp
|
||||
- port_num: 8443
|
||||
protocol: TCP
|
||||
|
||||
'''
|
||||
|
||||
VALID_PORT_FIELDS = ['port_num', 'protocol', 'status']
|
||||
|
||||
def validate_ports(module, ports):
|
||||
for item in ports:
|
||||
for key in item:
|
||||
if key not in VALID_PORT_FIELDS:
|
||||
module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS)))
|
||||
|
||||
# validate the port number is present and an integer
|
||||
if 'port_num' in item:
|
||||
try:
|
||||
item['port_num'] = int(item['port_num'])
|
||||
except:
|
||||
module.fail_json(msg="port_num entries in the port definitions must be integers")
|
||||
else:
|
||||
module.fail_json(msg="port definitions must define the port_num field")
|
||||
|
||||
# validate the port protocol is present, and convert it to
|
||||
# the internal API integer value (and validate it)
|
||||
if 'protocol' in item:
|
||||
protocol = axapi_get_port_protocol(item['protocol'])
|
||||
if not protocol:
|
||||
module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_PORT_PROTOCOLS))
|
||||
else:
|
||||
item['protocol'] = protocol
|
||||
else:
|
||||
module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_PORT_PROTOCOLS))
|
||||
|
||||
# convert the status to the internal API integer value
|
||||
if 'status' in item:
|
||||
item['status'] = axapi_enabled_disabled(item['status'])
|
||||
else:
|
||||
item['status'] = 1
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = a10_argument_spec()
|
||||
argument_spec.update(url_argument_spec())
|
||||
argument_spec.update(
|
||||
dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
server_name=dict(type='str', aliases=['server'], required=True),
|
||||
server_ip=dict(type='str', aliases=['ip', 'address']),
|
||||
server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']),
|
||||
server_ports=dict(type='list', aliases=['port'], default=[]),
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
host = module.params['host']
|
||||
username = module.params['username']
|
||||
password = module.params['password']
|
||||
state = module.params['state']
|
||||
write_config = module.params['write_config']
|
||||
slb_server = module.params['server_name']
|
||||
slb_server_ip = module.params['server_ip']
|
||||
slb_server_status = module.params['server_status']
|
||||
slb_server_ports = module.params['server_ports']
|
||||
|
||||
if slb_server is None:
|
||||
module.fail_json(msg='server_name is required')
|
||||
|
||||
axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host
|
||||
session_url = axapi_authenticate(module, axapi_base_url, username, password)
|
||||
|
||||
# validate the ports data structure
|
||||
validate_ports(module, slb_server_ports)
|
||||
|
||||
json_post = {
|
||||
'server': {
|
||||
'name': slb_server,
|
||||
'host': slb_server_ip,
|
||||
'status': axapi_enabled_disabled(slb_server_status),
|
||||
'port_list': slb_server_ports,
|
||||
}
|
||||
}
|
||||
|
||||
slb_server_data = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server}))
|
||||
slb_server_exists = not axapi_failure(slb_server_data)
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
if not slb_server_ip:
|
||||
module.fail_json(msg='you must specify an IP address when creating a server')
|
||||
|
||||
if not slb_server_exists:
|
||||
result = axapi_call(module, session_url + '&method=slb.server.create', json.dumps(json_post))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg="failed to create the server: %s" % result['response']['err']['msg'])
|
||||
changed = True
|
||||
else:
|
||||
def needs_update(src_ports, dst_ports):
|
||||
'''
|
||||
Checks to determine if the port definitions of the src_ports
|
||||
array are in or different from those in dst_ports. If there is
|
||||
a difference, this function returns true, otherwise false.
|
||||
'''
|
||||
for src_port in src_ports:
|
||||
found = False
|
||||
different = False
|
||||
for dst_port in dst_ports:
|
||||
if src_port['port_num'] == dst_port['port_num']:
|
||||
found = True
|
||||
for valid_field in VALID_PORT_FIELDS:
|
||||
if src_port[valid_field] != dst_port[valid_field]:
|
||||
different = True
|
||||
break
|
||||
if found or different:
|
||||
break
|
||||
if not found or different:
|
||||
return True
|
||||
# every port from the src exists in the dst, and none of them were different
|
||||
return False
|
||||
|
||||
defined_ports = slb_server_data.get('server', {}).get('port_list', [])
|
||||
|
||||
# we check for a needed update both ways, in case ports
|
||||
# are missing from either the ones specified by the user
|
||||
# or from those on the device
|
||||
if needs_update(defined_ports, slb_server_ports) or needs_update(slb_server_ports, defined_ports):
|
||||
result = axapi_call(module, session_url + '&method=slb.server.update', json.dumps(json_post))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg="failed to update the server: %s" % result['response']['err']['msg'])
|
||||
changed = True
|
||||
|
||||
# if we changed things, get the full info regarding
|
||||
# the service group for the return data below
|
||||
if changed:
|
||||
result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server}))
|
||||
else:
|
||||
result = slb_server_data
|
||||
elif state == 'absent':
|
||||
if slb_server_exists:
|
||||
result = axapi_call(module, session_url + '&method=slb.server.delete', json.dumps({'name': slb_server}))
|
||||
changed = True
|
||||
else:
|
||||
result = dict(msg="the server was not present")
|
||||
|
||||
# if the config has changed, save the config unless otherwise requested
|
||||
if changed and write_config:
|
||||
write_result = axapi_call(module, session_url + '&method=system.action.write_memory')
|
||||
if axapi_failure(write_result):
|
||||
module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg'])
|
||||
|
||||
# log out of the session nicely and exit
|
||||
axapi_call(module, session_url + '&method=session.close')
|
||||
module.exit_json(changed=changed, content=result)
|
||||
|
||||
# standard ansible module imports
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
from ansible.module_utils.a10 import *
|
||||
|
||||
main()
|
@ -1,341 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Ansible module to manage A10 Networks slb service-group objects
|
||||
(c) 2014, Mischa Peters <mpeters@a10networks.com>
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: a10_service_group
|
||||
version_added: 1.8
|
||||
short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices
|
||||
description:
|
||||
- Manage slb service-group objects on A10 Networks devices via aXAPI
|
||||
author: Mischa Peters
|
||||
notes:
|
||||
- Requires A10 Networks aXAPI 2.1
|
||||
- When a server doesn't exist and is added to the service-group the server will be created
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- hostname or ip of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
choices: []
|
||||
username:
|
||||
description:
|
||||
- admin account of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['user', 'admin']
|
||||
choices: []
|
||||
password:
|
||||
description:
|
||||
- admin password of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['pass', 'pwd']
|
||||
choices: []
|
||||
service_group:
|
||||
description:
|
||||
- slb service-group name
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['service', 'pool', 'group']
|
||||
choices: []
|
||||
service_group_protocol:
|
||||
description:
|
||||
- slb service-group protocol
|
||||
required: false
|
||||
default: tcp
|
||||
aliases: ['proto', 'protocol']
|
||||
choices: ['tcp', 'udp']
|
||||
service_group_method:
|
||||
description:
|
||||
- slb service-group loadbalancing method
|
||||
required: false
|
||||
default: round-robin
|
||||
aliases: ['method']
|
||||
choices: ['round-robin', 'weighted-rr', 'least-connection', 'weighted-least-connection', 'service-least-connection', 'service-weighted-least-connection', 'fastest-response', 'least-request', 'round-robin-strict', 'src-ip-only-hash', 'src-ip-hash']
|
||||
servers:
|
||||
description:
|
||||
- A list of servers to add to the service group. Each list item should be a
|
||||
dictionary which specifies the C(server:) and C(port:), but can also optionally
|
||||
specify the C(status:). See the examples below for details.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
choices: []
|
||||
write_config:
|
||||
description:
|
||||
- If C(yes), any changes will cause a write of the running configuration
|
||||
to non-volatile memory. This will save I(all) configuration changes,
|
||||
including those that may have been made manually or through other modules,
|
||||
so care should be taken when specifying C(yes).
|
||||
required: false
|
||||
default: "no"
|
||||
choices: ["yes", "no"]
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled devices using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a new service-group
|
||||
- a10_service_group:
|
||||
host: a10.mydomain.com
|
||||
username: myadmin
|
||||
password: mypassword
|
||||
service_group: sg-80-tcp
|
||||
servers:
|
||||
- server: foo1.mydomain.com
|
||||
port: 8080
|
||||
- server: foo2.mydomain.com
|
||||
port: 8080
|
||||
- server: foo3.mydomain.com
|
||||
port: 8080
|
||||
- server: foo4.mydomain.com
|
||||
port: 8080
|
||||
status: disabled
|
||||
|
||||
'''
|
||||
|
||||
VALID_SERVICE_GROUP_FIELDS = ['name', 'protocol', 'lb_method']
|
||||
VALID_SERVER_FIELDS = ['server', 'port', 'status']
|
||||
|
||||
def validate_servers(module, servers):
|
||||
for item in servers:
|
||||
for key in item:
|
||||
if key not in VALID_SERVER_FIELDS:
|
||||
module.fail_json(msg="invalid server field (%s), must be one of: %s" % (key, ','.join(VALID_SERVER_FIELDS)))
|
||||
|
||||
# validate the server name is present
|
||||
if 'server' not in item:
|
||||
module.fail_json(msg="server definitions must define the server field")
|
||||
|
||||
# validate the port number is present and an integer
|
||||
if 'port' in item:
|
||||
try:
|
||||
item['port'] = int(item['port'])
|
||||
except:
|
||||
module.fail_json(msg="server port definitions must be integers")
|
||||
else:
|
||||
module.fail_json(msg="server definitions must define the port field")
|
||||
|
||||
# convert the status to the internal API integer value
|
||||
if 'status' in item:
|
||||
item['status'] = axapi_enabled_disabled(item['status'])
|
||||
else:
|
||||
item['status'] = 1
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = a10_argument_spec()
|
||||
argument_spec.update(url_argument_spec())
|
||||
argument_spec.update(
|
||||
dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
service_group=dict(type='str', aliases=['service', 'pool', 'group'], required=True),
|
||||
service_group_protocol=dict(type='str', default='tcp', aliases=['proto', 'protocol'], choices=['tcp', 'udp']),
|
||||
service_group_method=dict(type='str', default='round-robin',
|
||||
aliases=['method'],
|
||||
choices=['round-robin',
|
||||
'weighted-rr',
|
||||
'least-connection',
|
||||
'weighted-least-connection',
|
||||
'service-least-connection',
|
||||
'service-weighted-least-connection',
|
||||
'fastest-response',
|
||||
'least-request',
|
||||
'round-robin-strict',
|
||||
'src-ip-only-hash',
|
||||
'src-ip-hash']),
|
||||
servers=dict(type='list', aliases=['server', 'member'], default=[]),
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
host = module.params['host']
|
||||
username = module.params['username']
|
||||
password = module.params['password']
|
||||
state = module.params['state']
|
||||
write_config = module.params['write_config']
|
||||
slb_service_group = module.params['service_group']
|
||||
slb_service_group_proto = module.params['service_group_protocol']
|
||||
slb_service_group_method = module.params['service_group_method']
|
||||
slb_servers = module.params['servers']
|
||||
|
||||
if slb_service_group is None:
|
||||
module.fail_json(msg='service_group is required')
|
||||
|
||||
axapi_base_url = 'https://' + host + '/services/rest/V2.1/?format=json'
|
||||
load_balancing_methods = {'round-robin': 0,
|
||||
'weighted-rr': 1,
|
||||
'least-connection': 2,
|
||||
'weighted-least-connection': 3,
|
||||
'service-least-connection': 4,
|
||||
'service-weighted-least-connection': 5,
|
||||
'fastest-response': 6,
|
||||
'least-request': 7,
|
||||
'round-robin-strict': 8,
|
||||
'src-ip-only-hash': 14,
|
||||
'src-ip-hash': 15}
|
||||
|
||||
if not slb_service_group_proto or slb_service_group_proto.lower() == 'tcp':
|
||||
protocol = 2
|
||||
else:
|
||||
protocol = 3
|
||||
|
||||
# validate the server data list structure
|
||||
validate_servers(module, slb_servers)
|
||||
|
||||
json_post = {
|
||||
'service_group': {
|
||||
'name': slb_service_group,
|
||||
'protocol': protocol,
|
||||
'lb_method': load_balancing_methods[slb_service_group_method],
|
||||
}
|
||||
}
|
||||
|
||||
# first we authenticate to get a session id
|
||||
session_url = axapi_authenticate(module, axapi_base_url, username, password)
|
||||
|
||||
# then we check to see if the specified group exists
|
||||
slb_result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group}))
|
||||
slb_service_group_exist = not axapi_failure(slb_result)
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
# before creating/updating we need to validate that servers
|
||||
# defined in the servers list exist to prevent errors
|
||||
checked_servers = []
|
||||
for server in slb_servers:
|
||||
result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': server['server']}))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg="the server %s specified in the servers list does not exist" % server['server'])
|
||||
checked_servers.append(server['server'])
|
||||
|
||||
if not slb_service_group_exist:
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.create', json.dumps(json_post))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg=result['response']['err']['msg'])
|
||||
changed = True
|
||||
else:
|
||||
# check to see if the service group definition without the
|
||||
# server members is different, and update that individually
|
||||
# if it needs it
|
||||
do_update = False
|
||||
for field in VALID_SERVICE_GROUP_FIELDS:
|
||||
if json_post['service_group'][field] != slb_result['service_group'][field]:
|
||||
do_update = True
|
||||
break
|
||||
|
||||
if do_update:
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.update', json.dumps(json_post))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg=result['response']['err']['msg'])
|
||||
changed = True
|
||||
|
||||
# next we pull the defined list of servers out of the returned
|
||||
# results to make it a bit easier to iterate over
|
||||
defined_servers = slb_result.get('service_group', {}).get('member_list', [])
|
||||
|
||||
# next we add/update new member servers from the user-specified
|
||||
# list if they're different or not on the target device
|
||||
for server in slb_servers:
|
||||
found = False
|
||||
different = False
|
||||
for def_server in defined_servers:
|
||||
if server['server'] == def_server['server']:
|
||||
found = True
|
||||
for valid_field in VALID_SERVER_FIELDS:
|
||||
if server[valid_field] != def_server[valid_field]:
|
||||
different = True
|
||||
break
|
||||
if found or different:
|
||||
break
|
||||
# add or update as required
|
||||
server_data = {
|
||||
"name": slb_service_group,
|
||||
"member": server,
|
||||
}
|
||||
if not found:
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.member.create', json.dumps(server_data))
|
||||
changed = True
|
||||
elif different:
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.member.update', json.dumps(server_data))
|
||||
changed = True
|
||||
|
||||
# finally, remove any servers that are on the target
|
||||
# device but were not specified in the list given
|
||||
for server in defined_servers:
|
||||
found = False
|
||||
for slb_server in slb_servers:
|
||||
if server['server'] == slb_server['server']:
|
||||
found = True
|
||||
break
|
||||
# remove if not found
|
||||
server_data = {
|
||||
"name": slb_service_group,
|
||||
"member": server,
|
||||
}
|
||||
if not found:
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.member.delete', json.dumps(server_data))
|
||||
changed = True
|
||||
|
||||
# if we changed things, get the full info regarding
|
||||
# the service group for the return data below
|
||||
if changed:
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group}))
|
||||
else:
|
||||
result = slb_result
|
||||
elif state == 'absent':
|
||||
if slb_service_group_exist:
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.delete', json.dumps({'name': slb_service_group}))
|
||||
changed = True
|
||||
else:
|
||||
result = dict(msg="the service group was not present")
|
||||
|
||||
# if the config has changed, save the config unless otherwise requested
|
||||
if changed and write_config:
|
||||
write_result = axapi_call(module, session_url + '&method=system.action.write_memory')
|
||||
if axapi_failure(write_result):
|
||||
module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg'])
|
||||
|
||||
# log out of the session nicely and exit
|
||||
axapi_call(module, session_url + '&method=session.close')
|
||||
module.exit_json(changed=changed, content=result)
|
||||
|
||||
# standard ansible module imports
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
from ansible.module_utils.a10 import *
|
||||
|
||||
main()
|
@ -1,299 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Ansible module to manage A10 Networks slb virtual server objects
|
||||
(c) 2014, Mischa Peters <mpeters@a10networks.com>
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: a10_virtual_server
|
||||
version_added: 1.8
|
||||
short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices
|
||||
description:
|
||||
- Manage slb virtual server objects on A10 Networks devices via aXAPI
|
||||
author: Mischa Peters
|
||||
notes:
|
||||
- Requires A10 Networks aXAPI 2.1
|
||||
requirements:
|
||||
- urllib2
|
||||
- re
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- hostname or ip of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
choices: []
|
||||
username:
|
||||
description:
|
||||
- admin account of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['user', 'admin']
|
||||
choices: []
|
||||
password:
|
||||
description:
|
||||
- admin password of your A10 Networks device
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['pass', 'pwd']
|
||||
choices: []
|
||||
virtual_server:
|
||||
description:
|
||||
- slb virtual server name
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['vip', 'virtual']
|
||||
choices: []
|
||||
virtual_server_ip:
|
||||
description:
|
||||
- slb virtual server ip address
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['ip', 'address']
|
||||
choices: []
|
||||
virtual_server_status:
|
||||
description:
|
||||
- slb virtual server status
|
||||
required: false
|
||||
default: enable
|
||||
aliases: ['status']
|
||||
choices: ['enabled', 'disabled']
|
||||
virtual_server_ports:
|
||||
description:
|
||||
- A list of ports to create for the virtual server. Each list item should be a
|
||||
dictionary which specifies the C(port:) and C(type:), but can also optionally
|
||||
specify the C(service_group:) as well as the C(status:). See the examples
|
||||
below for details. This parameter is required when C(state) is C(present).
|
||||
required: false
|
||||
write_config:
|
||||
description:
|
||||
- If C(yes), any changes will cause a write of the running configuration
|
||||
to non-volatile memory. This will save I(all) configuration changes,
|
||||
including those that may have been made manually or through other modules,
|
||||
so care should be taken when specifying C(yes).
|
||||
required: false
|
||||
default: "no"
|
||||
choices: ["yes", "no"]
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled devices using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a new virtual server
|
||||
- a10_virtual_server:
|
||||
host: a10.mydomain.com
|
||||
username: myadmin
|
||||
password: mypassword
|
||||
virtual_server: vserver1
|
||||
virtual_server_ip: 1.1.1.1
|
||||
virtual_server_ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
service_group: sg-80-tcp
|
||||
- port: 443
|
||||
protocol: HTTPS
|
||||
service_group: sg-443-https
|
||||
- port: 8080
|
||||
protocol: http
|
||||
status: disabled
|
||||
|
||||
'''
|
||||
|
||||
VALID_PORT_FIELDS = ['port', 'protocol', 'service_group', 'status']
|
||||
|
||||
def validate_ports(module, ports):
|
||||
for item in ports:
|
||||
for key in item:
|
||||
if key not in VALID_PORT_FIELDS:
|
||||
module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS)))
|
||||
|
||||
# validate the port number is present and an integer
|
||||
if 'port' in item:
|
||||
try:
|
||||
item['port'] = int(item['port'])
|
||||
except:
|
||||
module.fail_json(msg="port definitions must be integers")
|
||||
else:
|
||||
module.fail_json(msg="port definitions must define the port field")
|
||||
|
||||
# validate the port protocol is present, and convert it to
|
||||
# the internal API integer value (and validate it)
|
||||
if 'protocol' in item:
|
||||
protocol = axapi_get_vport_protocol(item['protocol'])
|
||||
if not protocol:
|
||||
module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_VPORT_PROTOCOLS))
|
||||
else:
|
||||
item['protocol'] = protocol
|
||||
else:
|
||||
module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_VPORT_PROTOCOLS))
|
||||
|
||||
# convert the status to the internal API integer value
|
||||
if 'status' in item:
|
||||
item['status'] = axapi_enabled_disabled(item['status'])
|
||||
else:
|
||||
item['status'] = 1
|
||||
|
||||
# ensure the service_group field is at least present
|
||||
if 'service_group' not in item:
|
||||
item['service_group'] = ''
|
||||
|
||||
def main():
|
||||
argument_spec = a10_argument_spec()
|
||||
argument_spec.update(url_argument_spec())
|
||||
argument_spec.update(
|
||||
dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
virtual_server=dict(type='str', aliases=['vip', 'virtual'], required=True),
|
||||
virtual_server_ip=dict(type='str', aliases=['ip', 'address'], required=True),
|
||||
virtual_server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']),
|
||||
virtual_server_ports=dict(type='list', required=True),
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
host = module.params['host']
|
||||
username = module.params['username']
|
||||
password = module.params['password']
|
||||
state = module.params['state']
|
||||
write_config = module.params['write_config']
|
||||
slb_virtual = module.params['virtual_server']
|
||||
slb_virtual_ip = module.params['virtual_server_ip']
|
||||
slb_virtual_status = module.params['virtual_server_status']
|
||||
slb_virtual_ports = module.params['virtual_server_ports']
|
||||
|
||||
if slb_virtual is None:
|
||||
module.fail_json(msg='virtual_server is required')
|
||||
|
||||
validate_ports(module, slb_virtual_ports)
|
||||
|
||||
axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host
|
||||
session_url = axapi_authenticate(module, axapi_base_url, username, password)
|
||||
|
||||
slb_virtual_data = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual}))
|
||||
slb_virtual_exists = not axapi_failure(slb_virtual_data)
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
json_post = {
|
||||
'virtual_server': {
|
||||
'name': slb_virtual,
|
||||
'address': slb_virtual_ip,
|
||||
'status': axapi_enabled_disabled(slb_virtual_status),
|
||||
'vport_list': slb_virtual_ports,
|
||||
}
|
||||
}
|
||||
|
||||
# before creating/updating we need to validate that any
|
||||
# service groups defined in the ports list exist since
|
||||
# since the API will still create port definitions for
|
||||
# them while indicating a failure occurred
|
||||
checked_service_groups = []
|
||||
for port in slb_virtual_ports:
|
||||
if 'service_group' in port and port['service_group'] not in checked_service_groups:
|
||||
# skip blank service group entries
|
||||
if port['service_group'] == '':
|
||||
continue
|
||||
result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': port['service_group']}))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg="the service group %s specified in the ports list does not exist" % port['service_group'])
|
||||
checked_service_groups.append(port['service_group'])
|
||||
|
||||
if not slb_virtual_exists:
|
||||
result = axapi_call(module, session_url + '&method=slb.virtual_server.create', json.dumps(json_post))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg'])
|
||||
changed = True
|
||||
else:
|
||||
def needs_update(src_ports, dst_ports):
|
||||
'''
|
||||
Checks to determine if the port definitions of the src_ports
|
||||
array are in or different from those in dst_ports. If there is
|
||||
a difference, this function returns true, otherwise false.
|
||||
'''
|
||||
for src_port in src_ports:
|
||||
found = False
|
||||
different = False
|
||||
for dst_port in dst_ports:
|
||||
if src_port['port'] == dst_port['port']:
|
||||
found = True
|
||||
for valid_field in VALID_PORT_FIELDS:
|
||||
if src_port[valid_field] != dst_port[valid_field]:
|
||||
different = True
|
||||
break
|
||||
if found or different:
|
||||
break
|
||||
if not found or different:
|
||||
return True
|
||||
# every port from the src exists in the dst, and none of them were different
|
||||
return False
|
||||
|
||||
defined_ports = slb_virtual_data.get('virtual_server', {}).get('vport_list', [])
|
||||
|
||||
# we check for a needed update both ways, in case ports
|
||||
# are missing from either the ones specified by the user
|
||||
# or from those on the device
|
||||
if needs_update(defined_ports, slb_virtual_ports) or needs_update(slb_virtual_ports, defined_ports):
|
||||
result = axapi_call(module, session_url + '&method=slb.virtual_server.update', json.dumps(json_post))
|
||||
if axapi_failure(result):
|
||||
module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg'])
|
||||
changed = True
|
||||
|
||||
# if we changed things, get the full info regarding
|
||||
# the service group for the return data below
|
||||
if changed:
|
||||
result = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual}))
|
||||
else:
|
||||
result = slb_virtual_data
|
||||
elif state == 'absent':
|
||||
if slb_virtual_exists:
|
||||
result = axapi_call(module, session_url + '&method=slb.virtual_server.delete', json.dumps({'name': slb_virtual}))
|
||||
changed = True
|
||||
else:
|
||||
result = dict(msg="the virtual server was not present")
|
||||
|
||||
# if the config has changed, save the config unless otherwise requested
|
||||
if changed and write_config:
|
||||
write_result = axapi_call(module, session_url + '&method=system.action.write_memory')
|
||||
if axapi_failure(write_result):
|
||||
module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg'])
|
||||
|
||||
# log out of the session nicely and exit
|
||||
axapi_call(module, session_url + '&method=session.close')
|
||||
module.exit_json(changed=changed, content=result)
|
||||
|
||||
# standard ansible module imports
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
from ansible.module_utils.a10 import *
|
||||
|
||||
main()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,464 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, serge van Ginderachter <serge@vanginderachter.be>
|
||||
# based on Matt Hite's bigip_pool module
|
||||
# (c) 2013, Matt Hite <mhite@hotmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: bigip_monitor_http
|
||||
short_description: "Manages F5 BIG-IP LTM http monitors"
|
||||
description:
|
||||
- "Manages F5 BIG-IP LTM monitors via iControl SOAP API"
|
||||
version_added: "1.4"
|
||||
author: Serge van Ginderachter
|
||||
notes:
|
||||
- "Requires BIG-IP software version >= 11"
|
||||
- "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
|
||||
- "Best run as a local_action in your playbook"
|
||||
- "Monitor API documentation: https://devcentral.f5.com/wiki/iControl.LocalLB__Monitor.ashx"
|
||||
requirements:
|
||||
- bigsuds
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- BIG-IP host
|
||||
required: true
|
||||
default: null
|
||||
user:
|
||||
description:
|
||||
- BIG-IP username
|
||||
required: true
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- BIG-IP password
|
||||
required: true
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- Monitor state
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: ['present', 'absent']
|
||||
name:
|
||||
description:
|
||||
- Monitor name
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['monitor']
|
||||
partition:
|
||||
description:
|
||||
- Partition for the monitor
|
||||
required: false
|
||||
default: 'Common'
|
||||
parent:
|
||||
description:
|
||||
- The parent template of this monitor template
|
||||
required: false
|
||||
default: 'http'
|
||||
parent_partition:
|
||||
description:
|
||||
- Partition for the parent monitor
|
||||
required: false
|
||||
default: 'Common'
|
||||
send:
|
||||
description:
|
||||
- The send string for the monitor call
|
||||
required: true
|
||||
default: none
|
||||
receive:
|
||||
description:
|
||||
- The receive string for the monitor call
|
||||
required: true
|
||||
default: none
|
||||
receive_disable:
|
||||
description:
|
||||
- The receive disable string for the monitor call
|
||||
required: true
|
||||
default: none
|
||||
ip:
|
||||
description:
|
||||
- IP address part of the ipport definition. The default API setting
|
||||
is "0.0.0.0".
|
||||
required: false
|
||||
default: none
|
||||
port:
|
||||
description:
|
||||
- port address part op the ipport definition. The default API
|
||||
setting is 0.
|
||||
required: false
|
||||
default: none
|
||||
interval:
|
||||
description:
|
||||
- The interval specifying how frequently the monitor instance
|
||||
of this template will run. By default, this interval is used for up and
|
||||
down states. The default API setting is 5.
|
||||
required: false
|
||||
default: none
|
||||
timeout:
|
||||
description:
|
||||
- The number of seconds in which the node or service must respond to
|
||||
the monitor request. If the target responds within the set time
|
||||
period, it is considered up. If the target does not respond within
|
||||
the set time period, it is considered down. You can change this
|
||||
number to any number you want, however, it should be 3 times the
|
||||
interval number of seconds plus 1 second. The default API setting
|
||||
is 16.
|
||||
required: false
|
||||
default: none
|
||||
time_until_up:
|
||||
description:
|
||||
- Specifies the amount of time in seconds after the first successful
|
||||
response before a node will be marked up. A value of 0 will cause a
|
||||
node to be marked up immediately after a valid response is received
|
||||
from the node. The default API setting is 0.
|
||||
required: false
|
||||
default: none
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: BIGIP F5 | Create HTTP Monitor
|
||||
local_action:
|
||||
module: bigip_monitor_http
|
||||
state: present
|
||||
server: "{{ f5server }}"
|
||||
user: "{{ f5user }}"
|
||||
password: "{{ f5password }}"
|
||||
name: "{{ item.monitorname }}"
|
||||
send: "{{ item.send }}"
|
||||
receive: "{{ item.receive }}"
|
||||
with_items: f5monitors
|
||||
- name: BIGIP F5 | Remove HTTP Monitor
|
||||
local_action:
|
||||
module: bigip_monitor_http
|
||||
state: absent
|
||||
server: "{{ f5server }}"
|
||||
user: "{{ f5user }}"
|
||||
password: "{{ f5password }}"
|
||||
name: "{{ monitorname }}"
|
||||
'''
|
||||
|
||||
try:
|
||||
import bigsuds
|
||||
except ImportError:
|
||||
bigsuds_found = False
|
||||
else:
|
||||
bigsuds_found = True
|
||||
|
||||
TEMPLATE_TYPE = 'TTYPE_HTTP'
|
||||
DEFAULT_PARENT_TYPE = 'http'
|
||||
|
||||
|
||||
# ===========================================
|
||||
# bigip_monitor module generic methods.
|
||||
# these should be re-useable for other monitor types
|
||||
#
|
||||
|
||||
def bigip_api(bigip, user, password):
|
||||
|
||||
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
|
||||
return api
|
||||
|
||||
|
||||
def check_monitor_exists(module, api, monitor, parent):
|
||||
|
||||
# hack to determine if monitor exists
|
||||
result = False
|
||||
try:
|
||||
ttype = api.LocalLB.Monitor.get_template_type(template_names=[monitor])[0]
|
||||
parent2 = api.LocalLB.Monitor.get_parent_template(template_names=[monitor])[0]
|
||||
if ttype == TEMPLATE_TYPE and parent == parent2:
|
||||
result = True
|
||||
else:
|
||||
module.fail_json(msg='Monitor already exists, but has a different type (%s) or parent(%s)' % (ttype, parent))
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
|
||||
def create_monitor(api, monitor, template_attributes):
|
||||
|
||||
try:
|
||||
api.LocalLB.Monitor.create_template(templates=[{'template_name': monitor, 'template_type': TEMPLATE_TYPE}], template_attributes=[template_attributes])
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "already exists" in str(e):
|
||||
return False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def delete_monitor(api, monitor):
|
||||
|
||||
try:
|
||||
api.LocalLB.Monitor.delete_template(template_names=[monitor])
|
||||
except bigsuds.OperationFailed, e:
|
||||
# maybe it was deleted since we checked
|
||||
if "was not found" in str(e):
|
||||
return False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def check_string_property(api, monitor, str_property):
|
||||
|
||||
try:
|
||||
return str_property == api.LocalLB.Monitor.get_template_string_property([monitor], [str_property['type']])[0]
|
||||
except bigsuds.OperationFailed, e:
|
||||
# happens in check mode if not created yet
|
||||
if "was not found" in str(e):
|
||||
return True
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
|
||||
|
||||
def set_string_property(api, monitor, str_property):
|
||||
|
||||
api.LocalLB.Monitor.set_template_string_property(template_names=[monitor], values=[str_property])
|
||||
|
||||
|
||||
def check_integer_property(api, monitor, int_property):
|
||||
|
||||
try:
|
||||
return int_property == api.LocalLB.Monitor.get_template_integer_property([monitor], [int_property['type']])[0]
|
||||
except bigsuds.OperationFailed, e:
|
||||
# happens in check mode if not created yet
|
||||
if "was not found" in str(e):
|
||||
return True
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
|
||||
|
||||
|
||||
def set_integer_property(api, monitor, int_property):
|
||||
|
||||
api.LocalLB.Monitor.set_template_int_property(template_names=[monitor], values=[int_property])
|
||||
|
||||
|
||||
def update_monitor_properties(api, module, monitor, template_string_properties, template_integer_properties):
|
||||
|
||||
changed = False
|
||||
for str_property in template_string_properties:
|
||||
if str_property['value'] is not None and not check_string_property(api, monitor, str_property):
|
||||
if not module.check_mode:
|
||||
set_string_property(api, monitor, str_property)
|
||||
changed = True
|
||||
for int_property in template_integer_properties:
|
||||
if int_property['value'] is not None and not check_integer_property(api, monitor, int_property):
|
||||
if not module.check_mode:
|
||||
set_integer_property(api, monitor, int_property)
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def get_ipport(api, monitor):
|
||||
|
||||
return api.LocalLB.Monitor.get_template_destination(template_names=[monitor])[0]
|
||||
|
||||
|
||||
def set_ipport(api, monitor, ipport):
|
||||
|
||||
try:
|
||||
api.LocalLB.Monitor.set_template_destination(template_names=[monitor], destinations=[ipport])
|
||||
return True, ""
|
||||
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "Cannot modify the address type of monitor" in str(e):
|
||||
return False, "Cannot modify the address type of monitor if already assigned to a pool."
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
|
||||
# ===========================================
|
||||
# main loop
|
||||
#
|
||||
# writing a module for other monitor types should
|
||||
# only need an updated main() (and monitor specific functions)
|
||||
|
||||
def main():
|
||||
|
||||
# begin monitor specific stuff
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
server = dict(required=True),
|
||||
user = dict(required=True),
|
||||
password = dict(required=True),
|
||||
partition = dict(default='Common'),
|
||||
state = dict(default='present', choices=['present', 'absent']),
|
||||
name = dict(required=True),
|
||||
parent = dict(default=DEFAULT_PARENT_TYPE),
|
||||
parent_partition = dict(default='Common'),
|
||||
send = dict(required=False),
|
||||
receive = dict(required=False),
|
||||
receive_disable = dict(required=False),
|
||||
ip = dict(required=False),
|
||||
port = dict(required=False, type='int'),
|
||||
interval = dict(required=False, type='int'),
|
||||
timeout = dict(required=False, type='int'),
|
||||
time_until_up = dict(required=False, type='int', default=0)
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
server = module.params['server']
|
||||
user = module.params['user']
|
||||
password = module.params['password']
|
||||
partition = module.params['partition']
|
||||
parent_partition = module.params['parent_partition']
|
||||
state = module.params['state']
|
||||
name = module.params['name']
|
||||
parent = "/%s/%s" % (parent_partition, module.params['parent'])
|
||||
monitor = "/%s/%s" % (partition, name)
|
||||
send = module.params['send']
|
||||
receive = module.params['receive']
|
||||
receive_disable = module.params['receive_disable']
|
||||
ip = module.params['ip']
|
||||
port = module.params['port']
|
||||
interval = module.params['interval']
|
||||
timeout = module.params['timeout']
|
||||
time_until_up = module.params['time_until_up']
|
||||
|
||||
# end monitor specific stuff
|
||||
|
||||
if not bigsuds_found:
|
||||
module.fail_json(msg="the python bigsuds module is required")
|
||||
api = bigip_api(server, user, password)
|
||||
monitor_exists = check_monitor_exists(module, api, monitor, parent)
|
||||
|
||||
|
||||
# ipport is a special setting
|
||||
if monitor_exists: # make sure to not update current settings if not asked
|
||||
cur_ipport = get_ipport(api, monitor)
|
||||
if ip is None:
|
||||
ip = cur_ipport['ipport']['address']
|
||||
if port is None:
|
||||
port = cur_ipport['ipport']['port']
|
||||
else: # use API defaults if not defined to create it
|
||||
if interval is None:
|
||||
interval = 5
|
||||
if timeout is None:
|
||||
timeout = 16
|
||||
if ip is None:
|
||||
ip = '0.0.0.0'
|
||||
if port is None:
|
||||
port = 0
|
||||
if send is None:
|
||||
send = ''
|
||||
if receive is None:
|
||||
receive = ''
|
||||
if receive_disable is None:
|
||||
receive_disable = ''
|
||||
|
||||
# define and set address type
|
||||
if ip == '0.0.0.0' and port == 0:
|
||||
address_type = 'ATYPE_STAR_ADDRESS_STAR_PORT'
|
||||
elif ip == '0.0.0.0' and port != 0:
|
||||
address_type = 'ATYPE_STAR_ADDRESS_EXPLICIT_PORT'
|
||||
elif ip != '0.0.0.0' and port != 0:
|
||||
address_type = 'ATYPE_EXPLICIT_ADDRESS_EXPLICIT_PORT'
|
||||
else:
|
||||
address_type = 'ATYPE_UNSET'
|
||||
|
||||
ipport = {'address_type': address_type,
|
||||
'ipport': {'address': ip,
|
||||
'port': port}}
|
||||
|
||||
template_attributes = {'parent_template': parent,
|
||||
'interval': interval,
|
||||
'timeout': timeout,
|
||||
'dest_ipport': ipport,
|
||||
'is_read_only': False,
|
||||
'is_directly_usable': True}
|
||||
|
||||
# monitor specific stuff
|
||||
template_string_properties = [{'type': 'STYPE_SEND',
|
||||
'value': send},
|
||||
{'type': 'STYPE_RECEIVE',
|
||||
'value': receive},
|
||||
{'type': 'STYPE_RECEIVE_DRAIN',
|
||||
'value': receive_disable}]
|
||||
|
||||
template_integer_properties = [{'type': 'ITYPE_INTERVAL',
|
||||
'value': interval},
|
||||
{'type': 'ITYPE_TIMEOUT',
|
||||
'value': timeout},
|
||||
{'type': 'ITYPE_TIME_UNTIL_UP',
|
||||
'value': time_until_up}]
|
||||
|
||||
# main logic, monitor generic
|
||||
|
||||
try:
|
||||
result = {'changed': False} # default
|
||||
|
||||
|
||||
if state == 'absent':
|
||||
if monitor_exists:
|
||||
if not module.check_mode:
|
||||
# possible race condition if same task
|
||||
# on other node deleted it first
|
||||
result['changed'] |= delete_monitor(api, monitor)
|
||||
else:
|
||||
result['changed'] |= True
|
||||
|
||||
else: # state present
|
||||
## check for monitor itself
|
||||
if not monitor_exists: # create it
|
||||
if not module.check_mode:
|
||||
# again, check changed status here b/c race conditions
|
||||
# if other task already created it
|
||||
result['changed'] |= create_monitor(api, monitor, template_attributes)
|
||||
else:
|
||||
result['changed'] |= True
|
||||
|
||||
## check for monitor parameters
|
||||
# whether it already existed, or was just created, now update
|
||||
# the update functions need to check for check mode but
|
||||
# cannot update settings if it doesn't exist which happens in check mode
|
||||
result['changed'] |= update_monitor_properties(api, module, monitor,
|
||||
template_string_properties,
|
||||
template_integer_properties)
|
||||
|
||||
# we just have to update the ipport if monitor already exists and it's different
|
||||
if monitor_exists and cur_ipport != ipport:
|
||||
set_ipport(api, monitor, ipport)
|
||||
result['changed'] |= True
|
||||
#else: monitor doesn't exist (check mode) or ipport is already ok
|
||||
|
||||
|
||||
except Exception, e:
|
||||
module.fail_json(msg="received exception: %s" % e)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
|
@ -1,489 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, serge van Ginderachter <serge@vanginderachter.be>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: bigip_monitor_tcp
|
||||
short_description: "Manages F5 BIG-IP LTM tcp monitors"
|
||||
description:
|
||||
- "Manages F5 BIG-IP LTM tcp monitors via iControl SOAP API"
|
||||
version_added: "1.4"
|
||||
author: Serge van Ginderachter
|
||||
notes:
|
||||
- "Requires BIG-IP software version >= 11"
|
||||
- "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
|
||||
- "Best run as a local_action in your playbook"
|
||||
- "Monitor API documentation: https://devcentral.f5.com/wiki/iControl.LocalLB__Monitor.ashx"
|
||||
requirements:
|
||||
- bigsuds
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- BIG-IP host
|
||||
required: true
|
||||
default: null
|
||||
user:
|
||||
description:
|
||||
- BIG-IP username
|
||||
required: true
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- BIG-IP password
|
||||
required: true
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- Monitor state
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: ['present', 'absent']
|
||||
name:
|
||||
description:
|
||||
- Monitor name
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['monitor']
|
||||
partition:
|
||||
description:
|
||||
- Partition for the monitor
|
||||
required: false
|
||||
default: 'Common'
|
||||
type:
|
||||
description:
|
||||
- The template type of this monitor template
|
||||
required: false
|
||||
default: 'tcp'
|
||||
choices: [ 'TTYPE_TCP', 'TTYPE_TCP_ECHO', 'TTYPE_TCP_HALF_OPEN']
|
||||
parent:
|
||||
description:
|
||||
- The parent template of this monitor template
|
||||
required: false
|
||||
default: 'tcp'
|
||||
choices: [ 'tcp', 'tcp_echo', 'tcp_half_open']
|
||||
parent_partition:
|
||||
description:
|
||||
- Partition for the parent monitor
|
||||
required: false
|
||||
default: 'Common'
|
||||
send:
|
||||
description:
|
||||
- The send string for the monitor call
|
||||
required: true
|
||||
default: none
|
||||
receive:
|
||||
description:
|
||||
- The receive string for the monitor call
|
||||
required: true
|
||||
default: none
|
||||
ip:
|
||||
description:
|
||||
- IP address part of the ipport definition. The default API setting
|
||||
is "0.0.0.0".
|
||||
required: false
|
||||
default: none
|
||||
port:
|
||||
description:
|
||||
- port address part op the ipport definition. The default API
|
||||
setting is 0.
|
||||
required: false
|
||||
default: none
|
||||
interval:
|
||||
description:
|
||||
- The interval specifying how frequently the monitor instance
|
||||
of this template will run. By default, this interval is used for up and
|
||||
down states. The default API setting is 5.
|
||||
required: false
|
||||
default: none
|
||||
timeout:
|
||||
description:
|
||||
- The number of seconds in which the node or service must respond to
|
||||
the monitor request. If the target responds within the set time
|
||||
period, it is considered up. If the target does not respond within
|
||||
the set time period, it is considered down. You can change this
|
||||
number to any number you want, however, it should be 3 times the
|
||||
interval number of seconds plus 1 second. The default API setting
|
||||
is 16.
|
||||
required: false
|
||||
default: none
|
||||
time_until_up:
|
||||
description:
|
||||
- Specifies the amount of time in seconds after the first successful
|
||||
response before a node will be marked up. A value of 0 will cause a
|
||||
node to be marked up immediately after a valid response is received
|
||||
from the node. The default API setting is 0.
|
||||
required: false
|
||||
default: none
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
- name: BIGIP F5 | Create TCP Monitor
|
||||
local_action:
|
||||
module: bigip_monitor_tcp
|
||||
state: present
|
||||
server: "{{ f5server }}"
|
||||
user: "{{ f5user }}"
|
||||
password: "{{ f5password }}"
|
||||
name: "{{ item.monitorname }}"
|
||||
type: tcp
|
||||
send: "{{ item.send }}"
|
||||
receive: "{{ item.receive }}"
|
||||
with_items: f5monitors-tcp
|
||||
- name: BIGIP F5 | Create TCP half open Monitor
|
||||
local_action:
|
||||
module: bigip_monitor_tcp
|
||||
state: present
|
||||
server: "{{ f5server }}"
|
||||
user: "{{ f5user }}"
|
||||
password: "{{ f5password }}"
|
||||
name: "{{ item.monitorname }}"
|
||||
type: tcp
|
||||
send: "{{ item.send }}"
|
||||
receive: "{{ item.receive }}"
|
||||
with_items: f5monitors-halftcp
|
||||
- name: BIGIP F5 | Remove TCP Monitor
|
||||
local_action:
|
||||
module: bigip_monitor_tcp
|
||||
state: absent
|
||||
server: "{{ f5server }}"
|
||||
user: "{{ f5user }}"
|
||||
password: "{{ f5password }}"
|
||||
name: "{{ monitorname }}"
|
||||
with_flattened:
|
||||
- f5monitors-tcp
|
||||
- f5monitors-halftcp
|
||||
|
||||
'''
|
||||
|
||||
try:
|
||||
import bigsuds
|
||||
except ImportError:
|
||||
bigsuds_found = False
|
||||
else:
|
||||
bigsuds_found = True
|
||||
|
||||
TEMPLATE_TYPE = DEFAULT_TEMPLATE_TYPE = 'TTYPE_TCP'
|
||||
TEMPLATE_TYPE_CHOICES = ['tcp', 'tcp_echo', 'tcp_half_open']
|
||||
DEFAULT_PARENT = DEFAULT_TEMPLATE_TYPE_CHOICE = DEFAULT_TEMPLATE_TYPE.replace('TTYPE_', '').lower()
|
||||
|
||||
|
||||
# ===========================================
|
||||
# bigip_monitor module generic methods.
|
||||
# these should be re-useable for other monitor types
|
||||
#
|
||||
|
||||
def bigip_api(bigip, user, password):
|
||||
|
||||
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
|
||||
return api
|
||||
|
||||
|
||||
def check_monitor_exists(module, api, monitor, parent):
|
||||
|
||||
# hack to determine if monitor exists
|
||||
result = False
|
||||
try:
|
||||
ttype = api.LocalLB.Monitor.get_template_type(template_names=[monitor])[0]
|
||||
parent2 = api.LocalLB.Monitor.get_parent_template(template_names=[monitor])[0]
|
||||
if ttype == TEMPLATE_TYPE and parent == parent2:
|
||||
result = True
|
||||
else:
|
||||
module.fail_json(msg='Monitor already exists, but has a different type (%s) or parent(%s)' % (ttype, parent))
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
|
||||
def create_monitor(api, monitor, template_attributes):
|
||||
|
||||
try:
|
||||
api.LocalLB.Monitor.create_template(templates=[{'template_name': monitor, 'template_type': TEMPLATE_TYPE}], template_attributes=[template_attributes])
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "already exists" in str(e):
|
||||
return False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def delete_monitor(api, monitor):
|
||||
|
||||
try:
|
||||
api.LocalLB.Monitor.delete_template(template_names=[monitor])
|
||||
except bigsuds.OperationFailed, e:
|
||||
# maybe it was deleted since we checked
|
||||
if "was not found" in str(e):
|
||||
return False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def check_string_property(api, monitor, str_property):
|
||||
|
||||
try:
|
||||
return str_property == api.LocalLB.Monitor.get_template_string_property([monitor], [str_property['type']])[0]
|
||||
except bigsuds.OperationFailed, e:
|
||||
# happens in check mode if not created yet
|
||||
if "was not found" in str(e):
|
||||
return True
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def set_string_property(api, monitor, str_property):
|
||||
|
||||
api.LocalLB.Monitor.set_template_string_property(template_names=[monitor], values=[str_property])
|
||||
|
||||
|
||||
def check_integer_property(api, monitor, int_property):
|
||||
|
||||
try:
|
||||
return int_property == api.LocalLB.Monitor.get_template_integer_property([monitor], [int_property['type']])[0]
|
||||
except bigsuds.OperationFailed, e:
|
||||
# happens in check mode if not created yet
|
||||
if "was not found" in str(e):
|
||||
return True
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def set_integer_property(api, monitor, int_property):
|
||||
|
||||
api.LocalLB.Monitor.set_template_int_property(template_names=[monitor], values=[int_property])
|
||||
|
||||
|
||||
def update_monitor_properties(api, module, monitor, template_string_properties, template_integer_properties):
|
||||
|
||||
changed = False
|
||||
for str_property in template_string_properties:
|
||||
if str_property['value'] is not None and not check_string_property(api, monitor, str_property):
|
||||
if not module.check_mode:
|
||||
set_string_property(api, monitor, str_property)
|
||||
changed = True
|
||||
for int_property in template_integer_properties:
|
||||
if int_property['value'] is not None and not check_integer_property(api, monitor, int_property):
|
||||
if not module.check_mode:
|
||||
set_integer_property(api, monitor, int_property)
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def get_ipport(api, monitor):
|
||||
|
||||
return api.LocalLB.Monitor.get_template_destination(template_names=[monitor])[0]
|
||||
|
||||
|
||||
def set_ipport(api, monitor, ipport):
|
||||
|
||||
try:
|
||||
api.LocalLB.Monitor.set_template_destination(template_names=[monitor], destinations=[ipport])
|
||||
return True, ""
|
||||
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "Cannot modify the address type of monitor" in str(e):
|
||||
return False, "Cannot modify the address type of monitor if already assigned to a pool."
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
|
||||
# ===========================================
|
||||
# main loop
|
||||
#
|
||||
# writing a module for other monitor types should
|
||||
# only need an updated main() (and monitor specific functions)
|
||||
|
||||
def main():
|
||||
|
||||
# begin monitor specific stuff
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
server = dict(required=True),
|
||||
user = dict(required=True),
|
||||
password = dict(required=True),
|
||||
partition = dict(default='Common'),
|
||||
state = dict(default='present', choices=['present', 'absent']),
|
||||
name = dict(required=True),
|
||||
type = dict(default=DEFAULT_TEMPLATE_TYPE_CHOICE, choices=TEMPLATE_TYPE_CHOICES),
|
||||
parent = dict(default=DEFAULT_PARENT),
|
||||
parent_partition = dict(default='Common'),
|
||||
send = dict(required=False),
|
||||
receive = dict(required=False),
|
||||
ip = dict(required=False),
|
||||
port = dict(required=False, type='int'),
|
||||
interval = dict(required=False, type='int'),
|
||||
timeout = dict(required=False, type='int'),
|
||||
time_until_up = dict(required=False, type='int', default=0)
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
server = module.params['server']
|
||||
user = module.params['user']
|
||||
password = module.params['password']
|
||||
partition = module.params['partition']
|
||||
parent_partition = module.params['parent_partition']
|
||||
state = module.params['state']
|
||||
name = module.params['name']
|
||||
type = 'TTYPE_' + module.params['type'].upper()
|
||||
parent = "/%s/%s" % (parent_partition, module.params['parent'])
|
||||
monitor = "/%s/%s" % (partition, name)
|
||||
send = module.params['send']
|
||||
receive = module.params['receive']
|
||||
ip = module.params['ip']
|
||||
port = module.params['port']
|
||||
interval = module.params['interval']
|
||||
timeout = module.params['timeout']
|
||||
time_until_up = module.params['time_until_up']
|
||||
|
||||
# tcp monitor has multiple types, so overrule
|
||||
global TEMPLATE_TYPE
|
||||
TEMPLATE_TYPE = type
|
||||
|
||||
# end monitor specific stuff
|
||||
|
||||
if not bigsuds_found:
|
||||
module.fail_json(msg="the python bigsuds module is required")
|
||||
api = bigip_api(server, user, password)
|
||||
monitor_exists = check_monitor_exists(module, api, monitor, parent)
|
||||
|
||||
|
||||
# ipport is a special setting
|
||||
if monitor_exists: # make sure to not update current settings if not asked
|
||||
cur_ipport = get_ipport(api, monitor)
|
||||
if ip is None:
|
||||
ip = cur_ipport['ipport']['address']
|
||||
if port is None:
|
||||
port = cur_ipport['ipport']['port']
|
||||
else: # use API defaults if not defined to create it
|
||||
if interval is None:
|
||||
interval = 5
|
||||
if timeout is None:
|
||||
timeout = 16
|
||||
if ip is None:
|
||||
ip = '0.0.0.0'
|
||||
if port is None:
|
||||
port = 0
|
||||
if send is None:
|
||||
send = ''
|
||||
if receive is None:
|
||||
receive = ''
|
||||
|
||||
# define and set address type
|
||||
if ip == '0.0.0.0' and port == 0:
|
||||
address_type = 'ATYPE_STAR_ADDRESS_STAR_PORT'
|
||||
elif ip == '0.0.0.0' and port != 0:
|
||||
address_type = 'ATYPE_STAR_ADDRESS_EXPLICIT_PORT'
|
||||
elif ip != '0.0.0.0' and port != 0:
|
||||
address_type = 'ATYPE_EXPLICIT_ADDRESS_EXPLICIT_PORT'
|
||||
else:
|
||||
address_type = 'ATYPE_UNSET'
|
||||
|
||||
ipport = {'address_type': address_type,
|
||||
'ipport': {'address': ip,
|
||||
'port': port}}
|
||||
|
||||
template_attributes = {'parent_template': parent,
|
||||
'interval': interval,
|
||||
'timeout': timeout,
|
||||
'dest_ipport': ipport,
|
||||
'is_read_only': False,
|
||||
'is_directly_usable': True}
|
||||
|
||||
# monitor specific stuff
|
||||
if type == 'TTYPE_TCP':
|
||||
template_string_properties = [{'type': 'STYPE_SEND',
|
||||
'value': send},
|
||||
{'type': 'STYPE_RECEIVE',
|
||||
'value': receive}]
|
||||
else:
|
||||
template_string_properties = []
|
||||
|
||||
template_integer_properties = [{'type': 'ITYPE_INTERVAL',
|
||||
'value': interval},
|
||||
{'type': 'ITYPE_TIMEOUT',
|
||||
'value': timeout},
|
||||
{'type': 'ITYPE_TIME_UNTIL_UP',
|
||||
'value': interval}]
|
||||
|
||||
# main logic, monitor generic
|
||||
|
||||
try:
|
||||
result = {'changed': False} # default
|
||||
|
||||
|
||||
if state == 'absent':
|
||||
if monitor_exists:
|
||||
if not module.check_mode:
|
||||
# possible race condition if same task
|
||||
# on other node deleted it first
|
||||
result['changed'] |= delete_monitor(api, monitor)
|
||||
else:
|
||||
result['changed'] |= True
|
||||
|
||||
else: # state present
|
||||
## check for monitor itself
|
||||
if not monitor_exists: # create it
|
||||
if not module.check_mode:
|
||||
# again, check changed status here b/c race conditions
|
||||
# if other task already created it
|
||||
result['changed'] |= create_monitor(api, monitor, template_attributes)
|
||||
else:
|
||||
result['changed'] |= True
|
||||
|
||||
## check for monitor parameters
|
||||
# whether it already existed, or was just created, now update
|
||||
# the update functions need to check for check mode but
|
||||
# cannot update settings if it doesn't exist which happens in check mode
|
||||
if monitor_exists and not module.check_mode:
|
||||
result['changed'] |= update_monitor_properties(api, module, monitor,
|
||||
template_string_properties,
|
||||
template_integer_properties)
|
||||
# else assume nothing changed
|
||||
|
||||
# we just have to update the ipport if monitor already exists and it's different
|
||||
if monitor_exists and cur_ipport != ipport:
|
||||
set_ipport(api, monitor, ipport)
|
||||
result['changed'] |= True
|
||||
#else: monitor doesn't exist (check mode) or ipport is already ok
|
||||
|
||||
|
||||
except Exception, e:
|
||||
module.fail_json(msg="received exception: %s" % e)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
|
@ -1,294 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Matt Hite <mhite@hotmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: bigip_node
|
||||
short_description: "Manages F5 BIG-IP LTM nodes"
|
||||
description:
|
||||
- "Manages F5 BIG-IP LTM nodes via iControl SOAP API"
|
||||
version_added: "1.4"
|
||||
author: Matt Hite
|
||||
notes:
|
||||
- "Requires BIG-IP software version >= 11"
|
||||
- "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
|
||||
- "Best run as a local_action in your playbook"
|
||||
requirements:
|
||||
- bigsuds
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- BIG-IP host
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
user:
|
||||
description:
|
||||
- BIG-IP username
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
password:
|
||||
description:
|
||||
- BIG-IP password
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- Pool member state
|
||||
required: true
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
aliases: []
|
||||
partition:
|
||||
description:
|
||||
- Partition
|
||||
required: false
|
||||
default: 'Common'
|
||||
choices: []
|
||||
aliases: []
|
||||
name:
|
||||
description:
|
||||
- "Node name"
|
||||
required: false
|
||||
default: null
|
||||
choices: []
|
||||
host:
|
||||
description:
|
||||
- "Node IP. Required when state=present and node does not exist. Error when state=absent."
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: ['address', 'ip']
|
||||
description:
|
||||
description:
|
||||
- "Node description."
|
||||
required: false
|
||||
default: null
|
||||
choices: []
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
## playbook task examples:
|
||||
|
||||
---
|
||||
# file bigip-test.yml
|
||||
# ...
|
||||
- hosts: bigip-test
|
||||
tasks:
|
||||
- name: Add node
|
||||
local_action: >
|
||||
bigip_node
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=present
|
||||
partition=matthite
|
||||
host="{{ ansible_default_ipv4["address"] }}"
|
||||
name="{{ ansible_default_ipv4["address"] }}"
|
||||
|
||||
# Note that the BIG-IP automatically names the node using the
|
||||
# IP address specified in previous play's host parameter.
|
||||
# Future plays referencing this node no longer use the host
|
||||
# parameter but instead use the name parameter.
|
||||
# Alternatively, you could have specified a name with the
|
||||
# name parameter when state=present.
|
||||
|
||||
- name: Modify node description
|
||||
local_action: >
|
||||
bigip_node
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=present
|
||||
partition=matthite
|
||||
name="{{ ansible_default_ipv4["address"] }}"
|
||||
description="Our best server yet"
|
||||
|
||||
- name: Delete node
|
||||
local_action: >
|
||||
bigip_node
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=absent
|
||||
partition=matthite
|
||||
name="{{ ansible_default_ipv4["address"] }}"
|
||||
|
||||
'''
|
||||
|
||||
try:
|
||||
import bigsuds
|
||||
except ImportError:
|
||||
bigsuds_found = False
|
||||
else:
|
||||
bigsuds_found = True
|
||||
|
||||
# ==========================
|
||||
# bigip_node module specific
|
||||
#
|
||||
|
||||
def bigip_api(bigip, user, password):
|
||||
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
|
||||
return api
|
||||
|
||||
def node_exists(api, address):
|
||||
# hack to determine if node exists
|
||||
result = False
|
||||
try:
|
||||
api.LocalLB.NodeAddressV2.get_object_status(nodes=[address])
|
||||
result = True
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
def create_node_address(api, address, name):
|
||||
try:
|
||||
api.LocalLB.NodeAddressV2.create(nodes=[name], addresses=[address], limits=[0])
|
||||
result = True
|
||||
desc = ""
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "already exists" in str(e):
|
||||
result = False
|
||||
desc = "referenced name or IP already in use"
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return (result, desc)
|
||||
|
||||
def get_node_address(api, name):
|
||||
return api.LocalLB.NodeAddressV2.get_address(nodes=[name])[0]
|
||||
|
||||
def delete_node_address(api, address):
|
||||
try:
|
||||
api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
|
||||
result = True
|
||||
desc = ""
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "is referenced by a member of pool" in str(e):
|
||||
result = False
|
||||
desc = "node referenced by pool"
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return (result, desc)
|
||||
|
||||
def set_node_description(api, name, description):
|
||||
api.LocalLB.NodeAddressV2.set_description(nodes=[name],
|
||||
descriptions=[description])
|
||||
|
||||
def get_node_description(api, name):
|
||||
return api.LocalLB.NodeAddressV2.get_description(nodes=[name])[0]
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
server = dict(type='str', required=True),
|
||||
user = dict(type='str', required=True),
|
||||
password = dict(type='str', required=True),
|
||||
state = dict(type='str', default='present', choices=['present', 'absent']),
|
||||
partition = dict(type='str', default='Common'),
|
||||
name = dict(type='str', required=True),
|
||||
host = dict(type='str', aliases=['address', 'ip']),
|
||||
description = dict(type='str')
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not bigsuds_found:
|
||||
module.fail_json(msg="the python bigsuds module is required")
|
||||
|
||||
server = module.params['server']
|
||||
user = module.params['user']
|
||||
password = module.params['password']
|
||||
state = module.params['state']
|
||||
partition = module.params['partition']
|
||||
host = module.params['host']
|
||||
name = module.params['name']
|
||||
address = "/%s/%s" % (partition, name)
|
||||
description = module.params['description']
|
||||
|
||||
if state == 'absent' and host is not None:
|
||||
module.fail_json(msg="host parameter invalid when state=absent")
|
||||
|
||||
try:
|
||||
api = bigip_api(server, user, password)
|
||||
result = {'changed': False} # default
|
||||
|
||||
if state == 'absent':
|
||||
if node_exists(api, address):
|
||||
if not module.check_mode:
|
||||
deleted, desc = delete_node_address(api, address)
|
||||
if not deleted:
|
||||
module.fail_json(msg="unable to delete: %s" % desc)
|
||||
else:
|
||||
result = {'changed': True}
|
||||
else:
|
||||
# check-mode return value
|
||||
result = {'changed': True}
|
||||
|
||||
elif state == 'present':
|
||||
if not node_exists(api, address):
|
||||
if host is None:
|
||||
module.fail_json(msg="host parameter required when " \
|
||||
"state=present and node does not exist")
|
||||
if not module.check_mode:
|
||||
created, desc = create_node_address(api, address=host, name=address)
|
||||
if not created:
|
||||
module.fail_json(msg="unable to create: %s" % desc)
|
||||
else:
|
||||
result = {'changed': True}
|
||||
if description is not None:
|
||||
set_node_description(api, address, description)
|
||||
result = {'changed': True}
|
||||
else:
|
||||
# check-mode return value
|
||||
result = {'changed': True}
|
||||
else:
|
||||
# node exists -- potentially modify attributes
|
||||
if host is not None:
|
||||
if get_node_address(api, address) != host:
|
||||
module.fail_json(msg="Changing the node address is " \
|
||||
"not supported by the API; " \
|
||||
"delete and recreate the node.")
|
||||
if description is not None:
|
||||
if get_node_description(api, address) != description:
|
||||
if not module.check_mode:
|
||||
set_node_description(api, address, description)
|
||||
result = {'changed': True}
|
||||
|
||||
except Exception, e:
|
||||
module.fail_json(msg="received exception: %s" % e)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
|
@ -1,536 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Matt Hite <mhite@hotmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: bigip_pool
|
||||
short_description: "Manages F5 BIG-IP LTM pools"
|
||||
description:
|
||||
- "Manages F5 BIG-IP LTM pools via iControl SOAP API"
|
||||
version_added: "1.2"
|
||||
author: Matt Hite
|
||||
notes:
|
||||
- "Requires BIG-IP software version >= 11"
|
||||
- "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
|
||||
- "Best run as a local_action in your playbook"
|
||||
requirements:
|
||||
- bigsuds
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- BIG-IP host
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
user:
|
||||
description:
|
||||
- BIG-IP username
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
password:
|
||||
description:
|
||||
- BIG-IP password
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- Pool/pool member state
|
||||
required: false
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
aliases: []
|
||||
name:
|
||||
description:
|
||||
- Pool name
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: ['pool']
|
||||
partition:
|
||||
description:
|
||||
- Partition of pool/pool member
|
||||
required: false
|
||||
default: 'Common'
|
||||
choices: []
|
||||
aliases: []
|
||||
lb_method:
|
||||
description:
|
||||
- Load balancing method
|
||||
version_added: "1.3"
|
||||
required: False
|
||||
default: 'round_robin'
|
||||
choices: ['round_robin', 'ratio_member', 'least_connection_member',
|
||||
'observed_member', 'predictive_member', 'ratio_node_address',
|
||||
'least_connection_node_address', 'fastest_node_address',
|
||||
'observed_node_address', 'predictive_node_address',
|
||||
'dynamic_ratio', 'fastest_app_response', 'least_sessions',
|
||||
'dynamic_ratio_member', 'l3_addr', 'unknown',
|
||||
'weighted_least_connection_member',
|
||||
'weighted_least_connection_node_address',
|
||||
'ratio_session', 'ratio_least_connection_member',
|
||||
'ratio_least_connection_node_address']
|
||||
aliases: []
|
||||
monitor_type:
|
||||
description:
|
||||
- Monitor rule type when monitors > 1
|
||||
version_added: "1.3"
|
||||
required: False
|
||||
default: null
|
||||
choices: ['and_list', 'm_of_n']
|
||||
aliases: []
|
||||
quorum:
|
||||
description:
|
||||
- Monitor quorum value when monitor_type is m_of_n
|
||||
version_added: "1.3"
|
||||
required: False
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
monitors:
|
||||
description:
|
||||
- Monitor template name list. Always use the full path to the monitor.
|
||||
version_added: "1.3"
|
||||
required: False
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
slow_ramp_time:
|
||||
description:
|
||||
- Sets the ramp-up time (in seconds) to gradually ramp up the load on newly added or freshly detected up pool members
|
||||
version_added: "1.3"
|
||||
required: False
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
service_down_action:
|
||||
description:
|
||||
- Sets the action to take when node goes down in pool
|
||||
version_added: "1.3"
|
||||
required: False
|
||||
default: null
|
||||
choices: ['none', 'reset', 'drop', 'reselect']
|
||||
aliases: []
|
||||
host:
|
||||
description:
|
||||
- "Pool member IP"
|
||||
required: False
|
||||
default: null
|
||||
choices: []
|
||||
aliases: ['address']
|
||||
port:
|
||||
description:
|
||||
- "Pool member port"
|
||||
required: False
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
## playbook task examples:
|
||||
|
||||
---
|
||||
# file bigip-test.yml
|
||||
# ...
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: Create pool
|
||||
local_action: >
|
||||
bigip_pool
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=present
|
||||
name=matthite-pool
|
||||
partition=matthite
|
||||
lb_method=least_connection_member
|
||||
slow_ramp_time=120
|
||||
|
||||
- name: Modify load balancer method
|
||||
local_action: >
|
||||
bigip_pool
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=present
|
||||
name=matthite-pool
|
||||
partition=matthite
|
||||
lb_method=round_robin
|
||||
|
||||
- hosts: bigip-test
|
||||
tasks:
|
||||
- name: Add pool member
|
||||
local_action: >
|
||||
bigip_pool
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=present
|
||||
name=matthite-pool
|
||||
partition=matthite
|
||||
host="{{ ansible_default_ipv4["address"] }}"
|
||||
port=80
|
||||
|
||||
- name: Remove pool member from pool
|
||||
local_action: >
|
||||
bigip_pool
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=absent
|
||||
name=matthite-pool
|
||||
partition=matthite
|
||||
host="{{ ansible_default_ipv4["address"] }}"
|
||||
port=80
|
||||
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: Delete pool
|
||||
local_action: >
|
||||
bigip_pool
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=absent
|
||||
name=matthite-pool
|
||||
partition=matthite
|
||||
|
||||
'''
|
||||
|
||||
try:
|
||||
import bigsuds
|
||||
except ImportError:
|
||||
bigsuds_found = False
|
||||
else:
|
||||
bigsuds_found = True
|
||||
|
||||
# ===========================================
|
||||
# bigip_pool module specific support methods.
|
||||
#
|
||||
|
||||
def bigip_api(bigip, user, password):
|
||||
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
|
||||
return api
|
||||
|
||||
def pool_exists(api, pool):
|
||||
# hack to determine if pool exists
|
||||
result = False
|
||||
try:
|
||||
api.LocalLB.Pool.get_object_status(pool_names=[pool])
|
||||
result = True
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
def create_pool(api, pool, lb_method):
|
||||
# create requires lb_method but we don't want to default
|
||||
# to a value on subsequent runs
|
||||
if not lb_method:
|
||||
lb_method = 'round_robin'
|
||||
lb_method = "LB_METHOD_%s" % lb_method.strip().upper()
|
||||
api.LocalLB.Pool.create_v2(pool_names=[pool], lb_methods=[lb_method],
|
||||
members=[[]])
|
||||
|
||||
def remove_pool(api, pool):
|
||||
api.LocalLB.Pool.delete_pool(pool_names=[pool])
|
||||
|
||||
def get_lb_method(api, pool):
|
||||
lb_method = api.LocalLB.Pool.get_lb_method(pool_names=[pool])[0]
|
||||
lb_method = lb_method.strip().replace('LB_METHOD_', '').lower()
|
||||
return lb_method
|
||||
|
||||
def set_lb_method(api, pool, lb_method):
|
||||
lb_method = "LB_METHOD_%s" % lb_method.strip().upper()
|
||||
api.LocalLB.Pool.set_lb_method(pool_names=[pool], lb_methods=[lb_method])
|
||||
|
||||
def get_monitors(api, pool):
|
||||
result = api.LocalLB.Pool.get_monitor_association(pool_names=[pool])[0]['monitor_rule']
|
||||
monitor_type = result['type'].split("MONITOR_RULE_TYPE_")[-1].lower()
|
||||
quorum = result['quorum']
|
||||
monitor_templates = result['monitor_templates']
|
||||
return (monitor_type, quorum, monitor_templates)
|
||||
|
||||
def set_monitors(api, pool, monitor_type, quorum, monitor_templates):
|
||||
monitor_type = "MONITOR_RULE_TYPE_%s" % monitor_type.strip().upper()
|
||||
monitor_rule = {'type': monitor_type, 'quorum': quorum, 'monitor_templates': monitor_templates}
|
||||
monitor_association = {'pool_name': pool, 'monitor_rule': monitor_rule}
|
||||
api.LocalLB.Pool.set_monitor_association(monitor_associations=[monitor_association])
|
||||
|
||||
def get_slow_ramp_time(api, pool):
|
||||
result = api.LocalLB.Pool.get_slow_ramp_time(pool_names=[pool])[0]
|
||||
return result
|
||||
|
||||
def set_slow_ramp_time(api, pool, seconds):
|
||||
api.LocalLB.Pool.set_slow_ramp_time(pool_names=[pool], values=[seconds])
|
||||
|
||||
def get_action_on_service_down(api, pool):
|
||||
result = api.LocalLB.Pool.get_action_on_service_down(pool_names=[pool])[0]
|
||||
result = result.split("SERVICE_DOWN_ACTION_")[-1].lower()
|
||||
return result
|
||||
|
||||
def set_action_on_service_down(api, pool, action):
|
||||
action = "SERVICE_DOWN_ACTION_%s" % action.strip().upper()
|
||||
api.LocalLB.Pool.set_action_on_service_down(pool_names=[pool], actions=[action])
|
||||
|
||||
def member_exists(api, pool, address, port):
|
||||
# hack to determine if member exists
|
||||
result = False
|
||||
try:
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.get_member_object_status(pool_names=[pool],
|
||||
members=[members])
|
||||
result = True
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
def delete_node_address(api, address):
|
||||
result = False
|
||||
try:
|
||||
api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
|
||||
result = True
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "is referenced by a member of pool" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
def remove_pool_member(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.remove_member_v2(pool_names=[pool], members=[members])
|
||||
|
||||
def add_pool_member(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.add_member_v2(pool_names=[pool], members=[members])
|
||||
|
||||
def main():
|
||||
lb_method_choices = ['round_robin', 'ratio_member',
|
||||
'least_connection_member', 'observed_member',
|
||||
'predictive_member', 'ratio_node_address',
|
||||
'least_connection_node_address',
|
||||
'fastest_node_address', 'observed_node_address',
|
||||
'predictive_node_address', 'dynamic_ratio',
|
||||
'fastest_app_response', 'least_sessions',
|
||||
'dynamic_ratio_member', 'l3_addr', 'unknown',
|
||||
'weighted_least_connection_member',
|
||||
'weighted_least_connection_node_address',
|
||||
'ratio_session', 'ratio_least_connection_member',
|
||||
'ratio_least_connection_node_address']
|
||||
|
||||
monitor_type_choices = ['and_list', 'm_of_n']
|
||||
|
||||
service_down_choices = ['none', 'reset', 'drop', 'reselect']
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
server = dict(type='str', required=True),
|
||||
user = dict(type='str', required=True),
|
||||
password = dict(type='str', required=True),
|
||||
state = dict(type='str', default='present', choices=['present', 'absent']),
|
||||
name = dict(type='str', required=True, aliases=['pool']),
|
||||
partition = dict(type='str', default='Common'),
|
||||
lb_method = dict(type='str', choices=lb_method_choices),
|
||||
monitor_type = dict(type='str', choices=monitor_type_choices),
|
||||
quorum = dict(type='int'),
|
||||
monitors = dict(type='list'),
|
||||
slow_ramp_time = dict(type='int'),
|
||||
service_down_action = dict(type='str', choices=service_down_choices),
|
||||
host = dict(type='str', aliases=['address']),
|
||||
port = dict(type='int')
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not bigsuds_found:
|
||||
module.fail_json(msg="the python bigsuds module is required")
|
||||
|
||||
server = module.params['server']
|
||||
user = module.params['user']
|
||||
password = module.params['password']
|
||||
state = module.params['state']
|
||||
name = module.params['name']
|
||||
partition = module.params['partition']
|
||||
pool = "/%s/%s" % (partition, name)
|
||||
lb_method = module.params['lb_method']
|
||||
if lb_method:
|
||||
lb_method = lb_method.lower()
|
||||
monitor_type = module.params['monitor_type']
|
||||
if monitor_type:
|
||||
monitor_type = monitor_type.lower()
|
||||
quorum = module.params['quorum']
|
||||
monitors = module.params['monitors']
|
||||
if monitors:
|
||||
monitors = []
|
||||
for monitor in module.params['monitors']:
|
||||
if "/" not in monitor:
|
||||
monitors.append("/%s/%s" % (partition, monitor))
|
||||
else:
|
||||
monitors.append(monitor)
|
||||
slow_ramp_time = module.params['slow_ramp_time']
|
||||
service_down_action = module.params['service_down_action']
|
||||
if service_down_action:
|
||||
service_down_action = service_down_action.lower()
|
||||
host = module.params['host']
|
||||
address = "/%s/%s" % (partition, host)
|
||||
port = module.params['port']
|
||||
|
||||
# sanity check user supplied values
|
||||
|
||||
if (host and not port) or (port and not host):
|
||||
module.fail_json(msg="both host and port must be supplied")
|
||||
|
||||
if 1 > port > 65535:
|
||||
module.fail_json(msg="valid ports must be in range 1 - 65535")
|
||||
|
||||
if monitors:
|
||||
if len(monitors) == 1:
|
||||
# set default required values for single monitor
|
||||
quorum = 0
|
||||
monitor_type = 'single'
|
||||
elif len(monitors) > 1:
|
||||
if not monitor_type:
|
||||
module.fail_json(msg="monitor_type required for monitors > 1")
|
||||
if monitor_type == 'm_of_n' and not quorum:
|
||||
module.fail_json(msg="quorum value required for monitor_type m_of_n")
|
||||
if monitor_type != 'm_of_n':
|
||||
quorum = 0
|
||||
elif monitor_type:
|
||||
# no monitors specified but monitor_type exists
|
||||
module.fail_json(msg="monitor_type require monitors parameter")
|
||||
elif quorum is not None:
|
||||
# no monitors specified but quorum exists
|
||||
module.fail_json(msg="quorum requires monitors parameter")
|
||||
|
||||
try:
|
||||
api = bigip_api(server, user, password)
|
||||
result = {'changed': False} # default
|
||||
|
||||
if state == 'absent':
|
||||
if host and port and pool:
|
||||
# member removal takes precedent
|
||||
if pool_exists(api, pool) and member_exists(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
remove_pool_member(api, pool, address, port)
|
||||
deleted = delete_node_address(api, address)
|
||||
result = {'changed': True, 'deleted': deleted}
|
||||
else:
|
||||
result = {'changed': True}
|
||||
elif pool_exists(api, pool):
|
||||
# no host/port supplied, must be pool removal
|
||||
if not module.check_mode:
|
||||
# hack to handle concurrent runs of module
|
||||
# pool might be gone before we actually remove it
|
||||
try:
|
||||
remove_pool(api, pool)
|
||||
result = {'changed': True}
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = {'changed': False}
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
else:
|
||||
# check-mode return value
|
||||
result = {'changed': True}
|
||||
|
||||
elif state == 'present':
|
||||
update = False
|
||||
if not pool_exists(api, pool):
|
||||
# pool does not exist -- need to create it
|
||||
if not module.check_mode:
|
||||
# a bit of a hack to handle concurrent runs of this module.
|
||||
# even though we've checked the pool doesn't exist,
|
||||
# it may exist by the time we run create_pool().
|
||||
# this catches the exception and does something smart
|
||||
# about it!
|
||||
try:
|
||||
create_pool(api, pool, lb_method)
|
||||
result = {'changed': True}
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "already exists" in str(e):
|
||||
update = True
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
else:
|
||||
if monitors:
|
||||
set_monitors(api, pool, monitor_type, quorum, monitors)
|
||||
if slow_ramp_time:
|
||||
set_slow_ramp_time(api, pool, slow_ramp_time)
|
||||
if service_down_action:
|
||||
set_action_on_service_down(api, pool, service_down_action)
|
||||
if host and port:
|
||||
add_pool_member(api, pool, address, port)
|
||||
else:
|
||||
# check-mode return value
|
||||
result = {'changed': True}
|
||||
else:
|
||||
# pool exists -- potentially modify attributes
|
||||
update = True
|
||||
|
||||
if update:
|
||||
if lb_method and lb_method != get_lb_method(api, pool):
|
||||
if not module.check_mode:
|
||||
set_lb_method(api, pool, lb_method)
|
||||
result = {'changed': True}
|
||||
if monitors:
|
||||
t_monitor_type, t_quorum, t_monitor_templates = get_monitors(api, pool)
|
||||
if (t_monitor_type != monitor_type) or (t_quorum != quorum) or (set(t_monitor_templates) != set(monitors)):
|
||||
if not module.check_mode:
|
||||
set_monitors(api, pool, monitor_type, quorum, monitors)
|
||||
result = {'changed': True}
|
||||
if slow_ramp_time and slow_ramp_time != get_slow_ramp_time(api, pool):
|
||||
if not module.check_mode:
|
||||
set_slow_ramp_time(api, pool, slow_ramp_time)
|
||||
result = {'changed': True}
|
||||
if service_down_action and service_down_action != get_action_on_service_down(api, pool):
|
||||
if not module.check_mode:
|
||||
set_action_on_service_down(api, pool, service_down_action)
|
||||
result = {'changed': True}
|
||||
if (host and port) and not member_exists(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
add_pool_member(api, pool, address, port)
|
||||
result = {'changed': True}
|
||||
|
||||
except Exception, e:
|
||||
module.fail_json(msg="received exception: %s" % e)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
|
@ -1,378 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Matt Hite <mhite@hotmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: bigip_pool_member
|
||||
short_description: "Manages F5 BIG-IP LTM pool members"
|
||||
description:
|
||||
- "Manages F5 BIG-IP LTM pool members via iControl SOAP API"
|
||||
version_added: "1.4"
|
||||
author: Matt Hite
|
||||
notes:
|
||||
- "Requires BIG-IP software version >= 11"
|
||||
- "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
|
||||
- "Best run as a local_action in your playbook"
|
||||
- "Supersedes bigip_pool for managing pool members"
|
||||
|
||||
requirements:
|
||||
- bigsuds
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- BIG-IP host
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
user:
|
||||
description:
|
||||
- BIG-IP username
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
password:
|
||||
description:
|
||||
- BIG-IP password
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- Pool member state
|
||||
required: true
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
aliases: []
|
||||
pool:
|
||||
description:
|
||||
- Pool name. This pool must exist.
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
partition:
|
||||
description:
|
||||
- Partition
|
||||
required: false
|
||||
default: 'Common'
|
||||
choices: []
|
||||
aliases: []
|
||||
host:
|
||||
description:
|
||||
- Pool member IP
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: ['address', 'name']
|
||||
port:
|
||||
description:
|
||||
- Pool member port
|
||||
required: true
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
connection_limit:
|
||||
description:
|
||||
- Pool member connection limit. Setting this to 0 disables the limit.
|
||||
required: false
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
description:
|
||||
description:
|
||||
- Pool member description
|
||||
required: false
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
rate_limit:
|
||||
description:
|
||||
- Pool member rate limit (connections-per-second). Setting this to 0 disables the limit.
|
||||
required: false
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
ratio:
|
||||
description:
|
||||
- Pool member ratio weight. Valid values range from 1 through 100. New pool members -- unless overriden with this value -- default to 1.
|
||||
required: false
|
||||
default: null
|
||||
choices: []
|
||||
aliases: []
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
## playbook task examples:
|
||||
|
||||
---
|
||||
# file bigip-test.yml
|
||||
# ...
|
||||
- hosts: bigip-test
|
||||
tasks:
|
||||
- name: Add pool member
|
||||
local_action: >
|
||||
bigip_pool_member
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=present
|
||||
pool=matthite-pool
|
||||
partition=matthite
|
||||
host="{{ ansible_default_ipv4["address"] }}"
|
||||
port=80
|
||||
description="web server"
|
||||
connection_limit=100
|
||||
rate_limit=50
|
||||
ratio=2
|
||||
|
||||
- name: Modify pool member ratio and description
|
||||
local_action: >
|
||||
bigip_pool_member
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=present
|
||||
pool=matthite-pool
|
||||
partition=matthite
|
||||
host="{{ ansible_default_ipv4["address"] }}"
|
||||
port=80
|
||||
ratio=1
|
||||
description="nginx server"
|
||||
|
||||
- name: Remove pool member from pool
|
||||
local_action: >
|
||||
bigip_pool_member
|
||||
server=lb.mydomain.com
|
||||
user=admin
|
||||
password=mysecret
|
||||
state=absent
|
||||
pool=matthite-pool
|
||||
partition=matthite
|
||||
host="{{ ansible_default_ipv4["address"] }}"
|
||||
port=80
|
||||
|
||||
'''
|
||||
|
||||
try:
|
||||
import bigsuds
|
||||
except ImportError:
|
||||
bigsuds_found = False
|
||||
else:
|
||||
bigsuds_found = True
|
||||
|
||||
# ===========================================
|
||||
# bigip_pool_member module specific support methods.
|
||||
#
|
||||
|
||||
def bigip_api(bigip, user, password):
|
||||
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
|
||||
return api
|
||||
|
||||
def pool_exists(api, pool):
|
||||
# hack to determine if pool exists
|
||||
result = False
|
||||
try:
|
||||
api.LocalLB.Pool.get_object_status(pool_names=[pool])
|
||||
result = True
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
def member_exists(api, pool, address, port):
|
||||
# hack to determine if member exists
|
||||
result = False
|
||||
try:
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.get_member_object_status(pool_names=[pool],
|
||||
members=[members])
|
||||
result = True
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "was not found" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
def delete_node_address(api, address):
|
||||
result = False
|
||||
try:
|
||||
api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
|
||||
result = True
|
||||
except bigsuds.OperationFailed, e:
|
||||
if "is referenced by a member of pool" in str(e):
|
||||
result = False
|
||||
else:
|
||||
# genuine exception
|
||||
raise
|
||||
return result
|
||||
|
||||
def remove_pool_member(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.remove_member_v2(pool_names=[pool], members=[members])
|
||||
|
||||
def add_pool_member(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.add_member_v2(pool_names=[pool], members=[members])
|
||||
|
||||
def get_connection_limit(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
result = api.LocalLB.Pool.get_member_connection_limit(pool_names=[pool], members=[members])[0][0]
|
||||
return result
|
||||
|
||||
def set_connection_limit(api, pool, address, port, limit):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.set_member_connection_limit(pool_names=[pool], members=[members], limits=[[limit]])
|
||||
|
||||
def get_description(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
result = api.LocalLB.Pool.get_member_description(pool_names=[pool], members=[members])[0][0]
|
||||
return result
|
||||
|
||||
def set_description(api, pool, address, port, description):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.set_member_description(pool_names=[pool], members=[members], descriptions=[[description]])
|
||||
|
||||
def get_rate_limit(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
result = api.LocalLB.Pool.get_member_rate_limit(pool_names=[pool], members=[members])[0][0]
|
||||
return result
|
||||
|
||||
def set_rate_limit(api, pool, address, port, limit):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.set_member_rate_limit(pool_names=[pool], members=[members], limits=[[limit]])
|
||||
|
||||
def get_ratio(api, pool, address, port):
|
||||
members = [{'address': address, 'port': port}]
|
||||
result = api.LocalLB.Pool.get_member_ratio(pool_names=[pool], members=[members])[0][0]
|
||||
return result
|
||||
|
||||
def set_ratio(api, pool, address, port, ratio):
|
||||
members = [{'address': address, 'port': port}]
|
||||
api.LocalLB.Pool.set_member_ratio(pool_names=[pool], members=[members], ratios=[[ratio]])
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
server = dict(type='str', required=True),
|
||||
user = dict(type='str', required=True),
|
||||
password = dict(type='str', required=True),
|
||||
state = dict(type='str', default='present', choices=['present', 'absent']),
|
||||
pool = dict(type='str', required=True),
|
||||
partition = dict(type='str', default='Common'),
|
||||
host = dict(type='str', required=True, aliases=['address', 'name']),
|
||||
port = dict(type='int', required=True),
|
||||
connection_limit = dict(type='int'),
|
||||
description = dict(type='str'),
|
||||
rate_limit = dict(type='int'),
|
||||
ratio = dict(type='int')
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not bigsuds_found:
|
||||
module.fail_json(msg="the python bigsuds module is required")
|
||||
|
||||
server = module.params['server']
|
||||
user = module.params['user']
|
||||
password = module.params['password']
|
||||
state = module.params['state']
|
||||
partition = module.params['partition']
|
||||
pool = "/%s/%s" % (partition, module.params['pool'])
|
||||
connection_limit = module.params['connection_limit']
|
||||
description = module.params['description']
|
||||
rate_limit = module.params['rate_limit']
|
||||
ratio = module.params['ratio']
|
||||
host = module.params['host']
|
||||
address = "/%s/%s" % (partition, host)
|
||||
port = module.params['port']
|
||||
|
||||
# sanity check user supplied values
|
||||
|
||||
if (host and not port) or (port and not host):
|
||||
module.fail_json(msg="both host and port must be supplied")
|
||||
|
||||
if 1 > port > 65535:
|
||||
module.fail_json(msg="valid ports must be in range 1 - 65535")
|
||||
|
||||
try:
|
||||
api = bigip_api(server, user, password)
|
||||
if not pool_exists(api, pool):
|
||||
module.fail_json(msg="pool %s does not exist" % pool)
|
||||
result = {'changed': False} # default
|
||||
|
||||
if state == 'absent':
|
||||
if member_exists(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
remove_pool_member(api, pool, address, port)
|
||||
deleted = delete_node_address(api, address)
|
||||
result = {'changed': True, 'deleted': deleted}
|
||||
else:
|
||||
result = {'changed': True}
|
||||
|
||||
elif state == 'present':
|
||||
if not member_exists(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
add_pool_member(api, pool, address, port)
|
||||
if connection_limit is not None:
|
||||
set_connection_limit(api, pool, address, port, connection_limit)
|
||||
if description is not None:
|
||||
set_description(api, pool, address, port, description)
|
||||
if rate_limit is not None:
|
||||
set_rate_limit(api, pool, address, port, rate_limit)
|
||||
if ratio is not None:
|
||||
set_ratio(api, pool, address, port, ratio)
|
||||
result = {'changed': True}
|
||||
else:
|
||||
# pool member exists -- potentially modify attributes
|
||||
if connection_limit is not None and connection_limit != get_connection_limit(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
set_connection_limit(api, pool, address, port, connection_limit)
|
||||
result = {'changed': True}
|
||||
if description is not None and description != get_description(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
set_description(api, pool, address, port, description)
|
||||
result = {'changed': True}
|
||||
if rate_limit is not None and rate_limit != get_rate_limit(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
set_rate_limit(api, pool, address, port, rate_limit)
|
||||
result = {'changed': True}
|
||||
if ratio is not None and ratio != get_ratio(api, pool, address, port):
|
||||
if not module.check_mode:
|
||||
set_ratio(api, pool, address, port, ratio)
|
||||
result = {'changed': True}
|
||||
|
||||
except Exception, e:
|
||||
module.fail_json(msg="received exception: %s" % e)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
|
@ -1,302 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: dnsimple
|
||||
version_added: "1.6"
|
||||
short_description: Interface with dnsimple.com (a DNS hosting service).
|
||||
description:
|
||||
- "Manages domains and records via the DNSimple API, see the docs: U(http://developer.dnsimple.com/)"
|
||||
options:
|
||||
account_email:
|
||||
description:
|
||||
- "Account email. If omitted, the env variables DNSIMPLE_EMAIL and DNSIMPLE_API_TOKEN will be looked for. If those aren't found, a C(.dnsimple) file will be looked for, see: U(https://github.com/mikemaccana/dnsimple-python#getting-started)"
|
||||
required: false
|
||||
default: null
|
||||
|
||||
account_api_token:
|
||||
description:
|
||||
- Account API token. See I(account_email) for info.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
domain:
|
||||
description:
|
||||
- Domain to work with. Can be the domain name (e.g. "mydomain.com") or the numeric ID of the domain in DNSimple. If omitted, a list of domains will be returned.
|
||||
- If domain is present but the domain doesn't exist, it will be created.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record:
|
||||
description:
|
||||
- Record to add, if blank a record for the domain will be created, supports the wildcard (*)
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record_ids:
|
||||
description:
|
||||
- List of records to ensure they either exist or don't exist
|
||||
required: false
|
||||
default: null
|
||||
|
||||
type:
|
||||
description:
|
||||
- The type of DNS record to create
|
||||
required: false
|
||||
choices: [ 'A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO', 'POOL' ]
|
||||
default: null
|
||||
|
||||
ttl:
|
||||
description:
|
||||
- The TTL to give the new record
|
||||
required: false
|
||||
default: 3600 (one hour)
|
||||
|
||||
value:
|
||||
description:
|
||||
- Record value
|
||||
- "Must be specified when trying to ensure a record exists"
|
||||
required: false
|
||||
default: null
|
||||
|
||||
priority:
|
||||
description:
|
||||
- Record priority
|
||||
required: false
|
||||
default: null
|
||||
|
||||
state:
|
||||
description:
|
||||
- whether the record should exist or not
|
||||
required: false
|
||||
choices: [ 'present', 'absent' ]
|
||||
default: null
|
||||
|
||||
solo:
|
||||
description:
|
||||
- Whether the record should be the only one for that record type and record name. Only use with state=present on a record
|
||||
required: false
|
||||
default: null
|
||||
|
||||
requirements: [ dnsimple ]
|
||||
author: Alex Coomans
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# authenicate using email and API token
|
||||
- local_action: dnsimple account_email=test@example.com account_api_token=dummyapitoken
|
||||
|
||||
# fetch all domains
|
||||
- local_action dnsimple
|
||||
register: domains
|
||||
|
||||
# fetch my.com domain records
|
||||
- local_action: dnsimple domain=my.com state=present
|
||||
register: records
|
||||
|
||||
# delete a domain
|
||||
- local_action: dnsimple domain=my.com state=absent
|
||||
|
||||
# create a test.my.com A record to point to 127.0.0.01
|
||||
- local_action: dnsimple domain=my.com record=test type=A value=127.0.0.1
|
||||
register: record
|
||||
|
||||
# and then delete it
|
||||
- local_action: dnsimple domain=my.com record_ids={{ record['id'] }}
|
||||
|
||||
# create a my.com CNAME record to example.com
|
||||
- local_action: dnsimple domain=my.com record= type=CNAME value=example.com state=present
|
||||
|
||||
# change it's ttl
|
||||
- local_action: dnsimple domain=my.com record= type=CNAME value=example.com ttl=600 state=present
|
||||
|
||||
# and delete the record
|
||||
- local_action: dnsimpledomain=my.com record= type=CNAME value=example.com state=absent
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
try:
|
||||
from dnsimple import DNSimple
|
||||
from dnsimple.dnsimple import DNSimpleException
|
||||
except ImportError:
|
||||
print "failed=True msg='dnsimple required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
account_email = dict(required=False),
|
||||
account_api_token = dict(required=False, no_log=True),
|
||||
domain = dict(required=False),
|
||||
record = dict(required=False),
|
||||
record_ids = dict(required=False, type='list'),
|
||||
type = dict(required=False, choices=['A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO', 'POOL']),
|
||||
ttl = dict(required=False, default=3600, type='int'),
|
||||
value = dict(required=False),
|
||||
priority = dict(required=False, type='int'),
|
||||
state = dict(required=False, choices=['present', 'absent']),
|
||||
solo = dict(required=False, type='bool'),
|
||||
),
|
||||
required_together = (
|
||||
['record', 'value']
|
||||
),
|
||||
supports_check_mode = True,
|
||||
)
|
||||
|
||||
account_email = module.params.get('account_email')
|
||||
account_api_token = module.params.get('account_api_token')
|
||||
domain = module.params.get('domain')
|
||||
record = module.params.get('record')
|
||||
record_ids = module.params.get('record_ids')
|
||||
record_type = module.params.get('type')
|
||||
ttl = module.params.get('ttl')
|
||||
value = module.params.get('value')
|
||||
priority = module.params.get('priority')
|
||||
state = module.params.get('state')
|
||||
is_solo = module.params.get('solo')
|
||||
|
||||
if account_email and account_api_token:
|
||||
client = DNSimple(email=account_email, api_token=account_api_token)
|
||||
elif os.environ.get('DNSIMPLE_EMAIL') and os.environ.get('DNSIMPLE_API_TOKEN'):
|
||||
client = DNSimple(email=os.environ.get('DNSIMPLE_EMAIL'), api_token=os.environ.get('DNSIMPLE_API_TOKEN'))
|
||||
else:
|
||||
client = DNSimple()
|
||||
|
||||
try:
|
||||
# Let's figure out what operation we want to do
|
||||
|
||||
# No domain, return a list
|
||||
if not domain:
|
||||
domains = client.domains()
|
||||
module.exit_json(changed=False, result=[d['domain'] for d in domains])
|
||||
|
||||
# Domain & No record
|
||||
if domain and record is None and not record_ids:
|
||||
domains = [d['domain'] for d in client.domains()]
|
||||
if domain.isdigit():
|
||||
dr = next((d for d in domains if d['id'] == int(domain)), None)
|
||||
else:
|
||||
dr = next((d for d in domains if d['name'] == domain), None)
|
||||
if state == 'present':
|
||||
if dr:
|
||||
module.exit_json(changed=False, result=dr)
|
||||
else:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.add_domain(domain)['domain'])
|
||||
elif state == 'absent':
|
||||
if dr:
|
||||
if not module.check_mode:
|
||||
client.delete(domain)
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
else:
|
||||
module.fail_json(msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
# need the not none check since record could be an empty string
|
||||
if domain and record is not None:
|
||||
records = [r['record'] for r in client.records(str(domain))]
|
||||
|
||||
if not record_type:
|
||||
module.fail_json(msg="Missing the record type")
|
||||
|
||||
if not value:
|
||||
module.fail_json(msg="Missing the record value")
|
||||
|
||||
rr = next((r for r in records if r['name'] == record and r['record_type'] == record_type and r['content'] == value), None)
|
||||
|
||||
if state == 'present':
|
||||
changed = False
|
||||
if is_solo:
|
||||
# delete any records that have the same name and record type
|
||||
same_type = [r['id'] for r in records if r['name'] == record and r['record_type'] == record_type]
|
||||
if rr:
|
||||
same_type = [rid for rid in same_type if rid != rr['id']]
|
||||
if same_type:
|
||||
if not module.check_mode:
|
||||
for rid in same_type:
|
||||
client.delete_record(str(domain), rid)
|
||||
changed = True
|
||||
if rr:
|
||||
# check if we need to update
|
||||
if rr['ttl'] != ttl or rr['prio'] != priority:
|
||||
data = {}
|
||||
if ttl: data['ttl'] = ttl
|
||||
if priority: data['prio'] = priority
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.update_record(str(domain), str(rr['id']), data)['record'])
|
||||
else:
|
||||
module.exit_json(changed=changed, result=rr)
|
||||
else:
|
||||
# create it
|
||||
data = {
|
||||
'name': record,
|
||||
'record_type': record_type,
|
||||
'content': value,
|
||||
}
|
||||
if ttl: data['ttl'] = ttl
|
||||
if priority: data['prio'] = priority
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.add_record(str(domain), data)['record'])
|
||||
elif state == 'absent':
|
||||
if rr:
|
||||
if not module.check_mode:
|
||||
client.delete_record(str(domain), rr['id'])
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
else:
|
||||
module.fail_json(msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
# Make sure these record_ids either all exist or none
|
||||
if domain and record_ids:
|
||||
current_records = [str(r['record']['id']) for r in client.records(str(domain))]
|
||||
wanted_records = [str(r) for r in record_ids]
|
||||
if state == 'present':
|
||||
difference = list(set(wanted_records) - set(current_records))
|
||||
if difference:
|
||||
module.fail_json(msg="Missing the following records: %s" % difference)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
elif state == 'absent':
|
||||
difference = list(set(wanted_records) & set(current_records))
|
||||
if difference:
|
||||
if not module.check_mode:
|
||||
for rid in difference:
|
||||
client.delete_record(str(domain), rid)
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
else:
|
||||
module.fail_json(msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
except DNSimpleException, e:
|
||||
module.fail_json(msg="Unable to contact DNSimple: %s" % e.message)
|
||||
|
||||
module.fail_json(msg="Unknown what you wanted me to do")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -1,329 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: dnsmadeeasy
|
||||
version_added: "1.3"
|
||||
short_description: Interface with dnsmadeeasy.com (a DNS hosting service).
|
||||
description:
|
||||
- "Manages DNS records via the v2 REST API of the DNS Made Easy service. It handles records only; there is no manipulation of domains or monitor/account support yet. See: U(http://www.dnsmadeeasy.com/services/rest-api/)"
|
||||
options:
|
||||
account_key:
|
||||
description:
|
||||
- Accout API Key.
|
||||
required: true
|
||||
default: null
|
||||
|
||||
account_secret:
|
||||
description:
|
||||
- Accout Secret Key.
|
||||
required: true
|
||||
default: null
|
||||
|
||||
domain:
|
||||
description:
|
||||
- Domain to work with. Can be the domain name (e.g. "mydomain.com") or the numeric ID of the domain in DNS Made Easy (e.g. "839989") for faster resolution.
|
||||
required: true
|
||||
default: null
|
||||
|
||||
record_name:
|
||||
description:
|
||||
- Record name to get/create/delete/update. If record_name is not specified; all records for the domain will be returned in "result" regardless of the state argument.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record_type:
|
||||
description:
|
||||
- Record type.
|
||||
required: false
|
||||
choices: [ 'A', 'AAAA', 'CNAME', 'HTTPRED', 'MX', 'NS', 'PTR', 'SRV', 'TXT' ]
|
||||
default: null
|
||||
|
||||
record_value:
|
||||
description:
|
||||
- "Record value. HTTPRED: <redirection URL>, MX: <priority> <target name>, NS: <name server>, PTR: <target name>, SRV: <priority> <weight> <port> <target name>, TXT: <text value>"
|
||||
- "If record_value is not specified; no changes will be made and the record will be returned in 'result' (in other words, this module can be used to fetch a record's current id, type, and ttl)"
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record_ttl:
|
||||
description:
|
||||
- record's "Time to live". Number of seconds the record remains cached in DNS servers.
|
||||
required: false
|
||||
default: 1800
|
||||
|
||||
state:
|
||||
description:
|
||||
- whether the record should exist or not
|
||||
required: true
|
||||
choices: [ 'present', 'absent' ]
|
||||
default: null
|
||||
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
|
||||
notes:
|
||||
- The DNS Made Easy service requires that machines interacting with the API have the proper time and timezone set. Be sure you are within a few seconds of actual time by using NTP.
|
||||
- This module returns record(s) in the "result" element when 'state' is set to 'present'. This value can be be registered and used in your playbooks.
|
||||
|
||||
requirements: [ urllib, urllib2, hashlib, hmac ]
|
||||
author: Brice Burgess
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# fetch my.com domain records
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present
|
||||
register: response
|
||||
|
||||
# create / ensure the presence of a record
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present record_name="test" record_type="A" record_value="127.0.0.1"
|
||||
|
||||
# update the previously created record
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present record_name="test" record_value="192.168.0.1"
|
||||
|
||||
# fetch a specific record
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present record_name="test"
|
||||
register: response
|
||||
|
||||
# delete a record / ensure it is absent
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=absent record_name="test"
|
||||
'''
|
||||
|
||||
# ============================================
|
||||
# DNSMadeEasy module specific support methods.
|
||||
#
|
||||
|
||||
IMPORT_ERROR = None
|
||||
try:
|
||||
import json
|
||||
from time import strftime, gmtime
|
||||
import hashlib
|
||||
import hmac
|
||||
except ImportError, e:
|
||||
IMPORT_ERROR = str(e)
|
||||
|
||||
class DME2:
|
||||
|
||||
def __init__(self, apikey, secret, domain, module):
|
||||
self.module = module
|
||||
|
||||
self.api = apikey
|
||||
self.secret = secret
|
||||
self.baseurl = 'https://api.dnsmadeeasy.com/V2.0/'
|
||||
self.domain = str(domain)
|
||||
self.domain_map = None # ["domain_name"] => ID
|
||||
self.record_map = None # ["record_name"] => ID
|
||||
self.records = None # ["record_ID"] => <record>
|
||||
|
||||
# Lookup the domain ID if passed as a domain name vs. ID
|
||||
if not self.domain.isdigit():
|
||||
self.domain = self.getDomainByName(self.domain)['id']
|
||||
|
||||
self.record_url = 'dns/managed/' + str(self.domain) + '/records'
|
||||
|
||||
def _headers(self):
|
||||
currTime = self._get_date()
|
||||
hashstring = self._create_hash(currTime)
|
||||
headers = {'x-dnsme-apiKey': self.api,
|
||||
'x-dnsme-hmac': hashstring,
|
||||
'x-dnsme-requestDate': currTime,
|
||||
'content-type': 'application/json'}
|
||||
return headers
|
||||
|
||||
def _get_date(self):
|
||||
return strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime())
|
||||
|
||||
def _create_hash(self, rightnow):
|
||||
return hmac.new(self.secret.encode(), rightnow.encode(), hashlib.sha1).hexdigest()
|
||||
|
||||
def query(self, resource, method, data=None):
|
||||
url = self.baseurl + resource
|
||||
if data and not isinstance(data, basestring):
|
||||
data = urllib.urlencode(data)
|
||||
|
||||
response, info = fetch_url(self.module, url, data=data, method=method, headers=self._headers())
|
||||
if info['status'] not in (200, 201, 204):
|
||||
self.module.fail_json(msg="%s returned %s, with body: %s" % (url, info['status'], info['msg']))
|
||||
|
||||
try:
|
||||
return json.load(response)
|
||||
except Exception, e:
|
||||
return {}
|
||||
|
||||
def getDomain(self, domain_id):
|
||||
if not self.domain_map:
|
||||
self._instMap('domain')
|
||||
|
||||
return self.domains.get(domain_id, False)
|
||||
|
||||
def getDomainByName(self, domain_name):
|
||||
if not self.domain_map:
|
||||
self._instMap('domain')
|
||||
|
||||
return self.getDomain(self.domain_map.get(domain_name, 0))
|
||||
|
||||
def getDomains(self):
|
||||
return self.query('dns/managed', 'GET')['data']
|
||||
|
||||
def getRecord(self, record_id):
|
||||
if not self.record_map:
|
||||
self._instMap('record')
|
||||
|
||||
return self.records.get(record_id, False)
|
||||
|
||||
def getRecordByName(self, record_name):
|
||||
if not self.record_map:
|
||||
self._instMap('record')
|
||||
|
||||
return self.getRecord(self.record_map.get(record_name, 0))
|
||||
|
||||
def getRecords(self):
|
||||
return self.query(self.record_url, 'GET')['data']
|
||||
|
||||
def _instMap(self, type):
|
||||
#@TODO cache this call so it's executed only once per ansible execution
|
||||
map = {}
|
||||
results = {}
|
||||
|
||||
# iterate over e.g. self.getDomains() || self.getRecords()
|
||||
for result in getattr(self, 'get' + type.title() + 's')():
|
||||
|
||||
map[result['name']] = result['id']
|
||||
results[result['id']] = result
|
||||
|
||||
# e.g. self.domain_map || self.record_map
|
||||
setattr(self, type + '_map', map)
|
||||
setattr(self, type + 's', results) # e.g. self.domains || self.records
|
||||
|
||||
def prepareRecord(self, data):
|
||||
return json.dumps(data, separators=(',', ':'))
|
||||
|
||||
def createRecord(self, data):
|
||||
#@TODO update the cache w/ resultant record + id when impleneted
|
||||
return self.query(self.record_url, 'POST', data)
|
||||
|
||||
def updateRecord(self, record_id, data):
|
||||
#@TODO update the cache w/ resultant record + id when impleneted
|
||||
return self.query(self.record_url + '/' + str(record_id), 'PUT', data)
|
||||
|
||||
def deleteRecord(self, record_id):
|
||||
#@TODO remove record from the cache when impleneted
|
||||
return self.query(self.record_url + '/' + str(record_id), 'DELETE')
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
account_key=dict(required=True),
|
||||
account_secret=dict(required=True, no_log=True),
|
||||
domain=dict(required=True),
|
||||
state=dict(required=True, choices=['present', 'absent']),
|
||||
record_name=dict(required=False),
|
||||
record_type=dict(required=False, choices=[
|
||||
'A', 'AAAA', 'CNAME', 'HTTPRED', 'MX', 'NS', 'PTR', 'SRV', 'TXT']),
|
||||
record_value=dict(required=False),
|
||||
record_ttl=dict(required=False, default=1800, type='int'),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
),
|
||||
required_together=(
|
||||
['record_value', 'record_ttl', 'record_type']
|
||||
)
|
||||
)
|
||||
|
||||
if IMPORT_ERROR:
|
||||
module.fail_json(msg="Import Error: " + IMPORT_ERROR)
|
||||
|
||||
DME = DME2(module.params["account_key"], module.params[
|
||||
"account_secret"], module.params["domain"], module)
|
||||
state = module.params["state"]
|
||||
record_name = module.params["record_name"]
|
||||
|
||||
# Follow Keyword Controlled Behavior
|
||||
if not record_name:
|
||||
domain_records = DME.getRecords()
|
||||
if not domain_records:
|
||||
module.fail_json(
|
||||
msg="The requested domain name is not accessible with this api_key; try using its ID if known.")
|
||||
module.exit_json(changed=False, result=domain_records)
|
||||
|
||||
# Fetch existing record + Build new one
|
||||
current_record = DME.getRecordByName(record_name)
|
||||
new_record = {'name': record_name}
|
||||
for i in ["record_value", "record_type", "record_ttl"]:
|
||||
if module.params[i]:
|
||||
new_record[i[len("record_"):]] = module.params[i]
|
||||
|
||||
# Compare new record against existing one
|
||||
changed = False
|
||||
if current_record:
|
||||
for i in new_record:
|
||||
if str(current_record[i]) != str(new_record[i]):
|
||||
changed = True
|
||||
new_record['id'] = str(current_record['id'])
|
||||
|
||||
# Follow Keyword Controlled Behavior
|
||||
if state == 'present':
|
||||
# return the record if no value is specified
|
||||
if not "value" in new_record:
|
||||
if not current_record:
|
||||
module.fail_json(
|
||||
msg="A record with name '%s' does not exist for domain '%s.'" % (record_name, domain))
|
||||
module.exit_json(changed=False, result=current_record)
|
||||
|
||||
# create record as it does not exist
|
||||
if not current_record:
|
||||
record = DME.createRecord(DME.prepareRecord(new_record))
|
||||
module.exit_json(changed=True, result=record)
|
||||
|
||||
# update the record
|
||||
if changed:
|
||||
DME.updateRecord(
|
||||
current_record['id'], DME.prepareRecord(new_record))
|
||||
module.exit_json(changed=True, result=new_record)
|
||||
|
||||
# return the record (no changes)
|
||||
module.exit_json(changed=False, result=current_record)
|
||||
|
||||
elif state == 'absent':
|
||||
# delete the record if it exists
|
||||
if current_record:
|
||||
DME.deleteRecord(current_record['id'])
|
||||
module.exit_json(changed=True)
|
||||
|
||||
# record does not exist, return w/o change.
|
||||
module.exit_json(changed=False)
|
||||
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,83 +0,0 @@
|
||||
#!/usr/bin/python -tt
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import subprocess
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: lldp
|
||||
version_added: 1.6
|
||||
short_description: get details reported by lldp
|
||||
description:
|
||||
- Reads data out of lldpctl
|
||||
options: {}
|
||||
author: Andy Hill
|
||||
notes:
|
||||
- Requires lldpd running and lldp enabled on switches
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Retrieve switch/port information
|
||||
- name: Gather information from lldp
|
||||
lldp:
|
||||
|
||||
- name: Print each switch/port
|
||||
debug: msg="{{ lldp[item]['chassis']['name'] }} / {{ lldp[item]['port']['ifalias'] }}
|
||||
with_items: lldp.keys()
|
||||
|
||||
# TASK: [Print each switch/port] ***********************************************************
|
||||
# ok: [10.13.0.22] => (item=eth2) => {"item": "eth2", "msg": "switch1.example.com / Gi0/24"}
|
||||
# ok: [10.13.0.22] => (item=eth1) => {"item": "eth1", "msg": "switch2.example.com / Gi0/3"}
|
||||
# ok: [10.13.0.22] => (item=eth0) => {"item": "eth0", "msg": "switch3.example.com / Gi0/3"}
|
||||
|
||||
'''
|
||||
|
||||
def gather_lldp():
|
||||
cmd = ['lldpctl', '-f', 'keyvalue']
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
(output, err) = proc.communicate()
|
||||
if output:
|
||||
output_dict = {}
|
||||
lldp_entries = output.split("\n")
|
||||
|
||||
for entry in lldp_entries:
|
||||
if entry:
|
||||
path, value = entry.strip().split("=", 1)
|
||||
path = path.split(".")
|
||||
path_components, final = path[:-1], path[-1]
|
||||
|
||||
current_dict = output_dict
|
||||
for path_component in path_components:
|
||||
current_dict[path_component] = current_dict.get(path_component, {})
|
||||
current_dict = current_dict[path_component]
|
||||
current_dict[final] = value
|
||||
return output_dict
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule({})
|
||||
|
||||
lldp_output = gather_lldp()
|
||||
try:
|
||||
data = {'lldp': lldp_output['lldp']}
|
||||
module.exit_json(ansible_facts=data)
|
||||
except TypeError:
|
||||
module.fail_json(msg="lldpctl command failed. is lldpd running?")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
|
@ -1,190 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Ansible module to manage Citrix NetScaler entities
|
||||
(c) 2013, Nandor Sivok <nandor@gawker.com>
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: netscaler
|
||||
version_added: "1.1"
|
||||
short_description: Manages Citrix NetScaler entities
|
||||
description:
|
||||
- Manages Citrix NetScaler server and service entities.
|
||||
options:
|
||||
nsc_host:
|
||||
description:
|
||||
- hostname or ip of your netscaler
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
nsc_protocol:
|
||||
description:
|
||||
- protocol used to access netscaler
|
||||
required: false
|
||||
default: https
|
||||
aliases: []
|
||||
user:
|
||||
description:
|
||||
- username
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
password:
|
||||
description:
|
||||
- password
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
action:
|
||||
description:
|
||||
- the action you want to perform on the entity
|
||||
required: false
|
||||
default: disable
|
||||
choices: ["enable", "disable"]
|
||||
aliases: []
|
||||
name:
|
||||
description:
|
||||
- name of the entity
|
||||
required: true
|
||||
default: hostname
|
||||
aliases: []
|
||||
type:
|
||||
description:
|
||||
- type of the entity
|
||||
required: false
|
||||
default: server
|
||||
choices: ["server", "service"]
|
||||
aliases: []
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates for the target url will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
|
||||
requirements: [ "urllib", "urllib2" ]
|
||||
author: Nandor Sivok
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Disable the server
|
||||
ansible host -m netscaler -a "nsc_host=nsc.example.com user=apiuser password=apipass"
|
||||
|
||||
# Enable the server
|
||||
ansible host -m netscaler -a "nsc_host=nsc.example.com user=apiuser password=apipass action=enable"
|
||||
|
||||
# Disable the service local:8080
|
||||
ansible host -m netscaler -a "nsc_host=nsc.example.com user=apiuser password=apipass name=local:8080 type=service action=disable"
|
||||
'''
|
||||
|
||||
|
||||
import json
|
||||
import base64
|
||||
import socket
|
||||
|
||||
|
||||
class netscaler(object):
|
||||
|
||||
_nitro_base_url = '/nitro/v1/'
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
def http_request(self, api_endpoint, data_json={}):
|
||||
request_url = self._nsc_protocol + '://' + self._nsc_host + self._nitro_base_url + api_endpoint
|
||||
|
||||
data_json = urllib.urlencode(data_json)
|
||||
if not len(data_json):
|
||||
data_json = None
|
||||
|
||||
auth = base64.encodestring('%s:%s' % (self._nsc_user, self._nsc_pass)).replace('\n', '').strip()
|
||||
headers = {
|
||||
'Authorization': 'Basic %s' % auth,
|
||||
'Content-Type' : 'application/x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
response, info = fetch_url(self.module, request_url, data=data_json, headers=headers)
|
||||
|
||||
return json.load(response)
|
||||
|
||||
def prepare_request(self, action):
|
||||
resp = self.http_request(
|
||||
'config',
|
||||
{
|
||||
"object":
|
||||
{
|
||||
"params": {"action": action},
|
||||
self._type: {"name": self._name}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def core(module):
|
||||
n = netscaler(module)
|
||||
n._nsc_host = module.params.get('nsc_host')
|
||||
n._nsc_user = module.params.get('user')
|
||||
n._nsc_pass = module.params.get('password')
|
||||
n._nsc_protocol = module.params.get('nsc_protocol')
|
||||
n._name = module.params.get('name')
|
||||
n._type = module.params.get('type')
|
||||
action = module.params.get('action')
|
||||
|
||||
r = n.prepare_request(action)
|
||||
|
||||
return r['errorcode'], r
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
nsc_host = dict(required=True),
|
||||
nsc_protocol = dict(default='https'),
|
||||
user = dict(required=True),
|
||||
password = dict(required=True),
|
||||
action = dict(default='enable', choices=['enable','disable']),
|
||||
name = dict(default=socket.gethostname()),
|
||||
type = dict(default='server', choices=['service', 'server']),
|
||||
validate_certs=dict(default='yes', type='bool'),
|
||||
)
|
||||
)
|
||||
|
||||
rc = 0
|
||||
try:
|
||||
rc, result = core(module)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(rc=rc, msg=result)
|
||||
else:
|
||||
result['changed'] = True
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
main()
|
@ -1,135 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, David Stygstra <david.stygstra@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: openvswitch_bridge
|
||||
version_added: 1.4
|
||||
author: David Stygstra
|
||||
short_description: Manage Open vSwitch bridges
|
||||
requirements: [ ovs-vsctl ]
|
||||
description:
|
||||
- Manage Open vSwitch bridges
|
||||
options:
|
||||
bridge:
|
||||
required: true
|
||||
description:
|
||||
- Name of bridge to manage
|
||||
state:
|
||||
required: false
|
||||
default: "present"
|
||||
choices: [ present, absent ]
|
||||
description:
|
||||
- Whether the bridge should exist
|
||||
timeout:
|
||||
required: false
|
||||
default: 5
|
||||
description:
|
||||
- How long to wait for ovs-vswitchd to respond
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a bridge named br-int
|
||||
- openvswitch_bridge: bridge=br-int state=present
|
||||
'''
|
||||
|
||||
|
||||
class OVSBridge(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.bridge = module.params['bridge']
|
||||
self.state = module.params['state']
|
||||
self.timeout = module.params['timeout']
|
||||
|
||||
def _vsctl(self, command):
|
||||
'''Run ovs-vsctl command'''
|
||||
return self.module.run_command(['ovs-vsctl', '-t', str(self.timeout)] + command)
|
||||
|
||||
def exists(self):
|
||||
'''Check if the bridge already exists'''
|
||||
rc, _, err = self._vsctl(['br-exists', self.bridge])
|
||||
if rc == 0: # See ovs-vsctl(8) for status codes
|
||||
return True
|
||||
if rc == 2:
|
||||
return False
|
||||
raise Exception(err)
|
||||
|
||||
def add(self):
|
||||
'''Create the bridge'''
|
||||
rc, _, err = self._vsctl(['add-br', self.bridge])
|
||||
if rc != 0:
|
||||
raise Exception(err)
|
||||
|
||||
def delete(self):
|
||||
'''Delete the bridge'''
|
||||
rc, _, err = self._vsctl(['del-br', self.bridge])
|
||||
if rc != 0:
|
||||
raise Exception(err)
|
||||
|
||||
def check(self):
|
||||
'''Run check mode'''
|
||||
try:
|
||||
if self.state == 'absent' and self.exists():
|
||||
changed = True
|
||||
elif self.state == 'present' and not self.exists():
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
except Exception, e:
|
||||
self.module.fail_json(msg=str(e))
|
||||
self.module.exit_json(changed=changed)
|
||||
|
||||
def run(self):
|
||||
'''Make the necessary changes'''
|
||||
changed = False
|
||||
try:
|
||||
if self.state == 'absent':
|
||||
if self.exists():
|
||||
self.delete()
|
||||
changed = True
|
||||
elif self.state == 'present':
|
||||
if not self.exists():
|
||||
self.add()
|
||||
changed = True
|
||||
except Exception, e:
|
||||
self.module.fail_json(msg=str(e))
|
||||
self.module.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec={
|
||||
'bridge': {'required': True},
|
||||
'state': {'default': 'present', 'choices': ['present', 'absent']},
|
||||
'timeout': {'default': 5, 'type': 'int'}
|
||||
},
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
br = OVSBridge(module)
|
||||
if module.check_mode:
|
||||
br.check()
|
||||
else:
|
||||
br.run()
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,139 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, David Stygstra <david.stygstra@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: openvswitch_port
|
||||
version_added: 1.4
|
||||
author: David Stygstra
|
||||
short_description: Manage Open vSwitch ports
|
||||
requirements: [ ovs-vsctl ]
|
||||
description:
|
||||
- Manage Open vSwitch ports
|
||||
options:
|
||||
bridge:
|
||||
required: true
|
||||
description:
|
||||
- Name of bridge to manage
|
||||
port:
|
||||
required: true
|
||||
description:
|
||||
- Name of port to manage on the bridge
|
||||
state:
|
||||
required: false
|
||||
default: "present"
|
||||
choices: [ present, absent ]
|
||||
description:
|
||||
- Whether the port should exist
|
||||
timeout:
|
||||
required: false
|
||||
default: 5
|
||||
description:
|
||||
- How long to wait for ovs-vswitchd to respond
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Creates port eth2 on bridge br-ex
|
||||
- openvswitch_port: bridge=br-ex port=eth2 state=present
|
||||
'''
|
||||
|
||||
|
||||
class OVSPort(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.bridge = module.params['bridge']
|
||||
self.port = module.params['port']
|
||||
self.state = module.params['state']
|
||||
self.timeout = module.params['timeout']
|
||||
|
||||
def _vsctl(self, command):
|
||||
'''Run ovs-vsctl command'''
|
||||
return self.module.run_command(['ovs-vsctl', '-t', str(self.timeout)] + command)
|
||||
|
||||
def exists(self):
|
||||
'''Check if the port already exists'''
|
||||
rc, out, err = self._vsctl(['list-ports', self.bridge])
|
||||
if rc != 0:
|
||||
raise Exception(err)
|
||||
return any(port.rstrip() == self.port for port in out.split('\n'))
|
||||
|
||||
def add(self):
|
||||
'''Add the port'''
|
||||
rc, _, err = self._vsctl(['add-port', self.bridge, self.port])
|
||||
if rc != 0:
|
||||
raise Exception(err)
|
||||
|
||||
def delete(self):
|
||||
'''Remove the port'''
|
||||
rc, _, err = self._vsctl(['del-port', self.bridge, self.port])
|
||||
if rc != 0:
|
||||
raise Exception(err)
|
||||
|
||||
def check(self):
|
||||
'''Run check mode'''
|
||||
try:
|
||||
if self.state == 'absent' and self.exists():
|
||||
changed = True
|
||||
elif self.state == 'present' and not self.exists():
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
except Exception, e:
|
||||
self.module.fail_json(msg=str(e))
|
||||
self.module.exit_json(changed=changed)
|
||||
|
||||
def run(self):
|
||||
'''Make the necessary changes'''
|
||||
changed = False
|
||||
try:
|
||||
if self.state == 'absent':
|
||||
if self.exists():
|
||||
self.delete()
|
||||
changed = True
|
||||
elif self.state == 'present':
|
||||
if not self.exists():
|
||||
self.add()
|
||||
changed = True
|
||||
except Exception, e:
|
||||
self.module.fail_json(msg=str(e))
|
||||
self.module.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec={
|
||||
'bridge': {'required': True},
|
||||
'port': {'required': True},
|
||||
'state': {'default': 'present', 'choices': ['present', 'absent']},
|
||||
'timeout': {'default': 5, 'type': 'int'}
|
||||
},
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
port = OVSPort(module)
|
||||
if module.check_mode:
|
||||
port.check()
|
||||
else:
|
||||
port.run()
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,143 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: campfire
|
||||
version_added: "1.2"
|
||||
short_description: Send a message to Campfire
|
||||
description:
|
||||
- Send a message to Campfire.
|
||||
- Messages with newlines will result in a "Paste" message being sent.
|
||||
version_added: "1.2"
|
||||
options:
|
||||
subscription:
|
||||
description:
|
||||
- The subscription name to use.
|
||||
required: true
|
||||
token:
|
||||
description:
|
||||
- API token.
|
||||
required: true
|
||||
room:
|
||||
description:
|
||||
- Room number to which the message should be sent.
|
||||
required: true
|
||||
msg:
|
||||
description:
|
||||
- The message body.
|
||||
required: true
|
||||
notify:
|
||||
description:
|
||||
- Send a notification sound before the message.
|
||||
required: false
|
||||
choices: ["56k", "bell", "bezos", "bueller", "clowntown",
|
||||
"cottoneyejoe", "crickets", "dadgummit", "dangerzone",
|
||||
"danielsan", "deeper", "drama", "greatjob", "greyjoy",
|
||||
"guarantee", "heygirl", "horn", "horror",
|
||||
"inconceivable", "live", "loggins", "makeitso", "noooo",
|
||||
"nyan", "ohmy", "ohyeah", "pushit", "rimshot",
|
||||
"rollout", "rumble", "sax", "secret", "sexyback",
|
||||
"story", "tada", "tmyk", "trololo", "trombone", "unix",
|
||||
"vuvuzela", "what", "whoomp", "yeah", "yodel"]
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ urllib2, cgi ]
|
||||
author: Adam Garside <adam.garside@gmail.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- campfire: subscription=foo token=12345 room=123 msg="Task completed."
|
||||
|
||||
- campfire: subscription=foo token=12345 room=123 notify=loggins
|
||||
msg="Task completed ... with feeling."
|
||||
'''
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
try:
|
||||
import urllib2
|
||||
except ImportError:
|
||||
module.fail_json(msg="urllib2 is required")
|
||||
|
||||
try:
|
||||
import cgi
|
||||
except ImportError:
|
||||
module.fail_json(msg="cgi is required")
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
subscription=dict(required=True),
|
||||
token=dict(required=True),
|
||||
room=dict(required=True),
|
||||
msg=dict(required=True),
|
||||
notify=dict(required=False,
|
||||
choices=["56k", "bell", "bezos", "bueller",
|
||||
"clowntown", "cottoneyejoe",
|
||||
"crickets", "dadgummit", "dangerzone",
|
||||
"danielsan", "deeper", "drama",
|
||||
"greatjob", "greyjoy", "guarantee",
|
||||
"heygirl", "horn", "horror",
|
||||
"inconceivable", "live", "loggins",
|
||||
"makeitso", "noooo", "nyan", "ohmy",
|
||||
"ohyeah", "pushit", "rimshot",
|
||||
"rollout", "rumble", "sax", "secret",
|
||||
"sexyback", "story", "tada", "tmyk",
|
||||
"trololo", "trombone", "unix",
|
||||
"vuvuzela", "what", "whoomp", "yeah",
|
||||
"yodel"]),
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
subscription = module.params["subscription"]
|
||||
token = module.params["token"]
|
||||
room = module.params["room"]
|
||||
msg = module.params["msg"]
|
||||
notify = module.params["notify"]
|
||||
|
||||
URI = "https://%s.campfirenow.com" % subscription
|
||||
NSTR = "<message><type>SoundMessage</type><body>%s</body></message>"
|
||||
MSTR = "<message><body>%s</body></message>"
|
||||
AGENT = "Ansible/1.2"
|
||||
|
||||
try:
|
||||
|
||||
# Setup basic auth using token as the username
|
||||
pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
pm.add_password(None, URI, token, 'X')
|
||||
|
||||
# Setup Handler and define the opener for the request
|
||||
handler = urllib2.HTTPBasicAuthHandler(pm)
|
||||
opener = urllib2.build_opener(handler)
|
||||
|
||||
target_url = '%s/room/%s/speak.xml' % (URI, room)
|
||||
|
||||
# Send some audible notification if requested
|
||||
if notify:
|
||||
req = urllib2.Request(target_url, NSTR % cgi.escape(notify))
|
||||
req.add_header('Content-Type', 'application/xml')
|
||||
req.add_header('User-agent', AGENT)
|
||||
response = opener.open(req)
|
||||
|
||||
# Send the message
|
||||
req = urllib2.Request(target_url, MSTR % cgi.escape(msg))
|
||||
req.add_header('Content-Type', 'application/xml')
|
||||
req.add_header('User-agent', AGENT)
|
||||
response = opener.open(req)
|
||||
|
||||
except urllib2.HTTPError, e:
|
||||
if not (200 <= e.code < 300):
|
||||
module.fail_json(msg="unable to send msg: '%s', campfire api"
|
||||
" returned error code: '%s'" %
|
||||
(msg, e.code))
|
||||
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to send msg: %s" % msg)
|
||||
|
||||
module.exit_json(changed=True, room=room, msg=msg, notify=notify)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,192 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Matt Coddington <coddington@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: flowdock
|
||||
version_added: "1.2"
|
||||
author: Matt Coddington
|
||||
short_description: Send a message to a flowdock
|
||||
description:
|
||||
- Send a message to a flowdock team inbox or chat using the push API (see https://www.flowdock.com/api/team-inbox and https://www.flowdock.com/api/chat)
|
||||
options:
|
||||
token:
|
||||
description:
|
||||
- API token.
|
||||
required: true
|
||||
type:
|
||||
description:
|
||||
- Whether to post to 'inbox' or 'chat'
|
||||
required: true
|
||||
choices: [ "inbox", "chat" ]
|
||||
msg:
|
||||
description:
|
||||
- Content of the message
|
||||
required: true
|
||||
tags:
|
||||
description:
|
||||
- tags of the message, separated by commas
|
||||
required: false
|
||||
external_user_name:
|
||||
description:
|
||||
- (chat only - required) Name of the "user" sending the message
|
||||
required: false
|
||||
from_address:
|
||||
description:
|
||||
- (inbox only - required) Email address of the message sender
|
||||
required: false
|
||||
source:
|
||||
description:
|
||||
- (inbox only - required) Human readable identifier of the application that uses the Flowdock API
|
||||
required: false
|
||||
subject:
|
||||
description:
|
||||
- (inbox only - required) Subject line of the message
|
||||
required: false
|
||||
from_name:
|
||||
description:
|
||||
- (inbox only) Name of the message sender
|
||||
required: false
|
||||
reply_to:
|
||||
description:
|
||||
- (inbox only) Email address for replies
|
||||
required: false
|
||||
project:
|
||||
description:
|
||||
- (inbox only) Human readable identifier for more detailed message categorization
|
||||
required: false
|
||||
link:
|
||||
description:
|
||||
- (inbox only) Link associated with the message. This will be used to link the message subject in Team Inbox.
|
||||
required: false
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ urllib, urllib2 ]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- flowdock: type=inbox
|
||||
token=AAAAAA
|
||||
from_address=user@example.com
|
||||
source='my cool app'
|
||||
msg='test from ansible'
|
||||
subject='test subject'
|
||||
|
||||
- flowdock: type=chat
|
||||
token=AAAAAA
|
||||
external_user_name=testuser
|
||||
msg='test from ansible'
|
||||
tags=tag1,tag2,tag3
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
token=dict(required=True),
|
||||
msg=dict(required=True),
|
||||
type=dict(required=True, choices=["inbox","chat"]),
|
||||
external_user_name=dict(required=False),
|
||||
from_address=dict(required=False),
|
||||
source=dict(required=False),
|
||||
subject=dict(required=False),
|
||||
from_name=dict(required=False),
|
||||
reply_to=dict(required=False),
|
||||
project=dict(required=False),
|
||||
tags=dict(required=False),
|
||||
link=dict(required=False),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
type = module.params["type"]
|
||||
token = module.params["token"]
|
||||
if type == 'inbox':
|
||||
url = "https://api.flowdock.com/v1/messages/team_inbox/%s" % (token)
|
||||
else:
|
||||
url = "https://api.flowdock.com/v1/messages/chat/%s" % (token)
|
||||
|
||||
params = {}
|
||||
|
||||
# required params
|
||||
params['content'] = module.params["msg"]
|
||||
|
||||
# required params for the 'chat' type
|
||||
if module.params['external_user_name']:
|
||||
if type == 'inbox':
|
||||
module.fail_json(msg="external_user_name is not valid for the 'inbox' type")
|
||||
else:
|
||||
params['external_user_name'] = module.params["external_user_name"]
|
||||
elif type == 'chat':
|
||||
module.fail_json(msg="%s is required for the 'inbox' type" % item)
|
||||
|
||||
# required params for the 'inbox' type
|
||||
for item in [ 'from_address', 'source', 'subject' ]:
|
||||
if module.params[item]:
|
||||
if type == 'chat':
|
||||
module.fail_json(msg="%s is not valid for the 'chat' type" % item)
|
||||
else:
|
||||
params[item] = module.params[item]
|
||||
elif type == 'inbox':
|
||||
module.fail_json(msg="%s is required for the 'inbox' type" % item)
|
||||
|
||||
# optional params
|
||||
if module.params["tags"]:
|
||||
params['tags'] = module.params["tags"]
|
||||
|
||||
# optional params for the 'inbox' type
|
||||
for item in [ 'from_name', 'reply_to', 'project', 'link' ]:
|
||||
if module.params[item]:
|
||||
if type == 'chat':
|
||||
module.fail_json(msg="%s is not valid for the 'chat' type" % item)
|
||||
else:
|
||||
params[item] = module.params[item]
|
||||
|
||||
# If we're in check mode, just exit pretending like we succeeded
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
# Send the data to Flowdock
|
||||
data = urllib.urlencode(params)
|
||||
response, info = fetch_url(module, url, data=data)
|
||||
if info['status'] != 200:
|
||||
module.fail_json(msg="unable to send msg: %s" % info['msg'])
|
||||
|
||||
module.exit_json(changed=True, msg=module.params["msg"])
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
||||
|
@ -1,99 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: grove
|
||||
version_added: 1.4
|
||||
short_description: Sends a notification to a grove.io channel
|
||||
description:
|
||||
- The M(grove) module sends a message for a service to a Grove.io
|
||||
channel.
|
||||
options:
|
||||
channel_token:
|
||||
description:
|
||||
- Token of the channel to post to.
|
||||
required: true
|
||||
service:
|
||||
description:
|
||||
- Name of the service (displayed as the "user" in the message)
|
||||
required: false
|
||||
default: ansible
|
||||
message:
|
||||
description:
|
||||
- Message content
|
||||
required: true
|
||||
url:
|
||||
description:
|
||||
- Service URL for the web client
|
||||
required: false
|
||||
icon_url:
|
||||
description:
|
||||
- Icon for the service
|
||||
required: false
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
author: Jonas Pfenniger <zimbatm@zimbatm.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- grove: >
|
||||
channel_token=6Ph62VBBJOccmtTPZbubiPzdrhipZXtg
|
||||
service=my-app
|
||||
message=deployed {{ target }}
|
||||
'''
|
||||
|
||||
BASE_URL = 'https://grove.io/api/notice/%s/'
|
||||
|
||||
# ==============================================================
|
||||
# do_notify_grove
|
||||
|
||||
def do_notify_grove(module, channel_token, service, message, url=None, icon_url=None):
|
||||
my_url = BASE_URL % (channel_token,)
|
||||
|
||||
my_data = dict(service=service, message=message)
|
||||
if url is not None:
|
||||
my_data['url'] = url
|
||||
if icon_url is not None:
|
||||
my_data['icon_url'] = icon_url
|
||||
|
||||
data = urllib.urlencode(my_data)
|
||||
response, info = fetch_url(module, my_url, data=data)
|
||||
if info['status'] != 200:
|
||||
module.fail_json(msg="failed to send notification: %s" % info['msg'])
|
||||
|
||||
# ==============================================================
|
||||
# main
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
channel_token = dict(type='str', required=True),
|
||||
message = dict(type='str', required=True),
|
||||
service = dict(type='str', default='ansible'),
|
||||
url = dict(type='str', default=None),
|
||||
icon_url = dict(type='str', default=None),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
)
|
||||
)
|
||||
|
||||
channel_token = module.params['channel_token']
|
||||
service = module.params['service']
|
||||
message = module.params['message']
|
||||
url = module.params['url']
|
||||
icon_url = module.params['icon_url']
|
||||
|
||||
do_notify_grove(module, channel_token, service, message, url, icon_url)
|
||||
|
||||
# Mission complete
|
||||
module.exit_json(msg="OK")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,149 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: hipchat
|
||||
version_added: "1.2"
|
||||
short_description: Send a message to hipchat
|
||||
description:
|
||||
- Send a message to hipchat
|
||||
options:
|
||||
token:
|
||||
description:
|
||||
- API token.
|
||||
required: true
|
||||
room:
|
||||
description:
|
||||
- ID or name of the room.
|
||||
required: true
|
||||
from:
|
||||
description:
|
||||
- Name the message will appear be sent from. max 15 characters.
|
||||
Over 15, will be shorten.
|
||||
required: false
|
||||
default: Ansible
|
||||
msg:
|
||||
description:
|
||||
- The message body.
|
||||
required: true
|
||||
default: null
|
||||
color:
|
||||
description:
|
||||
- Background color for the message. Default is yellow.
|
||||
required: false
|
||||
default: yellow
|
||||
choices: [ "yellow", "red", "green", "purple", "gray", "random" ]
|
||||
msg_format:
|
||||
description:
|
||||
- message format. html or text. Default is text.
|
||||
required: false
|
||||
default: text
|
||||
choices: [ "text", "html" ]
|
||||
notify:
|
||||
description:
|
||||
- notify or not (change the tab color, play a sound, etc)
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: [ "yes", "no" ]
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
api:
|
||||
description:
|
||||
- API url if using a self-hosted hipchat server
|
||||
required: false
|
||||
default: 'https://api.hipchat.com/v1/rooms/message'
|
||||
version_added: 1.6.0
|
||||
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ urllib, urllib2 ]
|
||||
author: WAKAYAMA Shirou
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- hipchat: token=AAAAAA room=notify msg="Ansible task finished"
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# HipChat module specific support methods.
|
||||
#
|
||||
|
||||
MSG_URI = "https://api.hipchat.com/v1/rooms/message"
|
||||
|
||||
def send_msg(module, token, room, msg_from, msg, msg_format='text',
|
||||
color='yellow', notify=False, api=MSG_URI):
|
||||
'''sending message to hipchat'''
|
||||
|
||||
params = {}
|
||||
params['room_id'] = room
|
||||
params['from'] = msg_from[:15] # max length is 15
|
||||
params['message'] = msg
|
||||
params['message_format'] = msg_format
|
||||
params['color'] = color
|
||||
params['api'] = api
|
||||
|
||||
if notify:
|
||||
params['notify'] = 1
|
||||
else:
|
||||
params['notify'] = 0
|
||||
|
||||
url = api + "?auth_token=%s" % (token)
|
||||
data = urllib.urlencode(params)
|
||||
response, info = fetch_url(module, url, data=data)
|
||||
if info['status'] == 200:
|
||||
return response.read()
|
||||
else:
|
||||
module.fail_json(msg="failed to send message, return status=%s" % str(info['status']))
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
token=dict(required=True),
|
||||
room=dict(required=True),
|
||||
msg=dict(required=True),
|
||||
msg_from=dict(default="Ansible", aliases=['from']),
|
||||
color=dict(default="yellow", choices=["yellow", "red", "green",
|
||||
"purple", "gray", "random"]),
|
||||
msg_format=dict(default="text", choices=["text", "html"]),
|
||||
notify=dict(default=True, type='bool'),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
api = dict(default=MSG_URI),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
token = module.params["token"]
|
||||
room = module.params["room"]
|
||||
msg = module.params["msg"]
|
||||
msg_from = module.params["msg_from"]
|
||||
color = module.params["color"]
|
||||
msg_format = module.params["msg_format"]
|
||||
notify = module.params["notify"]
|
||||
api = module.params["api"]
|
||||
|
||||
try:
|
||||
send_msg(module, token, room, msg_from, msg, msg_format, color, notify, api)
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to sent msg: %s" % e)
|
||||
|
||||
changed = True
|
||||
module.exit_json(changed=changed, room=room, msg_from=msg_from, msg=msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,215 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Jan-Piet Mens <jpmens () gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: irc
|
||||
version_added: "1.2"
|
||||
short_description: Send a message to an IRC channel
|
||||
description:
|
||||
- Send a message to an IRC channel. This is a very simplistic implementation.
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- IRC server name/address
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- IRC server port number
|
||||
required: false
|
||||
default: 6667
|
||||
nick:
|
||||
description:
|
||||
- Nickname. May be shortened, depending on server's NICKLEN setting.
|
||||
required: false
|
||||
default: ansible
|
||||
msg:
|
||||
description:
|
||||
- The message body.
|
||||
required: true
|
||||
default: null
|
||||
color:
|
||||
description:
|
||||
- Text color for the message. ("none" is a valid option in 1.6 or later, in 1.6 and prior, the default color is black, not "none").
|
||||
required: false
|
||||
default: "none"
|
||||
choices: [ "none", "yellow", "red", "green", "blue", "black" ]
|
||||
channel:
|
||||
description:
|
||||
- Channel name
|
||||
required: true
|
||||
key:
|
||||
description:
|
||||
- Channel key
|
||||
required: false
|
||||
version_added: 1.7
|
||||
passwd:
|
||||
description:
|
||||
- Server password
|
||||
required: false
|
||||
timeout:
|
||||
description:
|
||||
- Timeout to use while waiting for successful registration and join
|
||||
messages, this is to prevent an endless loop
|
||||
default: 30
|
||||
version_added: 1.5
|
||||
use_ssl:
|
||||
description:
|
||||
- Designates whether TLS/SSL should be used when connecting to the IRC server
|
||||
default: False
|
||||
version_added: 1.8
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ socket ]
|
||||
author: Jan-Piet Mens, Matt Martz
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- irc: server=irc.example.net channel="#t1" msg="Hello world"
|
||||
|
||||
- local_action: irc port=6669
|
||||
channel="#t1"
|
||||
msg="All finished at {{ ansible_date_time.iso8601 }}"
|
||||
color=red
|
||||
nick=ansibleIRC
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# IRC module support methods.
|
||||
#
|
||||
|
||||
import re
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
from time import sleep
|
||||
|
||||
|
||||
def send_msg(channel, msg, server='localhost', port='6667', key=None,
|
||||
nick="ansible", color='none', passwd=False, timeout=30, use_ssl=False):
|
||||
'''send message to IRC'''
|
||||
|
||||
colornumbers = {
|
||||
'black': "01",
|
||||
'red': "04",
|
||||
'green': "09",
|
||||
'yellow': "08",
|
||||
'blue': "12",
|
||||
}
|
||||
|
||||
try:
|
||||
colornumber = colornumbers[color]
|
||||
colortext = "\x03" + colornumber
|
||||
except:
|
||||
colortext = ""
|
||||
|
||||
message = colortext + msg
|
||||
|
||||
irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
if use_ssl:
|
||||
irc = ssl.wrap_socket(irc)
|
||||
irc.connect((server, int(port)))
|
||||
if passwd:
|
||||
irc.send('PASS %s\r\n' % passwd)
|
||||
irc.send('NICK %s\r\n' % nick)
|
||||
irc.send('USER %s %s %s :ansible IRC\r\n' % (nick, nick, nick))
|
||||
motd = ''
|
||||
start = time.time()
|
||||
while 1:
|
||||
motd += irc.recv(1024)
|
||||
# The server might send back a shorter nick than we specified (due to NICKLEN),
|
||||
# so grab that and use it from now on (assuming we find the 00[1-4] response).
|
||||
match = re.search('^:\S+ 00[1-4] (?P<nick>\S+) :', motd, flags=re.M)
|
||||
if match:
|
||||
nick = match.group('nick')
|
||||
break
|
||||
elif time.time() - start > timeout:
|
||||
raise Exception('Timeout waiting for IRC server welcome response')
|
||||
sleep(0.5)
|
||||
|
||||
if key:
|
||||
irc.send('JOIN %s %s\r\n' % (channel, key))
|
||||
else:
|
||||
irc.send('JOIN %s\r\n' % channel)
|
||||
|
||||
join = ''
|
||||
start = time.time()
|
||||
while 1:
|
||||
join += irc.recv(1024)
|
||||
if re.search('^:\S+ 366 %s %s :' % (nick, channel), join, flags=re.M):
|
||||
break
|
||||
elif time.time() - start > timeout:
|
||||
raise Exception('Timeout waiting for IRC JOIN response')
|
||||
sleep(0.5)
|
||||
|
||||
irc.send('PRIVMSG %s :%s\r\n' % (channel, message))
|
||||
sleep(1)
|
||||
irc.send('PART %s\r\n' % channel)
|
||||
irc.send('QUIT\r\n')
|
||||
sleep(1)
|
||||
irc.close()
|
||||
|
||||
# ===========================================
|
||||
# Main
|
||||
#
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
server=dict(default='localhost'),
|
||||
port=dict(default=6667),
|
||||
nick=dict(default='ansible'),
|
||||
msg=dict(required=True),
|
||||
color=dict(default="none", choices=["yellow", "red", "green",
|
||||
"blue", "black", "none"]),
|
||||
channel=dict(required=True),
|
||||
key=dict(),
|
||||
passwd=dict(),
|
||||
timeout=dict(type='int', default=30),
|
||||
use_ssl=dict(type='bool', default=False)
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
server = module.params["server"]
|
||||
port = module.params["port"]
|
||||
nick = module.params["nick"]
|
||||
msg = module.params["msg"]
|
||||
color = module.params["color"]
|
||||
channel = module.params["channel"]
|
||||
key = module.params["key"]
|
||||
passwd = module.params["passwd"]
|
||||
timeout = module.params["timeout"]
|
||||
use_ssl = module.params["use_ssl"]
|
||||
|
||||
try:
|
||||
send_msg(channel, msg, server, port, key, nick, color, passwd, timeout, use_ssl)
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to send to IRC: %s" % e)
|
||||
|
||||
module.exit_json(changed=False, channel=channel, nick=nick,
|
||||
msg=msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,146 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
version_added: "1.2"
|
||||
module: jabber
|
||||
short_description: Send a message to jabber user or chat room
|
||||
description:
|
||||
- Send a message to jabber
|
||||
options:
|
||||
user:
|
||||
description:
|
||||
User as which to connect
|
||||
required: true
|
||||
password:
|
||||
description:
|
||||
password for user to connect
|
||||
required: true
|
||||
to:
|
||||
description:
|
||||
user ID or name of the room, when using room use a slash to indicate your nick.
|
||||
required: true
|
||||
msg:
|
||||
description:
|
||||
- The message body.
|
||||
required: true
|
||||
default: null
|
||||
host:
|
||||
description:
|
||||
host to connect, overrides user info
|
||||
required: false
|
||||
port:
|
||||
description:
|
||||
port to connect to, overrides default
|
||||
required: false
|
||||
default: 5222
|
||||
encoding:
|
||||
description:
|
||||
message encoding
|
||||
required: false
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ xmpp ]
|
||||
author: Brian Coca
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# send a message to a user
|
||||
- jabber: user=mybot@example.net
|
||||
password=secret
|
||||
to=friend@example.net
|
||||
msg="Ansible task finished"
|
||||
|
||||
# send a message to a room
|
||||
- jabber: user=mybot@example.net
|
||||
password=secret
|
||||
to=mychaps@conference.example.net/ansiblebot
|
||||
msg="Ansible task finished"
|
||||
|
||||
# send a message, specifying the host and port
|
||||
- jabber user=mybot@example.net
|
||||
host=talk.example.net
|
||||
port=5223
|
||||
password=secret
|
||||
to=mychaps@example.net
|
||||
msg="Ansible task finished"
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
HAS_XMPP = True
|
||||
try:
|
||||
import xmpp
|
||||
except ImportError:
|
||||
HAS_XMPP = False
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
user=dict(required=True),
|
||||
password=dict(required=True),
|
||||
to=dict(required=True),
|
||||
msg=dict(required=True),
|
||||
host=dict(required=False),
|
||||
port=dict(required=False,default=5222),
|
||||
encoding=dict(required=False),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_XMPP:
|
||||
module.fail_json(msg="xmpp is not installed")
|
||||
|
||||
jid = xmpp.JID(module.params['user'])
|
||||
user = jid.getNode()
|
||||
server = jid.getDomain()
|
||||
port = module.params['port']
|
||||
password = module.params['password']
|
||||
try:
|
||||
to, nick = module.params['to'].split('/', 1)
|
||||
except ValueError:
|
||||
to, nick = module.params['to'], None
|
||||
|
||||
if module.params['host']:
|
||||
host = module.params['host']
|
||||
else:
|
||||
host = server
|
||||
if module.params['encoding']:
|
||||
xmpp.simplexml.ENCODING = params['encoding']
|
||||
|
||||
msg = xmpp.protocol.Message(body=module.params['msg'])
|
||||
|
||||
try:
|
||||
conn=xmpp.Client(server)
|
||||
if not conn.connect(server=(host,port)):
|
||||
module.fail_json(rc=1, msg='Failed to connect to server: %s' % (server))
|
||||
if not conn.auth(user,password,'Ansible'):
|
||||
module.fail_json(rc=1, msg='Failed to authorize %s on: %s' % (user,server))
|
||||
# some old servers require this, also the sleep following send
|
||||
conn.sendInitPresence(requestRoster=0)
|
||||
|
||||
if nick: # sending to room instead of user, need to join
|
||||
msg.setType('groupchat')
|
||||
msg.setTag('x', namespace='http://jabber.org/protocol/muc#user')
|
||||
conn.send(xmpp.Presence(to=module.params['to']))
|
||||
time.sleep(1)
|
||||
else:
|
||||
msg.setType('chat')
|
||||
|
||||
msg.setTo(to)
|
||||
if not module.check_mode:
|
||||
conn.send(msg)
|
||||
time.sleep(1)
|
||||
conn.disconnect()
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to send msg: %s" % e)
|
||||
|
||||
module.exit_json(changed=False, to=to, user=user, msg=msg.getBody())
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,252 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2012 Dag Wieers <dag@wieers.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
author: Dag Wieers
|
||||
module: mail
|
||||
short_description: Send an email
|
||||
description:
|
||||
- This module is useful for sending emails from playbooks.
|
||||
- One may wonder why automate sending emails? In complex environments
|
||||
there are from time to time processes that cannot be automated, either
|
||||
because you lack the authority to make it so, or because not everyone
|
||||
agrees to a common approach.
|
||||
- If you cannot automate a specific step, but the step is non-blocking,
|
||||
sending out an email to the responsible party to make him perform his
|
||||
part of the bargain is an elegant way to put the responsibility in
|
||||
someone else's lap.
|
||||
- Of course sending out a mail can be equally useful as a way to notify
|
||||
one or more people in a team that a specific action has been
|
||||
(successfully) taken.
|
||||
version_added: "0.8"
|
||||
options:
|
||||
from:
|
||||
description:
|
||||
- The email-address the mail is sent from. May contain address and phrase.
|
||||
default: root
|
||||
required: false
|
||||
to:
|
||||
description:
|
||||
- The email-address(es) the mail is being sent to. This is
|
||||
a comma-separated list, which may contain address and phrase portions.
|
||||
default: root
|
||||
required: false
|
||||
cc:
|
||||
description:
|
||||
- The email-address(es) the mail is being copied to. This is
|
||||
a comma-separated list, which may contain address and phrase portions.
|
||||
required: false
|
||||
bcc:
|
||||
description:
|
||||
- The email-address(es) the mail is being 'blind' copied to. This is
|
||||
a comma-separated list, which may contain address and phrase portions.
|
||||
required: false
|
||||
subject:
|
||||
description:
|
||||
- The subject of the email being sent.
|
||||
aliases: [ msg ]
|
||||
required: true
|
||||
body:
|
||||
description:
|
||||
- The body of the email being sent.
|
||||
default: $subject
|
||||
required: false
|
||||
host:
|
||||
description:
|
||||
- The mail server
|
||||
default: 'localhost'
|
||||
required: false
|
||||
port:
|
||||
description:
|
||||
- The mail server port
|
||||
default: '25'
|
||||
required: false
|
||||
version_added: "1.0"
|
||||
attach:
|
||||
description:
|
||||
- A space-separated list of pathnames of files to attach to the message.
|
||||
Attached files will have their content-type set to C(application/octet-stream).
|
||||
default: null
|
||||
required: false
|
||||
version_added: "1.0"
|
||||
headers:
|
||||
description:
|
||||
- A vertical-bar-separated list of headers which should be added to the message.
|
||||
Each individual header is specified as C(header=value) (see example below).
|
||||
default: null
|
||||
required: false
|
||||
version_added: "1.0"
|
||||
charset:
|
||||
description:
|
||||
- The character set of email being sent
|
||||
default: 'us-ascii'
|
||||
required: false
|
||||
"""
|
||||
|
||||
EXAMPLES = '''
|
||||
# Example playbook sending mail to root
|
||||
- local_action: mail msg='System {{ ansible_hostname }} has been successfully provisioned.'
|
||||
|
||||
# Send e-mail to a bunch of users, attaching files
|
||||
- local_action: mail
|
||||
host='127.0.0.1'
|
||||
port=2025
|
||||
subject="Ansible-report"
|
||||
body="Hello, this is an e-mail. I hope you like it ;-)"
|
||||
from="jane@example.net (Jane Jolie)"
|
||||
to="John Doe <j.d@example.org>, Suzie Something <sue@example.com>"
|
||||
cc="Charlie Root <root@localhost>"
|
||||
attach="/etc/group /tmp/pavatar2.png"
|
||||
headers=Reply-To=john@example.com|X-Special="Something or other"
|
||||
charset=utf8
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import smtplib
|
||||
|
||||
try:
|
||||
from email import encoders
|
||||
import email.utils
|
||||
from email.utils import parseaddr, formataddr
|
||||
from email.mime.base import MIMEBase
|
||||
from mail.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
except ImportError:
|
||||
from email import Encoders as encoders
|
||||
import email.Utils
|
||||
from email.Utils import parseaddr, formataddr
|
||||
from email.MIMEBase import MIMEBase
|
||||
from email.MIMEMultipart import MIMEMultipart
|
||||
from email.MIMEText import MIMEText
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
host = dict(default='localhost'),
|
||||
port = dict(default='25'),
|
||||
sender = dict(default='root', aliases=['from']),
|
||||
to = dict(default='root', aliases=['recipients']),
|
||||
cc = dict(default=None),
|
||||
bcc = dict(default=None),
|
||||
subject = dict(required=True, aliases=['msg']),
|
||||
body = dict(default=None),
|
||||
attach = dict(default=None),
|
||||
headers = dict(default=None),
|
||||
charset = dict(default='us-ascii')
|
||||
)
|
||||
)
|
||||
|
||||
host = module.params.get('host')
|
||||
port = module.params.get('port')
|
||||
sender = module.params.get('sender')
|
||||
recipients = module.params.get('to')
|
||||
copies = module.params.get('cc')
|
||||
blindcopies = module.params.get('bcc')
|
||||
subject = module.params.get('subject')
|
||||
body = module.params.get('body')
|
||||
attach_files = module.params.get('attach')
|
||||
headers = module.params.get('headers')
|
||||
charset = module.params.get('charset')
|
||||
|
||||
sender_phrase, sender_addr = parseaddr(sender)
|
||||
|
||||
if not body:
|
||||
body = subject
|
||||
|
||||
try:
|
||||
smtp = smtplib.SMTP(host, port=int(port))
|
||||
except Exception, e:
|
||||
module.fail_json(rc=1, msg='Failed to send mail to server %s on port %s: %s' % (host, port, e))
|
||||
|
||||
|
||||
msg = MIMEMultipart()
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = formataddr((sender_phrase, sender_addr))
|
||||
msg.preamble = "Multipart message"
|
||||
|
||||
if headers is not None:
|
||||
for hdr in [x.strip() for x in headers.split('|')]:
|
||||
try:
|
||||
h_key, h_val = hdr.split('=')
|
||||
msg.add_header(h_key, h_val)
|
||||
except:
|
||||
pass
|
||||
|
||||
if 'X-Mailer' not in msg:
|
||||
msg.add_header('X-Mailer', "Ansible")
|
||||
|
||||
to_list = []
|
||||
cc_list = []
|
||||
addr_list = []
|
||||
|
||||
if recipients is not None:
|
||||
for addr in [x.strip() for x in recipients.split(',')]:
|
||||
to_list.append( formataddr( parseaddr(addr)) )
|
||||
addr_list.append( parseaddr(addr)[1] ) # address only, w/o phrase
|
||||
if copies is not None:
|
||||
for addr in [x.strip() for x in copies.split(',')]:
|
||||
cc_list.append( formataddr( parseaddr(addr)) )
|
||||
addr_list.append( parseaddr(addr)[1] ) # address only, w/o phrase
|
||||
if blindcopies is not None:
|
||||
for addr in [x.strip() for x in blindcopies.split(',')]:
|
||||
addr_list.append( parseaddr(addr)[1] )
|
||||
|
||||
if len(to_list) > 0:
|
||||
msg['To'] = ", ".join(to_list)
|
||||
if len(cc_list) > 0:
|
||||
msg['Cc'] = ", ".join(cc_list)
|
||||
|
||||
part = MIMEText(body + "\n\n", _charset=charset)
|
||||
msg.attach(part)
|
||||
|
||||
if attach_files is not None:
|
||||
for file in attach_files.split():
|
||||
try:
|
||||
fp = open(file, 'rb')
|
||||
|
||||
part = MIMEBase('application', 'octet-stream')
|
||||
part.set_payload(fp.read())
|
||||
fp.close()
|
||||
|
||||
encoders.encode_base64(part)
|
||||
|
||||
part.add_header('Content-disposition', 'attachment', filename=os.path.basename(file))
|
||||
msg.attach(part)
|
||||
except Exception, e:
|
||||
module.fail_json(rc=1, msg="Failed to send mail: can't attach file %s: %s" % (file, e))
|
||||
sys.exit()
|
||||
|
||||
composed = msg.as_string()
|
||||
|
||||
try:
|
||||
smtp.sendmail(sender_addr, set(addr_list), composed)
|
||||
except Exception, e:
|
||||
module.fail_json(rc=1, msg='Failed to send mail to %s: %s' % (", ".join(addr_list), e))
|
||||
|
||||
smtp.quit()
|
||||
|
||||
module.exit_json(changed=False)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,166 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, 2014, Jan-Piet Mens <jpmens () gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: mqtt
|
||||
short_description: Publish a message on an MQTT topic for the IoT
|
||||
version_added: "1.2"
|
||||
description:
|
||||
- Publish a message on an MQTT topic.
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
- MQTT broker address/name
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- MQTT broker port number
|
||||
required: false
|
||||
default: 1883
|
||||
username:
|
||||
description:
|
||||
- Username to authenticate against the broker.
|
||||
required: false
|
||||
password:
|
||||
description:
|
||||
- Password for C(username) to authenticate against the broker.
|
||||
required: false
|
||||
client_id:
|
||||
description:
|
||||
- MQTT client identifier
|
||||
required: false
|
||||
default: hostname + pid
|
||||
topic:
|
||||
description:
|
||||
- MQTT topic name
|
||||
required: true
|
||||
default: null
|
||||
payload:
|
||||
description:
|
||||
- Payload. The special string C("None") may be used to send a NULL
|
||||
(i.e. empty) payload which is useful to simply notify with the I(topic)
|
||||
or to clear previously retained messages.
|
||||
required: true
|
||||
default: null
|
||||
qos:
|
||||
description:
|
||||
- QoS (Quality of Service)
|
||||
required: false
|
||||
default: 0
|
||||
choices: [ "0", "1", "2" ]
|
||||
retain:
|
||||
description:
|
||||
- Setting this flag causes the broker to retain (i.e. keep) the message so that
|
||||
applications that subsequently subscribe to the topic can received the last
|
||||
retained message immediately.
|
||||
required: false
|
||||
default: False
|
||||
|
||||
# informational: requirements for nodes
|
||||
requirements: [ mosquitto ]
|
||||
notes:
|
||||
- This module requires a connection to an MQTT broker such as Mosquitto
|
||||
U(http://mosquitto.org) and the I(Paho) C(mqtt) Python client (U(https://pypi.python.org/pypi/paho-mqtt)).
|
||||
author: Jan-Piet Mens
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- local_action: mqtt
|
||||
topic=service/ansible/{{ ansible_hostname }}
|
||||
payload="Hello at {{ ansible_date_time.iso8601 }}"
|
||||
qos=0
|
||||
retain=false
|
||||
client_id=ans001
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# MQTT module support methods.
|
||||
#
|
||||
|
||||
HAS_PAHOMQTT = True
|
||||
try:
|
||||
import socket
|
||||
import paho.mqtt.publish as mqtt
|
||||
except ImportError:
|
||||
HAS_PAHOMQTT = False
|
||||
|
||||
# ===========================================
|
||||
# Main
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
server = dict(default = 'localhost'),
|
||||
port = dict(default = 1883),
|
||||
topic = dict(required = True),
|
||||
payload = dict(required = True),
|
||||
client_id = dict(default = None),
|
||||
qos = dict(default="0", choices=["0", "1", "2"]),
|
||||
retain = dict(default=False, type='bool'),
|
||||
username = dict(default = None),
|
||||
password = dict(default = None),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_PAHOMQTT:
|
||||
module.fail_json(msg="Paho MQTT is not installed")
|
||||
|
||||
server = module.params.get("server", 'localhost')
|
||||
port = module.params.get("port", 1883)
|
||||
topic = module.params.get("topic")
|
||||
payload = module.params.get("payload")
|
||||
client_id = module.params.get("client_id", '')
|
||||
qos = int(module.params.get("qos", 0))
|
||||
retain = module.params.get("retain")
|
||||
username = module.params.get("username", None)
|
||||
password = module.params.get("password", None)
|
||||
|
||||
if client_id is None:
|
||||
client_id = "%s_%s" % (socket.getfqdn(), os.getpid())
|
||||
|
||||
if payload and payload == 'None':
|
||||
payload = None
|
||||
|
||||
auth=None
|
||||
if username is not None:
|
||||
auth = { 'username' : username, 'password' : password }
|
||||
|
||||
try:
|
||||
rc = mqtt.single(topic, payload,
|
||||
qos=qos,
|
||||
retain=retain,
|
||||
client_id=client_id,
|
||||
hostname=server,
|
||||
port=port,
|
||||
auth=auth)
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to publish to MQTT broker %s" % (e))
|
||||
|
||||
module.exit_json(changed=False, topic=topic)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,140 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Matt Martz <matt@sivel.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: nexmo
|
||||
short_description: Send a SMS via nexmo
|
||||
description:
|
||||
- Send a SMS message via nexmo
|
||||
version_added: 1.6
|
||||
author: Matt Martz
|
||||
options:
|
||||
api_key:
|
||||
description:
|
||||
- Nexmo API Key
|
||||
required: true
|
||||
api_secret:
|
||||
description:
|
||||
- Nexmo API Secret
|
||||
required: true
|
||||
src:
|
||||
description:
|
||||
- Nexmo Number to send from
|
||||
required: true
|
||||
dest:
|
||||
description:
|
||||
- Phone number(s) to send SMS message to
|
||||
required: true
|
||||
msg:
|
||||
description:
|
||||
- Message to text to send. Messages longer than 160 characters will be
|
||||
split into multiple messages
|
||||
required: true
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices:
|
||||
- 'yes'
|
||||
- 'no'
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Send notification message via Nexmo
|
||||
local_action:
|
||||
module: nexmo
|
||||
api_key: 640c8a53
|
||||
api_secret: 0ce239a6
|
||||
src: 12345678901
|
||||
dest:
|
||||
- 10987654321
|
||||
- 16789012345
|
||||
msg: "{{ inventory_hostname }} completed"
|
||||
"""
|
||||
|
||||
|
||||
NEXMO_API = 'https://rest.nexmo.com/sms/json'
|
||||
|
||||
|
||||
def send_msg(module):
|
||||
failed = list()
|
||||
responses = dict()
|
||||
msg = {
|
||||
'api_key': module.params.get('api_key'),
|
||||
'api_secret': module.params.get('api_secret'),
|
||||
'from': module.params.get('src'),
|
||||
'text': module.params.get('msg')
|
||||
}
|
||||
for number in module.params.get('dest'):
|
||||
msg['to'] = number
|
||||
url = "%s?%s" % (NEXMO_API, urllib.urlencode(msg))
|
||||
|
||||
headers = dict(Accept='application/json')
|
||||
response, info = fetch_url(module, url, headers=headers)
|
||||
if info['status'] != 200:
|
||||
failed.append(number)
|
||||
responses[number] = dict(failed=True)
|
||||
|
||||
try:
|
||||
responses[number] = json.load(response)
|
||||
except:
|
||||
failed.append(number)
|
||||
responses[number] = dict(failed=True)
|
||||
else:
|
||||
for message in responses[number]['messages']:
|
||||
if int(message['status']) != 0:
|
||||
failed.append(number)
|
||||
responses[number] = dict(failed=True, **responses[number])
|
||||
|
||||
if failed:
|
||||
msg = 'One or messages failed to send'
|
||||
else:
|
||||
msg = ''
|
||||
|
||||
module.exit_json(failed=bool(failed), msg=msg, changed=False,
|
||||
responses=responses)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = url_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
api_key=dict(required=True, no_log=True),
|
||||
api_secret=dict(required=True, no_log=True),
|
||||
src=dict(required=True, type='int'),
|
||||
dest=dict(required=True, type='list'),
|
||||
msg=dict(required=True),
|
||||
),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec
|
||||
)
|
||||
|
||||
send_msg(module)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
@ -1,74 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Michael DeHaan <michael@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: osx_say
|
||||
version_added: "1.2"
|
||||
short_description: Makes an OSX computer to speak.
|
||||
description:
|
||||
- makes an OS computer speak! Amuse your friends, annoy your coworkers!
|
||||
notes:
|
||||
- If you like this module, you may also be interested in the osx_say callback in the plugins/ directory of the source checkout.
|
||||
options:
|
||||
msg:
|
||||
description:
|
||||
What to say
|
||||
required: true
|
||||
voice:
|
||||
description:
|
||||
What voice to use
|
||||
required: false
|
||||
requirements: [ say ]
|
||||
author: Michael DeHaan
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- local_action: osx_say msg="{{inventory_hostname}} is all done" voice=Zarvox
|
||||
'''
|
||||
|
||||
DEFAULT_VOICE='Trinoids'
|
||||
|
||||
def say(module, msg, voice):
|
||||
module.run_command(["/usr/bin/say", msg, "--voice=%s" % (voice)], check_rc=True)
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
msg=dict(required=True),
|
||||
voice=dict(required=False, default=DEFAULT_VOICE),
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
if not os.path.exists("/usr/bin/say"):
|
||||
module.fail_json(msg="/usr/bin/say is not installed")
|
||||
|
||||
msg = module.params['msg']
|
||||
voice = module.params['voice']
|
||||
|
||||
say(module, msg, voice)
|
||||
|
||||
module.exit_json(msg=msg, changed=False)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,173 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Ramon de la Fuente <ramon@delafuente.nl>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: slack
|
||||
short_description: Send Slack notifications
|
||||
description:
|
||||
- The M(slack) module sends notifications to U(http://slack.com) via the Incoming WebHook integration
|
||||
version_added: 1.6
|
||||
author: Ramon de la Fuente <ramon@delafuente.nl>
|
||||
options:
|
||||
domain:
|
||||
description:
|
||||
- Slack (sub)domain for your environment without protocol.
|
||||
(i.e. C(future500.slack.com))
|
||||
required: true
|
||||
token:
|
||||
description:
|
||||
- Slack integration token
|
||||
required: true
|
||||
msg:
|
||||
description:
|
||||
- Message to send.
|
||||
required: true
|
||||
channel:
|
||||
description:
|
||||
- Channel to send the message to. If absent, the message goes to the channel selected for the I(token).
|
||||
required: false
|
||||
username:
|
||||
description:
|
||||
- This is the sender of the message.
|
||||
required: false
|
||||
default: ansible
|
||||
icon_url:
|
||||
description:
|
||||
- Url for the message sender's icon (default C(http://www.ansible.com/favicon.ico))
|
||||
required: false
|
||||
icon_emoji:
|
||||
description:
|
||||
- Emoji for the message sender. See Slack documentation for options.
|
||||
(if I(icon_emoji) is set, I(icon_url) will not be used)
|
||||
required: false
|
||||
link_names:
|
||||
description:
|
||||
- Automatically create links for channels and usernames in I(msg).
|
||||
required: false
|
||||
default: 1
|
||||
choices:
|
||||
- 1
|
||||
- 0
|
||||
parse:
|
||||
description:
|
||||
- Setting for the message parser at Slack
|
||||
required: false
|
||||
choices:
|
||||
- 'full'
|
||||
- 'none'
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices:
|
||||
- 'yes'
|
||||
- 'no'
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Send notification message via Slack
|
||||
local_action:
|
||||
module: slack
|
||||
domain: future500.slack.com
|
||||
token: thetokengeneratedbyslack
|
||||
msg: "{{ inventory_hostname }} completed"
|
||||
|
||||
- name: Send notification message via Slack all options
|
||||
local_action:
|
||||
module: slack
|
||||
domain: future500.slack.com
|
||||
token: thetokengeneratedbyslack
|
||||
msg: "{{ inventory_hostname }} completed"
|
||||
channel: "#ansible"
|
||||
username: "Ansible on {{ inventory_hostname }}"
|
||||
icon_url: "http://www.example.com/some-image-file.png"
|
||||
link_names: 0
|
||||
parse: 'none'
|
||||
|
||||
"""
|
||||
|
||||
|
||||
SLACK_INCOMING_WEBHOOK = 'https://%s/services/hooks/incoming-webhook?token=%s'
|
||||
|
||||
def build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse):
|
||||
payload = dict(text=text)
|
||||
|
||||
if channel is not None:
|
||||
payload['channel'] = channel if (channel[0] == '#') else '#'+channel
|
||||
if username is not None:
|
||||
payload['username'] = username
|
||||
if icon_emoji is not None:
|
||||
payload['icon_emoji'] = icon_emoji
|
||||
else:
|
||||
payload['icon_url'] = icon_url
|
||||
if link_names is not None:
|
||||
payload['link_names'] = link_names
|
||||
if parse is not None:
|
||||
payload['parse'] = parse
|
||||
|
||||
payload="payload=" + module.jsonify(payload)
|
||||
return payload
|
||||
|
||||
def do_notify_slack(module, domain, token, payload):
|
||||
slack_incoming_webhook = SLACK_INCOMING_WEBHOOK % (domain, token)
|
||||
|
||||
response, info = fetch_url(module, slack_incoming_webhook, data=payload)
|
||||
if info['status'] != 200:
|
||||
obscured_incoming_webhook = SLACK_INCOMING_WEBHOOK % (domain, '[obscured]')
|
||||
module.fail_json(msg=" failed to send %s to %s: %s" % (payload, obscured_incoming_webhook, info['msg']))
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
domain = dict(type='str', required=True),
|
||||
token = dict(type='str', required=True),
|
||||
msg = dict(type='str', required=True),
|
||||
channel = dict(type='str', default=None),
|
||||
username = dict(type='str', default='Ansible'),
|
||||
icon_url = dict(type='str', default='http://www.ansible.com/favicon.ico'),
|
||||
icon_emoji = dict(type='str', default=None),
|
||||
link_names = dict(type='int', default=1, choices=[0,1]),
|
||||
parse = dict(type='str', default=None, choices=['none', 'full']),
|
||||
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
)
|
||||
)
|
||||
|
||||
domain = module.params['domain']
|
||||
token = module.params['token']
|
||||
text = module.params['msg']
|
||||
channel = module.params['channel']
|
||||
username = module.params['username']
|
||||
icon_url = module.params['icon_url']
|
||||
icon_emoji = module.params['icon_emoji']
|
||||
link_names = module.params['link_names']
|
||||
parse = module.params['parse']
|
||||
|
||||
payload = build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse)
|
||||
do_notify_slack(module, domain, token, payload)
|
||||
|
||||
module.exit_json(msg="OK")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
main()
|
@ -1,190 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Michael J. Schultz <mjschultz@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: sns
|
||||
short_description: Send Amazon Simple Notification Service (SNS) messages
|
||||
description:
|
||||
- The M(sns) module sends notifications to a topic on your Amazon SNS account
|
||||
version_added: 1.6
|
||||
author: Michael J. Schultz <mjschultz@gmail.com>
|
||||
options:
|
||||
msg:
|
||||
description:
|
||||
- Default message to send.
|
||||
required: true
|
||||
aliases: [ "default" ]
|
||||
subject:
|
||||
description:
|
||||
- Subject line for email delivery.
|
||||
required: false
|
||||
topic:
|
||||
description:
|
||||
- The topic you want to publish to.
|
||||
required: true
|
||||
email:
|
||||
description:
|
||||
- Message to send to email-only subscription
|
||||
required: false
|
||||
sqs:
|
||||
description:
|
||||
- Message to send to SQS-only subscription
|
||||
required: false
|
||||
sms:
|
||||
description:
|
||||
- Message to send to SMS-only subscription
|
||||
required: false
|
||||
http:
|
||||
description:
|
||||
- Message to send to HTTP-only subscription
|
||||
required: false
|
||||
https:
|
||||
description:
|
||||
- Message to send to HTTPS-only subscription
|
||||
required: false
|
||||
aws_secret_key:
|
||||
description:
|
||||
- AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used.
|
||||
required: false
|
||||
default: None
|
||||
aliases: ['ec2_secret_key', 'secret_key']
|
||||
aws_access_key:
|
||||
description:
|
||||
- AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
|
||||
required: false
|
||||
default: None
|
||||
aliases: ['ec2_access_key', 'access_key']
|
||||
region:
|
||||
description:
|
||||
- The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
|
||||
required: false
|
||||
aliases: ['aws_region', 'ec2_region']
|
||||
|
||||
requirements: [ "boto" ]
|
||||
author: Michael J. Schultz
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Send default notification message via SNS
|
||||
local_action:
|
||||
module: sns
|
||||
msg: "{{ inventory_hostname }} has completed the play."
|
||||
subject: "Deploy complete!"
|
||||
topic: "deploy"
|
||||
|
||||
- name: Send notification messages via SNS with short message for SMS
|
||||
local_action:
|
||||
module: sns
|
||||
msg: "{{ inventory_hostname }} has completed the play."
|
||||
sms: "deployed!"
|
||||
subject: "Deploy complete!"
|
||||
topic: "deploy"
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.ec2 import *
|
||||
|
||||
try:
|
||||
import boto
|
||||
import boto.sns
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def arn_topic_lookup(connection, short_topic):
|
||||
response = connection.get_all_topics()
|
||||
result = response[u'ListTopicsResponse'][u'ListTopicsResult']
|
||||
# topic names cannot have colons, so this captures the full topic name
|
||||
lookup_topic = ':{}'.format(short_topic)
|
||||
for topic in result[u'Topics']:
|
||||
if topic[u'TopicArn'].endswith(lookup_topic):
|
||||
return topic[u'TopicArn']
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ec2_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
msg=dict(type='str', required=True, aliases=['default']),
|
||||
subject=dict(type='str', default=None),
|
||||
topic=dict(type='str', required=True),
|
||||
email=dict(type='str', default=None),
|
||||
sqs=dict(type='str', default=None),
|
||||
sms=dict(type='str', default=None),
|
||||
http=dict(type='str', default=None),
|
||||
https=dict(type='str', default=None),
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
|
||||
msg = module.params['msg']
|
||||
subject = module.params['subject']
|
||||
topic = module.params['topic']
|
||||
email = module.params['email']
|
||||
sqs = module.params['sqs']
|
||||
sms = module.params['sms']
|
||||
http = module.params['http']
|
||||
https = module.params['https']
|
||||
|
||||
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
|
||||
if not region:
|
||||
module.fail_json(msg="region must be specified")
|
||||
try:
|
||||
connection = connect_to_aws(boto.sns, region, **aws_connect_params)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
# .publish() takes full ARN topic id, but I'm lazy and type shortnames
|
||||
# so do a lookup (topics cannot contain ':', so thats the decider)
|
||||
if ':' in topic:
|
||||
arn_topic = topic
|
||||
else:
|
||||
arn_topic = arn_topic_lookup(connection, topic)
|
||||
|
||||
if not arn_topic:
|
||||
module.fail_json(msg='Could not find topic: {}'.format(topic))
|
||||
|
||||
dict_msg = {'default': msg}
|
||||
if email:
|
||||
dict_msg.update(email=email)
|
||||
if sqs:
|
||||
dict_msg.update(sqs=sqs)
|
||||
if sms:
|
||||
dict_msg.update(sms=sms)
|
||||
if http:
|
||||
dict_msg.update(http=http)
|
||||
if https:
|
||||
dict_msg.update(https=https)
|
||||
|
||||
json_msg = json.dumps(dict_msg)
|
||||
try:
|
||||
connection.publish(topic=arn_topic, subject=subject,
|
||||
message_structure='json', message=json_msg)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
module.exit_json(msg="OK")
|
||||
|
||||
main()
|
@ -1,135 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Matt Makai <matthew.makai@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
version_added: "1.6"
|
||||
module: twilio
|
||||
short_description: Sends a text message to a mobile phone through Twilio.
|
||||
description:
|
||||
- Sends a text message to a phone number through an the Twilio SMS service.
|
||||
notes:
|
||||
- Like the other notification modules, this one requires an external
|
||||
dependency to work. In this case, you'll need a Twilio account with
|
||||
a purchased or verified phone number to send the text message.
|
||||
options:
|
||||
account_sid:
|
||||
description:
|
||||
user's account id for Twilio found on the account page
|
||||
required: true
|
||||
auth_token:
|
||||
description: user's authentication token for Twilio found on the account page
|
||||
required: true
|
||||
msg:
|
||||
description:
|
||||
the body of the text message
|
||||
required: true
|
||||
to_number:
|
||||
description:
|
||||
what phone number to send the text message to, format +15551112222
|
||||
required: true
|
||||
from_number:
|
||||
description:
|
||||
what phone number to send the text message from, format +15551112222
|
||||
required: true
|
||||
|
||||
requirements: [ urllib, urllib2 ]
|
||||
author: Matt Makai
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# send a text message from the local server about the build status to (555) 303 5681
|
||||
# note: you have to have purchased the 'from_number' on your Twilio account
|
||||
- local_action: text msg="All servers with webserver role are now configured."
|
||||
account_sid={{ twilio_account_sid }}
|
||||
auth_token={{ twilio_auth_token }}
|
||||
from_number=+15552014545 to_number=+15553035681
|
||||
|
||||
# send a text message from a server to (555) 111 3232
|
||||
# note: you have to have purchased the 'from_number' on your Twilio account
|
||||
- text: msg="This server's configuration is now complete."
|
||||
account_sid={{ twilio_account_sid }}
|
||||
auth_token={{ twilio_auth_token }}
|
||||
from_number=+15553258899 to_number=+15551113232
|
||||
|
||||
'''
|
||||
|
||||
# =======================================
|
||||
# text module support methods
|
||||
#
|
||||
try:
|
||||
import urllib, urllib2
|
||||
except ImportError:
|
||||
module.fail_json(msg="urllib and urllib2 are required")
|
||||
|
||||
import base64
|
||||
|
||||
|
||||
def post_text(module, account_sid, auth_token, msg, from_number, to_number):
|
||||
URI = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json" \
|
||||
% (account_sid,)
|
||||
AGENT = "Ansible/1.5"
|
||||
|
||||
data = {'From':from_number, 'To':to_number, 'Body':msg}
|
||||
encoded_data = urllib.urlencode(data)
|
||||
request = urllib2.Request(URI)
|
||||
base64string = base64.encodestring('%s:%s' % \
|
||||
(account_sid, auth_token)).replace('\n', '')
|
||||
request.add_header('User-Agent', AGENT)
|
||||
request.add_header('Content-type', 'application/x-www-form-urlencoded')
|
||||
request.add_header('Accept', 'application/ansible')
|
||||
request.add_header('Authorization', 'Basic %s' % base64string)
|
||||
return urllib2.urlopen(request, encoded_data)
|
||||
|
||||
|
||||
# =======================================
|
||||
# Main
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
account_sid=dict(required=True),
|
||||
auth_token=dict(required=True),
|
||||
msg=dict(required=True),
|
||||
from_number=dict(required=True),
|
||||
to_number=dict(required=True),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
account_sid = module.params['account_sid']
|
||||
auth_token = module.params['auth_token']
|
||||
msg = module.params['msg']
|
||||
from_number = module.params['from_number']
|
||||
to_number = module.params['to_number']
|
||||
|
||||
try:
|
||||
response = post_text(module, account_sid, auth_token, msg,
|
||||
from_number, to_number)
|
||||
except Exception, e:
|
||||
module.fail_json(msg="unable to send text message to %s" % to_number)
|
||||
|
||||
module.exit_json(msg=msg, changed=False)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,116 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: typetalk
|
||||
version_added: "1.6"
|
||||
short_description: Send a message to typetalk
|
||||
description:
|
||||
- Send a message to typetalk using typetalk API ( http://developers.typetalk.in/ )
|
||||
options:
|
||||
client_id:
|
||||
description:
|
||||
- OAuth2 client ID
|
||||
required: true
|
||||
client_secret:
|
||||
description:
|
||||
- OAuth2 client secret
|
||||
required: true
|
||||
topic:
|
||||
description:
|
||||
- topic id to post message
|
||||
required: true
|
||||
msg:
|
||||
description:
|
||||
- message body
|
||||
required: true
|
||||
requirements: [ urllib, urllib2, json ]
|
||||
author: Takashi Someda <someda@isenshi.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- typetalk: client_id=12345 client_secret=12345 topic=1 msg="install completed"
|
||||
'''
|
||||
|
||||
try:
|
||||
import urllib
|
||||
except ImportError:
|
||||
urllib = None
|
||||
|
||||
try:
|
||||
import urllib2
|
||||
except ImportError:
|
||||
urllib2 = None
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
json = None
|
||||
|
||||
|
||||
def do_request(url, params, headers={}):
|
||||
data = urllib.urlencode(params)
|
||||
headers = dict(headers, **{
|
||||
'User-Agent': 'Ansible/typetalk module',
|
||||
})
|
||||
return urllib2.urlopen(urllib2.Request(url, data, headers))
|
||||
|
||||
|
||||
def get_access_token(client_id, client_secret):
|
||||
params = {
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'grant_type': 'client_credentials',
|
||||
'scope': 'topic.post'
|
||||
}
|
||||
res = do_request('https://typetalk.in/oauth2/access_token', params)
|
||||
return json.load(res)['access_token']
|
||||
|
||||
|
||||
def send_message(client_id, client_secret, topic, msg):
|
||||
"""
|
||||
send message to typetalk
|
||||
"""
|
||||
try:
|
||||
access_token = get_access_token(client_id, client_secret)
|
||||
url = 'https://typetalk.in/api/v1/topics/%d' % topic
|
||||
headers = {
|
||||
'Authorization': 'Bearer %s' % access_token,
|
||||
}
|
||||
do_request(url, {'message': msg}, headers)
|
||||
return True, {'access_token': access_token}
|
||||
except urllib2.HTTPError, e:
|
||||
return False, e
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
client_id=dict(required=True),
|
||||
client_secret=dict(required=True),
|
||||
topic=dict(required=True, type='int'),
|
||||
msg=dict(required=True),
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
if not (urllib and urllib2 and json):
|
||||
module.fail_json(msg="urllib, urllib2 and json modules are required")
|
||||
|
||||
client_id = module.params["client_id"]
|
||||
client_secret = module.params["client_secret"]
|
||||
topic = module.params["topic"]
|
||||
msg = module.params["msg"]
|
||||
|
||||
res, error = send_message(client_id, client_secret, topic, msg)
|
||||
if not res:
|
||||
module.fail_json(msg='fail to send message with response code %s' % error.code)
|
||||
|
||||
module.exit_json(changed=True, topic=topic, msg=msg)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,140 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Ansible module to manage symbolic link alternatives.
|
||||
(c) 2014, Gabe Mulley <gabe.mulley@gmail.com>
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: alternatives
|
||||
short_description: Manages alternative programs for common commands
|
||||
description:
|
||||
- Manages symbolic links using the 'update-alternatives' tool provided on debian-like systems.
|
||||
- Useful when multiple programs are installed but provide similar functionality (e.g. different editors).
|
||||
version_added: "1.6"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The generic name of the link.
|
||||
required: true
|
||||
path:
|
||||
description:
|
||||
- The path to the real executable that the link should point to.
|
||||
required: true
|
||||
link:
|
||||
description:
|
||||
- The path to the symbolic link that should point to the real executable.
|
||||
required: false
|
||||
requirements: [ update-alternatives ]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: correct java version selected
|
||||
alternatives: name=java path=/usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
|
||||
|
||||
- name: alternatives link created
|
||||
alternatives: name=hadoop-conf link=/etc/hadoop/conf path=/etc/hadoop/conf.ansible
|
||||
'''
|
||||
|
||||
DEFAULT_LINK_PRIORITY = 50
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
name = dict(required=True),
|
||||
path = dict(required=True),
|
||||
link = dict(required=False),
|
||||
)
|
||||
)
|
||||
|
||||
params = module.params
|
||||
name = params['name']
|
||||
path = params['path']
|
||||
link = params['link']
|
||||
|
||||
UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives',True)
|
||||
|
||||
current_path = None
|
||||
all_alternatives = []
|
||||
|
||||
(rc, query_output, query_error) = module.run_command(
|
||||
[UPDATE_ALTERNATIVES, '--query', name]
|
||||
)
|
||||
|
||||
# Gather the current setting and all alternatives from the query output.
|
||||
# Query output should look something like this:
|
||||
|
||||
# Name: java
|
||||
# Link: /usr/bin/java
|
||||
# Slaves:
|
||||
# java.1.gz /usr/share/man/man1/java.1.gz
|
||||
# Status: manual
|
||||
# Best: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
|
||||
# Value: /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java
|
||||
|
||||
# Alternative: /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java
|
||||
# Priority: 1061
|
||||
# Slaves:
|
||||
# java.1.gz /usr/lib/jvm/java-6-openjdk-amd64/jre/man/man1/java.1.gz
|
||||
|
||||
# Alternative: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
|
||||
# Priority: 1071
|
||||
# Slaves:
|
||||
# java.1.gz /usr/lib/jvm/java-7-openjdk-amd64/jre/man/man1/java.1.gz
|
||||
|
||||
if rc == 0:
|
||||
for line in query_output.splitlines():
|
||||
split_line = line.split(':')
|
||||
if len(split_line) == 2:
|
||||
key = split_line[0]
|
||||
value = split_line[1].strip()
|
||||
if key == 'Value':
|
||||
current_path = value
|
||||
elif key == 'Alternative':
|
||||
all_alternatives.append(value)
|
||||
elif key == 'Link' and not link:
|
||||
link = value
|
||||
|
||||
if current_path != path:
|
||||
try:
|
||||
# install the requested path if necessary
|
||||
if path not in all_alternatives:
|
||||
module.run_command(
|
||||
[UPDATE_ALTERNATIVES, '--install', link, name, path, str(DEFAULT_LINK_PRIORITY)],
|
||||
check_rc=True
|
||||
)
|
||||
|
||||
# select the requested path
|
||||
module.run_command(
|
||||
[UPDATE_ALTERNATIVES, '--set', name, path],
|
||||
check_rc=True
|
||||
)
|
||||
|
||||
module.exit_json(changed=True)
|
||||
except subprocess.CalledProcessError, cpe:
|
||||
module.fail_json(msg=str(dir(cpe)))
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,200 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2014, Richard Isaacson <richard.c.isaacson@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: at
|
||||
short_description: Schedule the execution of a command or script file via the at command.
|
||||
description:
|
||||
- Use this module to schedule a command or script file to run once in the future.
|
||||
- All jobs are executed in the 'a' queue.
|
||||
version_added: "1.5"
|
||||
options:
|
||||
command:
|
||||
description:
|
||||
- A command to be executed in the future.
|
||||
required: false
|
||||
default: null
|
||||
script_file:
|
||||
description:
|
||||
- An existing script file to be executed in the future.
|
||||
required: false
|
||||
default: null
|
||||
count:
|
||||
description:
|
||||
- The count of units in the future to execute the command or script file.
|
||||
required: true
|
||||
units:
|
||||
description:
|
||||
- The type of units in the future to execute the command or script file.
|
||||
required: true
|
||||
choices: ["minutes", "hours", "days", "weeks"]
|
||||
state:
|
||||
description:
|
||||
- The state dictates if the command or script file should be evaluated as present(added) or absent(deleted).
|
||||
required: false
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
unique:
|
||||
description:
|
||||
- If a matching job is present a new job will not be added.
|
||||
required: false
|
||||
default: false
|
||||
requirements:
|
||||
- at
|
||||
author: Richard Isaacson
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Schedule a command to execute in 20 minutes as root.
|
||||
- at: command="ls -d / > /dev/null" count=20 units="minutes"
|
||||
|
||||
# Match a command to an existing job and delete the job.
|
||||
- at: command="ls -d / > /dev/null" state="absent"
|
||||
|
||||
# Schedule a command to execute in 20 minutes making sure it is unique in the queue.
|
||||
- at: command="ls -d / > /dev/null" unique=true count=20 units="minutes"
|
||||
'''
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
def add_job(module, result, at_cmd, count, units, command, script_file):
|
||||
at_command = "%s now + %s %s -f %s" % (at_cmd, count, units, script_file)
|
||||
rc, out, err = module.run_command(at_command, check_rc=True)
|
||||
if command:
|
||||
os.unlink(script_file)
|
||||
result['changed'] = True
|
||||
|
||||
|
||||
def delete_job(module, result, at_cmd, command, script_file):
|
||||
for matching_job in get_matching_jobs(module, at_cmd, script_file):
|
||||
at_command = "%s -d %s" % (at_cmd, matching_job)
|
||||
rc, out, err = module.run_command(at_command, check_rc=True)
|
||||
result['changed'] = True
|
||||
if command:
|
||||
os.unlink(script_file)
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def get_matching_jobs(module, at_cmd, script_file):
|
||||
matching_jobs = []
|
||||
|
||||
atq_cmd = module.get_bin_path('atq', True)
|
||||
|
||||
# Get list of job numbers for the user.
|
||||
atq_command = "%s" % atq_cmd
|
||||
rc, out, err = module.run_command(atq_command, check_rc=True)
|
||||
current_jobs = out.splitlines()
|
||||
if len(current_jobs) == 0:
|
||||
return matching_jobs
|
||||
|
||||
# Read script_file into a string.
|
||||
script_file_string = open(script_file).read().strip()
|
||||
|
||||
# Loop through the jobs.
|
||||
# If the script text is contained in a job add job number to list.
|
||||
for current_job in current_jobs:
|
||||
split_current_job = current_job.split()
|
||||
at_command = "%s -c %s" % (at_cmd, split_current_job[0])
|
||||
rc, out, err = module.run_command(at_command, check_rc=True)
|
||||
if script_file_string in out:
|
||||
matching_jobs.append(split_current_job[0])
|
||||
|
||||
# Return the list.
|
||||
return matching_jobs
|
||||
|
||||
|
||||
def create_tempfile(command):
|
||||
filed, script_file = tempfile.mkstemp(prefix='at')
|
||||
fileh = os.fdopen(filed, 'w')
|
||||
fileh.write(command)
|
||||
fileh.close()
|
||||
return script_file
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
command=dict(required=False,
|
||||
type='str'),
|
||||
script_file=dict(required=False,
|
||||
type='str'),
|
||||
count=dict(required=False,
|
||||
type='int'),
|
||||
units=dict(required=False,
|
||||
default=None,
|
||||
choices=['minutes', 'hours', 'days', 'weeks'],
|
||||
type='str'),
|
||||
state=dict(required=False,
|
||||
default='present',
|
||||
choices=['present', 'absent'],
|
||||
type='str'),
|
||||
unique=dict(required=False,
|
||||
default=False,
|
||||
type='bool')
|
||||
),
|
||||
mutually_exclusive=[['command', 'script_file']],
|
||||
required_one_of=[['command', 'script_file']],
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
at_cmd = module.get_bin_path('at', True)
|
||||
|
||||
command = module.params['command']
|
||||
script_file = module.params['script_file']
|
||||
count = module.params['count']
|
||||
units = module.params['units']
|
||||
state = module.params['state']
|
||||
unique = module.params['unique']
|
||||
|
||||
if (state == 'present') and (not count or not units):
|
||||
module.fail_json(msg="present state requires count and units")
|
||||
|
||||
result = {'state': state, 'changed': False}
|
||||
|
||||
# If command transform it into a script_file
|
||||
if command:
|
||||
script_file = create_tempfile(command)
|
||||
|
||||
# if absent remove existing and return
|
||||
if state == 'absent':
|
||||
delete_job(module, result, at_cmd, command, script_file)
|
||||
|
||||
# if unique if existing return unchanged
|
||||
if unique:
|
||||
if len(get_matching_jobs(module, at_cmd, script_file)) != 0:
|
||||
if command:
|
||||
os.unlink(script_file)
|
||||
module.exit_json(**result)
|
||||
|
||||
result['script_file'] = script_file
|
||||
result['count'] = count
|
||||
result['units'] = units
|
||||
|
||||
add_job(module, result, at_cmd, count, units, command, script_file)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,187 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Nate Coraor <nate@bx.psu.edu>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: capabilities
|
||||
short_description: Manage Linux capabilities
|
||||
description:
|
||||
- This module manipulates files privileges using the Linux capabilities(7) system.
|
||||
version_added: "1.6"
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Specifies the path to the file to be managed.
|
||||
required: true
|
||||
default: null
|
||||
capability:
|
||||
description:
|
||||
- Desired capability to set (with operator and flags, if state is C(present)) or remove (if state is C(absent))
|
||||
required: true
|
||||
default: null
|
||||
aliases: [ 'cap' ]
|
||||
state:
|
||||
description:
|
||||
- Whether the entry should be present or absent in the file's capabilities.
|
||||
choices: [ "present", "absent" ]
|
||||
default: present
|
||||
notes:
|
||||
- The capabilities system will automatically transform operators and flags
|
||||
into the effective set, so (for example, cap_foo=ep will probably become
|
||||
cap_foo+ep). This module does not attempt to determine the final operator
|
||||
and flags to compare, so you will want to ensure that your capabilities
|
||||
argument matches the final capabilities.
|
||||
requirements: []
|
||||
author: Nate Coraor <nate@bx.psu.edu>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set cap_sys_chroot+ep on /foo
|
||||
- capabilities: path=/foo capability=cap_sys_chroot+ep state=present
|
||||
|
||||
# Remove cap_net_bind_service from /bar
|
||||
- capabilities: path=/bar capability=cap_net_bind_service state=absent
|
||||
'''
|
||||
|
||||
|
||||
OPS = ( '=', '-', '+' )
|
||||
|
||||
# ==============================================================
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
class CapabilitiesModule(object):
|
||||
|
||||
platform = 'Linux'
|
||||
distribution = None
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.path = module.params['path'].strip()
|
||||
self.capability = module.params['capability'].strip().lower()
|
||||
self.state = module.params['state']
|
||||
self.getcap_cmd = module.get_bin_path('getcap', required=True)
|
||||
self.setcap_cmd = module.get_bin_path('setcap', required=True)
|
||||
self.capability_tup = self._parse_cap(self.capability, op_required=self.state=='present')
|
||||
|
||||
self.run()
|
||||
|
||||
def run(self):
|
||||
|
||||
current = self.getcap(self.path)
|
||||
caps = [ cap[0] for cap in current ]
|
||||
|
||||
if self.state == 'present' and self.capability_tup not in current:
|
||||
# need to add capability
|
||||
if self.module.check_mode:
|
||||
self.module.exit_json(changed=True, msg='capabilities changed')
|
||||
else:
|
||||
# remove from current cap list if it's already set (but op/flags differ)
|
||||
current = filter(lambda x: x[0] != self.capability_tup[0], current)
|
||||
# add new cap with correct op/flags
|
||||
current.append( self.capability_tup )
|
||||
self.module.exit_json(changed=True, state=self.state, msg='capabilities changed', stdout=self.setcap(self.path, current))
|
||||
elif self.state == 'absent' and self.capability_tup[0] in caps:
|
||||
# need to remove capability
|
||||
if self.module.check_mode:
|
||||
self.module.exit_json(changed=True, msg='capabilities changed')
|
||||
else:
|
||||
# remove from current cap list and then set current list
|
||||
current = filter(lambda x: x[0] != self.capability_tup[0], current)
|
||||
self.module.exit_json(changed=True, state=self.state, msg='capabilities changed', stdout=self.setcap(self.path, current))
|
||||
self.module.exit_json(changed=False, state=self.state)
|
||||
|
||||
def getcap(self, path):
|
||||
rval = []
|
||||
cmd = "%s -v %s" % (self.getcap_cmd, path)
|
||||
rc, stdout, stderr = self.module.run_command(cmd)
|
||||
# If file xattrs are set but no caps are set the output will be:
|
||||
# '/foo ='
|
||||
# If file xattrs are unset the output will be:
|
||||
# '/foo'
|
||||
# If the file does not eixst the output will be (with rc == 0...):
|
||||
# '/foo (No such file or directory)'
|
||||
if rc != 0 or (stdout.strip() != path and stdout.count(' =') != 1):
|
||||
self.module.fail_json(msg="Unable to get capabilities of %s" % path, stdout=stdout.strip(), stderr=stderr)
|
||||
if stdout.strip() != path:
|
||||
caps = stdout.split(' =')[1].strip().split()
|
||||
for cap in caps:
|
||||
cap = cap.lower()
|
||||
# getcap condenses capabilities with the same op/flags into a
|
||||
# comma-separated list, so we have to parse that
|
||||
if ',' in cap:
|
||||
cap_group = cap.split(',')
|
||||
cap_group[-1], op, flags = self._parse_cap(cap_group[-1])
|
||||
for subcap in cap_group:
|
||||
rval.append( ( subcap, op, flags ) )
|
||||
else:
|
||||
rval.append(self._parse_cap(cap))
|
||||
return rval
|
||||
|
||||
def setcap(self, path, caps):
|
||||
caps = ' '.join([ ''.join(cap) for cap in caps ])
|
||||
cmd = "%s '%s' %s" % (self.setcap_cmd, caps, path)
|
||||
rc, stdout, stderr = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Unable to set capabilities of %s" % path, stdout=stdout, stderr=stderr)
|
||||
else:
|
||||
return stdout
|
||||
|
||||
def _parse_cap(self, cap, op_required=True):
|
||||
opind = -1
|
||||
try:
|
||||
i = 0
|
||||
while opind == -1:
|
||||
opind = cap.find(OPS[i])
|
||||
i += 1
|
||||
except:
|
||||
if op_required:
|
||||
self.module.fail_json(msg="Couldn't find operator (one of: %s)" % str(OPS))
|
||||
else:
|
||||
return (cap, None, None)
|
||||
op = cap[opind]
|
||||
cap, flags = cap.split(op)
|
||||
return (cap, op, flags)
|
||||
|
||||
# ==============================================================
|
||||
# main
|
||||
|
||||
def main():
|
||||
|
||||
# defining module
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
path = dict(aliases=['key'], required=True),
|
||||
capability = dict(aliases=['cap'], required=True),
|
||||
state = dict(default='present', choices=['present', 'absent']),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
CapabilitiesModule(module)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,170 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Ansible module to configure .deb packages.
|
||||
(c) 2014, Brian Coca <briancoca+ansible@gmail.com>
|
||||
|
||||
This file is part of Ansible
|
||||
|
||||
Ansible is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Ansible is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: debconf
|
||||
short_description: Configure a .deb package
|
||||
description:
|
||||
- Configure a .deb package using debconf-set-selections. Or just query
|
||||
existing selections.
|
||||
version_added: "1.6"
|
||||
notes:
|
||||
- This module requires the command line debconf tools.
|
||||
- A number of questions have to be answered (depending on the package).
|
||||
Use 'debconf-show <package>' on any Debian or derivative with the package
|
||||
installed to see questions/settings available.
|
||||
requirements: [ debconf, debconf-utils ]
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of package to configure.
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['pkg']
|
||||
question:
|
||||
description:
|
||||
- A debconf configuration setting
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['setting', 'selection']
|
||||
vtype:
|
||||
description:
|
||||
- The type of the value supplied
|
||||
required: false
|
||||
default: null
|
||||
choices: [string, password, boolean, select, multiselect, note, error, title, text]
|
||||
aliases: []
|
||||
value:
|
||||
description:
|
||||
- Value to set the configuration to
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['answer']
|
||||
unseen:
|
||||
description:
|
||||
- Do not set 'seen' flag when pre-seeding
|
||||
required: false
|
||||
default: False
|
||||
aliases: []
|
||||
author: Brian Coca
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set default locale to fr_FR.UTF-8
|
||||
debconf: name=locales question='locales/default_environment_locale' value=fr_FR.UTF-8 vtype='select'
|
||||
|
||||
# set to generate locales:
|
||||
debconf: name=locales question='locales/locales_to_be_generated' value='en_US.UTF-8 UTF-8, fr_FR.UTF-8 UTF-8' vtype='multiselect'
|
||||
|
||||
# Accept oracle license
|
||||
debconf: name='oracle-java7-installer' question='shared/accepted-oracle-license-v1-1' value='true' vtype='select'
|
||||
|
||||
# Specifying package you can register/return the list of questions and current values
|
||||
debconf: name='tzdata'
|
||||
'''
|
||||
|
||||
import pipes
|
||||
|
||||
def get_selections(module, pkg):
|
||||
cmd = [module.get_bin_path('debconf-show', True), pkg]
|
||||
rc, out, err = module.run_command(' '.join(cmd))
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
|
||||
selections = {}
|
||||
|
||||
for line in out.splitlines():
|
||||
(key, value) = line.split(':', 1)
|
||||
selections[ key.strip('*').strip() ] = value.strip()
|
||||
|
||||
return selections
|
||||
|
||||
|
||||
def set_selection(module, pkg, question, vtype, value, unseen):
|
||||
|
||||
data = ' '.join([ question, vtype, value ])
|
||||
|
||||
setsel = module.get_bin_path('debconf-set-selections', True)
|
||||
cmd = ["echo %s %s |" % (pipes.quote(pkg), pipes.quote(data)), setsel]
|
||||
if unseen:
|
||||
cmd.append('-u')
|
||||
|
||||
return module.run_command(' '.join(cmd), use_unsafe_shell=True)
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
name = dict(required=True, aliases=['pkg'], type='str'),
|
||||
question = dict(required=False, aliases=['setting', 'selection'], type='str'),
|
||||
vtype = dict(required=False, type='str', choices=['string', 'password', 'boolean', 'select', 'multiselect', 'note', 'error', 'title', 'text']),
|
||||
value= dict(required=False, type='str'),
|
||||
unseen = dict(required=False, type='bool'),
|
||||
),
|
||||
required_together = ( ['question','vtype', 'value'],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
#TODO: enable passing array of options and/or debconf file from get-selections dump
|
||||
pkg = module.params["name"]
|
||||
question = module.params["question"]
|
||||
vtype = module.params["vtype"]
|
||||
value = module.params["value"]
|
||||
unseen = module.params["unseen"]
|
||||
|
||||
prev = get_selections(module, pkg)
|
||||
diff = ''
|
||||
|
||||
changed = False
|
||||
msg = ""
|
||||
|
||||
if question is not None:
|
||||
if vtype is None or value is None:
|
||||
module.fail_json(msg="when supplying a question you must supply a valid vtype and value")
|
||||
|
||||
if not question in prev or prev[question] != value:
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
if not module.check_mode:
|
||||
rc, msg, e = set_selection(module, pkg, question, vtype, value, unseen)
|
||||
if rc:
|
||||
module.fail_json(msg=e)
|
||||
|
||||
curr = { question: value }
|
||||
if question in prev:
|
||||
prev = {question: prev[question]}
|
||||
else:
|
||||
prev[question] = ''
|
||||
|
||||
module.exit_json(changed=changed, msg=msg, current=curr, previous=prev)
|
||||
|
||||
module.exit_json(changed=changed, msg=msg, current=prev)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -1,56 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: facter
|
||||
short_description: Runs the discovery program I(facter) on the remote system
|
||||
description:
|
||||
- Runs the I(facter) discovery program
|
||||
(U(https://github.com/puppetlabs/facter)) on the remote system, returning
|
||||
JSON data that can be useful for inventory purposes.
|
||||
version_added: "0.2"
|
||||
options: {}
|
||||
notes: []
|
||||
requirements: [ "facter", "ruby-json" ]
|
||||
author: Michael DeHaan
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Example command-line invocation
|
||||
ansible www.example.net -m facter
|
||||
'''
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict()
|
||||
)
|
||||
|
||||
cmd = ["/usr/bin/env", "facter", "--json"]
|
||||
rc, out, err = module.run_command(cmd, check_rc=True)
|
||||
module.exit_json(**json.loads(out))
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
author: Alexander Bulimov
|
||||
module: filesystem
|
||||
short_description: Makes file system on block device
|
||||
description:
|
||||
- This module creates file system.
|
||||
version_added: "1.2"
|
||||
options:
|
||||
fstype:
|
||||
description:
|
||||
- File System type to be created.
|
||||
required: true
|
||||
dev:
|
||||
description:
|
||||
- Target block device.
|
||||
required: true
|
||||
force:
|
||||
choices: [ "yes", "no" ]
|
||||
default: "no"
|
||||
description:
|
||||
- If yes, allows to create new filesystem on devices that already has filesystem.
|
||||
required: false
|
||||
opts:
|
||||
description:
|
||||
- List of options to be passed to mkfs command.
|
||||
notes:
|
||||
- uses mkfs command
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a ext2 filesystem on /dev/sdb1.
|
||||
- filesystem: fstype=ext2 dev=/dev/sdb1
|
||||
|
||||
# Create a ext4 filesystem on /dev/sdb1 and check disk blocks.
|
||||
- filesystem: fstype=ext4 dev=/dev/sdb1 opts="-cc"
|
||||
'''
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
fstype=dict(required=True, aliases=['type']),
|
||||
dev=dict(required=True, aliases=['device']),
|
||||
opts=dict(),
|
||||
force=dict(type='bool', default='no'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
dev = module.params['dev']
|
||||
fstype = module.params['fstype']
|
||||
opts = module.params['opts']
|
||||
force = module.boolean(module.params['force'])
|
||||
|
||||
changed = False
|
||||
|
||||
if not os.path.exists(dev):
|
||||
module.fail_json(msg="Device %s not found."%dev)
|
||||
|
||||
cmd = module.get_bin_path('blkid', required=True)
|
||||
|
||||
rc,raw_fs,err = module.run_command("%s -c /dev/null -o value -s TYPE %s" % (cmd, dev))
|
||||
fs = raw_fs.strip()
|
||||
|
||||
|
||||
if fs == fstype:
|
||||
module.exit_json(changed=False)
|
||||
elif fs and not force:
|
||||
module.fail_json(msg="'%s' is already used as %s, use force=yes to overwrite"%(dev,fs), rc=rc, err=err)
|
||||
|
||||
### create fs
|
||||
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
mkfs = module.get_bin_path('mkfs', required=True)
|
||||
cmd = None
|
||||
if fstype in ['ext2', 'ext3', 'ext4', 'ext4dev']:
|
||||
force_flag="-F"
|
||||
elif fstype in ['btrfs']:
|
||||
force_flag="-f"
|
||||
else:
|
||||
force_flag=""
|
||||
|
||||
if opts is None:
|
||||
cmd = "%s -t %s %s '%s'" % (mkfs, fstype, force_flag, dev)
|
||||
else:
|
||||
cmd = "%s -t %s %s %s '%s'" % (mkfs, fstype, force_flag, opts, dev)
|
||||
rc,_,err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Creating filesystem %s on device '%s' failed"%(fstype,dev), rc=rc, err=err)
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,398 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Adam Miller (maxamillion@fedoraproject.org)
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: firewalld
|
||||
short_description: Manage arbitrary ports/services with firewalld
|
||||
description:
|
||||
- This module allows for addition or deletion of services and ports either tcp or udp in either running or permanent firewalld rules
|
||||
version_added: "1.4"
|
||||
options:
|
||||
service:
|
||||
description:
|
||||
- "Name of a service to add/remove to/from firewalld - service must be listed in /etc/services"
|
||||
required: false
|
||||
default: null
|
||||
port:
|
||||
description:
|
||||
- "Name of a port to add/remove to/from firewalld must be in the form PORT/PROTOCOL"
|
||||
required: false
|
||||
default: null
|
||||
rich_rule:
|
||||
description:
|
||||
- "Rich rule to add/remove to/from firewalld"
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- 'The firewalld zone to add/remove to/from (NOTE: default zone can be configured per system but "public" is default from upstream. Available choices can be extended based on per-system configs, listed here are "out of the box" defaults).'
|
||||
required: false
|
||||
default: system-default(public)
|
||||
choices: [ "work", "drop", "internal", "external", "trusted", "home", "dmz", "public", "block"]
|
||||
permanent:
|
||||
description:
|
||||
- "Should this configuration be in the running firewalld configuration or persist across reboots"
|
||||
required: true
|
||||
default: true
|
||||
state:
|
||||
description:
|
||||
- "Should this port accept(enabled) or reject(disabled) connections"
|
||||
required: true
|
||||
default: enabled
|
||||
timeout:
|
||||
description:
|
||||
- "The amount of time the rule should be in effect for when non-permanent"
|
||||
required: false
|
||||
default: 0
|
||||
notes:
|
||||
- Not tested on any debian based system
|
||||
requirements: [ firewalld >= 0.2.11 ]
|
||||
author: Adam Miller <maxamillion@fedoraproject.org>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- firewalld: service=https permanent=true state=enabled
|
||||
- firewalld: port=8081/tcp permanent=true state=disabled
|
||||
- firewalld: zone=dmz service=http permanent=true state=enabled
|
||||
- firewalld: rich_rule='rule service name="ftp" audit limit value="1/m" accept' permanent=true state=enabled
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
import firewall.config
|
||||
FW_VERSION = firewall.config.VERSION
|
||||
|
||||
from firewall.client import FirewallClient
|
||||
fw = FirewallClient()
|
||||
if not fw.connected:
|
||||
raise Exception('failed to connect to the firewalld daemon')
|
||||
except ImportError:
|
||||
print "failed=True msg='firewalld required for this module'"
|
||||
sys.exit(1)
|
||||
except Exception, e:
|
||||
print "failed=True msg='%s'" % str(e)
|
||||
sys.exit(1)
|
||||
|
||||
################
|
||||
# port handling
|
||||
#
|
||||
def get_port_enabled(zone, port_proto):
|
||||
if port_proto in fw.getPorts(zone):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_port_enabled(zone, port, protocol, timeout):
|
||||
fw.addPort(zone, port, protocol, timeout)
|
||||
|
||||
def set_port_disabled(zone, port, protocol):
|
||||
fw.removePort(zone, port, protocol)
|
||||
|
||||
def get_port_enabled_permanent(zone, port_proto):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
if tuple(port_proto) in fw_settings.getPorts():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_port_enabled_permanent(zone, port, protocol):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
fw_settings.addPort(port, protocol)
|
||||
fw_zone.update(fw_settings)
|
||||
|
||||
def set_port_disabled_permanent(zone, port, protocol):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
fw_settings.removePort(port, protocol)
|
||||
fw_zone.update(fw_settings)
|
||||
|
||||
|
||||
####################
|
||||
# service handling
|
||||
#
|
||||
def get_service_enabled(zone, service):
|
||||
if service in fw.getServices(zone):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_service_enabled(zone, service, timeout):
|
||||
fw.addService(zone, service, timeout)
|
||||
|
||||
def set_service_disabled(zone, service):
|
||||
fw.removeService(zone, service)
|
||||
|
||||
def get_service_enabled_permanent(zone, service):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
if service in fw_settings.getServices():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_service_enabled_permanent(zone, service):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
fw_settings.addService(service)
|
||||
fw_zone.update(fw_settings)
|
||||
|
||||
def set_service_disabled_permanent(zone, service):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
fw_settings.removeService(service)
|
||||
fw_zone.update(fw_settings)
|
||||
|
||||
|
||||
####################
|
||||
# rich rule handling
|
||||
#
|
||||
def get_rich_rule_enabled(zone, rule):
|
||||
if rule in fw.getRichRules(zone):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_rich_rule_enabled(zone, rule, timeout):
|
||||
fw.addRichRule(zone, rule, timeout)
|
||||
|
||||
def set_rich_rule_disabled(zone, rule):
|
||||
fw.removeRichRule(zone, rule)
|
||||
|
||||
def get_rich_rule_enabled_permanent(zone, rule):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
if rule in fw_settings.getRichRules():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_rich_rule_enabled_permanent(zone, rule):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
fw_settings.addRichRule(rule)
|
||||
fw_zone.update(fw_settings)
|
||||
|
||||
def set_rich_rule_disabled_permanent(zone, rule):
|
||||
fw_zone = fw.config().getZoneByName(zone)
|
||||
fw_settings = fw_zone.getSettings()
|
||||
fw_settings.removeRichRule(rule)
|
||||
fw_zone.update(fw_settings)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
service=dict(required=False,default=None),
|
||||
port=dict(required=False,default=None),
|
||||
rich_rule=dict(required=False,default=None),
|
||||
zone=dict(required=False,default=None),
|
||||
permanent=dict(type='bool',required=True),
|
||||
state=dict(choices=['enabled', 'disabled'], required=True),
|
||||
timeout=dict(type='int',required=False,default=0),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
## Pre-run version checking
|
||||
if FW_VERSION < "0.2.11":
|
||||
module.fail_json(msg='unsupported version of firewalld, requires >= 2.0.11')
|
||||
|
||||
## Global Vars
|
||||
changed=False
|
||||
msgs = []
|
||||
service = module.params['service']
|
||||
rich_rule = module.params['rich_rule']
|
||||
|
||||
if module.params['port'] != None:
|
||||
port, protocol = module.params['port'].split('/')
|
||||
if protocol == None:
|
||||
module.fail_json(msg='improper port format (missing protocol?)')
|
||||
else:
|
||||
port = None
|
||||
|
||||
if module.params['zone'] != None:
|
||||
zone = module.params['zone']
|
||||
else:
|
||||
zone = fw.getDefaultZone()
|
||||
|
||||
permanent = module.params['permanent']
|
||||
desired_state = module.params['state']
|
||||
timeout = module.params['timeout']
|
||||
|
||||
## Check for firewalld running
|
||||
try:
|
||||
if fw.connected == False:
|
||||
module.fail_json(msg='firewalld service must be running')
|
||||
except AttributeError:
|
||||
module.fail_json(msg="firewalld connection can't be established,\
|
||||
version likely too old. Requires firewalld >= 2.0.11")
|
||||
|
||||
modification_count = 0
|
||||
if service != None:
|
||||
modification_count += 1
|
||||
if port != None:
|
||||
modification_count += 1
|
||||
if rich_rule != None:
|
||||
modification_count += 1
|
||||
|
||||
if modification_count > 1:
|
||||
module.fail_json(msg='can only operate on port, service or rich_rule at once')
|
||||
|
||||
if service != None:
|
||||
if permanent:
|
||||
is_enabled = get_service_enabled_permanent(zone, service)
|
||||
msgs.append('Permanent operation')
|
||||
|
||||
if desired_state == "enabled":
|
||||
if is_enabled == False:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_service_enabled_permanent(zone, service)
|
||||
changed=True
|
||||
elif desired_state == "disabled":
|
||||
if is_enabled == True:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_service_disabled_permanent(zone, service)
|
||||
changed=True
|
||||
else:
|
||||
is_enabled = get_service_enabled(zone, service)
|
||||
msgs.append('Non-permanent operation')
|
||||
|
||||
|
||||
if desired_state == "enabled":
|
||||
if is_enabled == False:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_service_enabled(zone, service, timeout)
|
||||
changed=True
|
||||
elif desired_state == "disabled":
|
||||
if is_enabled == True:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_service_disabled(zone, service)
|
||||
changed=True
|
||||
|
||||
if changed == True:
|
||||
msgs.append("Changed service %s to %s" % (service, desired_state))
|
||||
|
||||
if port != None:
|
||||
if permanent:
|
||||
is_enabled = get_port_enabled_permanent(zone, [port, protocol])
|
||||
msgs.append('Permanent operation')
|
||||
|
||||
if desired_state == "enabled":
|
||||
if is_enabled == False:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_port_enabled_permanent(zone, port, protocol)
|
||||
changed=True
|
||||
elif desired_state == "disabled":
|
||||
if is_enabled == True:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_port_disabled_permanent(zone, port, protocol)
|
||||
changed=True
|
||||
else:
|
||||
is_enabled = get_port_enabled(zone, [port,protocol])
|
||||
msgs.append('Non-permanent operation')
|
||||
|
||||
if desired_state == "enabled":
|
||||
if is_enabled == False:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_port_enabled(zone, port, protocol, timeout)
|
||||
changed=True
|
||||
elif desired_state == "disabled":
|
||||
if is_enabled == True:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_port_disabled(zone, port, protocol)
|
||||
changed=True
|
||||
|
||||
if changed == True:
|
||||
msgs.append("Changed port %s to %s" % ("%s/%s" % (port, protocol), \
|
||||
desired_state))
|
||||
|
||||
if rich_rule != None:
|
||||
if permanent:
|
||||
is_enabled = get_rich_rule_enabled_permanent(zone, rich_rule)
|
||||
msgs.append('Permanent operation')
|
||||
|
||||
if desired_state == "enabled":
|
||||
if is_enabled == False:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_rich_rule_enabled_permanent(zone, rich_rule)
|
||||
changed=True
|
||||
elif desired_state == "disabled":
|
||||
if is_enabled == True:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_rich_rule_disabled_permanent(zone, rich_rule)
|
||||
changed=True
|
||||
else:
|
||||
is_enabled = get_rich_rule_enabled(zone, rich_rule)
|
||||
msgs.append('Non-permanent operation')
|
||||
|
||||
if desired_state == "enabled":
|
||||
if is_enabled == False:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_rich_rule_enabled(zone, rich_rule, timeout)
|
||||
changed=True
|
||||
elif desired_state == "disabled":
|
||||
if is_enabled == True:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
set_rich_rule_disabled(zone, rich_rule)
|
||||
changed=True
|
||||
|
||||
if changed == True:
|
||||
msgs.append("Changed rich_rule %s to %s" % (rich_rule, desired_state))
|
||||
|
||||
module.exit_json(changed=changed, msg=', '.join(msgs))
|
||||
|
||||
|
||||
#################################################
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
@ -1,143 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Brian Coca <brian.coca+dev@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: getent
|
||||
short_description: a wrapper to the unix getent utility
|
||||
description:
|
||||
- Runs getent against one of it's various databases and returns information into
|
||||
the host's facts
|
||||
version_added: "1.8"
|
||||
options:
|
||||
database:
|
||||
required: True
|
||||
description:
|
||||
- the name of a getent database supported by the target system (passwd, group,
|
||||
hosts, etc).
|
||||
key:
|
||||
required: False
|
||||
default: ''
|
||||
description:
|
||||
- key from which to return values from the specified database, otherwise the
|
||||
full contents are returned.
|
||||
split:
|
||||
required: False
|
||||
default: None
|
||||
description:
|
||||
- "character used to split the database values into lists/arrays such as ':' or '\t', otherwise it will try to pick one depending on the database"
|
||||
fail_key:
|
||||
required: False
|
||||
default: True
|
||||
description:
|
||||
- If a supplied key is missing this will make the task fail if True
|
||||
|
||||
notes:
|
||||
- "Not all databases support enumeration, check system documentation for details"
|
||||
requirements: [ ]
|
||||
author: Brian Coca
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# get root user info
|
||||
- getent: database=passwd key=root
|
||||
register: root_info
|
||||
|
||||
# get all groups
|
||||
- getent: database=group split=':'
|
||||
register: groups
|
||||
|
||||
# get all hosts, split by tab
|
||||
- getent: database=hosts
|
||||
register: hosts
|
||||
|
||||
# get http service info, no error if missing
|
||||
- getent: database=services key=http fail_key=False
|
||||
register: http_info
|
||||
|
||||
# get user password hash (requires sudo/root)
|
||||
- getent: database=shadow key=www-data split=:
|
||||
register: pw_hash
|
||||
|
||||
'''
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
database = dict(required=True),
|
||||
key = dict(required=False, default=None),
|
||||
split = dict(required=False, default=None),
|
||||
fail_key = dict(required=False, default=True),
|
||||
),
|
||||
supports_check_mode = True,
|
||||
)
|
||||
|
||||
colon = [ 'passwd', 'shadow', 'group', 'gshadow' ]
|
||||
|
||||
database = module.params['database']
|
||||
key = module.params.get('key')
|
||||
split = module.params.get('split')
|
||||
fail_key = module.params.get('fail_key')
|
||||
|
||||
getent_bin = module.get_bin_path('getent', True)
|
||||
|
||||
if key is not None:
|
||||
cmd = [ getent_bin, database, key ]
|
||||
else:
|
||||
cmd = [ getent_bin, database ]
|
||||
|
||||
if split is None and database in colon:
|
||||
split = ':'
|
||||
|
||||
try:
|
||||
rc, out, err = module.run_command(cmd)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
msg = "Unexpected failure!"
|
||||
dbtree = 'getent_%s' % database
|
||||
results = { dbtree: {} }
|
||||
|
||||
if rc == 0:
|
||||
for line in out.splitlines():
|
||||
record = line.split(split)
|
||||
results[dbtree][record[0]] = record[1:]
|
||||
|
||||
module.exit_json(ansible_facts=results)
|
||||
|
||||
elif rc == 1:
|
||||
msg = "Missing arguments, or database unknown."
|
||||
elif rc == 2:
|
||||
msg = "One or more supplied key could not be found in the database."
|
||||
if not fail_key:
|
||||
results[dbtree][key] = None
|
||||
module.exit_json(ansible_facts=results, msg=msg)
|
||||
elif rc == 3:
|
||||
msg = "Enumeration not supported on this database."
|
||||
|
||||
module.fail_json(msg=msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
@ -1,141 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Matthias Vogelgesang <matthias.vogelgesang@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: kernel_blacklist
|
||||
author: Matthias Vogelgesang
|
||||
version_added: 1.4
|
||||
short_description: Blacklist kernel modules
|
||||
description:
|
||||
- Add or remove kernel modules from blacklist.
|
||||
options:
|
||||
name:
|
||||
required: true
|
||||
description:
|
||||
- Name of kernel module to black- or whitelist.
|
||||
state:
|
||||
required: false
|
||||
default: "present"
|
||||
choices: [ present, absent ]
|
||||
description:
|
||||
- Whether the module should be present in the blacklist or absent.
|
||||
blacklist_file:
|
||||
required: false
|
||||
description:
|
||||
- If specified, use this blacklist file instead of
|
||||
C(/etc/modprobe.d/blacklist-ansible.conf).
|
||||
default: null
|
||||
requirements: []
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Blacklist the nouveau driver module
|
||||
- kernel_blacklist: name=nouveau state=present
|
||||
'''
|
||||
|
||||
|
||||
class Blacklist(object):
|
||||
def __init__(self, module, filename):
|
||||
if not os.path.exists(filename):
|
||||
open(filename, 'a').close()
|
||||
|
||||
self.filename = filename
|
||||
self.module = module
|
||||
|
||||
def get_pattern(self):
|
||||
return '^blacklist\s*' + self.module + '$'
|
||||
|
||||
def readlines(self):
|
||||
f = open(self.filename, 'r')
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
return lines
|
||||
|
||||
def module_listed(self):
|
||||
lines = self.readlines()
|
||||
pattern = self.get_pattern()
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('#'):
|
||||
continue
|
||||
|
||||
if re.match(pattern, stripped):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def remove_module(self):
|
||||
lines = self.readlines()
|
||||
pattern = self.get_pattern()
|
||||
|
||||
f = open(self.filename, 'w')
|
||||
|
||||
for line in lines:
|
||||
if not re.match(pattern, line.strip()):
|
||||
f.write(line)
|
||||
|
||||
f.close()
|
||||
|
||||
def add_module(self):
|
||||
f = open(self.filename, 'a')
|
||||
f.write('blacklist %s\n' % self.module)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(required=True),
|
||||
state=dict(required=False, choices=['present', 'absent'],
|
||||
default='present'),
|
||||
blacklist_file=dict(required=False, default=None)
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
args = dict(changed=False, failed=False,
|
||||
name=module.params['name'], state=module.params['state'])
|
||||
|
||||
filename = '/etc/modprobe.d/blacklist-ansible.conf'
|
||||
|
||||
if module.params['blacklist_file']:
|
||||
filename = module.params['blacklist_file']
|
||||
|
||||
blacklist = Blacklist(args['name'], filename)
|
||||
|
||||
if blacklist.module_listed():
|
||||
if args['state'] == 'absent':
|
||||
blacklist.remove_module()
|
||||
args['changed'] = True
|
||||
else:
|
||||
if args['state'] == 'present':
|
||||
blacklist.add_module()
|
||||
args['changed'] = True
|
||||
|
||||
module.exit_json(**args)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,151 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import os.path
|
||||
from subprocess import Popen, PIPE, call
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: locale_gen
|
||||
short_description: Creates of removes locales.
|
||||
description:
|
||||
- Manages locales by editing /etc/locale.gen and invoking locale-gen.
|
||||
version_added: "1.6"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name and encoding of the locale, such as "en_GB.UTF-8".
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- Whether the locale shall be present.
|
||||
required: false
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a locale exists.
|
||||
- locale_gen: name=de_CH.UTF-8 state=present
|
||||
'''
|
||||
|
||||
# ===========================================
|
||||
# location module specific support methods.
|
||||
#
|
||||
|
||||
def is_present(name):
|
||||
"""Checks if the given locale is currently installed."""
|
||||
output = Popen(["locale", "-a"], stdout=PIPE).communicate()[0]
|
||||
return any(fix_case(name) == fix_case(line) for line in output.splitlines())
|
||||
|
||||
def fix_case(name):
|
||||
"""locale -a might return the encoding in either lower or upper case.
|
||||
Passing through this function makes them uniform for comparisons."""
|
||||
return name.replace(".utf8", ".UTF-8")
|
||||
|
||||
def replace_line(existing_line, new_line):
|
||||
"""Replaces lines in /etc/locale.gen"""
|
||||
with open("/etc/locale.gen", "r") as f:
|
||||
lines = [line.replace(existing_line, new_line) for line in f]
|
||||
with open("/etc/locale.gen", "w") as f:
|
||||
f.write("".join(lines))
|
||||
|
||||
def apply_change(targetState, name, encoding):
|
||||
"""Create or remove locale.
|
||||
|
||||
Keyword arguments:
|
||||
targetState -- Desired state, either present or absent.
|
||||
name -- Name including encoding such as de_CH.UTF-8.
|
||||
encoding -- Encoding such as UTF-8.
|
||||
"""
|
||||
if targetState=="present":
|
||||
# Create locale.
|
||||
replace_line("# "+name+" "+encoding, name+" "+encoding)
|
||||
else:
|
||||
# Delete locale.
|
||||
replace_line(name+" "+encoding, "# "+name+" "+encoding)
|
||||
|
||||
localeGenExitValue = call("locale-gen")
|
||||
if localeGenExitValue!=0:
|
||||
raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned "+str(localeGenExitValue))
|
||||
|
||||
def apply_change_ubuntu(targetState, name, encoding):
|
||||
"""Create or remove locale.
|
||||
|
||||
Keyword arguments:
|
||||
targetState -- Desired state, either present or absent.
|
||||
name -- Name including encoding such as de_CH.UTF-8.
|
||||
encoding -- Encoding such as UTF-8.
|
||||
"""
|
||||
if targetState=="present":
|
||||
# Create locale.
|
||||
# Ubuntu's patched locale-gen automatically adds the new locale to /var/lib/locales/supported.d/local
|
||||
localeGenExitValue = call(["locale-gen", name])
|
||||
else:
|
||||
# Delete locale involves discarding the locale from /var/lib/locales/supported.d/local and regenerating all locales.
|
||||
with open("/var/lib/locales/supported.d/local", "r") as f:
|
||||
content = f.readlines()
|
||||
with open("/var/lib/locales/supported.d/local", "w") as f:
|
||||
for line in content:
|
||||
if line!=(name+" "+encoding+"\n"):
|
||||
f.write(line)
|
||||
# Purge locales and regenerate.
|
||||
# Please provide a patch if you know how to avoid regenerating the locales to keep!
|
||||
localeGenExitValue = call(["locale-gen", "--purge"])
|
||||
|
||||
if localeGenExitValue!=0:
|
||||
raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned "+str(localeGenExitValue))
|
||||
|
||||
# ==============================================================
|
||||
# main
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
name = dict(required=True),
|
||||
state = dict(choices=['present','absent'], required=True),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
name = module.params['name']
|
||||
if not "." in name:
|
||||
module.fail_json(msg="Locale does not match pattern. Did you specify the encoding?")
|
||||
state = module.params['state']
|
||||
|
||||
if not os.path.exists("/etc/locale.gen"):
|
||||
if os.path.exists("/var/lib/locales/supported.d/local"):
|
||||
# Ubuntu created its own system to manage locales.
|
||||
ubuntuMode = True
|
||||
else:
|
||||
module.fail_json(msg="/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package “locales” installed?")
|
||||
else:
|
||||
# We found the common way to manage locales.
|
||||
ubuntuMode = False
|
||||
|
||||
prev_state = "present" if is_present(name) else "absent"
|
||||
changed = (prev_state!=state)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=changed)
|
||||
else:
|
||||
encoding = name.split(".")[1]
|
||||
if changed:
|
||||
try:
|
||||
if ubuntuMode==False:
|
||||
apply_change(state, name, encoding)
|
||||
else:
|
||||
apply_change_ubuntu(state, name, encoding)
|
||||
except EnvironmentError as e:
|
||||
module.fail_json(msg=e.strerror, exitValue=e.errno)
|
||||
|
||||
module.exit_json(name=name, changed=changed, msg="OK")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -1,253 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
|
||||
# based on lvol module by Jeroen Hoekx <jeroen.hoekx@dsquare.be>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
author: Alexander Bulimov
|
||||
module: lvg
|
||||
short_description: Configure LVM volume groups
|
||||
description:
|
||||
- This module creates, removes or resizes volume groups.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
vg:
|
||||
description:
|
||||
- The name of the volume group.
|
||||
required: true
|
||||
pvs:
|
||||
description:
|
||||
- List of comma-separated devices to use as physical devices in this volume group. Required when creating or resizing volume group.
|
||||
required: false
|
||||
pesize:
|
||||
description:
|
||||
- The size of the physical extent in megabytes. Must be a power of 2.
|
||||
default: 4
|
||||
required: false
|
||||
vg_options:
|
||||
description:
|
||||
- Additional options to pass to C(vgcreate) when creating the volume group.
|
||||
default: null
|
||||
required: false
|
||||
version_added: "1.6"
|
||||
state:
|
||||
choices: [ "present", "absent" ]
|
||||
default: present
|
||||
description:
|
||||
- Control if the volume group exists.
|
||||
required: false
|
||||
force:
|
||||
choices: [ "yes", "no" ]
|
||||
default: "no"
|
||||
description:
|
||||
- If yes, allows to remove volume group with logical volumes.
|
||||
required: false
|
||||
notes:
|
||||
- module does not modify PE size for already present volume group
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a volume group on top of /dev/sda1 with physical extent size = 32MB.
|
||||
- lvg: vg=vg.services pvs=/dev/sda1 pesize=32
|
||||
|
||||
# Create or resize a volume group on top of /dev/sdb1 and /dev/sdc5.
|
||||
# If, for example, we already have VG vg.services on top of /dev/sdb1,
|
||||
# this VG will be extended by /dev/sdc5. Or if vg.services was created on
|
||||
# top of /dev/sda5, we first extend it with /dev/sdb1 and /dev/sdc5,
|
||||
# and then reduce by /dev/sda5.
|
||||
- lvg: vg=vg.services pvs=/dev/sdb1,/dev/sdc5
|
||||
|
||||
# Remove a volume group with name vg.services.
|
||||
- lvg: vg=vg.services state=absent
|
||||
'''
|
||||
|
||||
def parse_vgs(data):
|
||||
vgs = []
|
||||
for line in data.splitlines():
|
||||
parts = line.strip().split(';')
|
||||
vgs.append({
|
||||
'name': parts[0],
|
||||
'pv_count': int(parts[1]),
|
||||
'lv_count': int(parts[2]),
|
||||
})
|
||||
return vgs
|
||||
|
||||
def find_mapper_device_name(module, dm_device):
|
||||
dmsetup_cmd = module.get_bin_path('dmsetup', True)
|
||||
mapper_prefix = '/dev/mapper/'
|
||||
rc, dm_name, err = module.run_command("%s info -C --noheadings -o name %s" % (dmsetup_cmd, dm_device))
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed executing dmsetup command.", rc=rc, err=err)
|
||||
mapper_device = mapper_prefix + dm_name.rstrip()
|
||||
return mapper_device
|
||||
|
||||
def parse_pvs(module, data):
|
||||
pvs = []
|
||||
dm_prefix = '/dev/dm-'
|
||||
for line in data.splitlines():
|
||||
parts = line.strip().split(';')
|
||||
if parts[0].startswith(dm_prefix):
|
||||
parts[0] = find_mapper_device_name(module, parts[0])
|
||||
pvs.append({
|
||||
'name': parts[0],
|
||||
'vg_name': parts[1],
|
||||
})
|
||||
return pvs
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
vg=dict(required=True),
|
||||
pvs=dict(type='list'),
|
||||
pesize=dict(type='int', default=4),
|
||||
vg_options=dict(default=''),
|
||||
state=dict(choices=["absent", "present"], default='present'),
|
||||
force=dict(type='bool', default='no'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
vg = module.params['vg']
|
||||
state = module.params['state']
|
||||
force = module.boolean(module.params['force'])
|
||||
pesize = module.params['pesize']
|
||||
vgoptions = module.params['vg_options'].split()
|
||||
|
||||
if module.params['pvs']:
|
||||
dev_string = ' '.join(module.params['pvs'])
|
||||
dev_list = module.params['pvs']
|
||||
elif state == 'present':
|
||||
module.fail_json(msg="No physical volumes given.")
|
||||
|
||||
|
||||
|
||||
if state=='present':
|
||||
### check given devices
|
||||
for test_dev in dev_list:
|
||||
if not os.path.exists(test_dev):
|
||||
module.fail_json(msg="Device %s not found."%test_dev)
|
||||
|
||||
### get pv list
|
||||
pvs_cmd = module.get_bin_path('pvs', True)
|
||||
rc,current_pvs,err = module.run_command("%s --noheadings -o pv_name,vg_name --separator ';'" % pvs_cmd)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed executing pvs command.",rc=rc, err=err)
|
||||
|
||||
### check pv for devices
|
||||
pvs = parse_pvs(module, current_pvs)
|
||||
used_pvs = [ pv for pv in pvs if pv['name'] in dev_list and pv['vg_name'] and pv['vg_name'] != vg ]
|
||||
if used_pvs:
|
||||
module.fail_json(msg="Device %s is already in %s volume group."%(used_pvs[0]['name'],used_pvs[0]['vg_name']))
|
||||
|
||||
vgs_cmd = module.get_bin_path('vgs', True)
|
||||
rc,current_vgs,err = module.run_command("%s --noheadings -o vg_name,pv_count,lv_count --separator ';'" % vgs_cmd)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed executing vgs command.",rc=rc, err=err)
|
||||
|
||||
changed = False
|
||||
|
||||
vgs = parse_vgs(current_vgs)
|
||||
|
||||
for test_vg in vgs:
|
||||
if test_vg['name'] == vg:
|
||||
this_vg = test_vg
|
||||
break
|
||||
else:
|
||||
this_vg = None
|
||||
|
||||
if this_vg is None:
|
||||
if state == 'present':
|
||||
### create VG
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
### create PV
|
||||
pvcreate_cmd = module.get_bin_path('pvcreate', True)
|
||||
for current_dev in dev_list:
|
||||
rc,_,err = module.run_command("%s %s" % (pvcreate_cmd,current_dev))
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Creating physical volume '%s' failed" % current_dev, rc=rc, err=err)
|
||||
vgcreate_cmd = module.get_bin_path('vgcreate')
|
||||
rc,_,err = module.run_command([vgcreate_cmd] + vgoptions + ['-s', str(pesize), vg, dev_string])
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Creating volume group '%s' failed"%vg, rc=rc, err=err)
|
||||
else:
|
||||
if state == 'absent':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
if this_vg['lv_count'] == 0 or force:
|
||||
### remove VG
|
||||
vgremove_cmd = module.get_bin_path('vgremove', True)
|
||||
rc,_,err = module.run_command("%s --force %s" % (vgremove_cmd, vg))
|
||||
if rc == 0:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.fail_json(msg="Failed to remove volume group %s"%(vg),rc=rc, err=err)
|
||||
else:
|
||||
module.fail_json(msg="Refuse to remove non-empty volume group %s without force=yes"%(vg))
|
||||
|
||||
### resize VG
|
||||
current_devs = [ pv['name'] for pv in pvs if pv['vg_name'] == vg ]
|
||||
devs_to_remove = list(set(current_devs) - set(dev_list))
|
||||
devs_to_add = list(set(dev_list) - set(current_devs))
|
||||
|
||||
if devs_to_add or devs_to_remove:
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
if devs_to_add:
|
||||
devs_to_add_string = ' '.join(devs_to_add)
|
||||
### create PV
|
||||
pvcreate_cmd = module.get_bin_path('pvcreate', True)
|
||||
for current_dev in devs_to_add:
|
||||
rc,_,err = module.run_command("%s %s" % (pvcreate_cmd, current_dev))
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Creating physical volume '%s' failed"%current_dev, rc=rc, err=err)
|
||||
### add PV to our VG
|
||||
vgextend_cmd = module.get_bin_path('vgextend', True)
|
||||
rc,_,err = module.run_command("%s %s %s" % (vgextend_cmd, vg, devs_to_add_string))
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Unable to extend %s by %s."%(vg, devs_to_add_string),rc=rc,err=err)
|
||||
|
||||
### remove some PV from our VG
|
||||
if devs_to_remove:
|
||||
devs_to_remove_string = ' '.join(devs_to_remove)
|
||||
vgreduce_cmd = module.get_bin_path('vgreduce', True)
|
||||
rc,_,err = module.run_command("%s --force %s %s" % (vgreduce_cmd, vg, devs_to_remove_string))
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Unable to reduce %s by %s."%(vg, devs_to_remove_string),rc=rc,err=err)
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,235 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Jeroen Hoekx <jeroen.hoekx@dsquare.be>, Alexander Bulimov <lazywolf0@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
author: Jeroen Hoekx
|
||||
module: lvol
|
||||
short_description: Configure LVM logical volumes
|
||||
description:
|
||||
- This module creates, removes or resizes logical volumes.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
vg:
|
||||
description:
|
||||
- The volume group this logical volume is part of.
|
||||
required: true
|
||||
lv:
|
||||
description:
|
||||
- The name of the logical volume.
|
||||
required: true
|
||||
size:
|
||||
description:
|
||||
- The size of the logical volume, according to lvcreate(8) --size, by
|
||||
default in megabytes or optionally with one of [bBsSkKmMgGtTpPeE] units; or
|
||||
according to lvcreate(8) --extents as a percentage of [VG|PVS|FREE];
|
||||
resizing is not supported with percentages.
|
||||
state:
|
||||
choices: [ "present", "absent" ]
|
||||
default: present
|
||||
description:
|
||||
- Control if the logical volume exists.
|
||||
required: false
|
||||
force:
|
||||
version_added: "1.5"
|
||||
choices: [ "yes", "no" ]
|
||||
default: "no"
|
||||
description:
|
||||
- Shrink or remove operations of volumes requires this switch. Ensures that
|
||||
that filesystems get never corrupted/destroyed by mistake.
|
||||
required: false
|
||||
notes:
|
||||
- Filesystems on top of the volume are not resized.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a logical volume of 512m.
|
||||
- lvol: vg=firefly lv=test size=512
|
||||
|
||||
# Create a logical volume of 512g.
|
||||
- lvol: vg=firefly lv=test size=512g
|
||||
|
||||
# Create a logical volume the size of all remaining space in the volume group
|
||||
- lvol: vg=firefly lv=test size=100%FREE
|
||||
|
||||
# Extend the logical volume to 1024m.
|
||||
- lvol: vg=firefly lv=test size=1024
|
||||
|
||||
# Reduce the logical volume to 512m
|
||||
- lvol: vg=firefly lv=test size=512 force=yes
|
||||
|
||||
# Remove the logical volume.
|
||||
- lvol: vg=firefly lv=test state=absent force=yes
|
||||
'''
|
||||
|
||||
import re
|
||||
|
||||
decimal_point = re.compile(r"(\.|,)")
|
||||
|
||||
|
||||
def parse_lvs(data):
|
||||
lvs = []
|
||||
for line in data.splitlines():
|
||||
parts = line.strip().split(';')
|
||||
lvs.append({
|
||||
'name': parts[0],
|
||||
'size': int(decimal_point.split(parts[1])[0]),
|
||||
})
|
||||
return lvs
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
vg=dict(required=True),
|
||||
lv=dict(required=True),
|
||||
size=dict(),
|
||||
state=dict(choices=["absent", "present"], default='present'),
|
||||
force=dict(type='bool', default='no'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
vg = module.params['vg']
|
||||
lv = module.params['lv']
|
||||
size = module.params['size']
|
||||
state = module.params['state']
|
||||
force = module.boolean(module.params['force'])
|
||||
size_opt = 'L'
|
||||
size_unit = 'm'
|
||||
|
||||
if size:
|
||||
# LVCREATE(8) -l --extents option with percentage
|
||||
if '%' in size:
|
||||
size_parts = size.split('%', 1)
|
||||
size_percent = int(size_parts[0])
|
||||
if size_percent > 100:
|
||||
module.fail_json(msg="Size percentage cannot be larger than 100%")
|
||||
size_whole = size_parts[1]
|
||||
if size_whole == 'ORIGIN':
|
||||
module.fail_json(msg="Snapshot Volumes are not supported")
|
||||
elif size_whole not in ['VG', 'PVS', 'FREE']:
|
||||
module.fail_json(msg="Specify extents as a percentage of VG|PVS|FREE")
|
||||
size_opt = 'l'
|
||||
size_unit = ''
|
||||
|
||||
# LVCREATE(8) -L --size option unit
|
||||
elif size[-1].isalpha():
|
||||
if size[-1] in 'bBsSkKmMgGtTpPeE':
|
||||
size_unit = size[-1]
|
||||
if size[0:-1].isdigit():
|
||||
size = int(size[0:-1])
|
||||
else:
|
||||
module.fail_json(msg="Bad size specification for unit %s" % size_unit)
|
||||
size_opt = 'L'
|
||||
else:
|
||||
module.fail_json(msg="Size unit should be one of [bBsSkKmMgGtTpPeE]")
|
||||
# when no unit, megabytes by default
|
||||
elif size.isdigit():
|
||||
size = int(size)
|
||||
else:
|
||||
module.fail_json(msg="Bad size specification")
|
||||
|
||||
if size_opt == 'l':
|
||||
unit = 'm'
|
||||
else:
|
||||
unit = size_unit
|
||||
|
||||
rc, current_lvs, err = module.run_command(
|
||||
"lvs --noheadings -o lv_name,size --units %s --separator ';' %s" % (unit, vg))
|
||||
|
||||
if rc != 0:
|
||||
if state == 'absent':
|
||||
module.exit_json(changed=False, stdout="Volume group %s does not exist." % vg, stderr=False)
|
||||
else:
|
||||
module.fail_json(msg="Volume group %s does not exist." % vg, rc=rc, err=err)
|
||||
|
||||
changed = False
|
||||
|
||||
lvs = parse_lvs(current_lvs)
|
||||
|
||||
for test_lv in lvs:
|
||||
if test_lv['name'] == lv:
|
||||
this_lv = test_lv
|
||||
break
|
||||
else:
|
||||
this_lv = None
|
||||
|
||||
if state == 'present' and not size:
|
||||
if this_lv is None:
|
||||
module.fail_json(msg="No size given.")
|
||||
else:
|
||||
module.exit_json(changed=False, vg=vg, lv=this_lv['name'], size=this_lv['size'])
|
||||
|
||||
msg = ''
|
||||
if this_lv is None:
|
||||
if state == 'present':
|
||||
### create LV
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
rc, _, err = module.run_command("lvcreate -n %s -%s %s%s %s" % (lv, size_opt, size, size_unit, vg))
|
||||
if rc == 0:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg="Creating logical volume '%s' failed" % lv, rc=rc, err=err)
|
||||
else:
|
||||
if state == 'absent':
|
||||
### remove LV
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
if not force:
|
||||
module.fail_json(msg="Sorry, no removal of logical volume %s without force=yes." % (this_lv['name']))
|
||||
rc, _, err = module.run_command("lvremove --force %s/%s" % (vg, this_lv['name']))
|
||||
if rc == 0:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.fail_json(msg="Failed to remove logical volume %s" % (lv), rc=rc, err=err)
|
||||
|
||||
elif size_opt == 'l':
|
||||
module.exit_json(changed=False, msg="Resizing extents with percentage not supported.")
|
||||
else:
|
||||
### resize LV
|
||||
tool = None
|
||||
if size > this_lv['size']:
|
||||
tool = 'lvextend'
|
||||
elif size < this_lv['size']:
|
||||
if not force:
|
||||
module.fail_json(msg="Sorry, no shrinking of %s without force=yes." % (this_lv['name']))
|
||||
tool = 'lvreduce --force'
|
||||
|
||||
if tool:
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
rc, _, err = module.run_command("%s -%s %s%s %s/%s" % (tool, size_opt, size, size_unit, vg, this_lv['name']))
|
||||
if rc == 0:
|
||||
changed = True
|
||||
elif "matches existing size" in err:
|
||||
module.exit_json(changed=False, vg=vg, lv=this_lv['name'], size=this_lv['size'])
|
||||
else:
|
||||
module.fail_json(msg="Unable to resize %s to %s%s" % (lv, size, size_unit), rc=rc, err=err)
|
||||
|
||||
module.exit_json(changed=changed, msg=msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -1,115 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, David Stygstra <david.stygstra@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: modprobe
|
||||
short_description: Add or remove kernel modules
|
||||
requirements: []
|
||||
version_added: 1.4
|
||||
author: David Stygstra, Julien Dauphant, Matt Jeffery
|
||||
description:
|
||||
- Add or remove kernel modules.
|
||||
options:
|
||||
name:
|
||||
required: true
|
||||
description:
|
||||
- Name of kernel module to manage.
|
||||
state:
|
||||
required: false
|
||||
default: "present"
|
||||
choices: [ present, absent ]
|
||||
description:
|
||||
- Whether the module should be present or absent.
|
||||
params:
|
||||
required: false
|
||||
default: ""
|
||||
version_added: "1.6"
|
||||
description:
|
||||
- Modules parameters.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Add the 802.1q module
|
||||
- modprobe: name=8021q state=present
|
||||
# Add the dummy module
|
||||
- modprobe: name=dummy state=present params="numdummies=2"
|
||||
'''
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec={
|
||||
'name': {'required': True},
|
||||
'state': {'default': 'present', 'choices': ['present', 'absent']},
|
||||
'params': {'default': ''},
|
||||
},
|
||||
supports_check_mode=True,
|
||||
)
|
||||
args = {
|
||||
'changed': False,
|
||||
'failed': False,
|
||||
'name': module.params['name'],
|
||||
'state': module.params['state'],
|
||||
'params': module.params['params'],
|
||||
}
|
||||
|
||||
# Check if module is present
|
||||
try:
|
||||
modules = open('/proc/modules')
|
||||
present = False
|
||||
module_name = args['name'].replace('-', '_') + ' '
|
||||
for line in modules:
|
||||
if line.startswith(module_name):
|
||||
present = True
|
||||
break
|
||||
modules.close()
|
||||
except IOError, e:
|
||||
module.fail_json(msg=str(e), **args)
|
||||
|
||||
# Check only; don't modify
|
||||
if module.check_mode:
|
||||
if args['state'] == 'present' and not present:
|
||||
changed = True
|
||||
elif args['state'] == 'absent' and present:
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
# Add/remove module as needed
|
||||
if args['state'] == 'present':
|
||||
if not present:
|
||||
rc, _, err = module.run_command(['modprobe', args['name'], args['params']])
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err, **args)
|
||||
args['changed'] = True
|
||||
elif args['state'] == 'absent':
|
||||
if present:
|
||||
rc, _, err = module.run_command(['rmmod', args['name']])
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err, **args)
|
||||
args['changed'] = True
|
||||
|
||||
module.exit_json(**args)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -1,56 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ohai
|
||||
short_description: Returns inventory data from I(Ohai)
|
||||
description:
|
||||
- Similar to the M(facter) module, this runs the I(Ohai) discovery program
|
||||
(U(http://wiki.opscode.com/display/chef/Ohai)) on the remote host and
|
||||
returns JSON inventory data.
|
||||
I(Ohai) data is a bit more verbose and nested than I(facter).
|
||||
version_added: "0.6"
|
||||
options: {}
|
||||
notes: []
|
||||
requirements: [ "ohai" ]
|
||||
author: Michael DeHaan
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Retrieve (ohai) data from all Web servers and store in one-file per host
|
||||
ansible webservers -m ohai --tree=/tmp/ohaidata
|
||||
'''
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict()
|
||||
)
|
||||
cmd = ["/usr/bin/env", "ohai"]
|
||||
rc, out, err = module.run_command(cmd, check_rc=True)
|
||||
module.exit_json(**json.loads(out))
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
||||
|
@ -1,379 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Serge van Ginderachter <serge@vanginderachter.be>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: open_iscsi
|
||||
author: Serge van Ginderachter
|
||||
version_added: "1.4"
|
||||
short_description: Manage iscsi targets with open-iscsi
|
||||
description:
|
||||
- Discover targets on given portal, (dis)connect targets, mark targets to
|
||||
manually or auto start, return device nodes of connected targets.
|
||||
requirements:
|
||||
- open_iscsi library and tools (iscsiadm)
|
||||
options:
|
||||
portal:
|
||||
required: false
|
||||
aliases: [ip]
|
||||
description:
|
||||
- the ip address of the iscsi target
|
||||
port:
|
||||
required: false
|
||||
default: 3260
|
||||
description:
|
||||
- the port on which the iscsi target process listens
|
||||
target:
|
||||
required: false
|
||||
aliases: [name, targetname]
|
||||
description:
|
||||
- the iscsi target name
|
||||
login:
|
||||
required: false
|
||||
choices: [true, false]
|
||||
description:
|
||||
- whether the target node should be connected
|
||||
node_auth:
|
||||
required: false
|
||||
default: CHAP
|
||||
description:
|
||||
- discovery.sendtargets.auth.authmethod
|
||||
node_user:
|
||||
required: false
|
||||
description:
|
||||
- discovery.sendtargets.auth.username
|
||||
node_pass:
|
||||
required: false
|
||||
description:
|
||||
- discovery.sendtargets.auth.password
|
||||
auto_node_startup:
|
||||
aliases: [automatic]
|
||||
required: false
|
||||
choices: [true, false]
|
||||
description:
|
||||
- whether the target node should be automatically connected at startup
|
||||
discover:
|
||||
required: false
|
||||
choices: [true, false]
|
||||
description:
|
||||
- whether the list of target nodes on the portal should be
|
||||
(re)discovered and added to the persistent iscsi database.
|
||||
Keep in mind that iscsiadm discovery resets configurtion, like node.startup
|
||||
to manual, hence combined with auto_node_startup=yes will allways return
|
||||
a changed state.
|
||||
show_nodes:
|
||||
required: false
|
||||
choices: [true, false]
|
||||
description:
|
||||
- whether the list of nodes in the persistent iscsi database should be
|
||||
returned by the module
|
||||
|
||||
examples:
|
||||
- description: perform a discovery on 10.1.2.3 and show available target
|
||||
nodes
|
||||
code: >
|
||||
open_iscsi: show_nodes=yes discover=yes portal=10.1.2.3
|
||||
- description: discover targets on portal and login to the one available
|
||||
(only works if exactly one target is exported to the initiator)
|
||||
code: >
|
||||
open_iscsi: portal={{iscsi_target}} login=yes discover=yes
|
||||
- description: connect to the named target, after updating the local
|
||||
persistent database (cache)
|
||||
code: >
|
||||
open_iscsi: login=yes target=iqn.1986-03.com.sun:02:f8c1f9e0-c3ec-ec84-c9c9-8bfb0cd5de3d
|
||||
- description: discconnect from the cached named target
|
||||
code: >
|
||||
open_iscsi: login=no target=iqn.1986-03.com.sun:02:f8c1f9e0-c3ec-ec84-c9c9-8bfb0cd5de3d"
|
||||
'''
|
||||
|
||||
import glob
|
||||
import time
|
||||
|
||||
ISCSIADM = 'iscsiadm'
|
||||
|
||||
def compare_nodelists(l1, l2):
|
||||
|
||||
l1.sort()
|
||||
l2.sort()
|
||||
return l1 == l2
|
||||
|
||||
|
||||
def iscsi_get_cached_nodes(module, portal=None):
|
||||
|
||||
cmd = '%s --mode node' % iscsiadm_cmd
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
lines = out.splitlines()
|
||||
nodes = []
|
||||
for line in lines:
|
||||
# line format is "ip:port,target_portal_group_tag targetname"
|
||||
parts = line.split()
|
||||
if len(parts) > 2:
|
||||
module.fail_json(msg='error parsing output', cmd=cmd)
|
||||
target = parts[1]
|
||||
parts = parts[0].split(':')
|
||||
target_portal = parts[0]
|
||||
|
||||
if portal is None or portal == target_portal:
|
||||
nodes.append(target)
|
||||
|
||||
# older versions of scsiadm don't have nice return codes
|
||||
# for newer versions see iscsiadm(8); also usr/iscsiadm.c for details
|
||||
# err can contain [N|n]o records...
|
||||
elif rc == 21 or (rc == 255 and "o records found" in err):
|
||||
nodes = []
|
||||
else:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
def iscsi_discover(module, portal, port):
|
||||
|
||||
cmd = '%s --mode discovery --type sendtargets --portal %s:%s' % (iscsiadm_cmd, portal, port)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc > 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
|
||||
def target_loggedon(module, target):
|
||||
|
||||
cmd = '%s --mode session' % iscsiadm_cmd
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
return target in out
|
||||
elif rc == 21:
|
||||
return False
|
||||
else:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
|
||||
def target_login(module, target):
|
||||
|
||||
node_auth = module.params['node_auth']
|
||||
node_user = module.params['node_user']
|
||||
node_pass = module.params['node_pass']
|
||||
|
||||
if node_user:
|
||||
params = [('node.session.auth.authmethod', node_auth),
|
||||
('node.session.auth.username', node_user),
|
||||
('node.session.auth.password', node_pass)]
|
||||
for (name, value) in params:
|
||||
cmd = '%s --mode node --targetname %s --op=update --name %s --value %s' % (iscsiadm_cmd, target, name, value)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
if rc > 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
cmd = '%s --mode node --targetname %s --login' % (iscsiadm_cmd, target)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc > 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
|
||||
def target_logout(module, target):
|
||||
|
||||
cmd = '%s --mode node --targetname %s --logout' % (iscsiadm_cmd, target)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc > 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
|
||||
def target_device_node(module, target):
|
||||
|
||||
# if anyone know a better way to find out which devicenodes get created for
|
||||
# a given target...
|
||||
|
||||
devices = glob.glob('/dev/disk/by-path/*%s*' % target)
|
||||
if len(devices) == 0:
|
||||
return None
|
||||
else:
|
||||
devdisks = []
|
||||
for dev in devices:
|
||||
# exclude partitions
|
||||
if "-part" not in dev:
|
||||
devdisk = os.path.realpath(dev)
|
||||
# only add once (multi-path?)
|
||||
if devdisk not in devdisks:
|
||||
devdisks.append(devdisk)
|
||||
return devdisks
|
||||
|
||||
|
||||
def target_isauto(module, target):
|
||||
|
||||
cmd = '%s --mode node --targetname %s' % (iscsiadm_cmd, target)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
lines = out.splitlines()
|
||||
for line in lines:
|
||||
if 'node.startup' in line:
|
||||
return 'automatic' in line
|
||||
return False
|
||||
else:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
|
||||
def target_setauto(module, target):
|
||||
|
||||
cmd = '%s --mode node --targetname %s --op=update --name node.startup --value automatic' % (iscsiadm_cmd, target)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc > 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
|
||||
def target_setmanual(module, target):
|
||||
|
||||
cmd = '%s --mode node --targetname %s --op=update --name node.startup --value manual' % (iscsiadm_cmd, target)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc > 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, msg=err)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# load ansible module object
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
|
||||
# target
|
||||
portal = dict(required=False, aliases=['ip']),
|
||||
port = dict(required=False, default=3260),
|
||||
target = dict(required=False, aliases=['name', 'targetname']),
|
||||
node_auth = dict(required=False, default='CHAP'),
|
||||
node_user = dict(required=False),
|
||||
node_pass = dict(required=False),
|
||||
|
||||
# actions
|
||||
login = dict(type='bool', aliases=['state']),
|
||||
auto_node_startup = dict(type='bool', aliases=['automatic']),
|
||||
discover = dict(type='bool', default=False),
|
||||
show_nodes = dict(type='bool', default=False)
|
||||
),
|
||||
|
||||
required_together=[['discover_user', 'discover_pass'],
|
||||
['node_user', 'node_pass']],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
global iscsiadm_cmd
|
||||
iscsiadm_cmd = module.get_bin_path('iscsiadm', required=True)
|
||||
|
||||
# parameters
|
||||
portal = module.params['portal']
|
||||
target = module.params['target']
|
||||
port = module.params['port']
|
||||
login = module.params['login']
|
||||
automatic = module.params['auto_node_startup']
|
||||
discover = module.params['discover']
|
||||
show_nodes = module.params['show_nodes']
|
||||
|
||||
check = module.check_mode
|
||||
|
||||
cached = iscsi_get_cached_nodes(module, portal)
|
||||
|
||||
# return json dict
|
||||
result = {}
|
||||
result['changed'] = False
|
||||
|
||||
if discover:
|
||||
if portal is None:
|
||||
module.fail_json(msg = "Need to specify at least the portal (ip) to discover")
|
||||
elif check:
|
||||
nodes = cached
|
||||
else:
|
||||
iscsi_discover(module, portal, port)
|
||||
nodes = iscsi_get_cached_nodes(module, portal)
|
||||
if not compare_nodelists(cached, nodes):
|
||||
result['changed'] |= True
|
||||
result['cache_updated'] = True
|
||||
else:
|
||||
nodes = cached
|
||||
|
||||
if login is not None or automatic is not None:
|
||||
if target is None:
|
||||
if len(nodes) > 1:
|
||||
module.fail_json(msg = "Need to specify a target")
|
||||
else:
|
||||
target = nodes[0]
|
||||
else:
|
||||
# check given target is in cache
|
||||
check_target = False
|
||||
for node in nodes:
|
||||
if node == target:
|
||||
check_target = True
|
||||
break
|
||||
if not check_target:
|
||||
module.fail_json(msg = "Specified target not found")
|
||||
|
||||
if show_nodes:
|
||||
result['nodes'] = nodes
|
||||
|
||||
if login is not None:
|
||||
loggedon = target_loggedon(module,target)
|
||||
if (login and loggedon) or (not login and not loggedon):
|
||||
result['changed'] |= False
|
||||
if login:
|
||||
result['devicenodes'] = target_device_node(module,target)
|
||||
elif not check:
|
||||
if login:
|
||||
target_login(module, target)
|
||||
# give udev some time
|
||||
time.sleep(1)
|
||||
result['devicenodes'] = target_device_node(module,target)
|
||||
else:
|
||||
target_logout(module, target)
|
||||
result['changed'] |= True
|
||||
result['connection_changed'] = True
|
||||
else:
|
||||
result['changed'] |= True
|
||||
result['connection_changed'] = True
|
||||
|
||||
if automatic is not None:
|
||||
isauto = target_isauto(module, target)
|
||||
if (automatic and isauto) or (not automatic and not isauto):
|
||||
result['changed'] |= False
|
||||
result['automatic_changed'] = False
|
||||
elif not check:
|
||||
if automatic:
|
||||
target_setauto(module, target)
|
||||
else:
|
||||
target_setmanual(module, target)
|
||||
result['changed'] |= True
|
||||
result['automatic_changed'] = True
|
||||
else:
|
||||
result['changed'] |= True
|
||||
result['automatic_changed'] = True
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
@ -1,269 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Ahti Kitsik <ak@ahtik.com>
|
||||
# (c) 2014, Jarno Keskikangas <jarno.keskikangas@gmail.com>
|
||||
# (c) 2013, Aleksey Ovcharenko <aleksey.ovcharenko@gmail.com>
|
||||
# (c) 2013, James Martin <jmartin@basho.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ufw
|
||||
short_description: Manage firewall with UFW
|
||||
description:
|
||||
- Manage firewall with UFW.
|
||||
version_added: 1.6
|
||||
author: Aleksey Ovcharenko, Jarno Keskikangas, Ahti Kitsik
|
||||
notes:
|
||||
- See C(man ufw) for more examples.
|
||||
requirements:
|
||||
- C(ufw) package
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- C(enabled) reloads firewall and enables firewall on boot.
|
||||
- C(disabled) unloads firewall and disables firewall on boot.
|
||||
- C(reloaded) reloads firewall.
|
||||
- C(reset) disables and resets firewall to installation defaults.
|
||||
required: false
|
||||
choices: ['enabled', 'disabled', 'reloaded', 'reset']
|
||||
policy:
|
||||
description:
|
||||
- Change the default policy for incoming or outgoing traffic.
|
||||
required: false
|
||||
alias: default
|
||||
choices: ['allow', 'deny', 'reject']
|
||||
direction:
|
||||
description:
|
||||
- Select direction for a rule or default policy command.
|
||||
required: false
|
||||
choices: ['in', 'out', 'incoming', 'outgoing']
|
||||
logging:
|
||||
description:
|
||||
- Toggles logging. Logged packets use the LOG_KERN syslog facility.
|
||||
choices: ['on', 'off', 'low', 'medium', 'high', 'full']
|
||||
required: false
|
||||
insert:
|
||||
description:
|
||||
- Insert the corresponding rule as rule number NUM
|
||||
required: false
|
||||
rule:
|
||||
description:
|
||||
- Add firewall rule
|
||||
required: false
|
||||
choices: ['allow', 'deny', 'reject', 'limit']
|
||||
log:
|
||||
description:
|
||||
- Log new connections matched to this rule
|
||||
required: false
|
||||
choices: ['yes', 'no']
|
||||
from_ip:
|
||||
description:
|
||||
- Source IP address.
|
||||
required: false
|
||||
aliases: ['from', 'src']
|
||||
default: 'any'
|
||||
from_port:
|
||||
description:
|
||||
- Source port.
|
||||
required: false
|
||||
to_ip:
|
||||
description:
|
||||
- Destination IP address.
|
||||
required: false
|
||||
aliases: ['to', 'dest']
|
||||
default: 'any'
|
||||
to_port:
|
||||
description:
|
||||
- Destination port.
|
||||
required: false
|
||||
aliases: ['port']
|
||||
proto:
|
||||
description:
|
||||
- TCP/IP protocol.
|
||||
choices: ['any', 'tcp', 'udp', 'ipv6', 'esp', 'ah']
|
||||
required: false
|
||||
name:
|
||||
description:
|
||||
- Use profile located in C(/etc/ufw/applications.d)
|
||||
required: false
|
||||
aliases: ['app']
|
||||
delete:
|
||||
description:
|
||||
- Delete rule.
|
||||
required: false
|
||||
choices: ['yes', 'no']
|
||||
interface:
|
||||
description:
|
||||
- Specify interface for rule.
|
||||
required: false
|
||||
aliases: ['if']
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Allow everything and enable UFW
|
||||
ufw: state=enabled policy=allow
|
||||
|
||||
# Set logging
|
||||
ufw: logging=on
|
||||
|
||||
# Sometimes it is desirable to let the sender know when traffic is
|
||||
# being denied, rather than simply ignoring it. In these cases, use
|
||||
# reject instead of deny. In addition, log rejected connections:
|
||||
ufw: rule=reject port=auth log=yes
|
||||
|
||||
# ufw supports connection rate limiting, which is useful for protecting
|
||||
# against brute-force login attacks. ufw will deny connections if an IP
|
||||
# address has attempted to initiate 6 or more connections in the last
|
||||
# 30 seconds. See http://www.debian-administration.org/articles/187
|
||||
# for details. Typical usage is:
|
||||
ufw: rule=limit port=ssh proto=tcp
|
||||
|
||||
# Allow OpenSSH
|
||||
ufw: rule=allow name=OpenSSH
|
||||
|
||||
# Delete OpenSSH rule
|
||||
ufw: rule=allow name=OpenSSH delete=yes
|
||||
|
||||
# Deny all access to port 53:
|
||||
ufw: rule=deny port=53
|
||||
|
||||
# Allow all access to tcp port 80:
|
||||
ufw: rule=allow port=80 proto=tcp
|
||||
|
||||
# Allow all access from RFC1918 networks to this host:
|
||||
ufw: rule=allow src={{ item }}
|
||||
with_items:
|
||||
- 10.0.0.0/8
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
|
||||
# Deny access to udp port 514 from host 1.2.3.4:
|
||||
ufw: rule=deny proto=udp src=1.2.3.4 port=514
|
||||
|
||||
# Allow incoming access to eth0 from 1.2.3.5 port 5469 to 1.2.3.4 port 5469
|
||||
ufw: rule=allow interface=eth0 direction=in proto=udp src=1.2.3.5 from_port=5469 dest=1.2.3.4 to_port=5469
|
||||
|
||||
# Deny all traffic from the IPv6 2001:db8::/32 to tcp port 25 on this host.
|
||||
# Note that IPv6 must be enabled in /etc/default/ufw for IPv6 firewalling to work.
|
||||
ufw: rule=deny proto=tcp src=2001:db8::/32 port=25
|
||||
'''
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
state = dict(default=None, choices=['enabled', 'disabled', 'reloaded', 'reset']),
|
||||
default = dict(default=None, aliases=['policy'], choices=['allow', 'deny', 'reject']),
|
||||
logging = dict(default=None, choices=['on', 'off', 'low', 'medium', 'high', 'full']),
|
||||
direction = dict(default=None, choices=['in', 'incoming', 'out', 'outgoing']),
|
||||
delete = dict(default=False, type='bool'),
|
||||
insert = dict(default=None),
|
||||
rule = dict(default=None, choices=['allow', 'deny', 'reject', 'limit']),
|
||||
interface = dict(default=None, aliases=['if']),
|
||||
log = dict(default=False, type='bool'),
|
||||
from_ip = dict(default='any', aliases=['src', 'from']),
|
||||
from_port = dict(default=None),
|
||||
to_ip = dict(default='any', aliases=['dest', 'to']),
|
||||
to_port = dict(default=None, aliases=['port']),
|
||||
proto = dict(default=None, aliases=['protocol'], choices=['any', 'tcp', 'udp', 'ipv6', 'esp', 'ah']),
|
||||
app = dict(default=None, aliases=['name'])
|
||||
),
|
||||
supports_check_mode = True,
|
||||
mutually_exclusive = [['app', 'proto', 'logging']]
|
||||
)
|
||||
|
||||
cmds = []
|
||||
|
||||
def execute(cmd):
|
||||
cmd = ' '.join(map(itemgetter(-1), filter(itemgetter(0), cmd)))
|
||||
|
||||
cmds.append(cmd)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err or out)
|
||||
|
||||
params = module.params
|
||||
|
||||
# Ensure at least one of the command arguments are given
|
||||
command_keys = ['state', 'default', 'rule', 'logging']
|
||||
commands = dict((key, params[key]) for key in command_keys if params[key])
|
||||
|
||||
if len(commands) < 1:
|
||||
module.fail_json(msg="Not any of the command arguments %s given" % commands)
|
||||
|
||||
if('interface' in params and 'direction' not in params):
|
||||
module.fail_json(msg="Direction must be specified when creating a rule on an interface")
|
||||
|
||||
# Ensure ufw is available
|
||||
ufw_bin = module.get_bin_path('ufw', True)
|
||||
|
||||
# Save the pre state and rules in order to recognize changes
|
||||
(_, pre_state, _) = module.run_command(ufw_bin + ' status verbose')
|
||||
(_, pre_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user*.rules")
|
||||
|
||||
# Execute commands
|
||||
for (command, value) in commands.iteritems():
|
||||
cmd = [[ufw_bin], [module.check_mode, '--dry-run']]
|
||||
|
||||
if command == 'state':
|
||||
states = { 'enabled': 'enable', 'disabled': 'disable',
|
||||
'reloaded': 'reload', 'reset': 'reset' }
|
||||
execute(cmd + [['-f'], [states[value]]])
|
||||
|
||||
elif command == 'logging':
|
||||
execute(cmd + [[command], [value]])
|
||||
|
||||
elif command == 'default':
|
||||
execute(cmd + [[command], [value], [params['direction']]])
|
||||
|
||||
elif command == 'rule':
|
||||
# Rules are constructed according to the long format
|
||||
#
|
||||
# ufw [--dry-run] [delete] [insert NUM] allow|deny|reject|limit [in|out on INTERFACE] [log|log-all] \
|
||||
# [from ADDRESS [port PORT]] [to ADDRESS [port PORT]] \
|
||||
# [proto protocol] [app application]
|
||||
cmd.append([module.boolean(params['delete']), 'delete'])
|
||||
cmd.append([params['insert'], "insert %s" % params['insert']])
|
||||
cmd.append([value])
|
||||
cmd.append([module.boolean(params['log']), 'log'])
|
||||
|
||||
for (key, template) in [('direction', "%s" ), ('interface', "on %s" ),
|
||||
('from_ip', "from %s" ), ('from_port', "port %s" ),
|
||||
('to_ip', "to %s" ), ('to_port', "port %s" ),
|
||||
('proto', "proto %s"), ('app', "app '%s'")]:
|
||||
|
||||
value = params[key]
|
||||
cmd.append([value, template % (value)])
|
||||
|
||||
execute(cmd)
|
||||
|
||||
# Get the new state
|
||||
(_, post_state, _) = module.run_command(ufw_bin + ' status verbose')
|
||||
(_, post_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user*.rules")
|
||||
changed = (pre_state != post_state) or (pre_rules != post_rules)
|
||||
|
||||
return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -1,417 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: zfs
|
||||
short_description: Manage zfs
|
||||
description:
|
||||
- Manages ZFS file systems on Solaris and FreeBSD. Can manage file systems, volumes and snapshots. See zfs(1M) for more information about the properties.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- File system, snapshot or volume name e.g. C(rpool/myfs)
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether to create (C(present)), or remove (C(absent)) a file system, snapshot or volume.
|
||||
required: true
|
||||
choices: [present, absent]
|
||||
aclinherit:
|
||||
description:
|
||||
- The aclinherit property.
|
||||
required: False
|
||||
choices: [discard,noallow,restricted,passthrough,passthrough-x]
|
||||
aclmode:
|
||||
description:
|
||||
- The aclmode property.
|
||||
required: False
|
||||
choices: [discard,groupmask,passthrough]
|
||||
atime:
|
||||
description:
|
||||
- The atime property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
canmount:
|
||||
description:
|
||||
- The canmount property.
|
||||
required: False
|
||||
choices: ['on','off','noauto']
|
||||
casesensitivity:
|
||||
description:
|
||||
- The casesensitivity property.
|
||||
required: False
|
||||
choices: [sensitive,insensitive,mixed]
|
||||
checksum:
|
||||
description:
|
||||
- The checksum property.
|
||||
required: False
|
||||
choices: ['on','off',fletcher2,fletcher4,sha256]
|
||||
compression:
|
||||
description:
|
||||
- The compression property.
|
||||
required: False
|
||||
choices: ['on','off',lzjb,gzip,gzip-1,gzip-2,gzip-3,gzip-4,gzip-5,gzip-6,gzip-7,gzip-8,gzip-9,lz4,zle]
|
||||
copies:
|
||||
description:
|
||||
- The copies property.
|
||||
required: False
|
||||
choices: [1,2,3]
|
||||
dedup:
|
||||
description:
|
||||
- The dedup property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
devices:
|
||||
description:
|
||||
- The devices property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
exec:
|
||||
description:
|
||||
- The exec property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
jailed:
|
||||
description:
|
||||
- The jailed property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
logbias:
|
||||
description:
|
||||
- The logbias property.
|
||||
required: False
|
||||
choices: [latency,throughput]
|
||||
mountpoint:
|
||||
description:
|
||||
- The mountpoint property.
|
||||
required: False
|
||||
nbmand:
|
||||
description:
|
||||
- The nbmand property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
normalization:
|
||||
description:
|
||||
- The normalization property.
|
||||
required: False
|
||||
choices: [none,formC,formD,formKC,formKD]
|
||||
primarycache:
|
||||
description:
|
||||
- The primarycache property.
|
||||
required: False
|
||||
choices: [all,none,metadata]
|
||||
quota:
|
||||
description:
|
||||
- The quota property.
|
||||
required: False
|
||||
readonly:
|
||||
description:
|
||||
- The readonly property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
recordsize:
|
||||
description:
|
||||
- The recordsize property.
|
||||
required: False
|
||||
refquota:
|
||||
description:
|
||||
- The refquota property.
|
||||
required: False
|
||||
refreservation:
|
||||
description:
|
||||
- The refreservation property.
|
||||
required: False
|
||||
reservation:
|
||||
description:
|
||||
- The reservation property.
|
||||
required: False
|
||||
secondarycache:
|
||||
description:
|
||||
- The secondarycache property.
|
||||
required: False
|
||||
choices: [all,none,metadata]
|
||||
setuid:
|
||||
description:
|
||||
- The setuid property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
shareiscsi:
|
||||
description:
|
||||
- The shareiscsi property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
sharenfs:
|
||||
description:
|
||||
- The sharenfs property.
|
||||
required: False
|
||||
sharesmb:
|
||||
description:
|
||||
- The sharesmb property.
|
||||
required: False
|
||||
snapdir:
|
||||
description:
|
||||
- The snapdir property.
|
||||
required: False
|
||||
choices: [hidden,visible]
|
||||
sync:
|
||||
description:
|
||||
- The sync property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
utf8only:
|
||||
description:
|
||||
- The utf8only property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
volsize:
|
||||
description:
|
||||
- The volsize property.
|
||||
required: False
|
||||
volblocksize:
|
||||
description:
|
||||
- The volblocksize property.
|
||||
required: False
|
||||
vscan:
|
||||
description:
|
||||
- The vscan property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
xattr:
|
||||
description:
|
||||
- The xattr property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
zoned:
|
||||
description:
|
||||
- The zoned property.
|
||||
required: False
|
||||
choices: ['on','off']
|
||||
author: Johan Wiren
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a new file system called myfs in pool rpool
|
||||
- zfs: name=rpool/myfs state=present
|
||||
|
||||
# Create a new volume called myvol in pool rpool.
|
||||
- zfs: name=rpool/myvol state=present volsize=10M
|
||||
|
||||
# Create a snapshot of rpool/myfs file system.
|
||||
- zfs: name=rpool/myfs@mysnapshot state=present
|
||||
|
||||
# Create a new file system called myfs2 with snapdir enabled
|
||||
- zfs: name=rpool/myfs2 state=present snapdir=enabled
|
||||
'''
|
||||
|
||||
|
||||
import os
|
||||
|
||||
class Zfs(object):
|
||||
def __init__(self, module, name, properties):
|
||||
self.module = module
|
||||
self.name = name
|
||||
self.properties = properties
|
||||
self.changed = False
|
||||
|
||||
self.immutable_properties = [ 'casesensitivity', 'normalization', 'utf8only' ]
|
||||
|
||||
def exists(self):
|
||||
cmd = [self.module.get_bin_path('zfs', True)]
|
||||
cmd.append('list')
|
||||
cmd.append('-t all')
|
||||
cmd.append(self.name)
|
||||
(rc, out, err) = self.module.run_command(' '.join(cmd))
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def create(self):
|
||||
if self.module.check_mode:
|
||||
self.changed = True
|
||||
return
|
||||
properties=self.properties
|
||||
volsize = properties.pop('volsize', None)
|
||||
volblocksize = properties.pop('volblocksize', None)
|
||||
if "@" in self.name:
|
||||
action = 'snapshot'
|
||||
else:
|
||||
action = 'create'
|
||||
|
||||
cmd = [self.module.get_bin_path('zfs', True)]
|
||||
cmd.append(action)
|
||||
if volblocksize:
|
||||
cmd.append('-b %s' % volblocksize)
|
||||
if properties:
|
||||
for prop, value in properties.iteritems():
|
||||
cmd.append('-o %s="%s"' % (prop, value))
|
||||
if volsize:
|
||||
cmd.append('-V')
|
||||
cmd.append(volsize)
|
||||
cmd.append(self.name)
|
||||
(rc, err, out) = self.module.run_command(' '.join(cmd))
|
||||
if rc == 0:
|
||||
self.changed=True
|
||||
else:
|
||||
self.module.fail_json(msg=out)
|
||||
|
||||
def destroy(self):
|
||||
if self.module.check_mode:
|
||||
self.changed = True
|
||||
return
|
||||
cmd = [self.module.get_bin_path('zfs', True)]
|
||||
cmd.append('destroy')
|
||||
cmd.append(self.name)
|
||||
(rc, err, out) = self.module.run_command(' '.join(cmd))
|
||||
if rc == 0:
|
||||
self.changed = True
|
||||
else:
|
||||
self.module.fail_json(msg=out)
|
||||
|
||||
def set_property(self, prop, value):
|
||||
if self.module.check_mode:
|
||||
self.changed = True
|
||||
return
|
||||
cmd = self.module.get_bin_path('zfs', True)
|
||||
args = [cmd, 'set', prop + '=' + value, self.name]
|
||||
(rc, err, out) = self.module.run_command(args)
|
||||
if rc == 0:
|
||||
self.changed = True
|
||||
else:
|
||||
self.module.fail_json(msg=out)
|
||||
|
||||
def set_properties_if_changed(self):
|
||||
current_properties = self.get_current_properties()
|
||||
for prop, value in self.properties.iteritems():
|
||||
if current_properties[prop] != value:
|
||||
if prop in self.immutable_properties:
|
||||
self.module.fail_json(msg='Cannot change property %s after creation.' % prop)
|
||||
else:
|
||||
self.set_property(prop, value)
|
||||
|
||||
def get_current_properties(self):
|
||||
def get_properties_by_name(propname):
|
||||
cmd = [self.module.get_bin_path('zfs', True)]
|
||||
cmd += ['get', '-H', propname, self.name]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
return [l.split('\t')[1:3] for l in out.splitlines()]
|
||||
properties = dict(get_properties_by_name('all'))
|
||||
if 'share.*' in properties:
|
||||
# Some ZFS pools list the sharenfs and sharesmb properties
|
||||
# hierarchically as share.nfs and share.smb respectively.
|
||||
del properties['share.*']
|
||||
for p, v in get_properties_by_name('share.all'):
|
||||
alias = p.replace('.', '') # share.nfs -> sharenfs (etc)
|
||||
properties[alias] = v
|
||||
return properties
|
||||
|
||||
def run_command(self, cmd):
|
||||
progname = cmd[0]
|
||||
cmd[0] = module.get_bin_path(progname, True)
|
||||
return module.run_command(cmd)
|
||||
|
||||
def main():
|
||||
|
||||
# FIXME: should use dict() constructor like other modules, required=False is default
|
||||
module = AnsibleModule(
|
||||
argument_spec = {
|
||||
'name': {'required': True},
|
||||
'state': {'required': True, 'choices':['present', 'absent']},
|
||||
'aclinherit': {'required': False, 'choices':['discard', 'noallow', 'restricted', 'passthrough', 'passthrough-x']},
|
||||
'aclmode': {'required': False, 'choices':['discard', 'groupmask', 'passthrough']},
|
||||
'atime': {'required': False, 'choices':['on', 'off']},
|
||||
'canmount': {'required': False, 'choices':['on', 'off', 'noauto']},
|
||||
'casesensitivity': {'required': False, 'choices':['sensitive', 'insensitive', 'mixed']},
|
||||
'checksum': {'required': False, 'choices':['on', 'off', 'fletcher2', 'fletcher4', 'sha256']},
|
||||
'compression': {'required': False, 'choices':['on', 'off', 'lzjb', 'gzip', 'gzip-1', 'gzip-2', 'gzip-3', 'gzip-4', 'gzip-5', 'gzip-6', 'gzip-7', 'gzip-8', 'gzip-9', 'lz4', 'zle']},
|
||||
'copies': {'required': False, 'choices':['1', '2', '3']},
|
||||
'dedup': {'required': False, 'choices':['on', 'off']},
|
||||
'devices': {'required': False, 'choices':['on', 'off']},
|
||||
'exec': {'required': False, 'choices':['on', 'off']},
|
||||
# Not supported
|
||||
#'groupquota': {'required': False},
|
||||
'jailed': {'required': False, 'choices':['on', 'off']},
|
||||
'logbias': {'required': False, 'choices':['latency', 'throughput']},
|
||||
'mountpoint': {'required': False},
|
||||
'nbmand': {'required': False, 'choices':['on', 'off']},
|
||||
'normalization': {'required': False, 'choices':['none', 'formC', 'formD', 'formKC', 'formKD']},
|
||||
'primarycache': {'required': False, 'choices':['all', 'none', 'metadata']},
|
||||
'quota': {'required': False},
|
||||
'readonly': {'required': False, 'choices':['on', 'off']},
|
||||
'recordsize': {'required': False},
|
||||
'refquota': {'required': False},
|
||||
'refreservation': {'required': False},
|
||||
'reservation': {'required': False},
|
||||
'secondarycache': {'required': False, 'choices':['all', 'none', 'metadata']},
|
||||
'setuid': {'required': False, 'choices':['on', 'off']},
|
||||
'shareiscsi': {'required': False, 'choices':['on', 'off']},
|
||||
'sharenfs': {'required': False},
|
||||
'sharesmb': {'required': False},
|
||||
'snapdir': {'required': False, 'choices':['hidden', 'visible']},
|
||||
'sync': {'required': False, 'choices':['on', 'off']},
|
||||
# Not supported
|
||||
#'userquota': {'required': False},
|
||||
'utf8only': {'required': False, 'choices':['on', 'off']},
|
||||
'volsize': {'required': False},
|
||||
'volblocksize': {'required': False},
|
||||
'vscan': {'required': False, 'choices':['on', 'off']},
|
||||
'xattr': {'required': False, 'choices':['on', 'off']},
|
||||
'zoned': {'required': False, 'choices':['on', 'off']},
|
||||
},
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
state = module.params.pop('state')
|
||||
name = module.params.pop('name')
|
||||
|
||||
# Get all valid zfs-properties
|
||||
properties = dict()
|
||||
for prop, value in module.params.iteritems():
|
||||
if prop in ['CHECKMODE']:
|
||||
continue
|
||||
if value:
|
||||
properties[prop] = value
|
||||
|
||||
result = {}
|
||||
result['name'] = name
|
||||
result['state'] = state
|
||||
|
||||
zfs=Zfs(module, name, properties)
|
||||
|
||||
if state == 'present':
|
||||
if zfs.exists():
|
||||
zfs.set_properties_if_changed()
|
||||
else:
|
||||
zfs.create()
|
||||
|
||||
elif state == 'absent':
|
||||
if zfs.exists():
|
||||
zfs.destroy()
|
||||
|
||||
result.update(zfs.properties)
|
||||
result['changed'] = zfs.changed
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
Loading…
Reference in New Issue