#!/usr/bin/env python3 """ License: MIT Author: Bryan Newbold Date: July 2017 See README """ from __future__ import print_function import sys, os import argparse import requests import subprocess import logging as log class DivergenceProgram: def __init__(self, user, password, url, space): self.api = requests.Session() self.api.auth = (user, password) self.api.headers.update({'Content-Type': 'application/json'}) self.base_url = url self.space = space # TODO: find lua path? def get_page(self, title): """ Returns None if not found, otherwise a dict with id, space, and body (in storage format) """ resp = self.api.get(self.base_url + "/rest/api/content", params={"spaceKey": self.space, "title": title, "expand": "body.storage,version,space", "type": "page"}) log.debug(resp) log.debug(resp.content) assert resp.status_code == 200 respj = resp.json() if respj['size'] == 0: return None assert respj['size'] == 1, "Expect single result for title lookup" page = respj['results'][0] assert page['space']['key'].upper() == self.space.upper(), "Expect spaces to match" return {"id": int(page['id']), "version": int(page['version']['number']), "space": page['space']['key'], "body": page['body']['storage']['value']} def create_page(self, title, body): resp = self.api.post(self.base_url + "/rest/api/content", json={"space": { "key": self.space }, "type": "page", "title": title, "body": { "storage": { "representation": "storage", "value": body } } } ) log.debug(resp) log.debug(resp.content) assert resp.status_code == 200 def update_page(self, title, body, page_id, prev_version): resp = self.api.put(self.base_url + "/rest/api/content/%d" % page_id, json={"type": "page", "title": title, "version": {"number": prev_version+1}, "body": { "storage": { "representation": "storage", "value": body } } } ) log.debug(resp) log.debug(resp.content) assert resp.status_code == 200 def title_from_path(self, path): title = path.split('.')[0].replace('_', ' ') # TODO: only alphanum and spaces? return title def convert(self, f): proc = subprocess.run(["pandoc", "-t", "pandoc_confluence.lua", f], stdout=subprocess.PIPE) assert proc.returncode == 0 return proc.stdout.decode('UTF-8') def run(self, files): for f in files: title = self.title_from_path(f) log.debug(title) body = self.convert(f) prev = self.get_page(title) log.debug(prev) if prev is None: self.create_page(title, body) print(f + ": created") else: if prev['body'] != body: self.update_page(title, body, prev['id'], prev['version']) print(f + ": updated") else: print(f + ": no change") def main(): parser = argparse.ArgumentParser( description=""" Simple Markdown-to-Confluence uploader, using pandoc and the Confluence REST API. Specify credentials and site URL with environment variables: CONFLUENCE_USER CONFLUENCE_PASSWORD CONFLUENCE_URL """) #usage="%(prog)s [options] -s ") parser.add_argument("-v", "--verbose", action="count", default=0, help="Show more debugging statements (can be repeated)") parser.add_argument("-s", "--space-key", required=True, help='Confluence Space Key (usually like "PROJ" or "~username")') parser.add_argument("FILE", nargs='+') args = parser.parse_args() if args.verbose > 0: log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) else: log.basicConfig(format="%(levelname)s: %(message)s") try: user = os.environ['CONFLUENCE_USER'] password = os.environ['CONFLUENCE_PASSWORD'] url = os.environ['CONFLUENCE_URL'] except KeyError: parser.exit(-1, "Need to pass environment variable configs") if url.endswith('/'): url = url[:-1] dp = DivergenceProgram(user, password, url, args.space_key) dp.run(args.FILE) if __name__ == '__main__': main()