Exemple #1
0
def bump(latest):
    merge_request_id = extract_merge_request_id_from_commit()
    labels = retrieve_labels_from_merge_request(merge_request_id)
    new_version = None
    print('MR Labels', labels)
    if "bump-major" in labels:
        print('Bump major')
        new_version = semver.bump_major(latest)
    elif "bump-minor" in labels:
        print('Bump minor')
        new_version = semver.bump_minor(latest)
    elif "bump-patch" in labels:
        print('Bump patch')
        new_version = semver.bump_patch(latest)
    elif "finalize-rc" in labels:
        print('Finalize rc')
        new_version = semver.finalize_version(latest)
    elif "bump-build":
        print('Bump build')
        new_version = semver.bump_build(new_version if new_version else latest)

    if "bump-rc" in labels and not "finalize-rc" in labels:
        print('Bump rc')
        new_version = semver.bump_prerelease(
            new_version if new_version else latest)
        new_version = semver.bump_build(new_version)

    return new_version if new_version else latest
Exemple #2
0
def update_version() -> str:
    """
    Retrieves the current version from github, bumps the version, and updates the values in service_config.json before
    committing to the dst branch
    :return: The new version.
    """
    cur_version = get_current_version(args.stage)

    if args.stage == "prod":
        prv_version = get_current_version(stage='staging')
        new_version = semver.finalize_version(prv_version)
    elif args.stage == "staging":
        prv_version = get_current_version(stage='integration')
        assert '-integration' in prv_version
        new_version = prv_version.replace(
            '-integration', '-rc')  # don't bump the version number
    else:
        new_version = getattr(semver, f'bump_{args.release}')(str(cur_version))
        new_version = new_version if semver.parse_version_info(new_version).prerelease \
            else semver.bump_prerelease(new_version, token='integration')

    if cur_version == new_version:
        print("Nothing to promote")
        exit(0)
    else:
        print(f"Upgrading: {cur_version} -> {new_version}")
        return new_version
Exemple #3
0
 def test_from_ancestor_version(self):
     bumped = "4.5.7-dev.1"
     old, new, updates = self.call(
         persist_from=[Constants.FROM_VCS_PREVIOUS_VERSION])
     self.assertEqual(
         updates,
         {
             "VERSION": bumped,
             "VERSION_AGAIN": bumped,
             "STRICT_VERSION": semver.finalize_version(bumped),
         },
     )
Exemple #4
0
 def test_from_latest_of_all_time_release(self):
     bumped = "4.5.6"
     old, new, updates = self.call(
         persist_from=[Constants.FROM_VCS_LATEST_RELEASE])
     self.assertEqual(
         updates,
         {
             "VERSION": bumped,
             "VERSION_AGAIN": bumped,
             "STRICT_VERSION": semver.finalize_version(bumped),
         },
     )
Exemple #5
0
def get_final_version_string(release_mode, version):
    """Generates update dictionary entries for the version string"""
    production_version = semver.finalize_version(version)
    updates = {}
    if release_mode:
        updates[Constants.RELEASE_FIELD] = config.RELEASED_VALUE
        updates[Constants.VERSION_FIELD] = production_version
        updates[Constants.VERSION_STRICT_FIELD] = production_version
    else:
        updates[Constants.VERSION_FIELD] = version
        updates[Constants.VERSION_STRICT_FIELD] = production_version
    return updates
Exemple #6
0
def test_should_finalize_version():
    assert finalize_version('1.2.3') == '1.2.3'
    assert finalize_version('1.2.3-rc.5') == '1.2.3'
    assert finalize_version('1.2.3+build.2') == '1.2.3'
    assert finalize_version('1.2.3-rc.1+build.5') == '1.2.3'
    assert finalize_version('1.2.3-alpha') == '1.2.3'
    assert finalize_version('1.2.0') == '1.2.0'
Exemple #7
0
    def get_release_version(cls, version_file, next_git_tag_version):
        file_version = cls.read_version_file(version_file)
        if not file_version:
            return next_git_tag_version

        finalized_file_version = semver.finalize_version(file_version)
        result = semver.max_ver(finalized_file_version, next_git_tag_version)

        logging.info(
            'Release version is %s (max of git-version `%s` and '
            'file-version `%s`)', result, next_git_tag_version,
            finalized_file_version)

        return result
