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)