diff --git a/.gitignore b/.gitignore index 5b93be86..9732852b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ setup.sh .*.sw? roles/has_certificate/files/*.key +*.retry diff --git a/README.md b/README.md index 503d030f..5a96e007 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ARGO via Ansible -This repository contains a collection of Ansible roles and playbooks that aim at easing the deployment procedure of ARGO products. The goal for these roles and playbooks has been to be as generic as possible so that they are easily adaptable to different environments and e-Infrastructure requirements. Hence most of the variables used by default in these roles reside under the `roles/{role_name}/defaults/main.yml` files. +This repository contains a collection of Ansible roles and playbooks that aim at easing the deployment procedure of ARGO products. The goal for these roles and playbooks has been to be as generic as possible so that they are easily adaptable to different environments and e-Infrastructure requirements. Hence most of the variables used by default in these roles reside under the `roles/{role_name}/defaults/main.yml` files. The administrator of the ARGO product being deployed via these Ansible playbooks may use any of the following places in order to successfully overwrite the default values of the variables and adapt the ARGO product to the specific environment and requirements: - `roles/{role_name}/vars/main.yml` @@ -8,115 +8,43 @@ The administrator of the ARGO product being deployed via these Ansible playbooks - `host_vars/{inventory_hostname}` Per ARGO product more details on prerequisites and variables are given in the following subsections. +## Sysprep the VM +- Disable Selinux: Vi /etc/sycoconfig/selinux change SELINUX=enforcing to SELINUX=disabled +- Allocate interfaces to zones: e.g. firewall-cmd --zone=internal --change-interface=eth2 --permanent +- Upload Public keys inclunding GRNET_CI +- Reboot -## WebAPI deployment +## Run or Develop Ansible Playbooks -Contains Ansible playbook for the deployment of the ARGO datastore and API service. The play is split into four (4) roles: -- repos (includes tasks for the installation of the required repository definitions) -- has_certificate (task for uploading the certificate file onto the host under the appropriate path) -- mongodb (installation and configuration of mongodb datastore) -- webapi (installation and bootstrap of ARGO api service) +- In order to run an ansible playbook, you need to make sure that you are using `ansible 2.6`. -### Things to do before deployment +- In order to develop new playbooks you will also need to have `docker` and `molecule`. -- Obtain a key/certificate pair from a trusted CA and after place them both under roles/has_certificate/files with names `{{inventory_hostname}}.key` and `{{inventory_hostname}}.pem` respectively. As `{{inventory_hostname}}` use the exact name used within the `inventory` file. -- Edit inventory and replace `webapi.node` with the hostname that you intend to deploy the API onto. +To make the set up process easier, you will have to create a virtual environment executing the following steps: -### Prerequisites + - Make sure you have `python2.7` installed -- Deploy against CentOS 6.x node -- Make sure `libselinux-python` is installed on the target node -- Ansible version used is `1.7.2` + - Update `pip` -### How to deploy + `pip install --upgrade pip` -```bash -$ ansible-playbook -v webapi.yml -``` + - Install the virtualenv package + `pip install virtualenv` + - Create the new virtual environment -## Web UI deployment + `virtualenv --python=/usr/bin/python2.7 ./argo-ansible-env` -Contains Ansible playbook for the deployment of the ARGO Web UI service. The play is split into four (4) roles: -- firewall (configures iptables firewall rules) -- repos (includes tasks for the installation of the required repository definitions) -- has_certificate (task for uploading the certificate file onto the host under the appropriate path) -- webui (installation and bootstrap of ARGO Web UI service) + - Navigate inside the virtual environment and activate it -### Things to do before deployment + `cd argo-ansible-env && source ./bin/activate` -- Obtain a key/certificate pair from a trusted CA and after place them both under roles/has_certificate/files with names `{{inventory_hostname}}.key` and `{{inventory_hostname}}.pem` respectively. As `{{inventory_hostname}}` use the exact name used within the `inventory` file. -- Edit inventory and replace `webui.node` with the hostname that you intend to deploy the Web UI onto. -- Edit `roles/webui/vars/main.yml` file and change the values of the `certificate_password` and `keystore_password` variables to a stronger value. + - Clone the repo and install the appropriate packages -- Note that by default the EGI based web UI will be deployed on your target node. To change this behaviour use the `argo_web` and `branch_name` variables within the `roles/webui/vars/main.yml` file to point to another upstream lavoisier repository. + After cloning the repo,navigate inside it, and issue the command -### Prerequisites + `pip install -r requirements.txt` -- Deploy against CentOS 7.x node -- Ansible version used is `1.9.2` + - After setting up your environment, you will also need some pre-defined roles that our playbooks are using.To get these roles, issue the command: -### How to deploy - -```bash -$ ansible-playbook -v webui.yml -``` - -## POEM deployment - -Contains Ansible playbook for the deployment of the ARGO POEM service. The play is split into four (4) roles: -- firewall (configures iptables firewall rules) -- repos (includes tasks for the installation of the required repository definitions) -- has_certificate (task for uploading the certificate file onto the host under the appropriate path) -- poem (installs and bootstraps poem service) - -### Things to do before deployment - -- Obtain a key/certificate pair from a trusted CA and after place them both under roles/has_certificate/files with names `{{inventory_hostname}}.key` and `{{inventory_hostname}}.pem` respectively. As `{{inventory_hostname}}` use the exact name used within the `inventory` file. -- Edit inventory and replace `poem.node` with the hostname that you intend to deploy the POEM service onto. -- Create a `host_vars/{{inventory_hostname}}` file and place therein the variables found within the `roles/poem/defaults/main.yml` file in order to overwrite them. - - In order to generate a uuid to be used in the place of the `poem_secret` variable you may use the `uuidgen` linux cli utility. - -### Prerequisites - -- Deploy against CentOS 6.x node -- Make sure `libselinux-python` is installed on the target node -- Ansible version used is `1.9.2` - -### How to deploy - -```bash -$ ansible-playbook -v poem.yml -``` - -## Full standalone deployment - -Contains Ansible playbook for the deployment of all ARGO components. The play is split into six (6) roles: -- repos (includes tasks for the installation of the required repository definitions) -- ca_bundle (includes a task for the installation of the egi-ca-policy-core bundle) -- has_certificate (task for uploading the certificate file onto the host under the appropriate path) -- consumer (includes tasks for the installation of the ARGO consumer and feed components) -- mongodb (installation and configuration of mongodb datastore) -- webapi (installation and bootstrap of ARGO api service) - -### Things to do before deployment - -- Obtain a key/certificate pair from a trusted CA and after place them both under roles/has_certificate/files with names `{{inventory_hostname}}.key` and `{{inventory_hostname}}.pem` respectively. As `{{inventory_hostname}}` use the exact name used within the `inventory` file. -- Edit inventory and replace `standalone.node` with the hostname that you intend to deploy the complete ARGO stack onto. - -### Prerequisites - -- Deploy against CentOS 6.x node -- Make sure `libselinux-python` is installed on the target node -- Ansible version used is `1.7.2` - -### How to deploy - -```bash -$ ansible-playbook -v standalone.yml -``` - - -## Monitoring your services - -In case you are using Nagios or Icinga for health monitoring purposes a minimal `is_monitored` role is included in the repo. The puspose of this role is to install and configure the nrpe service on your target machines. Modify the remote host variable within the `roles/is_monitored/defaults/main.yml` file and include it in your playbooks. + `ansible-galaxy install -r requirements.yml` \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg deleted file mode 100644 index 4fba094b..00000000 --- a/ansible.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# -# config file for ansible -# https://raw.githubusercontent.com/ansible/ansible/devel/examples/ansible.cfg -# -[defaults] -remote_user = root -hostfile = inventory \ No newline at end of file diff --git a/group_vars/all b/group_vars/all deleted file mode 100644 index f794b2b5..00000000 --- a/group_vars/all +++ /dev/null @@ -1,8 +0,0 @@ ---- - -# Variable enabled_argo_repo specifies which RPM repository to use. -# To use the development repository set its value to argo-devel - -enabled_argo_repo: argo-prod - -cert_dir: /etc/grid-security diff --git a/group_vars/monitoring_engine b/group_vars/monitoring_engine deleted file mode 100644 index fb2eac8f..00000000 --- a/group_vars/monitoring_engine +++ /dev/null @@ -1,18 +0,0 @@ ---- -cert_path: /etc/pki/tls/certs/localhost.crt -key_path: /etc/pki/tls/private/localhost.key -ca_path: /etc/pki/tls/certs/ca-bundle.crt - -iptables_rules: - input: - - { dport: "80", proto: "tcp", policy: "accept"} - - { dport: "443", proto: "tcp", policy: "accept"} - -nagios_plugins: - - { name: nagios-plugins-tcp , repo: "" } - - { name: nagios-plugins-disk , repo: "" } - - { name: nagios-plugins-http , repo: "" } - - { name: nagios-plugins , repo: "" } - - { name: nagios-plugins-dummy , repo: "" } - - { name: nagios-plugins-procs , repo: "" } - - { name: nagios-plugins-ping , repo: "" } \ No newline at end of file diff --git a/group_vars/poem b/group_vars/poem deleted file mode 100644 index 4d6c1a3c..00000000 --- a/group_vars/poem +++ /dev/null @@ -1,5 +0,0 @@ ---- - -iptables_rules: - input: - - { dport: "443", proto: "tcp", policy: "accept"} diff --git a/group_vars/standalone b/group_vars/standalone deleted file mode 100644 index 1c863c2a..00000000 --- a/group_vars/standalone +++ /dev/null @@ -1,16 +0,0 @@ ---- - -mongo_bind_interfaces: 127.0.0.1 - -cert_path: /etc/grid-security/hostcert.pem -key_path: /etc/grid-security/hostkey.pem - -iptables_rules: - input: - - { dport: "443", proto: "tcp", policy: "accept"} - -hive_retention_in_days: 900 -mongo_retention_in_days: 900 -files_retention_in_days: 900 - - diff --git a/group_vars/webapi b/group_vars/webapi deleted file mode 100644 index 0ad6b2dd..00000000 --- a/group_vars/webapi +++ /dev/null @@ -1,11 +0,0 @@ ---- - -mongo_bind_interfaces: 0.0.0.0 - -cert_path: /etc/pki/tls/certs/localhost.crt -key_path: /etc/pki/tls/private/localhost.key - -iptables_rules: - input: - - { dport: "443", proto: "tcp", policy: "accept"} - - { dport: "27017", proto: "tcp", policy: "accept"} diff --git a/icinga-agent.yml b/icinga-agent.yml new file mode 100644 index 00000000..d98d3618 --- /dev/null +++ b/icinga-agent.yml @@ -0,0 +1,13 @@ +--- + +- hosts: all + become: yes + roles: + - { role: commons, task: firewall, tags: firewall } + + +- hosts: icinga_agent + become: yes + roles: + - { role: icinga_agent, tags: deploy_icinga_agent } + diff --git a/install.yml b/install.yml new file mode 100644 index 00000000..447221bd --- /dev/null +++ b/install.yml @@ -0,0 +1,137 @@ +--- + +- hosts: all + become: yes + roles: + - { role: commons, task: timezone, tags: timezone } + - { role: commons, task: repos, tags: repos } + - { role: commons, task: basic_utils, tags: basic_utils } + - { role: commons, task: users, tags: groups_users_sshKeys } + - { role: commons, task: sshd, tags: sshd } + - { role: commons, task: firewall, tags: firewall } + - { role: commons, task: fail2ban, tags: fail2ban_conf } + - { role: commons, task: cert, tags: cert } + - { role: commons, task: rsyslog, tags: rsyslog_conf } + - { role: nickhammond.logrotate, tags: logrotate } + # - { role: commons, task: is_monitored, tags: monitored } + # - { role: commons, task: backupamsmongo, tags: rsyslog_conf } + + +- hosts: connectors + become: yes + roles: + - { role: consumer, task: connectors, tags: connectors } + - { role: consumer, task: cron_jobs, tags: cron_jobs } + - { role: consumer, task: delete_files, tags: delete_files } + +- hosts: archiver + become: yes + roles: + - { role: archiver, task: archiver_setup, tags: archiver_setup } + +- hosts: poem + become: yes + roles: + - { role: poem, tags: poem} + - { role: httpd, tags: httpd } + +- hosts: haproxy + become: yes + roles: + - { role: haproxy, task: rsyslog } + - { role: haproxy, task: haproxy , tags: haproxy_install } + +- hosts: ams_store + become: yes + roles: + - { role: private_hosts } + - { role: mongodb } + - { role: ams, task: init_db, tags: ams_install } + +- hosts: ams + become: yes + roles: + - { role: private_hosts } + - { role: zookeeper, tags: zookeeper_install } + - { role: kafka, tags: kafka_install } + - { role: ams, task: deploy, tags: ams_install } + - { role: ams, task: deploy_metrics, tags: ams_install } + +- hosts: ams_push_server + become: yes + roles: + - { role: push-server, task: push-server-setup, tags: push_install } + +- hosts: authn + become: yes + roles: + - { role: mongodb } + - { role: argo-api-authn, task: authn-setup } + - { role: argo-api-authn, task: python-env-setup } + - { role: argo-api-authn, task: ams-create-users-gocdb-script } + - { role: argo-api-authn, task: ams-create-users-cloud-info-script } + - { role: argo-api-authn, task: scripts_cert } + +- hosts: metrics + become: yes + roles: + - { role: metrics, task: ams-metrics-ui } + - { role: metrics, task: ce_comp } + +- hosts: monbox + become: yes + roles: + - { role: monbox, task: deploy } + - { role: monbox, task: config } + +- hosts: alerta + become: yes + roles: + - { role: mongodb } + - { role: alerta, task: deploy, tags: alerta } + +- hosts: webapi + become: yes + roles: + - { role: mongodb } + - { role: webapi, task: deploy } + - { role: webapi, task: init_api } + +- hosts: swagger + become: yes + roles: + - { role: httpd, tags: httpd } + - { role: swagger } + +- hosts: c_cluster + become: yes + roles: + - { role: private_hosts, tags: private_hosts } + +- hosts: c_gateway + become: yes + roles: + - { role: squid } + - { role: cloudera_gateway} + +- hosts: c_private + become: yes + roles: + - { role: through_http_proxy } + - { role: cloudera_internal_node } + - { role: disable_ipv6, tags: disable_ipv6 } + +- hosts: c_manager + become: yes + roles: + - { role: cloudera_manager } + +- hosts: c_flink + become: yes + roles: + - { role: flink, tags: deploy_flink } + +- hosts: icinga_agent + become: yes + roles: + - { role: icinga_agent, tags: deploy_icinga_agent } diff --git a/inventory b/inventory deleted file mode 100644 index 70a9fc32..00000000 --- a/inventory +++ /dev/null @@ -1,15 +0,0 @@ - -[webapi] -webapi.node - -[standalone] -standalone.node - -[poem] -poem.node - -[webui] -webui.node - -[monitoring_engine] -monitoring_engine.node \ No newline at end of file diff --git a/monitoring_engine.yml b/monitoring_engine.yml deleted file mode 100644 index 4cb717e1..00000000 --- a/monitoring_engine.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- - -- hosts: monitoring_engine - sudo: true - roles: - - { role: firewall, tags: firewall } - - { role: repos, tags: repos } - - { role: ca_bundle, when: ca_bundle_install, tags: ca_bundle } - - { role: has_certificate, tags: certificate } - - { role: monitoring_engine, tags: monitoring_engine } diff --git a/poem.yml b/poem.yml deleted file mode 100644 index daf89061..00000000 --- a/poem.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- - -- hosts: poem - sudo: true - roles: - - { role: firewall, tags: firewall } - - { role: repos, tags: repos } - - { role: has_certificate, tags: certificate } - - { role: poem, tags: poem } diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..71f1ddd8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +ansible==2.6.1 +docker==3.4.1 +molecule==2.16.0 diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 00000000..8e7e4721 --- /dev/null +++ b/requirements.yml @@ -0,0 +1,7 @@ +# install some needed roles from galaxy + +# basic modifications for increased security +- src: dev-sec.os-hardening + +# role for handling logrotate configuration +- src: nickhammond.logrotate diff --git a/roles/alerta/.yamllint b/roles/alerta/.yamllint new file mode 100644 index 00000000..3a2255e4 --- /dev/null +++ b/roles/alerta/.yamllint @@ -0,0 +1,13 @@ +extends: default + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + line-length: disable + # NOTE(retr0h): Templates no longer fail this lint rule. + # Uncomment if running old Molecule templates. + # truthy: disable diff --git a/roles/alerta/README.md b/roles/alerta/README.md new file mode 100644 index 00000000..e952be12 --- /dev/null +++ b/roles/alerta/README.md @@ -0,0 +1,90 @@ +Role Name +========= + +Role to setup an alerta node with the following services +- alerta-server +- alerta-web-ui +- alerta-amqp plugin +- alerta-mailer +- mongodb +- argo-alert-publisher service +- argo-rulegen service +- supervisord +- nginx (https enabled) +- uwsgi + +Requirements +------------ +- OS: CentOS 7.x +- certificate already issued +- selinux disabled + + +Role Variables +-------------- + + +alerta_admin_list: a list of expected alerta admin usernames +alerta_plugin_list: a list of expected alerta enabled plugins +alerta_allowed_env_list: a list of expected alerta enabled environments +alerta_server_dir: path to install alerta server and python 3 virtual env +alerta_token: alerta service token to be used for cli/api access +alerta_dashboard_dir: '/var/www/html/' +alerta_dashboard_repo: https://github.com/alerta/alerta-webui.git +alerta_dashboard_release: version of dashboard +alerta_amqp_release: version (commit hash) of amqp plugin +alerta_mailer_release: version (commit hash) of mailer intergration +alerta_secret_key: alerta secret key hash +alerta: alerta version +alerta_server_version: alerta server version + +alerta_main_mail: mail mail for notifications +alerta_mail_from: mail from +alerta_smtp_host: smtp host used +alerta_smtp_port: smtp port used +ssmtp_debug: if smpt will run in debug log mode + +alerta_tenants: - list of alerta tenants + eudat: - tenant name + gocdb_auth_method: 'cert' - authentication method to gocdb + gocdb_api_endpoint: gocdb endpoint to call + gocdb_notification_flag: True/False if notification flag will be used + gocdb_verify: True/False ssl verify gocdb connection + gocdb_top_request: gocdb request pattern for top level group + gocdb_sub_request: gocdb request pattern for second level group + mail_template: path to mail text template to be used + mail_template_html: path to html template to be used + mail_type: html/text + mail_debug: true + alerta_environment: environment name to be used for each alert + alert_extra_emails: a list of extra emails to be notified + alert_timeout: how much until the alert to be considered stale + alert_group: top level group name to be used in alerts + ui_endpoint: url to the argo ui endpoint that serves a/r results + +Dependencies +------------ + +Dependent on mongo role using repo_mongo_4x: true + +Example Playbook +---------------- + +Including an example of how to use your role (for instance, with variables +passed in as parameters) is always nice for users too: + +- hosts: alerta + become: yes + roles: + - { role: mongodb } + - { role: alerta, task: deploy, tags: alerta } + +License +------- + +Apache 2 + +Author Information +------------------ + +GRNET diff --git a/roles/alerta/defaults/main.yml b/roles/alerta/defaults/main.yml new file mode 100644 index 00000000..6fd0149a --- /dev/null +++ b/roles/alerta/defaults/main.yml @@ -0,0 +1,49 @@ +--- +ssmtp_debug: YES; +alerta_admin_list: ['admin@localhost'] +alerta_plugin_list: ['reject','amqp'] +alerta_allowed_env_list: ['production'] +alerta_dashboard_dir: '/var/www/html/' +alerta_dashboard_repo: https://github.com/alerta/alerta-webui.git +alerta_dashboard_release: 'v7.2.11' +alerta_amqp_release: '8b1b3b3' +alerta_mailer_release: '74834e6' +alerta_secret_key: 'HOf%A)w6de5oJXJ^F=Jb@rfz4YDRKui9' +alerta_server_dir: '/opt/alerta/' +alerta_token: '' + + +alerta_www_url: 'localhost' +alerta_www_alias: 'localhost' +alerta_main_mail: '' +alerta_mail_from: '' +alerta_smtp_host: '' +alerta_smtp_port: 587 +alerta_mail_debug: true +alerta_version: 7.2.2 +alerta_server_version: 7.2.11 + +# default: use pip to install argo-alert from github repo using devel branch +argo_alert_repo: 'git+https://github.com/ARGOeu/argo-alert@devel#egg=argoalert' + +argo_alert_kafka: 'kafka.node.foo' + +ca_bundle: true + +cert_path: /etc/grid-security/hostcert.pem +cert_key_path: /etc/grid-security/hostkey.pem +cert_dir: /etc/grid-security/ +ca_path_bundle: /etc/pki/tls/certs/ca-bundle.crt + +alerta_tenants: + foo: + ui_endpoint: argo_ui_hostname + contact_api_type: 'contact.api.type' + contact_api_endpoint: 'https://contact.api.foo' + mail_template: ~/alerta-foo.tmpl + mail_template_html: ~/alerta-foo.html.tmpl + mail_type: html + mail_debug: true + mail_rule: ~/alerta-foo/mail-rules/mail-foo-rules.json + +smtp_debug: true diff --git a/roles/alerta/files/nginx.conf b/roles/alerta/files/nginx.conf new file mode 100644 index 00000000..7b1e6838 --- /dev/null +++ b/roles/alerta/files/nginx.conf @@ -0,0 +1,71 @@ +# For more information on configuration, see: +# * Official English Documentation: http://nginx.org/en/docs/ +# * Official Russian Documentation: http://nginx.org/ru/docs/ + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /run/nginx.pid; + +# Load dynamic modules. See /usr/share/nginx/README.dynamic. +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; + + + server { + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + + location /api { try_files $uri @api; } + location @api { + include uwsgi_params; + uwsgi_pass unix:/var/run/uwsgi/uwsgi.sock; + proxy_set_header Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + ssl_certificate "/etc/grid-security/hostcert.pem"; + ssl_certificate_key "/etc/grid-security/hostkey.pem"; + + ssl on; + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; + ssl_prefer_server_ciphers on; + + + + location / { + root /var/www/html; + try_files $uri $uri/ /index.html; + } + } + + server_names_hash_bucket_size 64; + +} \ No newline at end of file diff --git a/roles/alerta/files/uwsgi.ini b/roles/alerta/files/uwsgi.ini new file mode 100644 index 00000000..34ee0b5d --- /dev/null +++ b/roles/alerta/files/uwsgi.ini @@ -0,0 +1,18 @@ +[uwsgi] +chdir = /var/www +mount = /api=wsgi.py +callable = app +manage-script-name = true +env = BASE_URL=/api + +master = true +processes = 5 +logger = syslog:alertad + +socket = /var/run/uwsgi/uwsgi.sock +chmod-socket = 664 +uid = www-data +gid = www-data +vacuum = true + +die-on-term = true diff --git a/roles/alerta/files/uwsgi.service b/roles/alerta/files/uwsgi.service new file mode 100644 index 00000000..6e351f59 --- /dev/null +++ b/roles/alerta/files/uwsgi.service @@ -0,0 +1,25 @@ +[Unit] +Description=Alerta uWSGI service +After=syslog.target + +[Service] +User=uwsgi +Group=www-data +ExecStart=/opt/alerta/bin/uwsgi --ini /etc/uwsgi.ini +ExecStartPre=/usr/bin/mkdir -p /var/run/uwsgi +ExecStartPre=/usr/bin/chown uwsgi:www-data /var/run/uwsgi +ExecStartPre=/usr/bin/chmod 0755 /var/run/uwsgi +PermissionsStartOnly=true +PIDFile=/var/run/uwsgi/uwsgi.pid + +# Requires systemd version 211 or newer +RuntimeDirectory=uwsgi +Restart=always +#KillSignal=SIGQUIT +Type=notify +StandardError=syslog +NotifyAccess=all + + +[Install] +WantedBy=multi-user.target diff --git a/roles/alerta/files/wsgi.py b/roles/alerta/files/wsgi.py new file mode 100644 index 00000000..4b3bcd28 --- /dev/null +++ b/roles/alerta/files/wsgi.py @@ -0,0 +1 @@ +from alerta import app diff --git a/roles/alerta/handlers/main.yml b/roles/alerta/handlers/main.yml new file mode 100644 index 00000000..2940a38e --- /dev/null +++ b/roles/alerta/handlers/main.yml @@ -0,0 +1,15 @@ +--- +- name: restart nginx + service: name=nginx state=restarted enabled=yes + +- name: restart uwsgi + service: name=uwsgi state=restarted enabled=yes + +- name: restart supervisord + service: name=supervisord state=restarted enabled=yes + +- name: reload systemctl + shell: systemctl daemon-reload + +- name: reload firewalld + shell: firewall-cmd --reload \ No newline at end of file diff --git a/roles/alerta/meta/main.yml b/roles/alerta/meta/main.yml new file mode 100644 index 00000000..ba43fdd2 --- /dev/null +++ b/roles/alerta/meta/main.yml @@ -0,0 +1,58 @@ +--- +galaxy_info: + author: your name + description: your description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Some suggested licenses: + # - BSD (default) + # - MIT + # - GPLv2 + # - GPLv3 + # - Apache + # - CC-BY + license: license (GPLv2, CC-BY, etc) + + min_ansible_version: 1.2 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # Optionally specify the branch Galaxy will use when accessing the GitHub + # repo for this role. During role install, if no tags are available, + # Galaxy will use this branch. During import Galaxy will access files on + # this branch. If Travis integration is configured, only notifications for this + # branch will be accepted. Otherwise, in all cases, the repo's default branch + # (usually master) will be used. + # github_branch: + + # + # platforms is a list of platforms, and each platform has a name and a list of versions. + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] +# List your role dependencies here, one per line. Be sure to remove the '[]' above, +# if you add dependencies to this list. diff --git a/roles/alerta/molecule/default/Dockerfile.j2 b/roles/alerta/molecule/default/Dockerfile.j2 new file mode 100644 index 00000000..19692c20 --- /dev/null +++ b/roles/alerta/molecule/default/Dockerfile.j2 @@ -0,0 +1,14 @@ +# Molecule managed + +{% if item.registry is defined %} +FROM {{ item.registry.url }}/{{ item.image }} +{% else %} +FROM {{ item.image }} +{% endif %} + +RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get upgrade -y && apt-get install -y python sudo bash ca-certificates && apt-get clean; \ + elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python2-dnf bash && dnf clean all; \ + elif [ $(command -v yum) ]; then yum makecache fast && yum update -y && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \ + elif [ $(command -v zypper) ]; then zypper refresh && zypper update -y && zypper install -y python sudo bash python-xml && zypper clean -a; \ + elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \ + elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi diff --git a/roles/alerta/molecule/default/INSTALL.rst b/roles/alerta/molecule/default/INSTALL.rst new file mode 100644 index 00000000..3904805e --- /dev/null +++ b/roles/alerta/molecule/default/INSTALL.rst @@ -0,0 +1,26 @@ +******* +Docker driver installation guide +******* + +Requirements +============ + +* General molecule dependencies (see https://molecule.readthedocs.io/en/latest/installation.html) +* Docker Engine +* docker-py +* docker + +Install +======= + +Ansible < 2.6 + +.. code-block:: bash + + $ sudo pip install docker-py + +Ansible >= 2.6 + +.. code-block:: bash + + $ sudo pip install docker diff --git a/roles/alerta/molecule/default/molecule.yml b/roles/alerta/molecule/default/molecule.yml new file mode 100644 index 00000000..f5e3b111 --- /dev/null +++ b/roles/alerta/molecule/default/molecule.yml @@ -0,0 +1,20 @@ +--- +dependency: + name: galaxy +driver: + name: docker +lint: + name: yamllint +platforms: + - name: instance + image: centos:7 +provisioner: + name: ansible + lint: + name: ansible-lint +scenario: + name: default +verifier: + name: testinfra + lint: + name: flake8 diff --git a/roles/alerta/molecule/default/playbook.yml b/roles/alerta/molecule/default/playbook.yml new file mode 100644 index 00000000..1f30675f --- /dev/null +++ b/roles/alerta/molecule/default/playbook.yml @@ -0,0 +1,5 @@ +--- +- name: Converge + hosts: all + roles: + - role: alerta diff --git a/roles/alerta/molecule/default/tests/test_default.py b/roles/alerta/molecule/default/tests/test_default.py new file mode 100644 index 00000000..eedd64a1 --- /dev/null +++ b/roles/alerta/molecule/default/tests/test_default.py @@ -0,0 +1,14 @@ +import os + +import testinfra.utils.ansible_runner + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') + + +def test_hosts_file(host): + f = host.file('/etc/hosts') + + assert f.exists + assert f.user == 'root' + assert f.group == 'root' diff --git a/roles/alerta/molecule/default/tests/test_default.pyc b/roles/alerta/molecule/default/tests/test_default.pyc new file mode 100644 index 00000000..a4205e39 Binary files /dev/null and b/roles/alerta/molecule/default/tests/test_default.pyc differ diff --git a/roles/alerta/tasks/deploy.yml b/roles/alerta/tasks/deploy.yml new file mode 100644 index 00000000..90242958 --- /dev/null +++ b/roles/alerta/tasks/deploy.yml @@ -0,0 +1,361 @@ +--- + +- name: Install all needed packages including python3 + yum: + name: '{{ item }}' + state: present + loop: + - python36 + - python36-devel + - python36-setuptools + - python36-pip + - git + - supervisor + - nginx + - ssmtp + tags: alerta + +- name: install the 'Development tools' package group + yum: + name: "@Development tools" + state: present + +- name: Install Virtualenv + pip: + name: virtualenv + state: present + executable: pip3.6 + tags: alerta + +- name: Install Alerta + tags: alerta + pip: + name: alerta + version: '{{alerta_version}}' + virtualenv: '{{ alerta_server_dir }}' + virtualenv_python: python3.6 + state: forcereinstall + +- name: Install Alerta-server + tags: alerta + pip: + name: alerta-server + version: '{{alerta_server_version}}' + virtualenv: '{{ alerta_server_dir }}' + state: forcereinstall + +- name: Install uswgi through pip + tags: alerta + pip: + name: uwsgi + virtualenv: '{{ alerta_server_dir }}' + state: forcereinstall + +- name: (pip) Uninstall Werkzeug 1.0.0 [Fix 'werkzeug.contrib' problem (1/2)] + pip: + name: werkzeug + state: absent + virtualenv: '{{ alerta_server_dir }}' + +- name: (pip) Install Werkzeug 0.16.1 [Fix 'werkzeug.contrib' problem (2/2)] + pip: + name: werkzeug==0.16.1 + virtualenv: '{{ alerta_server_dir }}' + +- name: Add www-data group + tags: alerta + group: + name: www-data + state: present + +- name: Add the user www-data and append them to www-data group + tags: alerta + user: + name: www-data + groups: www-data + append: yes + +- name: append user nginx to www-data group + tags: alerta + user: + name: nginx + groups: www-data + append: yes + +- name: append user uwsgi to www-data group + tags: alerta + user: + name: uwsgi + groups: www-data + append: yes + +- name: create folder /var/run/uwsgi + file: + path: /var/run/uwsgi + owner: uwsgi + group: www-data + mode: '0774' + state: directory + +- name: create /var/www/ + file: + path: /var/www/html + state: directory + owner: www-data + group: www-data + +- name: create /var/www/html + file: + path: /var/www/html + state: directory + owner: www-data + group: www-data + +- name: copy wsgi app file to www-data + copy: + src: wsgi.py + dest: /var/www/wsgi.py + owner: www-data + group: www-data + mode: '0644' + +- name: copy uwsgi systemd service file + copy: + src: uwsgi.service + dest: /etc/systemd/system/uwsgi.service + owner: root + group: root + mode: '0644' + notify: reload systemctl + + +- name: copy uwsgi ini configuration file + copy: + src: uwsgi.ini + dest: /etc/uwsgi.ini + owner: root + group: root + mode: '0644' + notify: restart uwsgi + +- name: copy nginx configuration + copy: + src: nginx.conf + dest: /etc/nginx/nginx.conf + owner: root + group: root + mode: '0644' + notify: restart nginx + +- name: enable uwsgi service + service: + name: uwsgi + enabled: true + +- name: enable nginx service + service: + name: nginx + enabled: true + +- name: configure alertad + template: + src: alertad.conf.j2 + dest: /etc/alertad.conf + tags: alerta, alerta_token + notify: restart uwsgi + + +- name: clear /tmp/alerta_ui + file: + path: /tmp/alerta_ui + state: absent + +- name: clear /tmp/alerta_ui + file: + path: /tmp/alerta_ui + state: directory + +- name: download alerta dashboard + tags: alerta, dashboard + get_url: + url: https://github.com/alerta/alerta-webui/releases/download/v7.2.11/alerta-webui.tar.gz + dest: /tmp/alerta_ui.tar.gz + +- name: extract alerta web ui + unarchive: + src: /tmp/alerta_ui.tar.gz + dest: /tmp/alerta_ui/ + remote_src: yes + +- name: clear existing html folder + file: + path: /var/www/html + state: absent + +- name: copy dist to html + synchronize: + src: /tmp/alerta_ui/dist/ + dest: /var/www/html + delegate_to: "{{inventory_hostname}}" + +- name: configure permissiosn on web-ui folder + file: + path: /var/www/html + state: directory + owner: www-data + group: www-data + recurse: true + +- name: clear tmp folder + file: + path: /tmp/alerta_ui + state: absent + +- name: clear tmp tar.gz file + file: + path: /tmp/alerta_ui.tar.gz + state: absent + +- name: Configure Alerta Dashboard + tags: alerta , dashboard + template: + src: config.json.j2 + dest: '{{ alerta_dashboard_dir }}/config.json' + mode: 0444 + +- name: create /var/www/html + file: + path: /var/www/html + state: directory + owner: www-data + group: www-data + +- name: Install Alerta-amqp + pip: + name: 'git+https://github.com/alerta/alerta-contrib.git@{{alerta_amqp_release}}#subdirectory=plugins/amqp' + virtualenv: '{{ alerta_server_dir }}' + tags: alerta + +- name: Install Alerta-mailer + pip: + name: 'git+https://github.com/alerta/alerta-contrib.git@{{alerta_mailer_release}}#subdirectory=integrations/mailer' + virtualenv: '{{ alerta_server_dir }}' + tags: alerta + +- name: configure alerta cli + template: + src: alerta.conf.j2 + dest: /root/.alerta.conf + tags: alerta, alerta_token + +- name: establish argo-alert config directory + tags: alerta + file: + path: /etc/argo-alert + state: directory + +- name: establish argo-alert mail template directories + tags: alerta, mailer + file: + path: /etc/argo-alert/mail/templates + state: directory + +- name: establish argo-alert mail rule directories + tags: alerta, mailer + file: + path: /etc/argo-alert/mail/mailer-{{item.key}}/alerta.rules.d + state: directory + with_dict: '{{ alerta_tenants }}' + +- name: deploy argo-alert mail configurations + tags: alerta, mailer, alerta_token + template: + src: alerta-mailer.conf.j2 + dest: /etc/argo-alert/mail/mailer-{{item.key}}/mailer-{{item.key}}.conf + with_dict: '{{ alerta_tenants }}' + + + +- name: Copy mail templates + tags: certificate, alerta + copy: + src: 'private_files/{{ inventory_hostname }}/mail-templates/' + dest: /etc/argo-alert/mail/templates/ + directory_mode: yes + owner: root + group: root + mode: 0400 + + +- name: establish argo-alert log directory + tags: alerta + file: + path: /var/log/argo-alert + state: directory + +- name: Install argo-alert tools + tags: alerta + pip: + name: '{{argo_alert_repo}}' + virtualenv: '{{ alerta_server_dir }}' + +- name: Deploy argo-alert configurations + tags: alerta, argo-alert, alerta_token + template: + src: argo-alert.conf.j2 + dest: '/etc/argo-alert/argo-alert-{{item.key}}.conf' + mode: 0444 + with_dict: '{{ alerta_tenants }}' + +- name: Deploy updated hosts + tags: alerta + template: + src: hosts.j2 + dest: /etc/hosts + +- name: establish supervisord config directory + tags: alerta + file: + path: /etc/supervisord + state: directory + +- name: Configure supervisord (1/2) + tags: alerta + template: + src: supervisord.conf.j2 + dest: /etc/supervisord/supervisord.conf + mode: 0444 + notify: restart supervisord + +- name: Configure supervisord (2/2) + tags: alerta + template: + src: supervisord.conf.j2 + dest: /etc/supervisord.conf + mode: 0444 + notify: restart supervisord + +- name: enable and start supervisord + systemd: + name: supervisord + enabled: yes + state: started + masked: no + tags: alerta + +- name: configure email + template: + src: ssmtp.conf.j2 + dest: /etc/ssmtp/ssmtp.conf + +- name: ensure uwsgi is started + service: + name: uwsgi + state: started + +- name: ensure nginx is started + service: + name: nginx + state: started + +# vim: syntax=yaml ts=4 sw=4 sts=4 sr noet diff --git a/roles/alerta/tasks/main.yml b/roles/alerta/tasks/main.yml new file mode 100644 index 00000000..ffafb91a --- /dev/null +++ b/roles/alerta/tasks/main.yml @@ -0,0 +1,4 @@ +--- +# tasks file for alerta + +- include: '{{task}}.yml' diff --git a/roles/alerta/templates/alerta-mailer.conf.j2 b/roles/alerta/templates/alerta-mailer.conf.j2 new file mode 100644 index 00000000..4bfa58fe --- /dev/null +++ b/roles/alerta/templates/alerta-mailer.conf.j2 @@ -0,0 +1,27 @@ +[DEFAULT] +timezone = Europe/Athens +output = json +endpoint = https://{{alerta_www_alias}}/api +key = {{alerta_token}} +sslverify = off +timeout = 10.0 +debug = false +color = yes + +[alerta-mailer] +key = {{alerta_token}} +mail_to = {{alerta_main_mail}} +mail_from = {{alerta_mail_from}} +smtp_host = {{alerta_smtp_host}} +smtp_port = {{alerta_smtp_port}} +amqp_url = mongodb://localhost:27017/amqp +dashboard_url = https://{{alerta_www_alias}} +smtp_use_ssl = False +debug = {{ alerta_mail_debug }} +skip_mta = True +{% raw %} +mail_subject = {{alert.text}} +{% endraw %} +mail_template_html = {{ item.value.mail_template_html}} +mail_template = {{ item.value.mail_template }} +email_type = {{ item.value.mail_type }} diff --git a/roles/alerta/templates/alerta.conf.j2 b/roles/alerta/templates/alerta.conf.j2 new file mode 100644 index 00000000..355dc02a --- /dev/null +++ b/roles/alerta/templates/alerta.conf.j2 @@ -0,0 +1,9 @@ +[DEFAULT] +timezone = Europe/Athens +output = json +endpoint = https://{{inventory_hostname}}/api +key = {{alerta_token}} +sslverify = off +timeout = 10.0 +debug = yes +color = yes diff --git a/roles/alerta/templates/alertad.conf.j2 b/roles/alerta/templates/alertad.conf.j2 new file mode 100644 index 00000000..ddebe69c --- /dev/null +++ b/roles/alerta/templates/alertad.conf.j2 @@ -0,0 +1,31 @@ + + +DEBUG=True +ADMIN_USERS = {{alerta_admin_list}} + +SECRET_KEY='{{alerta_secret_key}}' +SEVERITY_MAP = { + 'fatal': 0, + 'critical': 1, + 'warning': 4, + 'indeterminate': 5, + 'ok': 5, + 'unknown': 9 +} +COLOR_MAP = { + 'severity': { + 'fatal': 'blue', + 'critical': 'red', + 'warning': '#1E90FF', + 'indeterminate': 'lightblue', + 'ok': '#00CC00', + 'unknown': 'silver' + } +} +PLUGINS={{alerta_plugin_list}} +ALLOWED_ENVIRONMENTS={{alerta_allowed_env_list}} +AMQP_URL = 'mongodb://localhost:27017/amqp' +AMQP_TOPIC = 'notify' + + +AUTH_REQUIRED = True diff --git a/roles/alerta/templates/argo-alert.conf.j2 b/roles/alerta/templates/argo-alert.conf.j2 new file mode 100644 index 00000000..e0963254 --- /dev/null +++ b/roles/alerta/templates/argo-alert.conf.j2 @@ -0,0 +1,29 @@ +[gocdb] +auth_method={{item.value.gocdb_auth_method}} +api={{item.value.gocdb_api_endpoint}} +cabundle={{ca_bundle}} +hostcert={{cert_path}} +hostkey={{cert_key_path}} +verify={{item.value.gocdb_verify}} +username={{gocdb_bauth_username | default("")}} +password={{gocdb_bauth_password | default("")}} +use_notifications_flag={{item.value.gocdb_notification_flag}} +top_request={{item.value.gocdb_top_request}} +sub_request={{item.value.gocdb_sub_request}} + +[kafka] +endpoint={{argo_alert_kafka}} +topic=alerta.{{item.key}} + +[alerta] +endpoint=https://{{alerta_www_alias}}/api +environment={{item.value.alerta_environment}} +token={{alerta_token}} +mail-rules=/etc/argo-alert/mail/mailer-{{item.key}}.conf.rules.d/rules-{{item.key}}.json +extra-emails={{item.value.alert_extra_emails}} +alert-timeout = {{item.value.alert_timeout}} +group-type = {{item.value.alert_group}} +ui_endpoint = {{item.value.ui_endpoint}} + +[logging] +level = INFO diff --git a/roles/alerta/templates/config.json.j2 b/roles/alerta/templates/config.json.j2 new file mode 100644 index 00000000..8cd1c635 --- /dev/null +++ b/roles/alerta/templates/config.json.j2 @@ -0,0 +1 @@ +{"endpoint": "/api"} \ No newline at end of file diff --git a/roles/alerta/templates/hosts.j2 b/roles/alerta/templates/hosts.j2 new file mode 100644 index 00000000..37d720d5 --- /dev/null +++ b/roles/alerta/templates/hosts.j2 @@ -0,0 +1,7 @@ +127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 +::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 + +# kafka cluster in private lan +{% for host_priv in kafka_cluster_priv %} +{{host_priv.address}} {{host_priv.alias}} +{% endfor %} diff --git a/roles/alerta/templates/housekeepingAlert.js b/roles/alerta/templates/housekeepingAlert.js new file mode 100644 index 00000000..90c4c4da --- /dev/null +++ b/roles/alerta/templates/housekeepingAlert.js @@ -0,0 +1,26 @@ +// housekeepingAlerts.js +// + now = new Date(); +// +// // mark timed out alerts as EXPIRED and update alert history + db.alerts.aggregate([ + { $project: { event: 1, status: 1, lastReceiveId: 1, timeout: 1, expireTime: { $add: [ "$lastReceiveTime", { $multiply: [ "$timeout", 1000 ]} ]} } }, + { $match: { status: { $ne: 'expired' }, expireTime: { $lt: now }, timeout: { $ne: 0 }}} + ]).forEach( function(alert) { + db.alerts.update( + { _id: alert._id }, + { + $set: { status: 'expired' }, + $push: { + history: { + event: alert.event, + status: 'expired', + text: "alert timeout status change", + id: alert.lastReceiveId, + updateTime: now + } + } + }, false, true); + }) + +db.alerts.remove({'status':'expired'}); diff --git a/roles/alerta/templates/ssmtp.conf.j2 b/roles/alerta/templates/ssmtp.conf.j2 new file mode 100644 index 00000000..a0760572 --- /dev/null +++ b/roles/alerta/templates/ssmtp.conf.j2 @@ -0,0 +1,47 @@ +Debug={{smtp_debug}} +# +# /etc/ssmtp.conf -- a config file for sSMTP sendmail. +# +# See the ssmtp.conf(5) man page for a more verbose explanation of the +# available options. +# +# The person who gets all mail for userids < 1000 +# Make this empty to disable rewriting. +root=postmaster + +# The place where the mail goes. The actual machine name is required +# no MX records are consulted. Commonly mailhosts are named mail.domain.com +# The example will fit if you are in domain.com and your mailhub is so named. +mailhub={{smtp_relay}} + +# Example for SMTP port number 2525 +# mailhub=mail.your.domain:2525 +# Example for SMTP port number 25 (Standard/RFC) +# mailhub=mail.your.domain +# Example for SSL encrypted connection +# mailhub=mail.your.domain:465 + +# Where will the mail seem to come from? +# RewriteDomain=grnet.gr +# The full hostname +#Hostname= + +# Set this to never rewrite the "From:" line (unless not given) and to +# use that address in the "from line" of the envelope. +# FromLineOverride=YES + +# Use SSL/TLS to send secure messages to server. +# UseTLS=YES +#IMPORTANT: The following line is mandatory for TLS authentication +# Use SSL/TLS certificate to authenticate against smtp host. +# UseTLSCert=YES +TLS_CA_File={{ ca_path_bundle }} +UseSTARTTLS=YES + +# Use this RSA certificate. +TLSCert={{ cert_path }} +TLSKey={{ cert_key_path }} +# Get enhanced (*really* enhanced) debugging information in the logs +# If you want to have debugging of the config file parsing, move this option +# to the top of the config file and uncomment +# Debug=YES diff --git a/roles/alerta/templates/supervisord.conf.j2 b/roles/alerta/templates/supervisord.conf.j2 new file mode 100644 index 00000000..6fcadd7e --- /dev/null +++ b/roles/alerta/templates/supervisord.conf.j2 @@ -0,0 +1,165 @@ +; Sample supervisor config file. +; +; For more information on the config file, please see: +; http://supervisord.org/configuration.html +; +; Notes: +; - Shell expansion ("~" or "$HOME") is not supported. Environment +; variables can be expanded using this syntax: "%(ENV_HOME)s". +; - Quotes around values are not supported, except in the case of +; the environment= options as shown below. +; - Comments must have a leading space: "a=b ;comment" not "a=b;comment". +; - Command will be truncated if it looks like a config file comment, e.g. +; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ". + +[unix_http_server] +file=/var/run/supervisor.sock ; the path to the socket file +;chmod=0700 ; socket file mode (default 0700) +;chown=nobody:nogroup ; socket file uid:gid owner +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +;[inet_http_server] ; inet (TCP) server disabled by default +;port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +[supervisord] +logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log +logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB +logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 +loglevel=info ; log level; default info; others: debug,warn,trace +pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid +nodaemon=false ; start in foreground if true; default false +minfds=1024 ; min. avail startup file descriptors; default 1024 +minprocs=200 ; min. avail process descriptors;default 200 +;umask=022 ; process file creation umask; default 022 +;user=chrism ; default is current user, required if root +;identifier=supervisor ; supervisord identifier, default is 'supervisor' +;directory=/tmp ; default is not to cd during start +;nocleanup=true ; don't clean up tempfiles at start; default false +;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP +;environment=KEY="value" ; key value pairs to add to environment +;strip_ansi=false ; strip ansi escape codes in logs; def. false + +; The rpcinterface:supervisor section must remain in the config file for +; RPC (supervisorctl/web interface) to work. Additional interfaces may be +; added by defining them in separate [rpcinterface:x] sections. + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +; The supervisorctl section configures how supervisorctl will connect to +; supervisord. configure it match the settings in either the unix_http_server +; or inet_http_server section. + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket +;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket +;username=chris ; should be same as in [*_http_server] if set +;password=123 ; should be same as in [*_http_server] if set +;prompt=mysupervisor ; cmd line prompt (default "supervisor") +;history_file=~/.sc_history ; use readline history if available + +; The sample program section below shows all possible program subsection values. +; Create one or more 'real' program: sections to be able to control them under +; supervisor. + +;[program:theprogramname] +;command=/bin/cat ; the program (relative uses PATH, can take args) +;process_name=%(program_name)s ; process_name expr (default %(program_name)s) +;numprocs=1 ; number of processes copies to start (def 1) +;directory=/tmp ; directory to cwd to before exec (def no cwd) +;umask=022 ; umask for process (default None) +;priority=999 ; the relative start priority (default 999) +;autostart=true ; start at supervisord start (default: true) +;startsecs=1 ; # of secs prog must stay up to be running (def. 1) +;startretries=3 ; max # of serial start failures when starting (default 3) +;autorestart=unexpected ; when to restart if exited after running (def: unexpected) +;exitcodes=0,2 ; 'expected' exit codes used with autorestart (default 0,2) +;stopsignal=QUIT ; signal used to kill process (default TERM) +;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) +;stopasgroup=false ; send stop signal to the UNIX process group (default false) +;killasgroup=false ; SIGKILL the UNIX process group (def false) +;user=chrism ; setuid to this UNIX account to run the program +;redirect_stderr=true ; redirect proc stderr to stdout (default false) +;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO +;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) +;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) +;stdout_events_enabled=false ; emit events on stdout writes (default false) +;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO +;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) +;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) +;stderr_events_enabled=false ; emit events on stderr writes (default false) +;environment=A="1",B="2" ; process environment additions (def no adds) +;serverurl=AUTO ; override serverurl computation (childutils) + +; The sample eventlistener section below shows all possible eventlistener +; subsection values. Create one or more 'real' eventlistener: sections to be +; able to handle event notifications sent by supervisord. + +;[eventlistener:theeventlistenername] +;command=/bin/eventlistener ; the program (relative uses PATH, can take args) +;process_name=%(program_name)s ; process_name expr (default %(program_name)s) +;numprocs=1 ; number of processes copies to start (def 1) +;events=EVENT ; event notif. types to subscribe to (req'd) +;buffer_size=10 ; event buffer queue size (default 10) +;directory=/tmp ; directory to cwd to before exec (def no cwd) +;umask=022 ; umask for process (default None) +;priority=-1 ; the relative start priority (default -1) +;autostart=true ; start at supervisord start (default: true) +;startsecs=1 ; # of secs prog must stay up to be running (def. 1) +;startretries=3 ; max # of serial start failures when starting (default 3) +;autorestart=unexpected ; autorestart if exited after running (def: unexpected) +;exitcodes=0,2 ; 'expected' exit codes used with autorestart (default 0,2) +;stopsignal=QUIT ; signal used to kill process (default TERM) +;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) +;stopasgroup=false ; send stop signal to the UNIX process group (default false) +;killasgroup=false ; SIGKILL the UNIX process group (def false) +;user=chrism ; setuid to this UNIX account to run the program +;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners +;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO +;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) +;stdout_events_enabled=false ; emit events on stdout writes (default false) +;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO +;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) +;stderr_events_enabled=false ; emit events on stderr writes (default false) +;environment=A="1",B="2" ; process environment additions +;serverurl=AUTO ; override serverurl computation (childutils) + +; The sample group section below shows all possible group values. Create one +; or more 'real' group: sections to create "heterogeneous" process groups. + +;[group:thegroupname] +;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions +;priority=999 ; the relative start priority (default 999) + +; The [include] section can just contain the "files" setting. This +; setting can list multiple files (separated by whitespace or +; newlines). It can also contain wildcards. The filenames are +; interpreted as relative to this file. Included files *cannot* +; include files themselves. + +;[include] +;files = relative/directory/*.ini + +{% for key, value in alerta_tenants.items() %} +[program:alert-pub-{{key}}] +command={{ alerta_server_dir }}/bin/python {{ alerta_server_dir }}/bin/argo-alert-publisher -c /etc/argo-alert/argo-alert-{{key}}.conf +autostart=false +autorestart=true +stderr_logfile=/var/log/argo-alert/alert-pub-{{key}}.err.log +stdout_logfile=/var/log/argo-alert/alert-pub-{{key}}.out.log + +[program:alert-mail-{{key}}] +command={{alerta_server_dir}}/bin/python {{alerta_server_dir}}/bin/alerta-mailer +autostart=false +autorestart=true +environment=ALERTA_CONF_FILE=/etc/argo-alert/mail/mailer-{{key}}/mailer-{{key}}.conf +stderr_logfile=/var/log/argo-alert/alert-mail-{{key}}.err.log +stdout_logfile=/var/log/argo-alert/alert-mail-{{key}}.out.log +{% endfor %} diff --git a/roles/alerta/vars/main.yml b/roles/alerta/vars/main.yml new file mode 100644 index 00000000..b3124f52 --- /dev/null +++ b/roles/alerta/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for alerta diff --git a/roles/ams/.yamllint b/roles/ams/.yamllint new file mode 100644 index 00000000..3a2255e4 --- /dev/null +++ b/roles/ams/.yamllint @@ -0,0 +1,13 @@ +extends: default + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + line-length: disable + # NOTE(retr0h): Templates no longer fail this lint rule. + # Uncomment if running old Molecule templates. + # truthy: disable diff --git a/roles/ams/README.md b/roles/ams/README.md new file mode 100644 index 00000000..b00c4199 --- /dev/null +++ b/roles/ams/README.md @@ -0,0 +1,84 @@ +AMS (Argo Messaging Service) +============================ + +Installs a clustered AMS service. + +Requirements +------------ + +Requires commons role, private cluster vars to be set and also the following +roles to be run in prior: + - zookeeper + - kafka + +Role Variables +-------------- +General configuation variables + +ams_bind_ip: '' +ams_port: 443 +ams_store_host: 'localhost' +ams_store_db: 'argo_msg' +ams_zookeeper_hosts: "localhost:2181" +ams_kafka_znode: '' +ams_per_resource_auth: 'true' +ams_log_level: 'INFO' +ams_metrics_token: 'secret_key' this should be overridden in host vars +ams_service_token: 'secret_key' this should be overridden in host vars + +Variables for initializing database with projects and users + ams_data_projects: + - name: TEST #the project name + uuid: project_UUID #the project UUID + + ams_data_users: + - username: default_service_admin + uuid: user_UUID1 #the user's UUID + key: user_key #the user's key + service_roles: + - service_admin + email: service_admin@argo.example.foo + - username: default_project_admin + uuid: user_UUID2 #the users UUID + projects: + - project_uuid: project_UUID #the project UUID + roles: + - project_admin + key: project_admin_key + email: project_admin@argo.example.foo + created_by: user_UUID1 + +Dependencies +------------ + +You need to specify the ansible inventory group that will consist your cluster +- cluster_group: kafka + +You need for each host to set up the following variable for the private interface +private: + - hostname: foo.host.priv # private hostname alias + ip: 192.168.0.1 # private hostname + id: 1 # private + +Example Playbook +---------------- + +Including an example of how to use your role (for instance, with variables +passed in as parameters) is always nice for users too: + + - hosts: servers + roles: + - { role: ams, task: deploy } + - { role: ams, task: init_db } + - { role: ams, task: metrics } + + +License +------- + +Apache 2 + +Author Information +------------------ + +GRNET diff --git a/roles/ams/defaults/main.yml b/roles/ams/defaults/main.yml new file mode 100644 index 00000000..4ff87169 --- /dev/null +++ b/roles/ams/defaults/main.yml @@ -0,0 +1,34 @@ +--- +# defaults file for ams + +ams_bind_ip: '' +ams_port: 443 +ams_store_host: 'localhost' +ams_store_db: 'argo_msg' +ams_zookeeper_hosts: "localhost:2181" +ams_kafka_znode: '' +ams_per_resource_auth: 'true' +ams_log_level: 'INFO' +# ams_metrics_token: 'secret_key' this should be overridden in host vars +# ams_service_token: 'secret_key' this should be overridden in host vars + +# ams_data_projects: +# - name: TEST #the project name +# uuid: project_UUID #the project UUID +# +# ams_data_users: +# - username: default_service_admin +# uuid: user_UUID1 #the user's UUID +# key: user_key #the user's key +# service_roles: +# - service_admin +# email: service_admin@argo.example.foo +# - username: default_project_admin +# uuid: user_UUID2 #the users UUID +# projects: +# - project_uuid: project_UUID #the project UUID +# roles: +# - project_admin +# key: 4d5c37781694ae20298554be8ba1e1d0a25a8c43 +# email: project_admin@argo.example.foo +# created_by: user_UUID1 diff --git a/roles/ams/handlers/main.yml b/roles/ams/handlers/main.yml new file mode 100644 index 00000000..ffaedff5 --- /dev/null +++ b/roles/ams/handlers/main.yml @@ -0,0 +1,5 @@ +--- +# handlers file for ams + +- name: restart argo-messaging + service: name=argo-messaging state=restarted diff --git a/roles/ams/meta/main.yml b/roles/ams/meta/main.yml new file mode 100644 index 00000000..ba43fdd2 --- /dev/null +++ b/roles/ams/meta/main.yml @@ -0,0 +1,58 @@ +--- +galaxy_info: + author: your name + description: your description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Some suggested licenses: + # - BSD (default) + # - MIT + # - GPLv2 + # - GPLv3 + # - Apache + # - CC-BY + license: license (GPLv2, CC-BY, etc) + + min_ansible_version: 1.2 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # Optionally specify the branch Galaxy will use when accessing the GitHub + # repo for this role. During role install, if no tags are available, + # Galaxy will use this branch. During import Galaxy will access files on + # this branch. If Travis integration is configured, only notifications for this + # branch will be accepted. Otherwise, in all cases, the repo's default branch + # (usually master) will be used. + # github_branch: + + # + # platforms is a list of platforms, and each platform has a name and a list of versions. + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] +# List your role dependencies here, one per line. Be sure to remove the '[]' above, +# if you add dependencies to this list. diff --git a/roles/ams/molecule/default/Dockerfile.j2 b/roles/ams/molecule/default/Dockerfile.j2 new file mode 100644 index 00000000..19692c20 --- /dev/null +++ b/roles/ams/molecule/default/Dockerfile.j2 @@ -0,0 +1,14 @@ +# Molecule managed + +{% if item.registry is defined %} +FROM {{ item.registry.url }}/{{ item.image }} +{% else %} +FROM {{ item.image }} +{% endif %} + +RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get upgrade -y && apt-get install -y python sudo bash ca-certificates && apt-get clean; \ + elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python2-dnf bash && dnf clean all; \ + elif [ $(command -v yum) ]; then yum makecache fast && yum update -y && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \ + elif [ $(command -v zypper) ]; then zypper refresh && zypper update -y && zypper install -y python sudo bash python-xml && zypper clean -a; \ + elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \ + elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi diff --git a/roles/ams/molecule/default/INSTALL.rst b/roles/ams/molecule/default/INSTALL.rst new file mode 100644 index 00000000..3904805e --- /dev/null +++ b/roles/ams/molecule/default/INSTALL.rst @@ -0,0 +1,26 @@ +******* +Docker driver installation guide +******* + +Requirements +============ + +* General molecule dependencies (see https://molecule.readthedocs.io/en/latest/installation.html) +* Docker Engine +* docker-py +* docker + +Install +======= + +Ansible < 2.6 + +.. code-block:: bash + + $ sudo pip install docker-py + +Ansible >= 2.6 + +.. code-block:: bash + + $ sudo pip install docker diff --git a/roles/ams/molecule/default/molecule.yml b/roles/ams/molecule/default/molecule.yml new file mode 100644 index 00000000..f5e3b111 --- /dev/null +++ b/roles/ams/molecule/default/molecule.yml @@ -0,0 +1,20 @@ +--- +dependency: + name: galaxy +driver: + name: docker +lint: + name: yamllint +platforms: + - name: instance + image: centos:7 +provisioner: + name: ansible + lint: + name: ansible-lint +scenario: + name: default +verifier: + name: testinfra + lint: + name: flake8 diff --git a/roles/ams/molecule/default/playbook.yml b/roles/ams/molecule/default/playbook.yml new file mode 100644 index 00000000..59ff1a07 --- /dev/null +++ b/roles/ams/molecule/default/playbook.yml @@ -0,0 +1,5 @@ +--- +- name: Converge + hosts: all + roles: + - role: ams diff --git a/roles/ams/molecule/default/tests/test_default.py b/roles/ams/molecule/default/tests/test_default.py new file mode 100644 index 00000000..eedd64a1 --- /dev/null +++ b/roles/ams/molecule/default/tests/test_default.py @@ -0,0 +1,14 @@ +import os + +import testinfra.utils.ansible_runner + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') + + +def test_hosts_file(host): + f = host.file('/etc/hosts') + + assert f.exists + assert f.user == 'root' + assert f.group == 'root' diff --git a/roles/ams/tasks/deploy.yml b/roles/ams/tasks/deploy.yml new file mode 100644 index 00000000..7e81f346 --- /dev/null +++ b/roles/ams/tasks/deploy.yml @@ -0,0 +1,44 @@ +--- +# tasks file for ams + +- name: Install argo-messaging package + yum: name=argo-messaging state=latest enablerepo={{ repo_argo_enabled }} update_cache=yes + notify: restart argo-messaging + tags: ams_install + +- name: copy cert to ams folder + copy: remote_src=yes src=/etc/grid-security/hostcert.pem dest=/var/www/argo-messaging/hostcert.pem backup=yes + owner=argo-messaging group=argo-messaging mode=0644 + tags: ams_install + notify: restart argo-messaging + +- name: copy key to ams folder + copy: remote_src=yes src=/etc/grid-security/hostkey.pem dest=/var/www/argo-messaging/hostkey.pem backup=yes + owner=argo-messaging group=argo-messaging mode=0644 + tags: ams_install + notify: restart argo-messaging + +- name: Create CA dir if not exists + file: dest=/var/www/argo-messaging/cas state=directory + owner=argo-messaging group=argo-messaging mode=0755 + tags: ams_install + notify: restart argo-messaging + +- name: Copy CA file + copy: src=private_files/{{ inventory_hostname }}/DigiCertCA.crt + dest=/var/www/argo-messaging/cas/DigiCertCA.pem + backup=yes + owner=argo-messaging group=argo-messaging mode=0644 + tags: ams_install + notify: restart argo-messaging + +- name: Configure argo-messaging api + template: src=config.json.j2 + dest=/etc/argo-messaging/config.json backup=yes + owner=root group=root mode=0644 + tags: ams_install + notify: restart argo-messaging + +- name: Start argo-messaging service + service: name=argo-messaging state=started enabled=true + tags: ams_install diff --git a/roles/ams/tasks/deploy_metrics.yml b/roles/ams/tasks/deploy_metrics.yml new file mode 100644 index 00000000..be1a8fca --- /dev/null +++ b/roles/ams/tasks/deploy_metrics.yml @@ -0,0 +1,21 @@ +--- + +- name: Configure argo-messaging check-metrics.sh + template: src=check-metrics.sh.j2 + dest=/var/www/argo-messaging/check-metrics.sh backup=yes + owner=root group=root mode=0755 + tags: + - ams_install + - metrics + +- name: Check metrics cron job + tags: + - ams_install + - metrics + cron: cron_file=ams_check_metrics + name=ams_check_metrics + minute=15 + hour=* + user=root + job="/var/www/argo-messaging/check-metrics.sh" + state=present diff --git a/roles/ams/tasks/init_db.yml b/roles/ams/tasks/init_db.yml new file mode 100644 index 00000000..368a507b --- /dev/null +++ b/roles/ams/tasks/init_db.yml @@ -0,0 +1,57 @@ +--- + +- name: Move init roles script + template: dest="/tmp/db_init_roles.js" owner=root group=root mode=640 src=db_init_roles.js.j2 + tags: + - ams_install + - init_db + - init_roles + +- name: Run init roles script + shell: mongo < /tmp/db_init_roles.js + tags: + - ams_install + - init_db + - init_roles + +- name: Move init projects script + template: dest="/tmp/db_init_projects.js" owner=root group=root mode=640 src=db_init_projects.js.j2 + tags: + - ams_install + - init_db + - init_projects + +- name: Run init projects script + shell: mongo < /tmp/db_init_projects.js + tags: + - ams_install + - init_db + - init_projects + +- name: Move init users script + template: dest="/tmp/db_init_users.js" owner=root group=root mode=640 src=db_init_users.js.j2 + tags: + - ams_install + - init_db + - init_projects + +- name: Run init users script + shell: mongo < /tmp/db_init_users.js + tags: + - ams_install + - init_db + - init_projects + +- name: Move init push worker script + template: dest="/tmp/db_init_pushworker.js" owner=root group=root mode=640 src=db_init_pushworker.js.j2 + tags: + - ams_install + - init_db + - init_push_worker + +- name: Run init push worker script + shell: mongo < /tmp/db_init_pushworker.js + tags: + - ams_install + - init_db + - init_push_worker diff --git a/roles/ams/tasks/main.yml b/roles/ams/tasks/main.yml new file mode 100644 index 00000000..4f4d2da5 --- /dev/null +++ b/roles/ams/tasks/main.yml @@ -0,0 +1,4 @@ +--- +# tasks file for ams + +- include: '{{task}}.yml' diff --git a/roles/ams/tasks/rollback.yml b/roles/ams/tasks/rollback.yml new file mode 100644 index 00000000..df019ab6 --- /dev/null +++ b/roles/ams/tasks/rollback.yml @@ -0,0 +1,11 @@ +--- + +- name: Rollback argo-messaging package to specific version + yum: name=argo-messaging-{{ artifact_version }} state=present enablerepo={{ repo_argo_enabled }} update_cache=yes + notify: restart argo-messaging + tags: ams_rollback + +- name: Reload the systemd daemon + systemd: + daemon_reload: yes + tags: ams_rollback \ No newline at end of file diff --git a/roles/ams/tasks/update.yml b/roles/ams/tasks/update.yml new file mode 100644 index 00000000..4bc14d65 --- /dev/null +++ b/roles/ams/tasks/update.yml @@ -0,0 +1,11 @@ +--- + +- name: Update argo-messaging package + yum: name=argo-messaging state=latest enablerepo={{ repo_argo_enabled }} update_cache=yes + notify: restart argo-messaging + tags: ams_update + +- name: Reload the systemd daemon + systemd: + daemon_reload: yes + tags: ams_update \ No newline at end of file diff --git a/roles/ams/templates/check-metrics.sh.j2 b/roles/ams/templates/check-metrics.sh.j2 new file mode 100644 index 00000000..f4fbd90f --- /dev/null +++ b/roles/ams/templates/check-metrics.sh.j2 @@ -0,0 +1 @@ +curl -k -X GET 'https://{{inventory_hostname}}/v1/metrics?key={{ams_metrics_token}}' -H 'cache-control: no-cache' -H 'content-type: application/json' diff --git a/roles/ams/templates/config.json.j2 b/roles/ams/templates/config.json.j2 new file mode 100644 index 00000000..0cf9d56d --- /dev/null +++ b/roles/ams/templates/config.json.j2 @@ -0,0 +1,26 @@ +{ + "bind_ip":"{{ams_bind_ip}}", + "port":{{ams_port}}, + "zookeeper_hosts":[{% for host in groups[cluster_group] %}"{{hostvars[host]['private']['hostname']}}:2181"{% if not loop.last %},{% endif %}{% endfor %}], + "kafka_znode":"{{ams_kafka_znode}}", + "store_host":"{{ams_store_host}}", + "store_db":"{{ams_store_db}}", + "certificate":"/var/www/argo-messaging/hostcert.pem", + "certificate_key":"/var/www/argo-messaging/hostkey.pem", + "per_resource_auth":{{ams_per_resource_auth}}, + {% if ams_service_token %} + "service_token":"{{ams_service_token}}", + {% endif %} + "log_level": "{{ams_log_level}}", + {% if ams_push_enabled %} + "push_enabled": {{ams_push_enabled}}, + "push_server_host": "{{ams_push_server_host}}", + "push_server_port": {{ams_push_server_port}}, + "push_worker_token": "{{ams_push_worker_token}}", + {% if ams_push_tls_enabled %} + "push_tls_enabled": {{ams_push_tls_enabled}}, + "verify_push_server": {{ams_verify_push_server}}, + "certificate_authorities_dir": "/var/www/argo-messaging/cas" + {% endif %} + {% endif %} +} diff --git a/roles/ams/templates/db_init_projects.js.j2 b/roles/ams/templates/db_init_projects.js.j2 new file mode 100644 index 00000000..5ec76dec --- /dev/null +++ b/roles/ams/templates/db_init_projects.js.j2 @@ -0,0 +1,19 @@ +// Open Database +use {{ams_store_db}} + +// Create projects + +{%- if ams_data_projects is defined -%} + +{%- for project in ams_data_projects -%} + + +// Create projects +db.projects.update({"name" : "{{project.name}}"},{ "name":"{{project.name}}","uuid" : "{{project.uuid}}", "created_on":new ISODate(), "modified_on": new ISODate()},{"upsert":"true"}) + + + +{%- endfor -%} + + +{%- endif -%} diff --git a/roles/ams/templates/db_init_pushworker.js.j2 b/roles/ams/templates/db_init_pushworker.js.j2 new file mode 100644 index 00000000..7ac7f458 --- /dev/null +++ b/roles/ams/templates/db_init_pushworker.js.j2 @@ -0,0 +1,13 @@ +use {{ams_store_db}} + +db.users.update( +{"token": "{{ams_push_worker_token}}"}, +{"$set": + { + "uuid":"{{ams_push_worker_uuid}}", + "name": "push_worker_0", + "email": "argo-ops@lists.grnet.gr", + "projects": [], + "service_roles": ["push_worker"]} +}, +{"upsert":"true"}) diff --git a/roles/ams/templates/db_init_roles.js.j2 b/roles/ams/templates/db_init_roles.js.j2 new file mode 100644 index 00000000..53b12e7f --- /dev/null +++ b/roles/ams/templates/db_init_roles.js.j2 @@ -0,0 +1,59 @@ +use {{ams_store_db}} +db.roles.drop() +db.roles.insert([ +{"resource" : "ams:metrics", "roles": ["service_admin", "metrics_viewer"]}, +{"resource" : "ams:dailyMessageAverage", "roles": ["service_admin", "metrics_viewer"]}, +{"resource" : "users:byUUID", "roles" : [ "service_admin", "argo_api_authn"] }, +{"resource" : "users:byToken", "roles" : [ "service_admin", "push_worker" ] }, +{"resource" : "users:list", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "users:show", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "users:create", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "users:update", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "users:refreshToken", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "users:delete", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "registrations:newUser", "roles" : [ "service_admin", "register_admin" ] }, +{"resource" : "registrations:acceptNewUser", "roles" : [ "service_admin" ] }, +{"resource" : "registrations:declineNewUser", "roles" : [ "service_admin" ] }, +{"resource" : "registrations:show", "roles" : [ "service_admin" ] }, +{"resource" : "registrations:list", "roles" : [ "service_admin" ] }, +{"resource" : "projects:showUser", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "projects:createUser", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "projects:metrics", "roles" : [ "service_admin", "project_admin"] }, +{"resource" : "projects:list", "roles" : [ "service_admin"] }, +{"resource" : "projects:create", "roles" : [ "service_admin" ] }, +{"resource" : "projects:show", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "projects:update", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "projects:delete", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "topics:list", "roles" : ["service_admin", "project_admin", "publisher" ] }, +{"resource" : "topics:publish", "roles" : [ "service_admin","project_admin", "publisher" ] }, +{"resource" : "topics:metrics", "roles" : [ "service_admin", "project_admin", "publisher" ] }, +{"resource" : "topics:list", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "topics:show", "roles" : [ "service_admin", "project_admin", "publisher" ] }, +{"resource" : "topics:create", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "topics:delete", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "topics:acl", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "topics:modifyAcl", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "subscriptions:create", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "subscriptions:delete", "roles" : ["service_admin", "project_admin" ] }, +{"resource" : "subscriptions:list", "roles" : [ "service_admin", "project_admin", "consumer" ] }, +{"resource" : "subscriptions:listByTopic", "roles" : [ "service_admin", "project_admin"] }, +{"resource" : "subscriptions:acknowledge", "roles" : [ "service_admin", "project_admin", "consumer", "push_worker" ] }, +{"resource" : "subscriptions:show", "roles" : [ "service_admin", "project_admin", "consumer", "push_worker" ] }, +{"resource" : "subscriptions:pull", "roles" : [ "service_admin", "project_admin", "consumer", "push_worker" ] }, +{"resource" : "subscriptions:offsets", "roles" : [ "service_admin", "project_admin", "consumer" ] }, +{"resource" : "subscriptions:modifyOffset", "roles" : [ "service_admin", "project_admin", "consumer" ] }, +{"resource" : "subscriptions:timeToOffset", "roles" : [ "service_admin", "project_admin", "consumer" ] }, +{"resource" : "subscriptions:metrics", "roles" : [ "service_admin", "project_admin", "consumer" ] }, +{"resource" : "subscriptions:acl", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "subscriptions:modifyAcl", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "subscriptions:verifyPushEndpoint", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "subscriptions:modifyPushConfig", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "subscriptions:modifyAckDeadline", "roles" : [ "service_admin", "project_admin", "consumer" ] }, +{"resource" : "ams:metrics", "roles" : [ "service_admin", "project_admin"] }, +{"resource" : "schemas:create", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "schemas:show", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "schemas:update", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "schemas:delete", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "schemas:list", "roles" : [ "service_admin", "project_admin" ] }, +{"resource" : "schemas:validateMessage", "roles" : [ "service_admin", "project_admin" ] } +]) diff --git a/roles/ams/templates/db_init_users.js.j2 b/roles/ams/templates/db_init_users.js.j2 new file mode 100644 index 00000000..057ec2ac --- /dev/null +++ b/roles/ams/templates/db_init_users.js.j2 @@ -0,0 +1,23 @@ +// Open Database +use {{ams_store_db}} + +// Begin inserting new users + +{%- if ams_data_users is defined -%} + +{%- for user in ams_data_users -%} + + +; + + + + +db.users.update({"uuid":"{{user.uuid}}", "name" : "{{user.username}}"},{ "uuid":"{{user.uuid}}", "name":"{{user.username}}","email" : "{{user.email}}", {%- if user.projects is defined -%} "projects" : [ {%- for prole in user.projects -%} { "project_uuid":"{{prole.project_uuid}}" {%- if prole.roles is defined -%}, "roles": [ {%- for role in prole.roles -%} "{{role}}" {%- endfor -%} ] {%- endif -%}} {%- endfor -%}],{%- endif -%} {%- if user.service_roles is defined -%} "service_roles": [ {%- for role in user.service_roles -%} "{{role}}" {%- endfor -%}], {%- endif -%} "token" : "{{user.key}}", {%- if user.created_by is defined -%} "created_by":"{{user.created_by}}", {%- endif -%} "created_on":new ISODate(), "modified_on": new ISODate()},{"upsert":"true"}) + + + +{%- endfor -%} + + +{%- endif -%} diff --git a/roles/ams/vars/main.yml b/roles/ams/vars/main.yml new file mode 100644 index 00000000..ca28a84d --- /dev/null +++ b/roles/ams/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for ams diff --git a/roles/archiver/.idea/archiver.iml b/roles/archiver/.idea/archiver.iml new file mode 100644 index 00000000..c956989b --- /dev/null +++ b/roles/archiver/.idea/archiver.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/roles/archiver/.idea/misc.xml b/roles/archiver/.idea/misc.xml new file mode 100644 index 00000000..28a804d8 --- /dev/null +++ b/roles/archiver/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/roles/archiver/.idea/modules.xml b/roles/archiver/.idea/modules.xml new file mode 100644 index 00000000..f027caf9 --- /dev/null +++ b/roles/archiver/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/roles/archiver/.idea/workspace.xml b/roles/archiver/.idea/workspace.xml new file mode 100644 index 00000000..ef407d6a --- /dev/null +++ b/roles/archiver/.idea/workspace.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),jb(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||"")||gb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),jb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||kb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||kb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute("disabled")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; +return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("