Exemple #8
0
    def bump(self, what):
        if self.bumped:
            debug('version already bumped, don\'t do it twice')
            return
        if '=' in what:
            what, what_name = what.split('=')
        else:
            what, what_name = what, None

        old = semver.format_version(*self.version)
        types = ['major', 'minor', 'patch', 'prerelease', 'build']
        if what == "last":
            # count how many non None parts there are
            parts = len([k for k in self.version if k is not None])
            new = semver_bump[parts - 1](old)
        elif what == 'finalize':
            new = semver.finalize_version(old)
        elif what in types:
            if what_name:
                if what == 'prerelease':  # to enable x.y.z-beta.1 -> x.y.z->rc.0
                    old_final = semver.finalize_version(old)
                    new = semver_bump[types.index(what)](old_final, what_name)
                elif what in ['major', 'minor',
                              'patch']:  # to go from 0.1.0 -> 0.2.0-beta1
                    new = semver_bump[types.index(what)](old)
                    new = semver.bump_prerelease(new, what_name)
                else:
                    new = semver_bump[types.index(what)](old, what_name)
            else:
                new = semver_bump[types.index(what)](old)
        else:
            error("unknown what: {}", what)
        info("version was {}, is now {}", old, new)
        self.version = [
            k for k in semver.parse_version_info(new) if k is not None
        ]
        self.bumped = True
Exemple #9
0
def next_semver(major, minor, prerelease=None, versions=None):
    # type: (int, int, str, list) -> str
    major = int(major)
    minor = int(minor)

    def stuff_we_care_about(v):
        """
    we only care about stuff that:
    - has our same major+minor version
    - is a release OR has our same prerelease token
    """
        if v.major != major:
            return False
        if v.minor != minor:
            return False
        if not prerelease:
            return True
        if v.prerelease is None:
            return True
        prefix = prerelease + "."
        if v.prerelease.startswith(prefix):
            # also needs to end in an int for us to care about it
            return bool(re.match("^\d+$", v.prerelease[len(prefix):]))
        return False

    semvers = [
        VersionInfo.parse(v[1:] if v.startswith("v") else v)
        for v in versions or []
    ]
    semvers = filter(stuff_we_care_about, semvers)
    if semvers:
        latest = sorted(semvers)[-1]
        version = str(latest)
        # do we bump patch?
        if not latest.prerelease:
            version = semver.bump_patch(version)
        # is this a final version?
        if prerelease:
            version = semver.bump_prerelease(version, prerelease)
        else:
            version = semver.finalize_version(version)
    else:
        # nothing exists on the current minor version so create a new one
        version = "%s.%s.0" % (major, minor)
        if prerelease:
            version += "-%s.1" % prerelease
    return "v" + version
Exemple #10
0
def get_semver_from_source(data):
    """Given a dictionary of all version data available, determine the current version"""
    # get the not-none values from data
    known = {
        key: data.get(alias)
        for key, alias in config._forward_aliases.items()
        if data.get(alias) is not None
    }
    _LOG.debug("valid, mapped keys: %r", known)

    # prefer the non-strict field, if available, because it retains more information
    potentials = [
        known.get(Constants.VERSION_FIELD, None),
        known.get(Constants.VERSION_STRICT_FIELD, None),
    ]

    # build from components, if they're defined
    from_components = {k: known.get(k) for k in SemVerSigFig if k in known}
    try:
        potentials.append(str(semver.VersionInfo(**from_components)))
    except TypeError:
        # we didn't have enough components
        pass

    versions = [
        potential for potential in potentials if from_text_or_none(potential)
    ]
    release_versions = {
        semver.finalize_version(version)
        for version in versions
    }

    if len(release_versions) > 1:
        raise ValueError(
            "conflicting versions within project: %s\nkeys were: %r" %
            (release_versions, known))

    if not versions:
        _LOG.debug("key pairs found: \n%r", known)
        raise ValueError("could not find existing semver")

    result = None
    if versions:
        result = versions[0]
    _LOG.info("latest version found in source: %r", result)
    return result
