Add support to configure SCM Import for projects
Change-Id: Ic95dd313542d13381c879b11bae4c79498300f55
diff --git a/_modules/rundeck.py b/_modules/rundeck.py
index 9d53361..f47c837 100644
--- a/_modules/rundeck.py
+++ b/_modules/rundeck.py
@@ -8,6 +8,8 @@
LOG = logging.getLogger(__name__)
+# Project
+
def get_project(name):
session, make_url = get_session()
resp = session.get(make_url("/api/18/project/{}".format(name)))
@@ -39,25 +41,6 @@
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(
@@ -80,6 +63,251 @@
.format(name, make_url.base_url, status_code))
+# SCM
+
+def get_plugin(project_name, integration):
+ session, make_url = get_session()
+ resp = session.get(make_url("/api/18/project/{}/scm/{}/config"
+ .format(project_name, integration)))
+ if resp.status_code == 200:
+ return True, resp.json()
+ elif resp.status_code == 404:
+ return True, None
+ return False, (
+ "Could not get config for the {} plugin of the {} project"
+ .format(integration, project_name))
+
+
+def get_plugin_status(project_name, integration):
+ def get_plugin(plugins, plugin_type):
+ for plugin in plugins:
+ if plugin['type'] == plugin_type:
+ return plugin
+ raise salt.exceptions.SaltInvocationError(
+ "Could not find status for the {}/{} plugin of the {} project"
+ .format(integration, plugin_type, project_name))
+
+ session, make_url = get_session()
+ resp = session.get(make_url("/api/18/project/{}/scm/{}/plugins"
+ .format(project_name, integration)))
+ if resp.status_code == 200:
+ plugin_type = "git-{}".format(integration)
+ status = get_plugin(resp.json()['plugins'], plugin_type)
+ return True, status
+ return False, (
+ "Could not get status for the {} plugin of the {} project"
+ .format(integration, project_name))
+
+
+def get_plugin_state(project_name, integration):
+ session, make_url = get_session()
+ resp = session.get(make_url("/api/18/project/{}/scm/{}/status"
+ .format(project_name, integration)))
+ if resp.status_code == 200:
+ return True, resp.json()
+ return False, (
+ "Could not get state for the {} plugin of the {} project"
+ .format(integration, project_name))
+
+
+def disable_plugin(project_name, integration):
+ session, make_url = get_session()
+ resp = session.post(make_url(
+ "/api/15/project/{}/scm/{}/plugin/git-{}/disable"
+ .format(project_name, integration, integration)))
+ if resp.status_code == 200:
+ msg = resp.json()
+ return True, msg['message']
+ return False, (
+ "Could not disable the {} plugin for the {} project: {}"
+ .format(integration, project_name, resp.status_code))
+
+
+def enable_plugin(project_name, integration):
+ session, make_url = get_session()
+ resp = session.post(make_url(
+ "/api/15/project/{}/scm/{}/plugin/git-{}/enable"
+ .format(project_name, integration, integration)))
+ if resp.status_code == 200:
+ msg = resp.json()
+ return True, msg['message']
+ return False, (
+ "Could not enable the {} plugin for the {} project: {}"
+ .format(integration, project_name, resp.status_code))
+
+
+# SCM Import
+
+def setup_scm_import(project_name, params):
+ session, make_url = get_session()
+ config = create_scm_import_config(project_name, params)
+ resp = session.post(
+ make_url("/api/15/project/{}/scm/import/plugin/git-import/setup"
+ .format(project_name)),
+ json={
+ 'config': config,
+ },
+ allow_redirects=False,
+ )
+ if resp.status_code == 200:
+ return True, resp.json()
+ return False, (
+ "Could not configure SCM Import for the {} project: {}/{}"
+ .format(project_name, resp.status_code, resp.text))
+
+
+def update_scm_import_config(project_name, plugin, config):
+ session, make_url = get_session()
+ resp = session.post(
+ make_url("/api/15/project/{}/scm/import/plugin/git-import/setup"
+ .format(project_name)),
+ json={
+ 'config': config,
+ },
+ allow_redirects=False,
+ )
+ if resp.status_code == 200:
+ return True, resp.json()
+ return False, (
+ "Could not update SCM Import for the {} project: {}/{}"
+ .format(project_name, resp.status_code, resp.text))
+
+
+def perform_scm_import_tracking(project_name, plugin, params):
+ format = plugin['config']['format']
+ file_pattern = params.get('file_pattern')
+ if not file_pattern:
+ file_pattern = DEFAULT_FILE_PATTERNS[format]
+
+ session, make_url = get_session()
+ resp = session.post(
+ make_url("/api/15/project/{}/scm/import/action/initialize-tracking"
+ .format(project_name)),
+ json={
+ 'input': {
+ 'filePattern': file_pattern,
+ 'useFilePattern': 'true',
+ },
+ 'jobs': [],
+ 'items': [],
+ 'deleted': [],
+ },
+ allow_redirects=False,
+ )
+ if resp.status_code == 200:
+ return True, resp.json()
+ return False, (
+ "Could not update SCM Import for the {} project: {}/{}"
+ .format(project_name, resp.status_code, resp.text))
+
+DEFAULT_FILE_PATTERNS = {
+ 'yaml': r'.*\.yaml',
+ 'xml': r'.*\.xml',
+}
+
+
+def perform_scm_import_pull(project_name, plugin, params):
+ session, make_url = get_session()
+ resp = session.post(
+ make_url("/api/15/project/{}/scm/import/action/remote-pull"
+ .format(project_name)),
+ json={
+ 'input': {},
+ 'jobs': [],
+ 'items': [],
+ 'deleted': [],
+ },
+ allow_redirects=False,
+ )
+ if resp.status_code == 200:
+ return True, resp.json()
+ return False, (
+ "Could not pull remote changes for the {} project: {}/{}"
+ .format(project_name, resp.status_code, resp.text))
+
+
+def perform_scm_import(project_name, plugin, params):
+ session, make_url = get_session()
+ ok, inputs = get_plugin_action_inputs(
+ project_name, 'import', 'import-all')
+ if not ok:
+ return False, inputs
+ items = list(item['itemId'] for item in inputs['importItems'])
+ resp = session.post(
+ make_url("/api/15/project/{}/scm/import/action/import-all"
+ .format(project_name)),
+ json={
+ 'input': {},
+ 'jobs': [],
+ 'items': items,
+ 'deleted': [],
+ },
+ allow_redirects=False,
+ )
+ if resp.status_code == 200:
+ return True, resp.json()
+ return False, (
+ "Could not import jobs for the {} project: {}/{}"
+ .format(project_name, resp.status_code, resp.text))
+
+
+# Utils
+
+def create_project_config(project_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(project_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 create_scm_import_config(project_name, params, config=None):
+ config = dict(config) if config else {}
+
+ format = params.get('format', 'yaml')
+ if format not in DEFAULT_FILE_PATTERNS:
+ supported_formats = DEFAULT_FILE_PATTERNS.keys()
+ raise salt.exceptions.SaltInvocationError(
+ "Unsupported format {} for the {} SCM import module, should be {}"
+ .format(format, project_name, ','.join(supported_formats)))
+
+ config.update({
+ 'dir': "/var/rundeck/projects/{}/scm".format(project_name),
+ 'url': params['address'],
+ 'branch': params.get('branch', 'master'),
+ 'fetchAutomatically': 'true',
+ 'format': format,
+ 'pathTemplate': params.get(
+ 'path_template', '${job.group}${job.name}.${config.format}'),
+ 'importUuidBehavior': params.get('import_uuid_behavior', 'remove'),
+ 'strictHostKeyChecking': 'yes',
+ })
+ return config
+
+
+def get_plugin_action_inputs(project_name, integration, action):
+ session, make_url = get_session()
+ resp = session.get(
+ make_url("/api/18/project/cicd/scm/import/action/import-all/input"))
+ if resp.status_code == 200:
+ return True, resp.json()
+ return False, (
+ "Could not get inputs for the {} action for the {} project: {}/{}"
+ .format(action, project_name, resp.status_code, resp.text))
+
+
+
def get_session():
def make_url(url):
return urljoin(make_url.base_url, url)
diff --git a/_states/rundeck_project.py b/_states/rundeck_project.py
index 5919297..29dc76a 100644
--- a/_states/rundeck_project.py
+++ b/_states/rundeck_project.py
@@ -36,13 +36,13 @@
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"
+ 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['changes'][name] = 'CREATED'
ret['result'] = True
return ret
diff --git a/_states/rundeck_scm.py b/_states/rundeck_scm.py
new file mode 100644
index 0000000..afe34f9
--- /dev/null
+++ b/_states/rundeck_scm.py
@@ -0,0 +1,166 @@
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+def __virtual__():
+ if 'rundeck.get_project' not in __salt__:
+ return (
+ False,
+ 'The rundeck_scm state module cannot be loaded: rundeck is '
+ 'unavailable',
+ )
+ return True
+
+
+def present_import(name, project_name, **params):
+ result = {
+ 'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': '',
+ 'pchanges': {},
+ }
+ if __opts__['test'] == True:
+ result['comment'] = 'There is nothing to change in the test mode.'
+ result['result'] = None
+ ok, plugin = __salt__['rundeck.get_plugin'](project_name, 'import')
+ if ok:
+ if plugin:
+ config = __salt__['rundeck.create_scm_import_config'](
+ project_name, params, config=plugin['config'])
+ LOG.debug("SCM Import for the %s project: %s/%s",
+ project_name, plugin["config"], config)
+ if plugin['config'] != config:
+ ok, plugin = __salt__['rundeck.update_scm_import_config'](
+ project_name, plugin, config)
+ result['comment'] = (
+ "SCM Import plugin for the {} project was updated."
+ .format(project_name))
+ result['changes'][name] = 'UPDATED'
+ else:
+ result['comment'] = (
+ "SCM Import plugin for the {} project is already up to "
+ "date.".format(project_name))
+ result['result'] = True
+ else:
+ ok, plugin = __salt__['rundeck.setup_scm_import'](
+ project_name, params)
+ if ok:
+ result['changes'][name] = 'CREATED'
+ result['comment'] = (
+ "SCM Import was configured for the {} project."
+ .format(project_name))
+ result['result'] = True
+ else:
+ result['comment'] = plugin
+ else:
+ result['comment'] = plugin
+ return result
+
+
+def sync_import(name, project_name, **params):
+ result = {
+ 'name': name,
+ 'changes': {},
+ 'result': True,
+ 'comment': '',
+ 'pchanges': {},
+ }
+
+ if __opts__['test'] == True:
+ result['comment'] = 'There is nothing to change in the test mode.'
+ result['result'] = None
+ return result
+
+ ok, plugin = __salt__['rundeck.get_plugin'](project_name, 'import')
+ if not ok:
+ result['comment'] = plugin
+ return result
+
+ ok, state = __salt__['rundeck.get_plugin_state'](project_name, 'import')
+ if not ok:
+ result['comment'] = state
+ return result
+
+ history = []
+
+ for action_name, action in [
+ ('initialize-tracking', 'rundeck.perform_scm_import_tracking'),
+ ('remote-pull', 'rundeck.perform_scm_import_pull'),
+ ('import-all', 'rundeck.perform_scm_import'),
+ ]:
+ if action_name in state['actions']:
+ ok, msg = __salt__[action](
+ project_name, plugin, params)
+ if not ok:
+ result['comment'] = msg
+ result['result'] = False
+ return result
+ else:
+ history.append(msg['message'])
+
+ ok, state = __salt__['rundeck.get_plugin_state'](
+ project_name, 'import')
+ if not ok:
+ result['comment'] = state
+ result['result'] = False
+ return result
+
+ if history:
+ result['changes'][name] = '\n'.join(history)
+ return result
+
+
+def disabled_import(name, project_name):
+ result = {
+ 'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': '',
+ 'pchanges': {},
+ }
+ if __opts__['test'] == True:
+ result['comment'] = 'There is nothing to change in the test mode.'
+ result['result'] = None
+ ok, status = __salt__['rundeck.get_plugin_status'](project_name, 'import')
+ if ok:
+ if status['enabled']:
+ ok, msg = __salt__['rundeck.disable_plugin'](project_name, 'import')
+ result['comment'] = msg
+ if ok:
+ result['changes'][name] = 'DISABLED'
+ result['result'] = True
+ else:
+ result['result'] = True
+ else:
+ result['comment'] = status
+ return result
+
+
+def enabled_import(name, project_name):
+ result = {
+ 'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': '',
+ 'pchanges': {},
+ }
+ if __opts__['test'] == True:
+ result['comment'] = 'There is nothing to change in the test mode.'
+ result['result'] = None
+ ok, status = __salt__['rundeck.get_plugin_status'](project_name, 'import')
+ if ok:
+ if status['configured'] and not status['enabled']:
+ ok, msg = __salt__['rundeck.enable_plugin'](project_name, 'import')
+ result['comment'] = msg
+ if ok:
+ result['changes'][name] = 'ENABLED'
+ result['result'] = True
+ elif not status['configured']:
+ result['comment'] = "Could not enable not configured SCM plugin."
+ else:
+ result['result'] = True
+ else:
+ result['comment'] = status
+ return result
diff --git a/rundeck/client/project.sls b/rundeck/client/project.sls
index 2dd212a..a143d92 100644
--- a/rundeck/client/project.sls
+++ b/rundeck/client/project.sls
@@ -5,12 +5,12 @@
{%- set project_name = project.name|default(name) %}
-rundeck_{{ project_name }}_project:
+rundeck-{{ project_name }}-project:
rundeck_project.present:
- name: {{ project_name }}
- - description: {{ project.description|default("") }}
+ - description: {{ project.description|default('') }}
-rundeck_{{ project_name }}_resources:
+rundeck-{{ project_name }}-resources:
file.managed:
- name: {{ server.root_dir }}/rundeck/projects/{{ project_name }}/etc/resources.yaml
- source: salt://rundeck/files/resources.yaml
@@ -21,6 +21,53 @@
- context:
project_name: {{ project_name }}
- require:
- - rundeck_project: rundeck_{{ project_name }}_project
+ - rundeck_project: rundeck-{{ project_name }}-project
+
+{%- set plugin = project.plugin|default({}) %}
+
+{%- if plugin.import is defined %}
+
+{%- set _import = plugin.import %}
+
+rundeck-{{ project_name }}-scm-import:
+ rundeck_scm.present_import:
+ - name: git-import
+ - address: {{ _import.address }}
+ - project_name: {{ project_name }}
+{%- if _import.format is defined %}
+ - format: {{ _import.format }}
+{%- endif %}
+{%- if _import.branch is defined %}
+ - branch: {{ _import.branch }}
+{%- endif %}
+{%- if _import.import_uuid_behavior is defined %}
+ - import_uuid_behavior: {{ _import.import_uuid_behavior }}
+{%- endif %}
+{%- if _import.path_template is defined %}
+ - path_template: {{ _import.path_template }}
+{%- endif %}
+ - require:
+ - rundeck_project: rundeck-{{ project_name }}-project
+
+rundeck-{{ project_name }}-scm-import-enable:
+ rundeck_scm.enabled_import:
+ - name: git-import
+ - project_name: {{ project_name }}
+ - require:
+ - rundeck_scm: rundeck-{{ project_name }}-scm-import
+
+rundeck-{{ project_name }}-scm-import-sync:
+ rundeck_scm.sync_import:
+ - name: git-import
+ - project_name: {{ project_name }}
+{%- if _import.file_pattern is defined %}
+ - file_pattern: {{ _import.file_pattern }}
+{%- endif %}
+ - require:
+ - rundeck_scm: rundeck-{{ project_name }}-scm-import
+ - watch:
+ - rundeck_scm: rundeck-{{ project_name }}-scm-import-enable
+
+{%- endif %}
{%- endfor %}