diff options
Diffstat (limited to 'gitlab')
-rw-r--r-- | gitlab/cli.py | 108 | ||||
-rw-r--r-- | gitlab/client.py | 82 | ||||
-rw-r--r-- | gitlab/config.py | 4 |
3 files changed, 187 insertions, 7 deletions
diff --git a/gitlab/cli.py b/gitlab/cli.py index c1a1334..a48b53b 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -19,6 +19,7 @@ import argparse import functools +import os import re import sys from types import ModuleType @@ -112,17 +113,25 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser: "-v", "--verbose", "--fancy", - help="Verbose mode (legacy format only)", + help="Verbose mode (legacy format only) [env var: GITLAB_VERBOSE]", action="store_true", + default=os.getenv("GITLAB_VERBOSE"), ) parser.add_argument( - "-d", "--debug", help="Debug mode (display HTTP requests)", action="store_true" + "-d", + "--debug", + help="Debug mode (display HTTP requests) [env var: GITLAB_DEBUG]", + action="store_true", + default=os.getenv("GITLAB_DEBUG"), ) parser.add_argument( "-c", "--config-file", action="append", - help="Configuration file to use. Can be used multiple times.", + help=( + "Configuration file to use. Can be used multiple times. " + "[env var: PYTHON_GITLAB_CFG]" + ), ) parser.add_argument( "-g", @@ -151,7 +160,86 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser: ), required=False, ) + parser.add_argument( + "--server-url", + help=("GitLab server URL [env var: GITLAB_URL]"), + required=False, + default=os.getenv("GITLAB_URL"), + ) + parser.add_argument( + "--ssl-verify", + help=( + "Whether SSL certificates should be validated. [env var: GITLAB_SSL_VERIFY]" + ), + required=False, + default=os.getenv("GITLAB_SSL_VERIFY"), + ) + parser.add_argument( + "--timeout", + help=( + "Timeout to use for requests to the GitLab server. " + "[env var: GITLAB_TIMEOUT]" + ), + required=False, + default=os.getenv("GITLAB_TIMEOUT"), + ) + parser.add_argument( + "--api-version", + help=("GitLab API version [env var: GITLAB_API_VERSION]"), + required=False, + default=os.getenv("GITLAB_API_VERSION"), + ) + parser.add_argument( + "--per-page", + help=( + "Number of entries to return per page in the response. " + "[env var: GITLAB_PER_PAGE]" + ), + required=False, + default=os.getenv("GITLAB_PER_PAGE"), + ) + parser.add_argument( + "--pagination", + help=( + "Whether to use keyset or offset pagination [env var: GITLAB_PAGINATION]" + ), + required=False, + default=os.getenv("GITLAB_PAGINATION"), + ) + parser.add_argument( + "--order-by", + help=("Set order_by globally [env var: GITLAB_ORDER_BY]"), + required=False, + default=os.getenv("GITLAB_ORDER_BY"), + ) + parser.add_argument( + "--user-agent", + help=( + "The user agent to send to GitLab with the HTTP request. " + "[env var: GITLAB_USER_AGENT]" + ), + required=False, + default=os.getenv("GITLAB_USER_AGENT"), + ) + tokens = parser.add_mutually_exclusive_group() + tokens.add_argument( + "--private-token", + help=("GitLab private access token [env var: GITLAB_PRIVATE_TOKEN]"), + required=False, + default=os.getenv("GITLAB_PRIVATE_TOKEN"), + ) + tokens.add_argument( + "--oauth-token", + help=("GitLab OAuth token [env var: GITLAB_OAUTH_TOKEN]"), + required=False, + default=os.getenv("GITLAB_OAUTH_TOKEN"), + ) + tokens.add_argument( + "--job-token", + help=("GitLab CI job token [env var: CI_JOB_TOKEN]"), + required=False, + ) return parser @@ -243,13 +331,23 @@ def main() -> None: "whaction", "version", "output", + "fields", + "server_url", + "ssl_verify", + "timeout", + "api_version", + "pagination", + "user_agent", + "private_token", + "oauth_token", + "job_token", ): args_dict.pop(item) args_dict = {k: _parse_value(v) for k, v in args_dict.items() if v is not None} try: - gl = gitlab.Gitlab.from_config(gitlab_id, config_files) - if gl.private_token or gl.oauth_token or gl.job_token: + gl = gitlab.Gitlab.merge_config(vars(options), gitlab_id, config_files) + if gl.private_token or gl.oauth_token: gl.auth() except Exception as e: die(str(e)) diff --git a/gitlab/client.py b/gitlab/client.py index c1e0825..b791c8f 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -16,6 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """Wrapper for the GitLab API.""" +import os import time from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union @@ -256,6 +257,87 @@ class Gitlab(object): retry_transient_errors=config.retry_transient_errors, ) + @classmethod + def merge_config( + cls, + options: dict, + gitlab_id: Optional[str] = None, + config_files: Optional[List[str]] = None, + ) -> "Gitlab": + """Create a Gitlab connection by merging configuration with + the following precedence: + + 1. Explicitly provided CLI arguments, + 2. Environment variables, + 3. Configuration files: + a. explicitly defined config files: + i. via the `--config-file` CLI argument, + ii. via the `PYTHON_GITLAB_CFG` environment variable, + b. user-specific config file, + c. system-level config file, + 4. Environment variables always present in CI (CI_SERVER_URL, CI_JOB_TOKEN). + + Args: + options: A dictionary of explicitly provided key-value options. + gitlab_id: ID of the configuration section. + config_files: List of paths to configuration files. + Returns: + (gitlab.Gitlab): A Gitlab connection. + + Raises: + gitlab.config.GitlabDataError: If the configuration is not correct. + """ + config = gitlab.config.GitlabConfigParser( + gitlab_id=gitlab_id, config_files=config_files + ) + url = ( + options.get("server_url") + or config.url + or os.getenv("CI_SERVER_URL") + or gitlab.const.DEFAULT_URL + ) + private_token, oauth_token, job_token = cls._merge_auth(options, config) + + return cls( + url=url, + private_token=private_token, + oauth_token=oauth_token, + job_token=job_token, + ssl_verify=options.get("ssl_verify") or config.ssl_verify, + timeout=options.get("timeout") or config.timeout, + api_version=options.get("api_version") or config.api_version, + per_page=options.get("per_page") or config.per_page, + pagination=options.get("pagination") or config.pagination, + order_by=options.get("order_by") or config.order_by, + user_agent=options.get("user_agent") or config.user_agent, + ) + + @staticmethod + def _merge_auth(options: dict, config: gitlab.config.GitlabConfigParser) -> Tuple: + """ + Return a tuple where at most one of 3 token types ever has a value. + Since multiple types of tokens may be present in the environment, + options, or config files, this precedence ensures we don't + inadvertently cause errors when initializing the client. + + This is especially relevant when executed in CI where user and + CI-provided values are both available. + """ + private_token = options.get("private_token") or config.private_token + oauth_token = options.get("oauth_token") or config.oauth_token + job_token = ( + options.get("job_token") or config.job_token or os.getenv("CI_JOB_TOKEN") + ) + + if private_token: + return (private_token, None, None) + if oauth_token: + return (None, oauth_token, None) + if job_token: + return (None, None, job_token) + + return (None, None, None) + def auth(self) -> None: """Performs an authentication using private token. diff --git a/gitlab/config.py b/gitlab/config.py index 154f063..c11a4e9 100644 --- a/gitlab/config.py +++ b/gitlab/config.py @@ -23,7 +23,7 @@ from os.path import expanduser, expandvars from pathlib import Path from typing import List, Optional, Union -from gitlab.const import DEFAULT_URL, USER_AGENT +from gitlab.const import USER_AGENT _DEFAULT_FILES: List[str] = [ "/etc/python-gitlab.cfg", @@ -119,7 +119,7 @@ class GitlabConfigParser(object): self.retry_transient_errors: bool = False self.ssl_verify: Union[bool, str] = True self.timeout: int = 60 - self.url: str = DEFAULT_URL + self.url: Optional[str] = None self.user_agent: str = USER_AGENT self._files = _get_config_files(config_files) |