def postrelease(ctx, version):
    """
    Finalise the release
    Running this task will:
     - bump the version to the next dev version
     - push changes to main
    """
    clean_version = semver.finalize_version(version)
    if clean_version == version:
        # last release was full release, bump patch
        new_version = semver.bump_patch(version) + "-dev"
    else:
        # last release was prerelease, revert to dev version
        new_version = clean_version + "-dev"

    info(f"Bumping version numbers to {new_version} and committing")
    set_source_version(new_version)
Exemple #12
0
 def test_to_tag(self):
     """writes a tag in to git
     """
     bumped = "5.0.0-dev.1"
     old, new, updates = self.call(
         persist_from=[Constants.FROM_VCS_LATEST_VERSION],
         persist_to=[Constants.TO_VCS],
         bump="major",
     )
     self.addCleanup(subprocess.check_call,
                     shlex.split("git tag --delete release/5.0.0-dev.1"))
     self.assertEqual(
         updates,
         {
             "VERSION": bumped,
             "VERSION_AGAIN": bumped,
             "STRICT_VERSION": semver.finalize_version(bumped),
         },
     )
     version = auto_version_tool.get_dvcs_repo_latest_version_semver()
     self.assertEqual(
         dict(version._asdict()),
         dict(major=5, minor=0, patch=0, build=None, prerelease="dev.1"),
     )
Exemple #13
0
def update_version(repo, src, dst) -> str:
    """
    Retrieves the current version from github, bumps the version, and updates the values in service_config.json before
    committing to the dst branch
    :return: The new version.
    """
    cur_version = get_current_version(repo, cmd_args.stage)

    if src == "prod":
        prv_version = get_current_version(repo, _stage=dst)
        _new_version = semver.finalize_version(prv_version)
    else:
        _new_version = getattr(semver,
                               f"bump_{cmd_args.release}")(str(cur_version))
        _new_version = (_new_version
                        if semver.parse_version_info(_new_version).prerelease
                        else semver.bump_prerelease(_new_version, token="rc"))

    if cur_version == _new_version:
        print("Nothing to promote")
        exit(0)
    else:
        print(f"Upgrading: {cur_version} -> {_new_version}")
        return _new_version
Exemple #14
0
def test_should_finalize_version(version, expected):
    assert finalize_version(version) == expected
