Ejemplo n.º 1
0
def publish_tag(version, ref, clone_dir):
    ctx_obj = click.get_current_context().obj
    ctx_obj.version = version
    ref = try_context(ctx_obj, ref, "ref", "rc_ref")
    clone_dir = try_context(ctx_obj, clone_dir, "clone_dir", "clone_dir")

    repo = git.Repo(clone_dir)
    repo.create_tag(version, ref, message=version)
    repo.remotes.origin.push(version)
    logger.info(
        "Tag '{tag}' successfully pushed to repository.".format(tag=version))
Ejemplo n.º 2
0
def create_release(repo, version, bodyfile):
    ctx_obj = click.get_current_context().obj
    # Attempt to read release_notes from context
    # They may have been set by release.generate_release_notes
    try:
        with open(bodyfile, "r") as bodyfile_open:
            release_notes = bodyfile_open.read()
    except IOError as e:
        logger.error("Failed to open release notes file: {f} {e}".format(
            f=bodyfile, e=e))
        click.get_current_context().exit(-1)

    version = try_context(ctx_obj, version, "version", "version")
    # Store version in context for use in notifications
    ctx_obj.version = version
    try:
        release = repo.create_release(
            tag_name=version,
            name=version,
            body=release_notes,
        )
        logger.info("Release {} created.".format(version))
    except github3.models.GitHubError as e:
        logger.error("Error creating release: {}".format(e))
        if e.code == 422:
            logger.error("Failed to create release, tag already exists?")
            raise SystemExit(5)
        if e.code == 404:
            logger.error("Failed to create release, Jenkins lacks repo perms?")
            raise SystemExit(6)
        else:
            raise e
    else:
        ctx_obj.release_url = release.html_url
Ejemplo n.º 3
0
def clone(url, ref, refspec):
    ctx_obj = click.get_current_context().obj
    url = try_context(ctx_obj, url, "url", "ssh_url")
    ctx_obj.rc_ref = ref
    clone_dir = "{o}/{r}".format(
        o=ctx_obj.owner.login,
        r=ctx_obj.name
    )
    ctx_obj.clone_dir = clone_dir
    logging.debug("Cloning {url}@{ref} to {dir}".format(
        url=url, ref=ref, dir=clone_dir))
    repo = git.Repo.init(clone_dir)
    try:
        origin = repo.remotes.origin
        origin.set_url(url)
    except Exception as e:
        origin = repo.create_remote('origin', url)
    repo.git.fetch(["-u", "-v", "-f", url] + refspec.split())
    try:
        getattr(origin.refs, ref).checkout()
    except AttributeError as e:
        logging.error("Ref {ref} not found in {url}".format(
            ref=ref,
            url=url
        ))
        raise e
    logging.debug("Clone complete, current ref: {sha}/{message}".format(
        sha=repo.head.commit.hexsha, message=repo.head.commit.message
    ))
Ejemplo n.º 4
0
def publish_tag(version, ref, clone_dir):
    ctx_obj = click.get_current_context().obj
    ctx_obj.version = version
    ref = try_context(ctx_obj, ref, "ref", "rc_ref")
    clone_dir = try_context(ctx_obj, clone_dir, "clone_dir", "clone_dir")

    repo = git.Repo(clone_dir)
    refsha = repo.rev_parse(ref).hexsha
    tagsha = None
    try:
        tagsha = repo.tags[version].commit.hexsha
    except IndexError:
        pass
    if tagsha is not None:
        # tag exists
        if tagsha == refsha:
            # Existing tag SHA matches ref
            logging.info("Tag '{tag}' already exists and the ref matches,"
                         "nothing to do.".format(tag=version))
        else:
            # Existing tag SHA doesn't not match ref
            if ctx_obj.re_release:
                # Overwrite flag is set so we remove the existing tag
                # and recreate it pointing to ref
                repo.delete_tag(version)
                repo.remote().push(":{tag}".format(tag=version))
                logging.info("Tag '{tag}@{tagsha}' removed.".format(
                    tag=version, tagsha=tagsha))
                _publish_tag(repo, version, ref)
            else:
                # Tag exists, doesn't match and overwrite flag not set, bail.
                raise ReleaseFailureException(
                    "Trying to create tag {tag}@{refsha}"
                    " but {tag}@{tagsha} already exists."
                    "Failing because re release was not specified.".format(
                        tag=version, refsha=refsha, tagsha=tagsha))
    else:
        # Tag doesn't exist, create it.
        _publish_tag(repo, version, ref)
