diff --git a/.github/renovate-config.js b/.github/renovate-config.js new file mode 100644 index 0000000..b2f8e0e --- /dev/null +++ b/.github/renovate-config.js @@ -0,0 +1,16 @@ +module.exports = { + onboarding: false, + platform: 'github', + repositories: [ + 'logchange/valhalla' + ], + //allowPostUpgradeCommandTemplating: true, + //postUpgradeTasks: { + // commands: ['curl -sSL https://raw.githubusercontent.com/logchange/logchange/main/add_entry.sh | bash -s -- -DfileName=renovate-{{{depNameSanitized}}}-{{{newVersion}}}.yml -Dtitle="Upgraded {{{depName}}} from {{{currentVersion}}} to {{{newVersion}}}" -Dauthor=marwin1991 -Dtype=dependency_update -Dlink.name=notes -Dlink.url={{{url}}}'], + // commands: [ + // 'install-tool maven 3.9.3', + // 'mvn dev.logchange:logchange-maven-plugin:add --non-recursive -DbatchMode -DfileName=renovate-{{{depNameSanitized}}}-{{{newVersion}}}.yml -Dtitle="Upgraded {{{depName}}} from {{{currentVersion}}} to {{{newVersion}}}" -Dauthor="logchange-bot;logchange-bot;team@logchange.dev" -Dtype=dependency_update -Dlink.name=notes -Dlink.url={{{url}}} || true'], + // fileFilters: ['**/*.yml'], + // executionMode: 'update', + //} +}; diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..9a3abf6 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "assignees": [ + "marwin1991", + "witx98" + ] +} diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 61d21af..1557ddb 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -23,4 +23,9 @@ jobs: run: pip install -r requirements.txt - name: Run tests - run: python -m unittest discover -s test -t test -p '*_test.py' \ No newline at end of file + run: pytest --cov --cov-branch --cov-report=xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000..db23926 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,24 @@ +name: Renovate + +on: + workflow_dispatch: + schedule: + - cron: "0 7 * * *" + +jobs: + renovate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Self-hosted Renovate + uses: renovatebot/github-action@v43.0.14 + with: + token: ${{ secrets.LOGCHANGE_PAT_TOKEN }} + configurationFile: .github/renovate-config.js + renovate-version: full + env: + RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: ".*" + LOG_LEVEL: debug + + diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 7efe971..23e264b 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -3,4 +3,22 @@ To run tests in this project, use the following command: ```bash python -m unittest discover -s test -t test -p '*_test.py' +``` + +# Test template + +When creating a new test, please use the following template: +```python +import unittest + +class SomeClassTest(unittest.TestCase): + + def test_some_method(self, mock_run): + # given: + ... + # when: + ... + # then: + ... + ``` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8fa9216..4753fe0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN apk --update --no-cache add \ npm && \ npm install -g pnpm -RUN wget https://github.com/logchange/logchange/releases/download/1.15.0/logchange-linuxx64.zip \ +RUN wget https://github.com/logchange/logchange/releases/download/1.19.10/logchange-linuxx64.zip \ && unzip logchange-linuxx64.zip \ && mv bins/logchange-linuxx64/logchange /usr/local/bin/logchange \ && chmod +x /usr/local/bin/logchange \ diff --git a/README.md b/README.md index 7e11281..b5b3212 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ - [dockerhub](https://hub.docker.com/repository/docker/logchange/valhalla/) +## [Documentation](https://logchange.dev/tools/valhalla/) + +Visit **[logchange.dev/tools/valhalla](https://logchange.dev/tools/valhalla/)** to read the full documentation, explore +usage examples, and learn how to get the most out of Valhalla. + + ### 📐 background and basic concept - **complex releasing process:** Creating a new software release involves a multitude of intricate steps. This @@ -25,76 +31,7 @@ saving time, and promoting compliance with established regulations. ### ⚙️ configuration -- create `valhalla.yml` in your project (check - out [examples](https://github.com/logchange/valhalla/tree/master/examples)) - -```yml -# This file is used by valhalla tool to create release 🌌 -# Visit https://github.com/logchange/valhalla and leave a star 🌟 -# More info about configuration you can find https://github.com/logchange/valhalla#%EF%B8%8F-configuration ⬅️ -extends: # You can extend any file from URL! This helps keep configuration in one place! - - https://raw.githubusercontent.com/logchange/valhalla/master/valhalla-extends.yml - -# Define custom variables which can be used in any string with {} -variables: - MY_VARIABLE: Some value -git_host: gitlab # your project ci provider, supported [gitlab] - -# define actions which have to happen before release and output should be committed -commit_before_release: - enabled: True # if this is True commands from before will be performed and committed to branch - username: Test1234 # git config username - email: test-valhalla@logchange.dev # git config email - msg: Releasing version {VERSION} # commit message, you can use string predefined variables - before: # list of bash commands, you can use string predefined variables, custom variables or system environment variables! - - echo "test" > some_file4.md - - mkdir -p changelog/v{VERSION} - - echo "Super release description for tests generated at {CI_COMMIT_TIMESTAMP}" > changelog/v{VERSION}/version_summary.md - -# definition of release which will be created -release: - name: "Release {VERSION}" # optional filed, you can use string predefined variables (default name is VERSION) - description: - # bash command with will be executed and output will be used as - # release description, you can use string predefined variables - from_command: "cat changelog/v{VERSION}/version_summary.md" - milestones: - - M {VERSION_MAJOR}.{VERSION_MINOR} - - Main - assets: # https://docs.gitlab.com/ee/api/releases/#create-a-release - links: - - name: Documentation # you can use string predefined variables - url: https://google.com/q?={VERSION} # you can use string predefined variables - link_type: other # The type of the link: other, runbook, image, package. - - name: Docker Image # you can use string predefined variables - url: https://dockerhub.com/q?={VERSION} # you can use string predefined variables - link_type: image # The type of the link: other, runbook, image, package. - -# definition of tag which will be created -tag: - name: "Tag {VERSION}" # optional filed, you can use string predefined variables (default name is VERSION) - -# define actions which have to happen after release and output should be committed -commit_after_release: - enabled: True - username: Test1234 - email: test-valhalla@logchange.dev - msg: Preparation for next development cycle - before: - - echo "test" > prepare_next_iteration.md - -# define merge request from release breach to your default -# branch with changes from commit_before_release and commit_after_release -merge_request: - enabled: True # if this is True merge request will be created - target_branch: hotfix-{VERSION} # optional property (default branch if empty) defining target branch for merge/pull request. Supports regexp - title: Releasing version {VERSION} and preparation for next development cycle # you can use string predefined variables - description: Hello world! I have just released {VERSION} # optional filed, you can use string predefined variables - reviewers: - - peter.zmilczak # usernames which will be reviews of created MR - - some_unknown_nick # if username cannot be found you can check logs -``` - +- Create `valhalla.yml` in your project (check out [examples](https://github.com/logchange/valhalla/tree/master/examples)) (check out [reference](https://logchange.dev/tools/valhalla/reference/#basic-structure)) - Create access token and pass it to CI as environment variable `VALHALLA_TOKEN` - Update or CI/CD scripts to use valhalla (see below for examples) - Update your `.gitignore` ! see [link](#-gitignore) diff --git a/requirements.txt b/requirements.txt index b23ac2b..6372f3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,5 @@ PyYAML==6.0.3 GitPython==3.1.45 python-gitlab==6.4.0 requests==2.32.5 +pytest==8.4.2 +pytest-cov==7.0.0 \ No newline at end of file diff --git a/test/ci_provider/gitlab/get_version_test.py b/test/ci_provider/gitlab/get_version_test.py index 396cba7..f09e94a 100644 --- a/test/ci_provider/gitlab/get_version_test.py +++ b/test/ci_provider/gitlab/get_version_test.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import patch -from valhalla.ci_provider.gitlab.get_version import get_version_to_release +from valhalla.ci_provider.gitlab.get_version import get_version_to_release_from_branch_name from valhalla.version.version_to_release import ReleaseKind @@ -14,7 +14,7 @@ def test_release_branch(self, mock_env_get): release_kinds = [ReleaseKind("valhalla.yml", "", ".")] # when: - result = get_version_to_release(release_kinds) + result = get_version_to_release_from_branch_name(release_kinds) # then: self.assertEqual(result.version_number_to_release, '1.0.0') @@ -29,7 +29,7 @@ def test_non_release_branch(self, mock_exit, mock_env_get): release_kinds = [ReleaseKind("valhalla.yml", "", ".")] # when: - result = get_version_to_release(release_kinds) + result = get_version_to_release_from_branch_name(release_kinds) # then: self.assertIsNone(result) @@ -43,7 +43,7 @@ def test_no_ci_commit_branch(self, mock_exit, mock_env_get): release_kinds = [ReleaseKind("valhalla.yml", "", ".")] # when: - result = get_version_to_release(release_kinds) + result = get_version_to_release_from_branch_name(release_kinds) # then: self.assertIsNone(result) @@ -57,7 +57,7 @@ def test_release_hotfix_branch(self, mock_env_get): ReleaseKind("valhalla-hotfix.yml", "-hotfix", ".")] # when: - result = get_version_to_release(release_kinds) + result = get_version_to_release_from_branch_name(release_kinds) # then: self.assertEqual(result.version_number_to_release, '1.2.3') @@ -72,7 +72,7 @@ def test_no_matching_valhalla_branch(self, mock_exit, mock_env_get): release_kinds = [ReleaseKind("valhalla-hotfix.yml", "-hotfix", ".")] # when: - result = get_version_to_release(release_kinds) + result = get_version_to_release_from_branch_name(release_kinds) # then: self.assertIsNone(result) diff --git a/test/common/get_config_test.py b/test/common/get_config_test.py index 7e07c06..4fc0e87 100644 --- a/test/common/get_config_test.py +++ b/test/common/get_config_test.py @@ -54,6 +54,27 @@ def test_get_config(self, mock_open_file): mock_open_file.assert_called_once_with(self.config_path) + @patch( + "builtins.open", + new_callable=mock_open, + read_data=""" + version: + from_command: "cat version.txt" + git_host: gitlab + release: + name: "Test Release Name" + """, + ) + def test_get_version_config(self, mock_open_file): + config = get_config(self.config_path) + + mock_open_file.assert_called_once_with(self.config_path) + + self.assertIsNotNone(config.version_config) + self.assertEqual(config.version_config.from_command, "cat version.txt") + + mock_open_file.assert_called_once_with(self.config_path) + @patch( "builtins.open", new_callable=mock_open, @@ -76,10 +97,10 @@ def test_get_config_no_mr_section(self, mock_open_file): mock_open_file.assert_called_once_with(self.config_path) self.assertEqual(config.merge_request.enabled, False) - self.assertEqual(config.merge_request.target_branch, None) - self.assertEqual(config.merge_request.title, None) - self.assertEqual(config.merge_request.description, None) - self.assertEqual(config.merge_request.reviewers, None) + self.assertEqual(config.merge_request.target_branch, "") + self.assertEqual(config.merge_request.title, "") + self.assertEqual(config.merge_request.description, "") + self.assertEqual(config.merge_request.reviewers, []) mock_open_file.assert_called_once_with(self.config_path) @@ -123,7 +144,6 @@ def test_get_config_file_not_found(self, mock_open_file): mock_open_file.assert_called_once_with(self.config_path) self.assertEqual(context.exception.code, -1) - @patch( "builtins.open", new_callable=mock_open, @@ -145,7 +165,8 @@ def test_get_release_config(self, mock_open_file): self.assertIsNotNone(config.release_config) self.assertEqual(config.release_config.name, "Test Release Name {VERSION}") self.assertEqual(config.release_config.milestones, ['Test Milestone']) - self.assertEqual(config.release_config.description_config.from_command, "cat changelog/v{VERSION}/version_summary.md") + self.assertEqual(config.release_config.description_config.from_command, + "cat changelog/v{VERSION}/version_summary.md") mock_open_file.assert_called_once_with(self.config_path) diff --git a/test/common/get_from_dict_test.py b/test/common/get_from_dict_test.py new file mode 100644 index 0000000..97bdc29 --- /dev/null +++ b/test/common/get_from_dict_test.py @@ -0,0 +1,24 @@ +import unittest +from unittest.mock import patch + +from valhalla.common.get_config import get_from_dict + + +class GetFromDictTest(unittest.TestCase): + + @patch('valhalla.common.get_config.error') + def test_required_missing_raises(self, mock_error): + with self.assertRaises(RuntimeError) as ctx: + get_from_dict({}, 'git_host', True) + self.assertIn('Missing required git_host in valhalla.yml!', str(ctx.exception)) + mock_error.assert_called() # ensure error was logged + + @patch('valhalla.common.get_config.info') + def test_optional_missing_returns_none_and_logs(self, mock_info): + value = get_from_dict({}, 'optional_key', False) + self.assertIsNone(value) + mock_info.assert_called() # ensure info was logged + + +if __name__ == '__main__': + unittest.main() diff --git a/test/common/release_assets_links_parsing_test.py b/test/common/release_assets_links_parsing_test.py new file mode 100644 index 0000000..a90d4e1 --- /dev/null +++ b/test/common/release_assets_links_parsing_test.py @@ -0,0 +1,53 @@ +import unittest +from unittest.mock import mock_open, patch + +from valhalla.common.get_config import get_config + + +class ReleaseAssetsLinksParsingTest(unittest.TestCase): + + @patch( + 'builtins.open', + new_callable=mock_open, + read_data=''' + git_host: gitlab + release: + name: "Rel" + assets: + links: + - name: "Documentation" + url: "https://example.com/docs" + link_type: "doc" + - name: "Image" + url: "https://example.com/diagram.png" + link_type: "image" + ''' + ) + def test_parse_assets_links(self, mock_open_file): + cfg = get_config('valhalla.yml') + self.assertIsNotNone(cfg.release_config) + self.assertIsNotNone(cfg.release_config.assets_config) + links = cfg.release_config.assets_config.links + self.assertEqual(2, len(links)) + self.assertEqual(('Documentation', 'https://example.com/docs', 'doc'), + (links[0].name, links[0].url, links[0].link_type)) + self.assertEqual(('Image', 'https://example.com/diagram.png', 'image'), + (links[1].name, links[1].url, links[1].link_type)) + + @patch( + 'builtins.open', + new_callable=mock_open, + read_data=''' + git_host: gitlab + release: + name: "Rel" + assets: {} + ''' + ) + def test_assets_links_none(self, mock_open_file): + cfg = get_config('valhalla.yml') + self.assertEqual([], cfg.release_config.assets_config.links) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/version/from_config_test.py b/test/version/from_config_test.py new file mode 100644 index 0000000..f2b69bd --- /dev/null +++ b/test/version/from_config_test.py @@ -0,0 +1,64 @@ +import unittest +from unittest.mock import patch, MagicMock + +from valhalla.version.version_to_release import VersionToRelease, ReleaseKind + + +class VersionFromCommandTest(unittest.TestCase): + + @patch('subprocess.run') + def test_from_config_executes_command_and_sets_version(self, mock_run): + # given: + proc = MagicMock() + proc.stdout = '1.2.3' + proc.stderr = '' + proc.returncode = 0 + mock_run.return_value = proc + + # Minimal config stub to match the current implementation (expects config.version) + class _Version: + def __init__(self, from_command): + self.from_command = from_command + + class _Cfg: + def __init__(self, from_command): + self.version = _Version(from_command) + + vtr = VersionToRelease('', ReleaseKind('valhalla.yml', '', '.')) + + # when: + vtr.from_config(_Cfg('echo 1.2.3')) + + # then + self.assertEqual('1.2.3', vtr.version_number_to_release) + mock_run.assert_called_once() + + @patch('valhalla.version.version_to_release.info') + def test_from_config_skips_when_no_command(self, mock_info): + # given: + class _Cfg: + def __init__(self, version): + self.version = version + + vtr = VersionToRelease('', ReleaseKind('valhalla.yml', '', '.')) + + # None version + vtr.from_config(_Cfg(None)) + + # Empty from_command + class _V: + def __init__(self, fc=None): + self.from_command = fc + + # when: + vtr.from_config(_Cfg(_V(None))) + v = _V(' ') + vtr.from_config(_Cfg(v)) + + # then: + self.assertEqual('', vtr.version_number_to_release) + mock_info.assert_called() + + +if __name__ == '__main__': + unittest.main() diff --git a/valhalla.yml b/valhalla.yml index 4b1b007..d80ec33 100644 --- a/valhalla.yml +++ b/valhalla.yml @@ -3,6 +3,8 @@ # More info about configuration you can find https://github.com/logchange/valhalla#%EF%B8%8F-configuration ⬅️ extends: - https://raw.githubusercontent.com/logchange/valhalla/master/valhalla-extends.yml +version: + from_command: "mvn help:evaluate -Dexpression=project.version -q -DforceStdout | sed 's/-SNAPSHOT//'" variables: MY_VARIABLE: Some value PARENT_VARIABLE_OVERRIDE: new value from child yml! It works! diff --git a/valhalla/ci_provider/gitlab/get_version.py b/valhalla/ci_provider/gitlab/get_version.py index 33724c7..f1e9f0b 100644 --- a/valhalla/ci_provider/gitlab/get_version.py +++ b/valhalla/ci_provider/gitlab/get_version.py @@ -6,7 +6,7 @@ get_version_to_release_from_str -def get_version_to_release(release_kinds: List[ReleaseKind]) -> VersionToRelease: +def get_version_to_release_from_branch_name(release_kinds: List[ReleaseKind]) -> VersionToRelease: ci_commit_branch = os.environ.get('CI_COMMIT_BRANCH') if ci_commit_branch: @@ -16,7 +16,7 @@ def get_version_to_release(release_kinds: List[ReleaseKind]) -> VersionToRelease return get_version_to_release_from_str(ci_commit_branch, release_kinds) else: error('This is not a release branch! This script should not be run! The name of the branch must be ' - 'release-X.X.X') + f'{BASE_PREFIX}X.X.X or just {BASE_PREFIX} if you want to use version from command') error('Check valhalla configration and manual !') exit(-1) else: diff --git a/valhalla/commit/before.py b/valhalla/commit/before.py index e4f6a68..2efc379 100644 --- a/valhalla/commit/before.py +++ b/valhalla/commit/before.py @@ -3,6 +3,7 @@ from valhalla.common.logger import error, info from valhalla.common.resolver import resolve +from valhalla.version.version_to_release import BASE_PREFIX def execute(commands: List[str]): @@ -22,12 +23,12 @@ def execute(commands: List[str]): f"Executing command {command} finished with code: {result.returncode} , valhalla cannot \n" + f"continue releasing process! Fix it and retry!\n" + f"Delete this branch (and tag if created), fix your main branch \n" - f"and create release-* branch again, this simplifies fixes and reduce mistakes \n" + f"and create {BASE_PREFIX}* branch again, this simplifies fixes and reduce mistakes \n" f"-----------------------------------------------------------\n\n") exit(result.returncode) else: info(f"Successfully executed command: '{command}'") - except subprocess.CalledProcessError as e: # run is called with check=False, so it should not be called + except subprocess.CalledProcessError as e: # run is called with check=False, so it should not be called error(f"Error executing command '{e.cmd}': {e.stderr}") for line in e.output.splitlines(): error("------" + line) @@ -35,4 +36,3 @@ def execute(commands: List[str]): except Exception as e: error(f"Exception occurred: {str(e)} during executing command") exit(1) - diff --git a/valhalla/common/get_config.py b/valhalla/common/get_config.py index a42ad49..c3edced 100644 --- a/valhalla/common/get_config.py +++ b/valhalla/common/get_config.py @@ -1,10 +1,22 @@ from typing import List + from yaml import safe_load -from valhalla.common.logger import info, error, warn +from valhalla.common.logger import info, error from valhalla.extends.valhalla_extends import ValhallaExtends +class VersionConfig: + def __init__(self, from_command: str): + self.from_command = from_command + + def __repr__(self): + return f"\n" \ + f" VersionConfig( \n" \ + f" from_command={self.from_command} \n" \ + f" )" + + class MergeRequestConfig: def __init__(self, enabled: bool, target_branch: str, title: str, description: str, reviewers: List[str]): self.enabled = enabled @@ -80,7 +92,8 @@ def __repr__(self): class ReleaseConfig: - def __init__(self, description_config: ReleaseDescriptionConfig, milestones: List[str], name: str, assets_config: ReleaseAssetsConfig): + def __init__(self, description_config: ReleaseDescriptionConfig, milestones: List[str], name: str, + assets_config: ReleaseAssetsConfig): self.description_config = description_config self.milestones = milestones self.name = name @@ -94,7 +107,8 @@ def __repr__(self): f" release_title={self.name} \n" \ f" assets_config={self.assets_config} \n" \ f" )" - + + class TagConfig: def __init__(self, name: str): self.name = name @@ -108,6 +122,7 @@ def __repr__(self): class Config: def __init__(self, + version_config: VersionConfig, variables: dict, git_host: str, commit_before_release: CommitConfig, @@ -115,6 +130,7 @@ def __init__(self, tag_config: TagConfig, commit_after_release: CommitConfig, merge_request: MergeRequestConfig): + self.version_config = version_config self.variables = variables self.git_host = git_host self.commit_before_release = commit_before_release @@ -125,6 +141,7 @@ def __init__(self, def __repr__(self): return f" Config( \n" \ + f" version_config={self.version_config} \n" \ f" variables={self.variables} \n" \ f" git_host={self.git_host} \n" \ f" commit_before_release={self.commit_before_release} \n" \ @@ -147,6 +164,9 @@ def get_config(path: str) -> Config: info("yml_dict to read config from: " + str(yml_dict)) + version_dict = get_from_dict(yml_dict, 'version', False) + version = get_version_part(version_dict) + variables = get_from_dict(yml_dict, 'variables', False) git_host = get_from_dict(yml_dict, 'git_host', True) @@ -167,6 +187,7 @@ def get_config(path: str) -> Config: merge_request = get_merge_request_part(merge_request_dict) config = Config( + version, variables, git_host, commit_before_release, @@ -185,6 +206,14 @@ def get_config(path: str) -> Config: exit(-1) +def get_version_part(version_dict) -> VersionConfig | None: + if version_dict is None or version_dict == {}: + return VersionConfig("") + + from_command = get_from_dict(version_dict, 'from_command', False) + return VersionConfig(from_command) + + def get_commit_part(commit_config_dict: dict) -> CommitConfig | None: if commit_config_dict is None: return None @@ -201,9 +230,9 @@ def get_commit_part(commit_config_dict: dict) -> CommitConfig | None: def get_release_config_part(release_config_dict: dict) -> ReleaseConfig: - description_dict = release_config_dict['description'] + description_dict = get_from_dict(release_config_dict, 'description', False) description = get_release_description_config_part(description_dict) - + milestones = get_from_dict(release_config_dict, 'milestones', False) name = get_from_dict(release_config_dict, 'name', False) @@ -214,7 +243,9 @@ def get_release_config_part(release_config_dict: dict) -> ReleaseConfig: def get_release_description_config_part(description_dict: dict) -> ReleaseDescriptionConfig: - from_command = description_dict['from_command'] + if description_dict is None or description_dict == {}: + return ReleaseDescriptionConfig("") + from_command = get_from_dict(description_dict, 'from_command', True) return ReleaseDescriptionConfig(from_command) @@ -222,7 +253,7 @@ def get_release_assets_config_part(assets_dict: dict) -> ReleaseAssetsConfig: if assets_dict is None: return ReleaseAssetsConfig([]) - links_dict = assets_dict['links'] + links_dict = get_from_dict(assets_dict, 'links', False) links = get_release_assets_links_config_part(links_dict) return ReleaseAssetsConfig(links) @@ -243,6 +274,7 @@ def get_release_assets_links_config_part(links_list_of_dicts: List[dict]) -> Lis return result + def get_tag_config_part(tag_config_dict: dict) -> TagConfig | None: if tag_config_dict is None: return None @@ -253,7 +285,7 @@ def get_tag_config_part(tag_config_dict: dict) -> TagConfig | None: def get_merge_request_part(merge_request_dict: dict) -> MergeRequestConfig: if merge_request_dict is None: - return MergeRequestConfig(False, None, None, None, None) + return MergeRequestConfig(False, "", "", "", []) enabled = get_from_dict(merge_request_dict, 'enabled', True) merge_request_other_options_required = enabled @@ -266,14 +298,14 @@ def get_merge_request_part(merge_request_dict: dict) -> MergeRequestConfig: reviewers = get_from_dict(merge_request_dict, 'reviewers', False) return MergeRequestConfig(enabled, target_branch, title, description, reviewers) + def get_from_dict(d: dict, key: str, required: bool): try: return d[key] - except KeyError as e: + except KeyError as _: if required: - print(e) error(f"Missing required {key} in valhalla.yml!") - exit(1) + raise RuntimeError(f"Missing required {key} in valhalla.yml!") else: info(f"Could not find optional filed: {key}") return None diff --git a/valhalla/main.py b/valhalla/main.py index 21b5772..57e9d96 100644 --- a/valhalla/main.py +++ b/valhalla/main.py @@ -1,23 +1,36 @@ from valhalla.ci_provider.gitlab.common import get_author from valhalla.ci_provider.get_token import get_valhalla_token -from valhalla.ci_provider.gitlab.get_version import get_version_to_release +from valhalla.ci_provider.gitlab.get_version import get_version_to_release_from_branch_name from valhalla.ci_provider.gitlab.merge_request import GitLabValhallaMergeRequest from valhalla.ci_provider.gitlab.release import GitLabValhallaRelease from valhalla.commit import before from valhalla.commit.commit import GitRepository from valhalla.common.get_config import get_config, CommitConfig, MergeRequestConfig, Config -from valhalla.common.logger import info, init_logger +from valhalla.common.logger import info, error, init_logger from valhalla.version.release_command import get_version_to_release_from_command from valhalla.common.resolver import init_str_resolver, init_str_resolver_custom_variables, resolve from valhalla.release.assets import Assets from valhalla.release.description import Description from valhalla.version.version_to_release import get_release_kinds, VersionToRelease +from valhalla.version.version_to_release import BASE_PREFIX def start(): - print(f'Release the Valhalla!') + info(f'Release the Valhalla!') version_to_release = __version_to_release() + config = get_config(version_to_release.get_config_file_path()) + + if version_to_release.is_version_empty(): + version_to_release.from_config(config) + + if version_to_release.is_version_empty(): + error(f"Version to release is empty, exiting! Create branch with name {BASE_PREFIX}X.X.X or just {BASE_PREFIX}\n" + f"and define in valhalla.yml version to release. You can also you VALHALLA_RELEASE_CMD to define it.\n" + f"Check https://logchange.dev/tools/valhalla/ for more info.\n") + exit(-1) + + info(f'Project version that is going to be released: {version_to_release.version_number_to_release}') token = get_valhalla_token() author = get_author() @@ -25,7 +38,6 @@ def start(): init_str_resolver(version_to_release.version_number_to_release, token, author) - config = get_config(version_to_release.get_config_file_path()) init_str_resolver_custom_variables(config.variables) commit(config.commit_before_release, token) @@ -43,7 +55,7 @@ def __version_to_release() -> VersionToRelease: version_to_release = get_version_to_release_from_command(release_kinds) if version_to_release is None: - version_to_release = get_version_to_release(release_kinds) + version_to_release = get_version_to_release_from_branch_name(release_kinds) return version_to_release diff --git a/valhalla/release/description.py b/valhalla/release/description.py index fef57e9..a96b7e6 100644 --- a/valhalla/release/description.py +++ b/valhalla/release/description.py @@ -32,5 +32,7 @@ def __get_from_command(self): return stdout except subprocess.CalledProcessError as e: error(f"Error executing command '{e.cmd}': {e.stderr}") + return "error, check valhalla release logs" except Exception as e: error(f"Error occurred: {str(e)}") + return "error, check valhalla release logs" diff --git a/valhalla/version/release_command.py b/valhalla/version/release_command.py index c28209d..03ec486 100644 --- a/valhalla/version/release_command.py +++ b/valhalla/version/release_command.py @@ -16,7 +16,7 @@ def get_version_to_release_from_command(release_kinds: List[ReleaseKind]) -> Ver return get_version_to_release_from_str(command, release_kinds) else: error( - 'This is not a correct release command! VALHALLA_RELEASE_CMD should start from release- prefix, f.e. release-1.2.3') + f'This is not a correct release command! VALHALLA_RELEASE_CMD should start from {BASE_PREFIX} prefix, f.e. {BASE_PREFIX}1.2.3') exit(-1) else: info("Cloud not find VALHALLA_RELEASE_CMD environment variable, skipping and going to check branch name") diff --git a/valhalla/version/version_to_release.py b/valhalla/version/version_to_release.py index 7cf3ea4..0c793ab 100644 --- a/valhalla/version/version_to_release.py +++ b/valhalla/version/version_to_release.py @@ -1,6 +1,6 @@ -import re import os - +import re +import subprocess from typing import List from valhalla.common.logger import info, error @@ -28,6 +28,33 @@ def __init__(self, version_number_to_release: str, release_kind: ReleaseKind): def get_config_file_path(self): return self.release_kind.path + "/" + self.release_kind.filename + def is_version_empty(self): + return self.version_number_to_release is None or self.version_number_to_release.strip() == "" + + def from_config(self, config): + if config.version is None or config.version.from_command is None or config.version.from_command.strip() == "": + info("Version is not specified in valhalla.yml, skipping") + return + else: + self.__get_version_from_command(config.version.from_command) + pass + + def __get_version_from_command(self, from_command: str): + try: + result = subprocess.run(from_command, shell=True, check=True, capture_output=True, text=True) + stdout = result.stdout + stderr = result.stderr + if stdout: + info(f"Output for version from command '{from_command}':\n{stdout}") + if stderr: + error(f"Error output for version from command '{from_command}':\n{stderr}") + + self.version_number_to_release = stdout + except subprocess.CalledProcessError as e: + error(f"Error executing command '{e.cmd}': {e.stderr}") + except Exception as e: + error(f"Error occurred: {str(e)}") + def get_release_kinds(path: str) -> List[ReleaseKind]: info(f"Searching for valhalla*.yml files in: {path}") @@ -72,14 +99,14 @@ def get_version_to_release_from_str(value: str, release_kinds: List[ReleaseKind] return __matched(value, prefix, release_kind) info(f"{value} doesn't match specific release kind, checking main kind") - # now if specific release kind not found we search for main + # now if specific release kind not found, we search for the main for release_kind in release_kinds: if release_kind.suffix == "": prefix = BASE_PREFIX if value.startswith(prefix): return __matched(value, prefix, release_kind) - __no_matching_release_kind(value, release_kinds) + return __no_matching_release_kind(value, release_kinds) def __get_branch_prefix(release_kind: ReleaseKind) -> str: @@ -94,7 +121,6 @@ def __matched(value: str, prefix: str, release_kind: ReleaseKind) -> VersionToRe info( f"Branch name or VALHALLA_RELEASE_CMD is {value} and has prefix {prefix} and matches with release kind {release_kind}") project_version = value[len(prefix):] - info(f'Project version that is going to be released: {project_version}') return VersionToRelease(project_version, release_kind)