def main(set_to=None,
         commit_count_as=None,
         release=None,
         bump=None,
         lock=None,
         enable_file_triggers=None,
         incr_from_release=None,
         config_path=None,
         persist_from=None,
         persist_to=None,
         dry_run=None,
         **extra_updates):
    """Main workflow.

    Load config from cli and file
    Detect "bump triggers" - things that cause a version increment
    Find the current version
    Create a new version
    Write out new version and any other requested variables

    :param set_to: explicitly set semver to this version string
    :param commit_count_as: uses the commit count for the specified sigfig
    :param release: marks with a production flag
                just sets a single flag as per config
    :param bump: string indicating major/minor/patch
                more significant bumps will zero the less significant ones
    :param lock: locks the version string for the next call to autoversion
                lock only removed if a version bump would have occurred
    :param enable_file_triggers: whether to enable bumping based on file triggers
                bumping occurs once if any file(s) exist that match the config
    :param incr_from_release: dynamically generates the bump by comparing the
                proposed triggers for the current version, with the significance of the previous release
                to ensure e.g. adding new major changes to a prerelease should probably trigger a new major version
                specifically, the bump is:
                if (max trigger sigfig) > (max sigfig since release):
                    (max trigger sigfig)
                else
                    (min trigger sigfig)
    :param config_path: path to config file
    :param extra_updates:
    :return:
    """
    updates = {}
    persist_to = persist_to or [Constants.TO_SOURCE]
    persist_from = persist_from or [Constants.FROM_SOURCE]
    load_config(config_path)

    all_data = {}
    last_release_semver = None
    if incr_from_release:
        if (Constants.FROM_VCS_PREVIOUS_VERSION
                in persist_from) or (Constants.FROM_VCS_PREVIOUS_RELEASE
                                     in persist_from):
            last_release_semver = get_dvcs_previous_release_semver()
        else:
            last_release_semver = get_dvcs_repo_latest_release_semver()
    _LOG.debug("found previous full release: %s", last_release_semver)
    current_semver = get_current_version(persist_from)
    release_commit = get_dvcs_commit_for_version(current_semver, persist_from)
    triggers = get_all_triggers(bump, enable_file_triggers, release_commit)
    updates.update(get_lock_behaviour(triggers, all_data, lock))
    updates.update(get_dvcs_info())

    new_version = current_semver
    if set_to:
        _LOG.debug("setting version directly: %s", set_to)
        # parse it - validation failure will raise a ValueError
        new_version = semver.parse_version_info(set_to)
        if not lock:
            warnings.warn(
                "After setting version manually, does it need locking for a CI flow, to avoid an extraneous increment?",
                UserWarning,
            )
    elif triggers:
        # use triggers if the version is not set directly
        _LOG.debug("auto-incrementing version (triggers: %s)", triggers)
        overrides = get_overrides(updates, commit_count_as)
        new_version = utils.make_new_semver(current_semver,
                                            last_release_semver, triggers,
                                            **overrides)

    release_string = semver.finalize_version(str(new_version))
    release_version = semver.parse_version_info(release_string)
    if release:
        new_version = release_version
        updates[Constants.RELEASE_FIELD] = config.RELEASED_VALUE
        updates[Constants.VERSION_FIELD] = release_string
        updates[Constants.VERSION_STRICT_FIELD] = release_string
    else:
        updates[Constants.VERSION_FIELD] = str(new_version)
        updates[Constants.VERSION_STRICT_FIELD] = release_string

    # write out the individual parts of the version
    updates.update(new_version._asdict())

    # only rewrite a field that the user has specified in the configuration
    source_file_updates = {
        native: updates[key]
        for native, key in config.key_aliases.items() if key in updates
    }

    # finally, add in commandline overrides
    source_file_updates.update(extra_updates)

    if not dry_run:
        if Constants.TO_SOURCE in persist_to:
            write_targets(config.targets, **source_file_updates)

        if Constants.TO_VCS in persist_to:
            add_dvcs_tag(updates[Constants.VERSION_FIELD])
    else:
        _LOG.warning("dry run: no changes were made")

    return str(current_semver), str(new_version), source_file_updates
Exemple #16
0
 def finalize(self):
     return semver.finalize_version(str(self))
Exemple #17
0
#!/usr/bin/python3

import semver
import functools
import subprocess


def isValid(tag):
    if not tag.startswith('v'):
        return False
    tag = tag[1:]
    try:
        return semver.match(tag, ">=0.0.0")
    except:
        return False


tags = subprocess.run(["git", "tag", "-l"], capture_output=True,
                      text=True).stdout.split()
tags = map(lambda tag: tag[1:], filter(lambda tag: isValid(tag), tags))
tags = sorted(tags, key=functools.cmp_to_key(semver.compare))
stable_tags = list(
    filter(lambda tag: tag == semver.finalize_version(tag), tags))