Ejemplo n.º 5
0
def create_release(repo, version, ref, bodyfile):
    ctx_obj = click.get_current_context().obj
    # Attempt to read release_notes from context
    # They may have been set by release.generate_release_notes
    try:
        with open(bodyfile, "r") as bodyfile_open:
            release_notes = bodyfile_open.read()
    except IOError as e:
        logger.error("Failed to open release notes file: {f} {e}".format(
            f=bodyfile, e=e
        ))
        click.get_current_context().exit(-1)

    ref = try_context(ctx_obj, ref, "ref", "rc_ref")
    # Store version in context for use in notifications
    ctx_obj.version = version
    # Create a subject for use by notifications
    ctx_obj.release_subject = "Version {v} of {o}/{r} released".format(
        v=version,
        o=repo.owner.login,
        r=repo.name
    )
    try:
        repo.create_release(
            version,            # tag name
            ref,                # tag reference
            version,            # release name
            release_notes       # release body
        )
        logger.info("Release {} created.".format(version))
    except github3.models.GitHubError as e:
        logger.error("Error creating release: {}".format(e))
        if e.code == 422:
            logger.error("Failed to create release, tag already exists?")
            raise SystemExit(5)
        if e.code == 404:
            logger.error("Failed to create release, Jenkins lacks repo perms?")
            raise SystemExit(6)
        else:
            raise e
Ejemplo n.º 6
0
def create_release(repo, version, bodyfile):
    # Attempt to read release_notes from context
    # They may have been set by release.generate_release_notes
    try:
        with open(bodyfile, "r") as bodyfile_open:
            release_notes = bodyfile_open.read()
    except IOError as e:
        logging.error("Failed to open release notes file: {f} {e}".format(
            f=bodyfile, e=e
        ))
        click.get_current_context().exit(-1)

    version = try_context(repo, version, "version", "version")
    # Store version in context for use in notifications
    repo.version = version
    try:
        release = [r for r in repo.iter_releases() if r.name == version][0]
        repo.release_url = release.html_url
    except IndexError:
        release = _create_release(repo, version, release_notes)
    else:
        if release.body != release_notes:
            if repo.re_release:
                release.delete()  # doesn't remove tag
                # (but tag should have been recreated if necessary by now)
                logging.info("Release {} removed."
                             .format(version))
                release = _create_release(repo, version, release_notes)
            else:
                raise ReleaseFailureException(
                    "Release failed. Github release {version}"
                    " already exists but its release notes"
                    " don't match the release notes"
                    " currently being supplied."
                    .format(version=version)
                )
        else:
            logging.info("Release {} already exists, not creating."
                         .format(version))
Ejemplo n.º 7
0
def update_rc_branch(ctx, mainline, rc):
    """Update rc branch.

    1. Store branch protection data
    2. Delete rc branch
    3. Create rc branch from head of mainline
    4. Enable branch protection with skeleton or previously stored settings.
    """
    repo = ctx.obj
    rc = try_context(repo, rc, "rc", "rc_ref")

    if mainline == rc:
        raise ValueError("Specifying the same branch for mainline and rc"
                         " will result in dataloss. The mainline branch"
                         " will be deleted, then the rc branch will be"
                         " created from the now non-existent mainline branch")

    branch_protection_enabled = False

    # check if branch exists
    if rc in (b.name for b in repo.iter_branches()):
        logger.debug("Branch {} exists".format(rc))
        # rc branch exists
        branch_protection_response = branch_api_request(repo, rc, 'GET')
        if branch_protection_response.status_code == 200:
            # rc branch exists and protection enabled
            logger.debug("Branch {branch} has protection enabled,"
                         " config: {bp_config}".format(
                             branch=rc,
                             bp_config=branch_protection_response.json()))
            branch_protection_enabled = True
            # disable branch protection
            r = branch_api_request(repo, rc, 'DELETE')
            r.raise_for_status()
            logger.debug("Branch protection disabled")
        elif branch_protection_response.status_code == 404:
            # rc branch exists without protection, so it doesn't need
            # to be disabled
            # TODO: create jira issue about unprotected branch?
            pass
        else:
            # failure retrieving branch protection status
            branch_protection_response.raise_for_status()

        # Delete branch
        r = repo._session.request(
            'DELETE', repo.git_refs_urlt.expand(sha="heads/{}".format(rc)))
        r.raise_for_status()
        logger.debug("Branch {} deleted".format(rc))

    mainline_sha = repo.branch(mainline).commit.sha
    logger.debug("Mainline SHA: {}".format(mainline_sha))

    # create rc branch pointing at head of mainline
    repo.create_ref("refs/heads/{}".format(rc), mainline_sha)
    logger.debug("Branch {} created".format(rc))

    # Skeleton branch protection data, used to protect a new branch.
    protection_data = {
        "required_status_checks": None,
        "enforce_admins": True,
        "required_pull_request_reviews": {
            "dismissal_restrictions": {},
            "dismiss_stale_reviews": False,
            "require_code_owner_reviews": False
        },
        "restrictions": None
    }

    # Incorporate previous branch protection data if the branch was
    # protected perviously
    if branch_protection_enabled:
        stored_bpd = branch_protection_response.json()
        protection_data.update(stored_bpd)
        # The github api returns enforce_admins as dict, but requires it to
        # be sent as a bool.
        protection_data['enforce_admins'] \
            = stored_bpd['enforce_admins']['enabled']

    # Enable branch protection
    r = branch_api_request(repo, rc, 'PUT', data=json.dumps(protection_data))
    r.raise_for_status()
    logger.debug("Branch Protection enabled for branch {}".format(rc))

    # Ensure the rc branch was not updated to anything else while it was
    # unprotected. Stored mainline_sha is used incase mainline has
    # moved on since the SHA was acquired.
    assert mainline_sha == repo.branch(rc).commit.sha
    logger.debug("rc branch update complete")
