File indexing completed on 2024-04-21 13:18:23

0001 """
0002 Module containing classes for working with configuration
0003 """
0004 
0005 # SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im>
0006 #
0007 # SPDX-License-Identifier: GPL-2.0-or-later
0008 
0009 import json
0010 import os
0011 import sys
0012 import subprocess
0013 from enum import Enum, auto
0014 from pathlib import Path
0015 
0016 from typing import TextIO, Dict, Optional, Any, Tuple
0017 
0018 from appdirs import user_config_dir
0019 from lab.utils import Utils, LogType
0020 
0021 
0022 class Config:
0023     """
0024     Class that can load and store settings
0025 
0026     Config file layout:
0027     {
0028         "version": 1,
0029         "instances": {
0030             "gitlab.com": {
0031                 "auth_type": "token",
0032                 "token": "dkasjdlaksjdlkj",
0033                 "command": None
0034             },
0035             "invent.kde.org": {
0036                 "auth_type": "command",
0037                 "command": "gpg --decrypt",
0038                 "token": None
0039             }
0040         }
0041     }
0042     """
0043 
0044     config_path: str = user_config_dir("gitlabconfig")
0045 
0046     __file: TextIO
0047     __config: Dict[str, Any]
0048 
0049     def __migrate_to_version_1(self) -> None:
0050         if "version" not in self.__config:
0051             Utils.log(LogType.INFO, "Migrating configuration file to version 1")
0052 
0053             new_config: Dict[str, Any] = {"version": 1, "instances": {}}
0054 
0055             for hostname in self.__config.keys():
0056                 new_config["instances"][hostname] = {
0057                     "auth_type": "token",
0058                     "token": self.__config[hostname],
0059                 }
0060 
0061             self.__config = new_config
0062             self.save()
0063 
0064     def __init__(self) -> None:
0065         if not os.path.isfile(self.config_path):
0066             old_config_path: str = os.path.expanduser("~/.gitlabconfig")
0067             config_dir = Path(self.config_path).parent
0068             if os.path.isfile(old_config_path):
0069                 if not os.path.isdir(config_dir):
0070                     os.mkdir(config_dir)
0071                 os.rename(old_config_path, self.config_path)
0072             else:
0073                 if not os.path.isdir(config_dir):
0074                     os.mkdir(config_dir)
0075                 with open(self.config_path, "w+") as file:
0076                     json.dump({"version": 1, "instances": {}}, file)
0077                     file.close()
0078 
0079         self.__file = open(self.config_path, "r+")
0080         self.__config = json.load(self.__file)
0081 
0082         self.__migrate_to_version_1()
0083 
0084     def save(self) -> None:
0085         """
0086         Save the config to disk. This function has to be manually called,
0087         otherwise the config won't be saved.
0088         """
0089         self.__file.seek(0)
0090         json.dump(self.__config, self.__file, indent=4)
0091         self.__file.truncate()
0092         self.__file.flush()
0093 
0094     def token(self, hostname: str) -> Optional[str]:
0095         """
0096         Returns the token for a GitLab instance.
0097         If none was found, it returns None
0098         """
0099         if hostname in self.__config["instances"]:
0100             # Command case
0101             if (
0102                 "auth_type" in self.__config["instances"][hostname]
0103                 and self.__config["instances"][hostname]["auth_type"] == "command"
0104             ):
0105                 return (
0106                     subprocess.check_output(
0107                         self.__config["instances"][hostname]["command"], shell=True
0108                     )
0109                     .decode()
0110                     .strip()
0111                 )
0112 
0113             # Token case
0114             token = self.__config["instances"][hostname]["token"]
0115             if isinstance(token, str):
0116                 return token
0117 
0118         return None
0119 
0120     def set_token(self, hostname: str, token: str) -> None:
0121         """
0122         Sets the token for a GitLab instance
0123         """
0124         if hostname not in self.__config["instances"]:
0125             self.__config["instances"][hostname] = {}
0126 
0127         self.__config["instances"][hostname]["token"] = token
0128         self.__config["instances"][hostname]["auth_type"] = "token"
0129 
0130     def set_auth_command(self, hostname: str, command: str) -> None:
0131         """
0132         Sets the command that git-lab runs when it needs an access token
0133         """
0134         if hostname not in self.__config["instances"]:
0135             self.__config["instances"][hostname] = {}
0136 
0137         self.__config["instances"][hostname]["command"] = command
0138         self.__config["instances"][hostname]["auth_type"] = "command"
0139 
0140     def instances(self) -> Tuple[str, ...]:
0141         """
0142         Returns the list of known instances
0143         """
0144         try:
0145             return tuple(self.__config["instances"].keys())
0146         except KeyError:
0147             return ()
0148 
0149         return ()
0150 
0151 
0152 class Workflow(Enum):
0153     """
0154     Different values for workflow
0155     """
0156 
0157     # Never reorder this values! The config file stores the actual numbers.
0158     FORK = auto()  # push merge request branches to a fork of the upstream repository
0159     WORKBRANCH = auto()  # push merge request branches to the upstream repository
0160 
0161 
0162 class RepositoryConfig:
0163     """
0164     Per-repository config file
0165     """
0166 
0167     config_path: str
0168     __file: TextIO
0169     __config: Dict[str, Any]
0170 
0171     def __init__(self) -> None:
0172         repository_path: Optional[str] = Utils.find_dotgit(os.getcwd())
0173         if repository_path:
0174             self.config_path = repository_path + os.path.sep + ".git" + os.path.sep + "gitlabconfig"
0175         else:
0176             Utils.log(LogType.ERROR, "Current directory is not a git repository")
0177             sys.exit(1)
0178 
0179         if not os.path.isfile(self.config_path):
0180             with open(self.config_path, "w+") as file:
0181                 json.dump({}, file)
0182                 file.close()
0183 
0184         self.__file = open(self.config_path, "r+")
0185         self.__config = json.load(self.__file)
0186 
0187     def workflow(self) -> Workflow:
0188         """
0189         get the workflow used for the repository. Defaults to RepositoryConfig.workflow.fork
0190         """
0191         value: Any
0192         try:
0193             value = self.__config["workflow"]
0194         except KeyError:
0195             return Workflow.FORK
0196 
0197         return Workflow(value)
0198 
0199     def set_workflow(self, workflow: Workflow) -> None:
0200         """
0201         Set the workflow to one of RepositoryConfig.Workflow
0202         """
0203 
0204         # Make sure not to corrupt the config file with invalid numbers
0205         if not isinstance(workflow, Workflow):
0206             raise TypeError()
0207 
0208         self.__config["workflow"] = workflow.value
0209 
0210     def save(self) -> None:
0211         """
0212         Save the config to disk. This function has to be manually called,
0213         otherwise the config won't be saved.
0214         """
0215         self.__file.seek(0)
0216         json.dump(self.__config, self.__file, indent=4)
0217         self.__file.truncate()
0218         self.__file.flush()