Skip to content

Commit

Permalink
feat(config): enable default environment token per hvcs (#774)
Browse files Browse the repository at this point in the history
  • Loading branch information
codejedi365 committed Dec 22, 2023
1 parent 38046d5 commit 26528eb
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 19 deletions.
48 changes: 29 additions & 19 deletions semantic_release/cli/config.py
Expand Up @@ -6,14 +6,15 @@
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union

from git import Actor
from git.repo.base import Repo
from jinja2 import Environment
from pydantic import BaseModel
from pydantic import BaseModel, model_validator
from typing_extensions import Literal

from semantic_release import hvcs
from semantic_release.changelog import environment
from semantic_release.cli.const import DEFAULT_CONFIG_FILE
from semantic_release.cli.masking_filter import MaskingFilter
Expand All @@ -29,7 +30,6 @@
from semantic_release.const import COMMIT_MESSAGE, DEFAULT_COMMIT_AUTHOR, SEMVER_REGEX
from semantic_release.errors import InvalidConfiguration, NotAReleaseBranch
from semantic_release.helpers import dynamic_import
from semantic_release.hvcs import Gitea, Github, Gitlab, HvcsBase
from semantic_release.version import VersionTranslator
from semantic_release.version.declaration import (
PatternVersionDeclaration,
Expand All @@ -46,6 +46,21 @@ class HvcsClient(str, Enum):
GITEA = "gitea"


_known_commit_parsers = {
"angular": AngularCommitParser,
"emoji": EmojiCommitParser,
"scipy": ScipyCommitParser,
"tag": TagCommitParser,
}


_known_hvcs: Dict[HvcsClient, Type[hvcs.HvcsBase]] = {
HvcsClient.GITHUB: hvcs.Github,
HvcsClient.GITLAB: hvcs.Gitlab,
HvcsClient.GITEA: hvcs.Gitea,
}


class EnvConfigVar(BaseModel):
env: str
default: Optional[str] = None
Expand Down Expand Up @@ -90,13 +105,22 @@ class BranchConfig(BaseModel):

class RemoteConfig(BaseModel):
name: str = "origin"
token: MaybeFromEnv = EnvConfigVar(env="GH_TOKEN")
token: MaybeFromEnv = ""
url: Optional[MaybeFromEnv] = None
type: HvcsClient = HvcsClient.GITHUB
domain: Optional[str] = None
api_domain: Optional[str] = None
ignore_token_for_push: bool = False

@model_validator(mode="after")
def set_default_token(self) -> "RemoteConfig":
# Set the default token name for the given VCS when no user input is given
if not self.token and self.type in _known_hvcs:
default_token_name = _known_hvcs[self.type].DEFAULT_ENV_TOKEN_NAME
if default_token_name:
self.token = EnvConfigVar(env=default_token_name)
return self


class PublishConfig(BaseModel):
dist_glob_patterns: Tuple[str, ...] = ("dist/*",)
Expand Down Expand Up @@ -172,20 +196,6 @@ def _recursive_getattr(obj: Any, path: str) -> Any:
return out


_known_commit_parsers = {
"angular": AngularCommitParser,
"emoji": EmojiCommitParser,
"scipy": ScipyCommitParser,
"tag": TagCommitParser,
}

_known_hvcs = {
HvcsClient.GITHUB: Github,
HvcsClient.GITLAB: Gitlab,
HvcsClient.GITEA: Gitea,
}


@dataclass
class RuntimeContext:
_mask_attrs_: ClassVar[List[str]] = ["hvcs_client.token"]
Expand All @@ -200,7 +210,7 @@ class RuntimeContext:
commit_message: str
changelog_excluded_commit_patterns: Tuple[re.Pattern[str], ...]
version_declarations: Tuple[VersionDeclarationABC, ...]
hvcs_client: HvcsBase
hvcs_client: hvcs.HvcsBase
changelog_file: Path
ignore_token_for_push: bool
template_environment: Environment
Expand Down
2 changes: 2 additions & 0 deletions semantic_release/hvcs/__init__.py
Expand Up @@ -3,3 +3,5 @@
from semantic_release.hvcs.github import Github
from semantic_release.hvcs.gitlab import Gitlab
from semantic_release.hvcs.token_auth import TokenAuth

__all__ = ["Gitea", "Github", "Gitlab", "HvcsBase", "TokenAuth"]
2 changes: 2 additions & 0 deletions semantic_release/hvcs/_base.py
Expand Up @@ -33,6 +33,8 @@ class HvcsBase:
checking for NotImplemented around every method call.
"""

DEFAULT_ENV_TOKEN_NAME = "HVCS_TOKEN"

def __init__(
self,
remote_url: str,
Expand Down
1 change: 1 addition & 0 deletions semantic_release/hvcs/gitea.py
Expand Up @@ -36,6 +36,7 @@ class Gitea(HvcsBase):
DEFAULT_DOMAIN = "gitea.com"
DEFAULT_API_PATH = "/api/v1"
DEFAULT_API_DOMAIN = f"{DEFAULT_DOMAIN}{DEFAULT_API_PATH}"
DEFAULT_ENV_TOKEN_NAME = "GITEA_TOKEN"

# pylint: disable=super-init-not-called
def __init__(
Expand Down
1 change: 1 addition & 0 deletions semantic_release/hvcs/github.py
Expand Up @@ -37,6 +37,7 @@ class Github(HvcsBase):
DEFAULT_DOMAIN = "github.com"
DEFAULT_API_DOMAIN = "api.github.com"
DEFAULT_UPLOAD_DOMAIN = "uploads.github.com"
DEFAULT_ENV_TOKEN_NAME = "GH_TOKEN"

def __init__(
self,
Expand Down
4 changes: 4 additions & 0 deletions semantic_release/hvcs/gitlab.py
Expand Up @@ -38,6 +38,10 @@ class Gitlab(HvcsBase):
API domain
"""

DEFAULT_ENV_TOKEN_NAME = "GITLAB_TOKEN"
# purposefully not CI_JOB_TOKEN as it is not a personal access token,
# It is missing the permission to push to the repository, but has all others (releases, packages, etc.)

DEFAULT_DOMAIN = "gitlab.com"

def __init__(
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/semantic_release/cli/test_config.py
@@ -1,15 +1,48 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from unittest import mock

import pytest
import tomlkit
from pydantic import ValidationError

from semantic_release.cli.config import (
EnvConfigVar,
GlobalCommandLineOptions,
HvcsClient,
RawConfig,
RuntimeContext,
)
from semantic_release.const import DEFAULT_COMMIT_AUTHOR

if TYPE_CHECKING:
from typing import Any


@pytest.mark.parametrize("remote_config, expected_token", [
({ "type": HvcsClient.GITHUB.value }, EnvConfigVar(env="GH_TOKEN")),
({ "type": HvcsClient.GITLAB.value }, EnvConfigVar(env="GITLAB_TOKEN")),
({ "type": HvcsClient.GITEA.value }, EnvConfigVar(env="GITEA_TOKEN")),
({}, EnvConfigVar(env="GH_TOKEN")), # default not provided -> means Github
])
def test_load_hvcs_default_token(remote_config: dict[str, Any], expected_token):
raw_config = RawConfig.model_validate({
"remote": remote_config,
})
assert expected_token == raw_config.remote.token


@pytest.mark.parametrize("remote_config", [
{ "type": "nonexistent" }
])
def test_invalid_hvcs_type(remote_config: dict[str, Any]):
with pytest.raises(ValidationError) as excinfo:
RawConfig.model_validate({
"remote": remote_config,
})
assert "remote.type" in str(excinfo.value)


def test_default_toml_config_valid(example_project):
default_config_file = example_project / "default.toml"
Expand Down

0 comments on commit 26528eb

Please sign in to comment.