Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task scheduler #22

Merged
merged 4 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ currently tested with different ansible versions (2.9 - 2.16) and:
* debian 11
* ubuntu 22.04
* ubuntu 20.04

## scheduled task creation

it includes a script and vars to configure scheduled reccuring task creation.

it requires [hiyapyco](https://github.com/zerwes/hiyapyco)
24 changes: 24 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,28 @@ openproject_update_installerdat: {}
# install the health check for ckeckmk
openproject_use_checkmk_check: false

# vault this!
# openproject_automation_api_token: ...

# scheduled task creation
openproject_scheduled_reccuring_tasks: {}
# example:
# openproject_scheduled_reccuring_tasks:
# puddingtime:
# # crontab time: min hour dayofmonth month dayofweek
# cron: '30 14 * * * *'
# # check: additional check like [ "$(date '+\%u')" = "3" ]
# args:
# subject: 'Test Task @ {NOW}'
# description: |
# Test for {MY} created {TODAY}
# Now {NOW} it's pudding time!
# projectid: 10
# # assigngroupid: 13
# assignuserid: 5
openproject_scheduled_reccuring_cronfile: /etc/cron.d/openproject-scheduled-tasks
openproject_scheduled_reccuring_confprefix: /usr/local/etc/openprojectscheduled-
openproject_scheduled_reccuring_user: root


# vim: tabstop=2 expandtab shiftwidth=2 softtabstop=2 smartindent nu ft=yaml
22 changes: 22 additions & 0 deletions tasks/automation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---

- name: copy automation script openprojectcreatetask.py
ansible.builtin.template:
src: openprojectcreatetask.py.j2
dest: /usr/local/sbin/openprojectcreatetask.py
mode: '0700'

- name: create config files for openproject scheduled reccuring tasks
ansible.builtin.template:
src: openproject_scheduled_reccuring_config.j2
dest: "{{ openproject_scheduled_reccuring_confprefix }}{{ item.key }}"
mode: '0600'
with_dict: "{{ openproject_scheduled_reccuring_tasks }}"

- name: create cronfile {{ openproject_scheduled_reccuring_cronfile }}
ansible.builtin.template:
src: openproject_scheduled_reccuring_cronfile.j2
dest: "{{ openproject_scheduled_reccuring_cronfile }}"
mode: '0644'

...
12 changes: 12 additions & 0 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,16 @@
mode: '0644'
notify: apache2 restart

- name: include automation tasks
tags:
- always
ansible.builtin.include_tasks:
file: automation.yml
apply:
tags:
- automation
when:
- openproject_automation_api_token is defined
- openproject_scheduled_reccuring_tasks

# vim: tabstop=2 expandtab shiftwidth=2 softtabstop=2 smartindent nu ft=yaml
8 changes: 8 additions & 0 deletions templates/openproject_scheduled_reccuring_config.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# {{ ansible_managed }}

# {{ item.key }}

{{ item.value.args | to_yaml(indent=4) }}

...
11 changes: 11 additions & 0 deletions templates/openproject_scheduled_reccuring_cronfile.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# {{ ansible_managed }}

MAILTO=""
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

{% for task, taskkeys in openproject_scheduled_reccuring_tasks.items() %}

# {{ task }}
{{ taskkeys.cron }} {{ openproject_scheduled_reccuring_user }} {{ taskkeys.check | default('test -x /usr/local/sbin/openprojectcreatetask.py') }} && /usr/local/sbin/openprojectcreatetask.py -l INFO --conf {{ openproject_scheduled_reccuring_confprefix }}{{ task }} 2>&1 > /var/log/{{ openproject_scheduled_reccuring_confprefix | basename }}{{task }}

{% endfor %}
151 changes: 151 additions & 0 deletions templates/openprojectcreatetask.py.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#! /usr/bin/env python3
# vim: set fileencoding=utf-8
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 smartindent ft=python
# pylint: disable=fixme,missing-module-docstring,missing-class-docstring

import sys
import logging
import argparse
import json
import datetime
import requests
import urllib3
import hiyapyco

OPJ_HOST = '{{ openproject_server.hostname }}'
OPJ_URL = f'https://{OPJ_HOST}/api/v3/work_packages'
API_TOKEN = '{{ openproject_automation_api_token }}'

# can be used as format string {TODAY}
TODAY = datetime.date.today()
MY = datetime.date.today().strftime("%m/%Y")
NOW = datetime.datetime.today().replace(microsecond=0)

class LoggingAction(argparse.Action):
# pylint: disable=redefined-outer-name
def __call__(self, parser, namespace, values, option_string=None):
logger = logging.getLogger()
logger.setLevel(values)
setattr(namespace, self.dest, values)

def _formatstr(fstr):
return fstr.format(NOW=NOW, TODAY=TODAY, MY=MY, OPJ_HOST=OPJ_HOST)

logger = logging.getLogger()
logging.basicConfig(
level=logging.WARN,
format='%(levelname)s\t[%(name)s] %(funcName)s: %(message)s'
)

parser = argparse.ArgumentParser(
description='create openproject task via API',
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)

# TODO: since python 3.11 we can use logging.getLevelNamesMapping().keys()
# pylint: disable=protected-access
parser.add_argument(
'-l', '--loglevel',
help='set loglevel',
type=str,
choices=[k for k in list(logging._nameToLevel.keys()) if isinstance(k, str)],
action=LoggingAction
)

parser.add_argument('-C', '--conf', action='append',
help='read settings from config file(s)')
parser.add_argument('-D', '--dump-config', action='store_true',
dest='dumpconfig', default=False, help='dump settings')
parser.add_argument('-p', '--project-id', type=int,
dest='projectid', help='project id of the task')
parser.add_argument('-g', '--assign-group-id', type=int,
dest='assigngroupid', help='assign task to group id')
parser.add_argument('-u', '--assign-user-id', type=int,
dest='assignuserid', help='assign task to user id')
parser.add_argument('-P', '--priority-id', type=int,
dest='priorityid', help='priority id of the task')
parser.add_argument('-S', '--status-id', type=int,
dest='statusid', help='status id of the task')
parser.add_argument('-t', '--type-id', type=int, default=1,
dest='typeid', help='type id of the task')
parser.add_argument('-s', '--subject', type=str, default='Test Task',
help='subject / title of the task')
parser.add_argument('-d', '--description', type=str,
default='Test task created {NOW}',
help='description of the task (markdown can be used)')

# if you need to add the certificate to urllib:
# https://urllib3.readthedocs.io/en/latest/user-guide.html#certificate-verification
parser.add_argument('-v', '--no-verify-ssl', dest='verify', action="store_false",
default=True, help='do not verify ssl cert on connecting to zammad api')

args = parser.parse_args()
if args.conf is not None:
# TODO: loglevel via conffile is not working
parser.set_defaults(**hiyapyco.load(
args.conf,
method=hiyapyco.METHOD_MERGE,
interpolate=True
))
# reload command line args
args = parser.parse_args()

# debug args
if logger.isEnabledFor(logging.DEBUG):
print(args)
if args.dumpconfig:
print(args)
sys.exit(0)

if not args.projectid:
logger.fatal('ERROR: missing project-id')
sys.exit(2)

headers = {
'Content-Type': 'application/json'
}

# work package data
data = {
'subject': _formatstr(args.subject),
'description': {
'format': 'markdown',
'raw': _formatstr(args.description)
},
'_links': {
'type': {
'href': f'/api/v3/types/{args.typeid}'
},
'project': {
'href': f'/api/v3/projects/{args.projectid}'
}
}
}
if args.assignuserid:
data['_links']['assignee'] = {'href': f'/api/v3/users/{args.assignuserid}'}
elif args.assigngroupid:
data['_links']['assignee'] = {'href': f'/api/v3/groups/{args.assigngroupid}'}
if args.priorityid:
data['_links']['priority'] = {'href': f'/api/v3/priorities/{args.priorityid}'}
if args.statusid:
data['_links']['status'] = {'href': f'/api/v3/statuses/{args.statusid}'}

response = requests.post(
OPJ_URL,
auth=('apikey', API_TOKEN),
headers=headers,
data=json.dumps(data),
timeout=urllib3.Timeout(connect=2.0),
verify=args.verify
)

if response.status_code == 201:
logger.info('Work Package created successfully!')
logger.debug(response.json())
sys.exit(0)

logger.fatal('Failed to create work package')
logger.critical('Status Code: %s', response.status_code)
logger.critical('Response: %s', response.text)
logger.info(response.json)
sys.exit(1)