print(stable_tags[-1])
print(tags[-1])
Exemple #18
0
def make(ctx, check, version, initial_release, skip_sign, sign_only):
    """Perform a set of operations needed to release a single check:

    \b
      * update the version in __about__.py
      * update the changelog
      * update the requirements-agent-release.txt file
      * update in-toto metadata
      * commit the above changes

    You can release everything at once by setting the check to `all`.

    \b
    If you run into issues signing:
    \b
      - Ensure you did `gpg --import <YOUR_KEY_ID>.gpg.pub`
    """
    # Import lazily since in-toto runs a subprocess to check for gpg2 on load
    from ..signing import update_link_metadata

    root = get_root()
    releasing_all = check == 'all'

    valid_checks = get_valid_checks()
    if not releasing_all and check not in valid_checks:
        abort('Check `{}` is not an Agent-based Integration'.format(check))

    # don't run the task on the master branch
    if get_current_branch() == 'master':
        abort(
            'This task will commit, you do not want to add commits to master directly'
        )

    if releasing_all:
        if version:
            abort('You cannot bump every check to the same version')
        checks = sorted(valid_checks)
    else:
        checks = [check]

    if initial_release:
        version = '1.0.0'

    for check in checks:
        if sign_only:
            break
        elif initial_release and check in BETA_PACKAGES:
            continue

        # Initial releases will only bump if not already 1.0.0 so no need to always output
        if not initial_release:
            echo_success('Check `{}`'.format(check))

        if version:
            # sanity check on the version provided
            cur_version = get_version_string(check)

            if version == 'final':
                # Remove any pre-release metadata
                version = finalize_version(cur_version)
            else:
                # Keep track of intermediate version bumps
                prev_version = cur_version
                for method in version.split(','):
                    # Apply any supported version bumping methods. Chaining is required for going
                    # from mainline releases to development releases since e.g. x.y.z > x.y.z-rc.A.
                    # So for an initial bug fix dev release you can do `fix,rc`.
                    if method in VERSION_BUMP:
                        version = VERSION_BUMP[method](prev_version)
                        prev_version = version

            p_version = parse_version_info(version)
            p_current = parse_version_info(cur_version)
            if p_version <= p_current:
                if initial_release:
                    continue
                else:
                    abort('Current version is {}, cannot bump to {}'.format(
                        cur_version, version))
        else:
            cur_version, changelog_types = ctx.invoke(changes,
                                                      check=check,
                                                      dry_run=True)
            if not changelog_types:
                echo_warning('No changes for {}, skipping...'.format(check))
                continue
            bump_function = get_bump_function(changelog_types)
            version = bump_function(cur_version)

        if initial_release:
            echo_success('Check `{}`'.format(check))

        # update the version number
        echo_info('Current version of check {}: {}'.format(check, cur_version))
        echo_waiting('Bumping to {}... '.format(version), nl=False)
        update_version_module(check, cur_version, version)
        echo_success('success!')

        # update the CHANGELOG
        echo_waiting('Updating the changelog... ', nl=False)
        # TODO: Avoid double GitHub API calls when bumping all checks at once
        ctx.invoke(changelog,
                   check=check,
                   version=version,
                   old_version=cur_version,
                   initial=initial_release,
                   quiet=True,
                   dry_run=False)
        echo_success('success!')

        commit_targets = [check]

        # update the global requirements file
        if check not in NOT_CHECKS:
            commit_targets.append(AGENT_REQ_FILE)
            req_file = os.path.join(root, AGENT_REQ_FILE)
            echo_waiting('Updating the Agent requirements file... ', nl=False)
            update_agent_requirements(
                req_file, check, get_agent_requirement_line(check, version))
            echo_success('success!')

        echo_waiting('Committing files...')

        # commit the changes.
        # do not use [ci skip] so releases get built https://docs.gitlab.com/ee/ci/yaml/#skipping-jobs
        msg = '[Release] Bumped {} version to {}'.format(check, version)
        git_commit(commit_targets, msg)

        if not initial_release:
            # Reset version
            version = None

    if sign_only or not skip_sign:
        echo_waiting('Updating release metadata...')
        echo_info(
            'Please touch your Yubikey immediately after entering your PIN!')
        commit_targets = update_link_metadata(checks)

        git_commit(commit_targets, '[Release] Update metadata', force=True)

    # done
    echo_success(
        'All done, remember to push to origin and open a PR to merge these changes on master'
    )
