blob: 8911608702914b4ad0adf0d09000b0d6bccf2d3a [file] [log] [blame]
import logging
import salt.exceptions
import requests
from requests.compat import urljoin
LOG = logging.getLogger(__name__)
# Project
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, resp.text))
def create_project(name, params):
session, make_url = get_session()
config = create_project_config(name, params)
resp = session.post(
make_url("/api/18/projects"),
json={
'name': name,
'config': config,
},
allow_redirects=False,
)
if resp.status_code == 201:
return resp.json()
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()
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, resp.text))
# 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, resp.text))
def get_plugin_status(project_name, integration):
def get_plugin(plugins, plugin_type):
for plugin in plugins:
if plugin['type'] == plugin_type:
return plugin
LOG.debug(
"Could not find the %s integration among available plugins of "
"the %s projects: %s", integration, project_name, plugins)
raise salt.exceptions.SaltInvocationError(
"Could not find status for the {}/{} plugin of the {} project, "
"this integration is not available in your deployment."
.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, resp.text))
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, resp.text))
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, resp.text))
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, resp.text))
# 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))
# Key Store
def get_secret_metadata(path):
session, make_url = get_session()
resp = session.get(
make_url("/api/11/storage/keys/{}".format(path)),
allow_redirects=False,
)
if resp.status_code == 200:
return True, resp.json()
elif resp.status_code == 404:
return True, None
return False, (
"Could not retrieve metadata for the {} secret key: {}/{}"
.format(path, resp.status_code, resp.text))
def upload_secret(path, type, content, update=False):
session, make_url = get_session()
session.headers['Content-Type'] = SECRET_CONTENT_TYPE[type]
method = session.put if update else session.post
resp = method(
make_url("/api/11/storage/keys/{}".format(path)),
data=content,
allow_redirects=False,
)
if resp.status_code in (200, 201):
return True, resp.json()
return False, (
"Could not create or update the {} secret key with the type {}: {}/{}"
.format(path, type, resp.status_code, resp.text))
SECRET_CONTENT_TYPE = {
"private": "application/octet-stream",
"public": "application/pgp-keys",
"password": "application/x-rundeck-data-password",
}
def delete_secret(path):
session, make_url = get_session()
resp = session.delete(
make_url("/api/11/storage/keys/{}".format(path)),
allow_redirects=False,
)
if resp.status_code == 204:
return True, None
return False, (
"Could not delete the {} secret key: {}/{}"
.format(path, 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)
rundeck_url = __salt__['config.get']('rundeck.url')
api_token = __salt__['config.get']('rundeck.api_token')
username = __salt__['config.get']('rundeck.username')
password = __salt__['config.get']('rundeck.password')
if not rundeck_url:
raise salt.exceptions.SaltInvocationError(
"The 'rundeck.url' parameter have to be set as non-empty value in "
"the minion's configuration file.")
elif not (api_token or username and password):
raise salt.exceptions.SaltInvocationError(
"Either the 'rundeck.api_token' parameter or a pair of "
"'rundeck.username' and 'rundeck.password' parameters have to be "
"set as non-empty values in the minion's configuration file.")
make_url.base_url = rundeck_url
session = requests.Session()
if api_token:
session.headers.update({
'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/password authorization failed in Rundeck {} for "
"user {}".format(rundeck_url, username))
session.params.update({
'format': 'json',
})
return session, make_url