patchwork-bot: add support for --tokens-file

We want to be able to share our configuration with others, which
requires that we keep secrets in a different file than the rest of the
logic.

Suggested-by: Kees Cook <kees@outflux.net>
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
diff --git a/git-patchwork-bot.example.yaml b/git-patchwork-bot.example.yaml
index 94ab334..6a5539c 100644
--- a/git-patchwork-bot.example.yaml
+++ b/git-patchwork-bot.example.yaml
@@ -2,8 +2,10 @@
 patchworks:
   # Entries are full URLs to the patchwork server toplevel
   'https://patchwork.kernel.org':
-    # You can use an API token, or you can add username/password
-    # to the $HOME/.netrc file
+    # You can comment 'apitoken' out and pass a --tokens-file option, which is a separate yaml file
+    # containing just the api tokens. The format is the same as this file, but everything other
+    # than patchworks->[url]->apitoken is ignored. This allows splitting secrets from logic
+    # in order to share configuration with others.
     apitoken: 'your-api-token'
     projects:
       # URL subpath name of the project
diff --git a/git-patchwork-bot.py b/git-patchwork-bot.py
index 6adef71..6353047 100755
--- a/git-patchwork-bot.py
+++ b/git-patchwork-bot.py
@@ -96,8 +96,10 @@
             'User-Agent': f'git-patchwork-bot/{__VERSION__}',
         }
         apitoken = CONFIG['patchworks'][server].get('apitoken', None)
-        if apitoken:
-            headers['Authorization'] = f'Token {apitoken}'
+        if not apitoken:
+            logger.critical('We require an apitoken for anything to work')
+            sys.exit(1)
+        headers['Authorization'] = f'Token {apitoken}'
         self.session.headers.update(headers)
 
     def get_unpaginated(self, url: str, params: list) -> List[dict]:
@@ -1377,6 +1379,8 @@
                         help='During initial database creation, consider this many ancestor commits as fresh')
     parser.add_argument('--pwhash', default=None, type=int, metavar='PATCH-ID',
                         help='Debug pwhash mismatches. Compare patchwork hash of diff from stdin to patch id')
+    parser.add_argument('--tokens-file', default=None,
+                        help='Separate configuration file containing just API tokens')
 
     cmdargs = parser.parse_args()
 
@@ -1422,6 +1426,15 @@
         cfgyaml = fh.read()
     CONFIG = ruamel.yaml.safe_load(cfgyaml)
 
+    if cmdargs.tokens_file:
+        with open(cmdargs.tokens_file, 'r') as fh:
+            tkyaml = fh.read()
+        tks = ruamel.yaml.safe_load(tkyaml)
+        for _pserver, _sconfig in tks['patchworks'].items():
+            if _pserver in CONFIG['patchworks']:
+                logger.debug('Taking apitoken info for %s from %s', _pserver, cmdargs.tokens_file)
+                CONFIG['patchworks'][_pserver]['apitoken'] = _sconfig.get('apitoken')
+
     if not os.path.isdir(CACHEDIR):
         os.makedirs(CACHEDIR, exist_ok=True)