Exemple #19
0
def make(ctx, checks, version, initial_release, skip_sign, sign_only, exclude,
         allow_master):
    """Perform a set of operations needed to release checks:

    \b
      * update the version in `__about__.py`
      * update the changelog
      * update the `requirements-agent-release.txt` file
      * update in-toto metadata
      * commit the above changes

    You can release everything at once by setting the check to `all`.

    \b
    If you run into issues signing:
    \b
      - Ensure you did `gpg --import <YOUR_KEY_ID>.gpg.pub`
    """
    # Import lazily since in-toto runs a subprocess to check for gpg2 on load
    from ...signing import update_link_metadata, YubikeyException

    releasing_all = 'all' in checks

    valid_checks = get_valid_checks()
    if not releasing_all:
        for check in checks:
            if check not in valid_checks:
                abort(f'Check `{check}` is not an Agent-based Integration')

    if releasing_all:
        if version:
            abort('You cannot bump every check to the same version')
        checks = sorted(valid_checks)
    else:
        checks = sorted(checks)

    if exclude:
        for check in exclude.split(','):
            with suppress(ValueError):
                checks.remove(check)

    if initial_release:
        version = '1.0.0'

    repo_choice = ctx.obj['repo_choice']
    core_workflow = repo_choice == 'core'

    if get_current_branch() == 'master' and (core_workflow
                                             or not allow_master):
        abort(
            'Please create a release branch, you do not want to commit to master directly.'
        )

    # Signing is done by a pipeline in a separate commit
    if not core_workflow and not sign_only:
        skip_sign = True

    # Keep track of the list of checks that have been updated.
    updated_checks = []
    for check in checks:
        if sign_only:
            updated_checks.append(check)
            continue
        elif initial_release and check in BETA_PACKAGES:
            continue

        # Initial releases will only bump if not already 1.0.0 so no need to always output
        if not initial_release:
            echo_success(f'Check `{check}`')

        if version:
            # sanity check on the version provided
            cur_version = get_version_string(check)

            if version == 'final':
                # Remove any pre-release metadata
                version = finalize_version(cur_version)
            else:
                # Keep track of intermediate version bumps
                prev_version = cur_version
                for method in version.split(','):
                    # Apply any supported version bumping methods. Chaining is required for going
                    # from mainline releases to development releases since e.g. x.y.z > x.y.z-rc.A.
                    # So for an initial bug fix dev release you can do `fix,rc`.
                    if method in VERSION_BUMP:
                        version = VERSION_BUMP[method](prev_version)
                        prev_version = version

            p_version = parse_version_info(version)
            p_current = parse_version_info(cur_version)
            if p_version <= p_current:
                if initial_release:
                    continue
                else:
                    abort(
                        f'Current version is {cur_version}, cannot bump to {version}'
                    )
        else:
            cur_version, changelog_types = ctx.invoke(changes,
                                                      check=check,
                                                      dry_run=True)
            if not changelog_types:
                echo_warning(f'No changes for {check}, skipping...')
                continue
            bump_function = get_bump_function(changelog_types)
            version = bump_function(cur_version)

        if initial_release:
            echo_success(f'Check `{check}`')

        # update the version number
        echo_info(f'Current version of check {check}: {cur_version}')
        echo_waiting(f'Bumping to {version}... ', nl=False)
        update_version_module(check, cur_version, version)
        echo_success('success!')

        # update the CHANGELOG
        echo_waiting('Updating the changelog... ', nl=False)
        # TODO: Avoid double GitHub API calls when bumping all checks at once
        ctx.invoke(
            changelog,
            check=check,
            version=version,
            old_version=cur_version,
            initial=initial_release,
            quiet=True,
            dry_run=False,
        )
        echo_success('success!')

        commit_targets = [check]
        updated_checks.append(check)
        # update the list of integrations to be shipped with the Agent
        if repo_choice == 'core' and check not in NOT_CHECKS:
            req_file = get_agent_release_requirements()
            commit_targets.append(os.path.basename(req_file))
            echo_waiting('Updating the Agent requirements file... ', nl=False)
            update_agent_requirements(
                req_file, check, get_agent_requirement_line(check, version))
            echo_success('success!')

        echo_waiting('Committing files...')

        # commit the changes.
        # do not use [ci skip] so releases get built https://docs.gitlab.com/ee/ci/yaml/#skipping-jobs
        msg = f'[Release] Bumped {check} version to {version}'
        git_commit(commit_targets, msg, update=True)

        if not initial_release:
            # Reset version
            version = None

    if sign_only or not skip_sign:
        if not updated_checks:
            abort('There are no new checks to sign and release!')
        echo_waiting('Updating release metadata...')
        if core_workflow:
            echo_info(
                'Please touch your Yubikey immediately after entering your PIN!'
            )
        try:
            commit_targets = update_link_metadata(updated_checks,
                                                  core_workflow=core_workflow)
            git_commit(commit_targets, '[Release] Update metadata', force=True)
        except YubikeyException as e:
            abort(f'A problem occurred while signing metadata: {e}')

    # done
    echo_success(
        'All done, remember to push to origin and open a PR to merge these changes on master'
    )
Exemple #20
0
def test_should_finalize_version(version, expected):
    assert finalize_version(version) == expected