From patchwork Wed Dec 17 02:28:09 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 963 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938613; bh=8lGWb7UiZfCdhftVZrKnRIjs+XKC9PBMfV0QRX/4zTk=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ZQVoXJOKDjr2he4Iz4Q4OrG2J1qr1pNuxCKY/uz0JiA6t8JjJTmafmmyn/I/yigko H74ZVV8e3pUOr/lLGwyimw6ZWvNMFhhuzx82RaxJ7IGLpBqCU8aGwfNl808lq4K8GQ F9DiTMVPgW4T11z96xGtrVjz6jU1mjgu1BR3QjQmcda5ufVgZI+jHYYZ6+a1Zw4+tS FxosJppMBkw3ysTvLYH/5UH8gmNl22ZeqURA2/4nSjgMILuxMe6HNaAgUEt4+OdTuo xSHTaHjy1vW60OBb2HMiwEuxn4ZUafSbS0iaeNGyRwIbgOknou+aeFesaGu2TuCayf Cavn/Yc7uEhgg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5C04D68BC7 for ; Tue, 16 Dec 2025 19:30:13 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id dkv0AFs-mg0L for ; Tue, 16 Dec 2025 19:30:13 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938613; bh=8lGWb7UiZfCdhftVZrKnRIjs+XKC9PBMfV0QRX/4zTk=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ZQVoXJOKDjr2he4Iz4Q4OrG2J1qr1pNuxCKY/uz0JiA6t8JjJTmafmmyn/I/yigko H74ZVV8e3pUOr/lLGwyimw6ZWvNMFhhuzx82RaxJ7IGLpBqCU8aGwfNl808lq4K8GQ F9DiTMVPgW4T11z96xGtrVjz6jU1mjgu1BR3QjQmcda5ufVgZI+jHYYZ6+a1Zw4+tS FxosJppMBkw3ysTvLYH/5UH8gmNl22ZeqURA2/4nSjgMILuxMe6HNaAgUEt4+OdTuo xSHTaHjy1vW60OBb2HMiwEuxn4ZUafSbS0iaeNGyRwIbgOknou+aeFesaGu2TuCayf Cavn/Yc7uEhgg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4BDE668BA8 for ; Tue, 16 Dec 2025 19:30:13 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938612; bh=qJ7qX4a1oqfDsnv7UsQlWFW0dUQO5HRt9Xe5D74JJvU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RUj8umUfV4Erdx/8oeT1HJ4wpONBB+dM+GoNNHxeNWMxcJDEpibhVxWd+EKK/w0b0 2ebBTetJUTjANUwwSxtuKCtTlkRs/WWnt8j8NRjh9x7aJi7ZTIUiVGvO64vNj4UydO SeXTc2BFJg0ZGbed9mktjIgvjf+qLk3phdglD2eXc8UPmq8bE/ly8kVqYMaXuPB8pr fHH7yDIYwnf/eRtNTG8S9rbGdDrwhfehzw8+8BUZfloD1HIsdLwdwFHpoxkieGSru+ 1Ko6RlJSb/o9JJN/ZZSF4j4sJxzmAsGM68TzspV/XrCwv81LAcxahvP/QbHe0OB0Qw S8xXYsTImNWFA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 53F4E68AFD; Tue, 16 Dec 2025 19:30:12 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id NbMTmvoJHNRk; Tue, 16 Dec 2025 19:30:12 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938608; bh=342ZcnMtFPOmK4nGcWrWtsRbeHFekg42kR9ce+y0WkE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GII6I8nCXSWFfhNQge4pMrrKhaKHGwxKlS5aMxl71XW06b0ItXbO7n0GmI5brFwvD NA3j7t6PtGBdP+G384qd7+mUvaB1ieLslL5AQuwvjcZ5YKRA3i3ta8z+3P/WknmYbZ nssCdpp3ogxKhRoAoxdq+3yChPJtw12eublcIR3ZICVZ/gKyZti2T5i5IlTqkit5Q2 TZYWOJC3GBhYtarQqHqVHLpc0wIOWpHsUnfZv2bUxl93OvWEBxNmVemgjFFaqqdQl9 4wbVcFfnEBI8MTFY6c04byC4p8tPfTQvyNWtlC7YFN4x/HmW4lqXTLTzJkB1fwERS6 tfXr+y430kHCg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id DE0626884F; Tue, 16 Dec 2025 19:30:07 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Tue, 16 Dec 2025 19:28:09 -0700 Message-ID: <20251217022823.392557-21-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20251217022823.392557-1-sjg@u-boot.org> References: <20251217022823.392557-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: QYLEYEEYJ3FSIQHTY3K5KHPA4P3ZL33Z X-Message-ID-Hash: QYLEYEEYJ3FSIQHTY3K5KHPA4P3ZL33Z X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 20/24] pickman: Add config-file support for GitLab token List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add support for storing a GitLab API token in ~/.config/pickman.conf instead of an environment variable. This allows using a dedicated bot account for pickman without affecting the user's personal GitLab credentials. Config file format: [gitlab] token = glpat-xxxxxxxxxxxxxxxxxxxx This falls back to GITLAB_TOKEN environment variable if config file is not present. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- tools/pickman/README.rst | 17 ++++++++++--- tools/pickman/ftest.py | 49 +++++++++++++++++++++++++++++++++++++ tools/pickman/gitlab_api.py | 39 ++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 4 deletions(-) diff --git a/tools/pickman/README.rst b/tools/pickman/README.rst index 4b99de552b2..ce520f09a45 100644 --- a/tools/pickman/README.rst +++ b/tools/pickman/README.rst @@ -192,9 +192,20 @@ To use the ``-p`` (push) option for GitLab integration, install python-gitlab:: pip install python-gitlab -You will also need a GitLab API token set in the ``GITLAB_TOKEN`` environment -variable. See `GitLab Personal Access Tokens`_ for instructions on creating one. -The token needs ``api`` scope. +You will also need a GitLab API token. The token can be configured in a config +file or environment variable. Pickman checks in this order: + +1. Config file ``~/.config/pickman.conf``:: + + [gitlab] + token = glpat-xxxxxxxxxxxxxxxxxxxx + +2. ``GITLAB_TOKEN`` environment variable +3. ``GITLAB_API_TOKEN`` environment variable + +See `GitLab Personal Access Tokens`_ for instructions on creating a token. +The token needs ``api`` scope. Using a dedicated bot account for pickman is +recommended. .. _GitLab Personal Access Tokens: https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index 3e898eff188..66e087e6625 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -1431,6 +1431,55 @@ class TestCheckAvailable(unittest.TestCase): self.assertTrue(result) +class TestConfigFile(unittest.TestCase): + """Tests for config file support.""" + + def setUp(self): + """Set up test fixtures.""" + self.config_dir = tempfile.mkdtemp() + self.config_file = os.path.join(self.config_dir, 'pickman.conf') + + def tearDown(self): + """Clean up test fixtures.""" + shutil.rmtree(self.config_dir) + + def test_get_token_from_config(self): + """Test getting token from config file.""" + with open(self.config_file, 'w', encoding='utf-8') as fhandle: + fhandle.write('[gitlab]\ntoken = test-config-token\n') + + with mock.patch.object(gitlab_api, 'CONFIG_FILE', self.config_file): + token = gitlab_api.get_token() + self.assertEqual(token, 'test-config-token') + + def test_get_token_fallback_to_env(self): + """Test falling back to environment variable.""" + # Config file doesn't exist + with mock.patch.object(gitlab_api, 'CONFIG_FILE', '/nonexistent/path'): + with mock.patch.dict(os.environ, {'GITLAB_TOKEN': 'env-token'}): + token = gitlab_api.get_token() + self.assertEqual(token, 'env-token') + + def test_get_token_config_missing_section(self): + """Test config file without gitlab section.""" + with open(self.config_file, 'w', encoding='utf-8') as fhandle: + fhandle.write('[other]\nkey = value\n') + + with mock.patch.object(gitlab_api, 'CONFIG_FILE', self.config_file): + with mock.patch.dict(os.environ, {'GITLAB_TOKEN': 'env-token'}): + token = gitlab_api.get_token() + self.assertEqual(token, 'env-token') + + def test_get_config_value(self): + """Test get_config_value function.""" + with open(self.config_file, 'w', encoding='utf-8') as fhandle: + fhandle.write('[section1]\nkey1 = value1\n') + + with mock.patch.object(gitlab_api, 'CONFIG_FILE', self.config_file): + value = gitlab_api.get_config_value('section1', 'key1') + self.assertEqual(value, 'value1') + + class TestUpdateMrDescription(unittest.TestCase): """Tests for update_mr_description function.""" diff --git a/tools/pickman/gitlab_api.py b/tools/pickman/gitlab_api.py index 508168aa75c..d2297f40c93 100644 --- a/tools/pickman/gitlab_api.py +++ b/tools/pickman/gitlab_api.py @@ -6,6 +6,7 @@ """GitLab integration for pickman - push branches and create merge requests.""" from collections import namedtuple +import configparser import os import re import sys @@ -50,12 +51,48 @@ def check_available(): return True +CONFIG_FILE = os.path.expanduser('~/.config/pickman.conf') + + +def get_config_value(section, key): + """Get a value from the pickman config file + + Args: + section (str): Config section name + key (str): Config key name + + Returns: + str: Value or None if not found + """ + if not os.path.exists(CONFIG_FILE): + return None + + config = configparser.ConfigParser() + config.read(CONFIG_FILE) + + try: + return config.get(section, key) + except (configparser.NoSectionError, configparser.NoOptionError): + return None + + def get_token(): - """Get GitLab API token from environment + """Get GitLab API token from config file or environment + + Checks in order: + 1. Config file (~/.config/pickman.conf) [gitlab] token + 2. GITLAB_TOKEN environment variable + 3. GITLAB_API_TOKEN environment variable Returns: str: Token or None if not set """ + # Try config file first + token = get_config_value('gitlab', 'token') + if token: + return token + + # Fall back to environment variables return os.environ.get('GITLAB_TOKEN') or os.environ.get('GITLAB_API_TOKEN')