Add basic client for Rundeck
Change-Id: I4b1a8deedb858ec664d4f888ef78458bd19f102f
diff --git a/_modules/rundeck.py b/_modules/rundeck.py
new file mode 100644
index 0000000..9d53361
--- /dev/null
+++ b/_modules/rundeck.py
@@ -0,0 +1,117 @@
+import logging
+
+import salt.exceptions
+
+import requests
+from requests.compat import urljoin
+
+LOG = logging.getLogger(__name__)
+
+
+def get_project(name):
+ session, make_url = get_session()
+ resp = session.get(make_url("/api/18/project/{}".format(name)))
+ status_code = resp.status_code
+ if status_code == 200:
+ return resp.json()
+ elif status_code == 404:
+ return None
+ raise salt.exceptions.SaltInvocationError(
+ "Could not retrieve information about project {} from Rundeck {}: {}"
+ .format(name, make_url.base_url, status_code))
+
+
+def create_project(name, params):
+ session, make_url = get_session()
+ config = create_project_config(name, params)
+ LOG.debug("create_project: %s", name)
+ LOG.warning("create_project.config: %s/%s", name, config)
+ resp = session.post(
+ make_url("/api/18/projects"),
+ json={
+ 'name': name,
+ 'config': config,
+ },
+ allow_redirects=False,
+ )
+ if resp.status_code == 201:
+ return resp.json()
+ LOG.debug("create_project: %s", name)
+
+
+def create_project_config(name, params, config=None):
+ config = dict(config) if config else {}
+ if params['description']:
+ config['project.description'] = params['description']
+ else:
+ config.pop('project.description', None)
+ config.update({
+ 'resources.source.1.config.file':
+ "/var/rundeck/projects/{}/etc/resources.yaml".format(name),
+ 'resources.source.1.config.format': 'resourceyaml',
+ 'resources.source.1.config.generateFileAutomatically': 'true',
+ 'resources.source.1.config.includeServerNode': 'false',
+ 'resources.source.1.config.requireFileExists': 'false',
+ 'project.ssh-keypath': '/var/rundeck/.ssh/id_rsa',
+ 'resources.source.1.type': 'file',
+ })
+ return config
+
+
+def update_project_config(name, project, config):
+ session, make_url = get_session()
+ resp = session.put(
+ make_url("/api/18/project/{}/config".format(name)),
+ json=config,
+ allow_redirects=False,
+ )
+ if resp.status_code == 201:
+ return resp.json()
+ LOG.debug("update_project: %s", name)
+
+
+def delete_project(name):
+ session, make_url = get_session()
+ resp = session.delete(make_url("/api/18/project/{}".format(name)))
+ status_code = resp.status_code
+ if status_code != 204:
+ raise salt.exceptions.SaltInvocationError(
+ "Could not remove project {} from Rundeck {}: {}"
+ .format(name, make_url.base_url, status_code))
+
+
+def get_session():
+ def make_url(url):
+ return urljoin(make_url.base_url, url)
+
+ rundeck_url = __salt__['config.get']('rundeck.url')
+ make_url.base_url = rundeck_url
+
+ api_token = __salt__['config.get']('rundeck.api_token')
+ username = __salt__['config.get']('rundeck.username')
+ password = __salt__['config.get']('rundeck.password')
+
+ session = requests.Session()
+
+ if api_token:
+ session.headers.update({
+ 'Content-Type': 'application/json',
+ 'X-Rundeck-Auth-Token': api_token,
+ })
+ else:
+ resp = session.post(make_url('/j_security_check'),
+ data={
+ 'j_username': username,
+ 'j_password': password,
+ },
+ )
+ if (resp.status_code != 200 or
+ '/user/error' in resp.url or
+ '/user/login' in resp.url):
+ raise salt.exceptions.SaltInvocationError(
+ "Username/passowrd authorization failed in Rundeck {} for "
+ "user {}".format(rundeck_url, username))
+ session.params.update({
+ 'format': 'json',
+ })
+ return session, make_url
diff --git a/_states/rundeck_project.py b/_states/rundeck_project.py
new file mode 100644
index 0000000..2e877a6
--- /dev/null
+++ b/_states/rundeck_project.py
@@ -0,0 +1,58 @@
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+def present(name, description=''):
+ ret = {
+ 'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': '',
+ 'pchanges': {},
+ }
+ if __opts__['test'] == True:
+ ret['comment'] = 'Nothing to change in the test mode.'
+ ret['result'] = None
+ return ret
+ params = {
+ "description": description,
+ }
+ project = __salt__['rundeck.get_project'](name)
+ if project:
+ config = __salt__['rundeck.create_project_config'](
+ name, params, config=project["config"])
+ if project["config"] != config:
+ LOG.warning("{}: {}".format(project["config"], config))
+ __salt__['rundeck.update_project_config'](name, project, config)
+ ret['comment'] = "Project {} was updated.".format(name)
+ ret['changes'][name] = "UPDATED"
+ else:
+ ret['comment'] = "Project {} is already up to date.".format(name)
+ else:
+ __salt__['rundeck.create_project'](name, params)
+ ret['comment'] = "Project {} was created.".format(name)
+ ret['changes'][name] = "CREATED"
+ ret['result'] = True
+ return ret
+
+
+def absent(name):
+ ret = {
+ 'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': '',
+ 'pchanges': {},
+ }
+ if __opts__['test'] == True:
+ ret['comment'] = 'Nothing to remove in the test mode.'
+ ret['result'] = None
+ return ret
+ project = __salt__['rundeck.get_project'](name)
+ if project:
+ __salt__['rundeck.delete_project'](name)
+ ret['changes'][name] = 'DELETED'
+ ret['comment'] = "Project {} was removed.".format(name)
+ ret['result'] = True
+ return ret
diff --git a/debian/control b/debian/control
index 5cc8cd3..5ac5f59 100644
--- a/debian/control
+++ b/debian/control
@@ -10,6 +10,6 @@
Package: salt-formula-rundeck
Architecture: all
-Depends: ${misc:Depends}, salt-master, reclass
+Depends: ${misc:Depends}, salt-master, reclass, python-requests
Description: rundeck salt formula
Install and configure Rundeck.
diff --git a/debian/install b/debian/install
index 796f558..c3dc990 100644
--- a/debian/install
+++ b/debian/install
@@ -1,3 +1,4 @@
rundeck/* /usr/share/salt-formulas/env/rundeck/
metadata/service/* /usr/share/salt-formulas/reclass/service/rundeck/
_states/* /usr/share/salt-formulas/env/_states/
+_modules/* /usr/share/salt-formulas/env/_modules/
diff --git a/metadata/service/client/init.yml b/metadata/service/client/init.yml
new file mode 100644
index 0000000..deefeb0
--- /dev/null
+++ b/metadata/service/client/init.yml
@@ -0,0 +1,6 @@
+applications:
+ - rundeck
+parameters:
+ rundeck:
+ client:
+ enabled: true
diff --git a/metadata/service/common.yml b/metadata/service/common.yml
new file mode 100644
index 0000000..233451a
--- /dev/null
+++ b/metadata/service/common.yml
@@ -0,0 +1,6 @@
+parameters:
+ _param:
+ rundeck_runbook_user: runbook
+ rundeck_admin_username: admin
+ rundeck_admin_password: password
+ rundeck_admin_token: password
diff --git a/metadata/service/server/single.yml b/metadata/service/server/single.yml
index 0524e89..d4c767c 100644
--- a/metadata/service/server/single.yml
+++ b/metadata/service/server/single.yml
@@ -1,12 +1,9 @@
applications:
- rundeck
classes:
+ - service.rundeck.common
- service.rundeck.support
parameters:
- _param:
- rundeck_admin_username: admin
- rundeck_admin_password: password
- rundeck_admin_token: password
rundeck:
server:
enabled: true
diff --git a/rundeck/client/init.sls b/rundeck/client/init.sls
new file mode 100644
index 0000000..e0c7eff
--- /dev/null
+++ b/rundeck/client/init.sls
@@ -0,0 +1,13 @@
+{%- from "rundeck/map.jinja" import client with context %}
+
+{%- if client.enabled|default(False) %}
+include:
+{%- if client.project is defined %}
+ - rundeck.client.project
+{%- endif %}
+{%- endif %}
+
+/etc/salt/minion.d/_rundeck.conf:
+ file.managed:
+ - source: salt://rundeck/files/_rundeck.conf
+ - template: jinja
diff --git a/rundeck/client/project.sls b/rundeck/client/project.sls
new file mode 100644
index 0000000..2dd212a
--- /dev/null
+++ b/rundeck/client/project.sls
@@ -0,0 +1,26 @@
+{% from "rundeck/map.jinja" import server with context %}
+{%- from "rundeck/map.jinja" import client with context %}
+
+{%- for name, project in client.project.items() %}
+
+{%- set project_name = project.name|default(name) %}
+
+rundeck_{{ project_name }}_project:
+ rundeck_project.present:
+ - name: {{ project_name }}
+ - description: {{ project.description|default("") }}
+
+rundeck_{{ project_name }}_resources:
+ file.managed:
+ - name: {{ server.root_dir }}/rundeck/projects/{{ project_name }}/etc/resources.yaml
+ - source: salt://rundeck/files/resources.yaml
+ - template: jinja
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
+ - mode: 640
+ - context:
+ project_name: {{ project_name }}
+ - require:
+ - rundeck_project: rundeck_{{ project_name }}_project
+
+{%- endfor %}
diff --git a/rundeck/files/_rundeck.conf b/rundeck/files/_rundeck.conf
new file mode 100644
index 0000000..7ab8449
--- /dev/null
+++ b/rundeck/files/_rundeck.conf
@@ -0,0 +1,10 @@
+{%- from "rundeck/map.jinja" import client with context %}
+{%- from "rundeck/map.jinja" import make_url with context %}
+{%- set creds = client.server.credentials %}
+rundeck.url: "{{ make_url(client.server.endpoint) }}"
+{%- if creds.api_token is defined %}
+rundeck.api_token: "{{ creds.api_token }}"
+{%- else %}
+rundeck.username: "{{ creds.username }}"
+rundeck.password: "{{ creds.password }}"
+{%- endif %}
diff --git a/rundeck/files/framework.properties b/rundeck/files/framework.properties
index 566de8e..0f30e08 100644
--- a/rundeck/files/framework.properties
+++ b/rundeck/files/framework.properties
@@ -6,8 +6,8 @@
framework.server.username={{ admin.name }}
framework.server.password={{ admin.password }}
-framework.server.hostname={{ server.api.hostname }}
-framework.server.name={{ server.api.hostname }}
+framework.server.hostname={{ server.api.host }}
+framework.server.name={{ server.api.host }}
framework.server.port={{ server.api.port }}
{%- set server_url = make_url(server.api) %}
@@ -16,7 +16,7 @@
framework.server.url={{ server_url }}
framework.ssh.user={{ server.ssh.user }}
-framework.ssh.keypath=/var/lib/rundeck/.ssh/id_rsa
+framework.ssh.keypath=/var/rundeck/.ssh/id_rsa
framework.ssh.timeout={{ server.ssh.timeout }}
rdeck.base=/var/lib/rundeck
diff --git a/rundeck/files/private_key b/rundeck/files/private_key
new file mode 100644
index 0000000..f53aacb
--- /dev/null
+++ b/rundeck/files/private_key
@@ -0,0 +1 @@
+{%- from "rundeck/map.jinja" import server with context -%}{{ server.ssh.private_key }}
diff --git a/rundeck/files/resources.yaml b/rundeck/files/resources.yaml
new file mode 100644
index 0000000..3436ce0
--- /dev/null
+++ b/rundeck/files/resources.yaml
@@ -0,0 +1,11 @@
+{%- from "rundeck/map.jinja" import client with context %}
+
+{%- for node in client.project.get(project_name, {}).get("node", {}).values() %}
+
+{{ node.nodename }}:
+ hostname: {{ node.hostname }}
+ nodename: {{ node.nodename }}
+ username: {{ node.username }}
+ tags: {{ node.get("tags", [])|yaml }}
+
+{%- endfor %}
diff --git a/rundeck/map.jinja b/rundeck/map.jinja
index 143d47a..bae03e9 100644
--- a/rundeck/map.jinja
+++ b/rundeck/map.jinja
@@ -2,7 +2,6 @@
'Debian': {
'home_dir': '/var/lib/rundeck',
'root_dir': '/srv/rundeck',
- 'secure': False,
'user': {
'name': 'rundeck',
'group': 'rundeck',
@@ -20,7 +19,9 @@
},
}, merge=salt['pillar.get']('rundeck:server')) %}
+{%- set client = salt['pillar.get']('rundeck:client') %}
+
{% macro make_url(endpoint) -%}
{%- if endpoint.get('https', False) -%}https://{%- else -%}http://{%- endif -%}
-{{ endpoint.hostname }}:{{ endpoint.port }}
+{{ endpoint.host }}:{{ endpoint.port }}
{%- endmacro %}
diff --git a/rundeck/server/init.sls b/rundeck/server/init.sls
index db89cd5..6ca6120 100644
--- a/rundeck/server/init.sls
+++ b/rundeck/server/init.sls
@@ -1,4 +1,4 @@
-{% from "rundeck/map.jinja" import server with context %}
+{%- from "rundeck/map.jinja" import server with context %}
{%- if server.enabled|default(False) %}
rundeck_group:
@@ -13,7 +13,7 @@
user.present:
- name: {{ server.user.name }}
- home: {{ server.home_dir }}
- - shell: /bin/bash
+ - shell: /bin/false
{%- if server.user.uid is defined %}
- uid: {{ server.user.uid }}
{%- endif %}
@@ -22,15 +22,15 @@
{%- endif %}
- system: True
- groups:
- - rundeck
+ - {{ server.user.group }}
- require:
- group: rundeck_group
rundeck_home_dir:
file.directory:
- name: {{ server.home_dir }}
- - user: rundeck
- - group: rundeck
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
- mode: 755
- require:
- user: rundeck_user
@@ -38,17 +38,31 @@
rundeck_root_dir:
file.directory:
- name: {{ server.root_dir }}
- - user: rundeck
- - group: rundeck
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
- mode: 755
- require:
- user: rundeck_user
+rundeck_lib_dirs:
+ file.directory:
+ - names:
+ - {{ server.root_dir }}/log
+ - {{ server.root_dir }}/logs
+ - {{ server.root_dir }}/plugins
+ - {{ server.root_dir }}/rundeck
+ - {{ server.root_dir }}/storage
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
+ - mode: 755
+ - require:
+ - file: rundeck_root_dir
+
rundeck_etc_dir:
file.directory:
- name: {{ server.root_dir }}/etc
- - user: rundeck
- - group: rundeck
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
- mode: 755
- require:
- user: rundeck_user
@@ -58,8 +72,8 @@
- name: {{ server.root_dir }}/etc/framework.properties
- source: salt://rundeck/files/framework.properties
- template: jinja
- - user: rundeck
- - group: rundeck
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
- mode: 640
- require:
- file: rundeck_etc_dir
@@ -69,8 +83,8 @@
- name: {{ server.root_dir }}/etc/tokens.properties
- source: salt://rundeck/files/tokens.properties
- template: jinja
- - user: rundeck
- - group: rundeck
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
- mode: 640
- require:
- file: rundeck_etc_dir
@@ -80,8 +94,8 @@
- name: {{ server.root_dir }}/etc/realm.properties
- source: salt://rundeck/files/realm.properties
- template: jinja
- - user: rundeck
- - group: rundeck
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
- mode: 640
- require:
- file: rundeck_etc_dir
@@ -91,10 +105,30 @@
- name: {{ server.root_dir }}/etc/rundeck-config.properties
- source: salt://rundeck/files/rundeck-config.properties
- template: jinja
- - user: rundeck
- - group: rundeck
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
- mode: 640
- require:
- file: rundeck_etc_dir
+rundeck_ssh_dir:
+ file.directory:
+ - name: {{ server.root_dir }}/rundeck/.ssh
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
+ - mode: 700
+ - require:
+ - file: rundeck_root_dir
+
+rundeck_ssh_private_key:
+ file.managed:
+ - name: {{ server.root_dir }}/rundeck/.ssh/id_rsa
+ - source: salt://rundeck/files/private_key
+ - template: jinja
+ - user: {{ server.user.name }}
+ - group: {{ server.user.group }}
+ - mode: 600
+ - require:
+ - file: rundeck_ssh_dir
+
{%- endif %}