Ejemplo n.º 8
0
def generate_release_notes(scripts, rnfile, text, version, prev_version,
                           clone_dir, dst_file):
    ctx_obj = click.get_current_context().obj
    clone_dir = try_context(ctx_obj, clone_dir, "clone_dir", "clone_dir")
    os.system("mkdir -p {}".format(os.path.dirname(dst_file)))
    if scripts:
        version = try_context(ctx_obj, version, "version", "version")
        logging.debug(
            "Generating release notes from scripts: {}".format(scripts))
        sub_env = os.environ.copy()
        sub_env["RE_HOOK_PREVIOUS_VERSION"] = prev_version.encode('ascii')
        sub_env["RE_HOOK_VERSION"] = version.encode('ascii')
        sub_env["RE_HOOK_RELEASE_NOTES"] = dst_file.encode('ascii')
        sub_env["RE_HOOK_REPO_HTTP_URL"] = ctx_obj.clone_url.encode('ascii')
        script_work_dir = "{cwd}/{clone_dir}".format(cwd=os.getcwd(),
                                                     clone_dir=clone_dir)
        logging.debug("script work dir: {}".format(script_work_dir))
        logging.debug("script env: {}".format(sub_env))
        for script in scripts:
            try:
                optional = False
                if script.startswith("optional:"):
                    script = script.replace("optional:", "")
                    optional = True
                script_path = "{script_work_dir}/{script}".format(
                    script_work_dir=script_work_dir, script=script)
                if os.path.exists(script_path):
                    logging.debug("Executing script: {}".format(script_path))
                    proc = subprocess.Popen(script_path,
                                            env=sub_env,
                                            cwd=script_work_dir)
                    proc.communicate()
                    logging.debug(
                        "Script Execution Complete: {}".format(script_path))
                    if proc.returncode != 0:
                        logging.error(
                            "Script {s} failed. (Return Code: {rc})".format(
                                s=script_path, rc=proc.returncode))
                        click.get_current_context().exit(-1)
                else:
                    if not optional:
                        logging.error(
                            "Aborting as required hook script not found: {}".
                            format(script_path))
                        click.get_current_context().exit(-1)
                    else:
                        logging.info(
                            "Optional hook script not found: {}".format(
                                script_path))

            except Exception as e:
                logging.error(traceback.format_exc())
                logging.error("Failed to generate release notes: {}".format(e))
                click.get_current_context().exit(-1)
        if not os.path.exists(dst_file):
            logging.error("Scripts did not generate release notes or did not"
                          " place them in"
                          " $WORKSPACE/{df}".format(df=dst_file))
            click.get_current_context().exit(-1)
    elif rnfile:
        # Release notes are already in a file, just copy them to where
        # they need to be
        logging.debug("Reading release notes from file: {}".format(rnfile))
        return_code = subprocess.check_call(
            ["cp", pipes.quote(rnfile), dst_file])
        if return_code != 0:
            logging.error(
                "Failed to read release notes file: {}".format(rnfile))
            click.get_current_context().exit(-1)
    elif text:
        logging.debug("Release notes supplied inline: {}".format(text))
        with open(dst_file, "w") as out:
            out.write(text)
    else:
        logging.error("One of script, text or file must be specified. "
                      "No release notes generated")
        click.get_current_context().exit(-1)