Пример #1
0
def _fmt_error_message(errors, version):
    msg = (
        "The recipe did not change in the version migration, a URL did "
        "not hash, or there is jinja2 syntax the bot cannot handle!\n\n"
        "Please check the URLs in your recipe with version '%s' to make sure "
        "they exist!\n\n" % version)
    if len(errors) > 0:
        msg += "We also found the following errors:\n\n - %s" % ("\n - ".join(
            e for e in errors))
        msg += "\n"
    return sanitize_string(msg)
Пример #2
0
def _set_pre_pr_migrator_fields(attrs, migrator_name, error_str):
    pre_key = "pre_pr_migrator_status"
    pre_key_att = "pre_pr_migrator_attempts"

    if pre_key not in attrs:
        attrs[pre_key] = {}
    attrs[pre_key][migrator_name] = sanitize_string(
        error_str,
    )

    if pre_key_att not in attrs:
        attrs[pre_key_att] = {}
    if migrator_name not in attrs[pre_key_att]:
        attrs[pre_key_att][migrator_name] = 0
    attrs[pre_key_att][migrator_name] += 1
Пример #3
0
    def migrate(
        self,
        recipe_dir: str,
        attrs: "AttrsTypedDict",
        hash_type: str = "sha256",
        **kwargs: Any,
    ) -> "MigrationUidTypedDict":
        errors = set()

        version = attrs["new_version"]

        # record the attempt
        if "new_version_attempts" not in attrs:
            attrs["new_version_attempts"] = {}
        if version not in attrs["new_version_attempts"]:
            attrs["new_version_attempts"][version] = 0
        attrs["new_version_attempts"][version] += 1
        if "new_version_errors" not in attrs:
            attrs["new_version_errors"] = {}

        if not isinstance(version, str):
            errors.add(
                "the version '%s' is not a string and must be for the bot" % version,
            )
            attrs["new_version_errors"][version] = _fmt_error_message(errors, version)
            logger.critical(
                "the version '%s' is not a string and must be for the bot",
                version,
            )
            return {}

        try:
            with open(os.path.join(recipe_dir, "meta.yaml")) as fp:
                cmeta = CondaMetaYAML(fp.read())
        except Exception as e:
            tb = io.StringIO()
            traceback.print_tb(e.__traceback__, file=tb)
            tb.seek(0)
            tb = tb.read()
            attrs["new_version_errors"][version] = sanitize_string(
                "We found a problem parsing the recipe for version '"
                + version
                + "': \n\n"
                + repr(e)
                + "\n\ntraceback:\n"
                + tb,
            )
            logger.critical(
                "We found a problem parsing the recipe: \n\n%s\n\n%s",
                str(e),
                tb,
            )
            return {}

        # cache round-tripped yaml for testing later
        s = io.StringIO()
        cmeta.dump(s)
        s.seek(0)
        old_meta_yaml = s.read()

        # if is a git url, then we error
        if _recipe_has_git_url(cmeta) and not _recipe_has_url(cmeta):
            logger.critical("Migrations do not work on `git_url`s!")
            errors.add("migrations do not work on `git_url`s")
            attrs["new_version_errors"][version] = _fmt_error_message(errors, version)
            return {}

        # mangle the version if it is R
        r_url = False
        for src_key in _gen_key_selector(cmeta.meta, "source"):
            r_url |= _has_r_url(cmeta.meta[src_key])
        for key, val in cmeta.jinja2_vars.items():
            if isinstance(val, str):
                r_url |= _is_r_url(val)
        if r_url:
            version = version.replace("_", "-")

        # replace the version
        if "version" in cmeta.jinja2_vars:
            # cache old version for testing later
            old_version = cmeta.jinja2_vars["version"]
            cmeta.jinja2_vars["version"] = version
        else:
            logger.critical(
                "Migrations do not work on versions not specified with jinja2!",
            )
            errors.add("migrations do not work on versions not specified with jinja2")
            attrs["new_version_errors"][version] = _fmt_error_message(errors, version)
            return {}

        if len(list(_gen_key_selector(cmeta.meta, "source"))) > 0:
            did_update = True
            for src_key in _gen_key_selector(cmeta.meta, "source"):
                if isinstance(cmeta.meta[src_key], collections.abc.MutableSequence):
                    for src in cmeta.meta[src_key]:
                        _did_update, _errors = _try_to_update_version(
                            cmeta,
                            src,
                            hash_type,
                        )
                        if _did_update is not None:
                            did_update &= _did_update
                            errors |= _errors
                else:
                    _did_update, _errors = _try_to_update_version(
                        cmeta,
                        cmeta.meta[src_key],
                        hash_type,
                    )
                    if _did_update is not None:
                        did_update &= _did_update
                        errors |= _errors
                if _errors:
                    logger.critical("%s", _errors)
        else:
            did_update = False
            errors.add("no source sections found in the recipe")
            logger.critical("no source sections found in the recipe")

        if did_update:
            # if the yaml did not change, then we did not migrate actually
            cmeta.jinja2_vars["version"] = old_version
            s = io.StringIO()
            cmeta.dump(s)
            s.seek(0)
            still_the_same = s.read() == old_meta_yaml
            cmeta.jinja2_vars["version"] = version  # put back version

            if still_the_same and old_version != version:
                did_update = False
                errors.add(
                    "recipe did not appear to change even "
                    "though the bot said it should have",
                )
                logger.critical(
                    "Recipe did not change in version migration "
                    "but the code indicates an update was done!",
                )

        if did_update:
            with indir(recipe_dir):
                with open("meta.yaml", "w") as fp:
                    cmeta.dump(fp)
                self.set_build_number("meta.yaml")

            return super().migrate(recipe_dir, attrs)
        else:
            logger.critical("Recipe did not change in version migration!")
            attrs["new_version_errors"][version] = _fmt_error_message(errors, version)
            return {}
Пример #4
0
def main(args: "CLIArgs") -> None:

    # logging
    if args.debug:
        setup_logger(logging.getLogger("conda_forge_tick"), level="debug")
    else:
        setup_logger(logging.getLogger("conda_forge_tick"))

    github_username = env.get("USERNAME", "")
    github_password = env.get("PASSWORD", "")
    github_token = env.get("GITHUB_TOKEN")

    mctx, temp, migrators = initialize_migrators(
        github_username=github_username,
        github_password=github_password,
        dry_run=args.dry_run,
        github_token=github_token,
    )

    # compute the time per migrator
    print("computing time per migration", flush=True)
    (num_nodes, time_per_migrator,
     tot_time_per_migrator) = _compute_time_per_migrator(
         mctx,
         migrators,
     )
    for i, migrator in enumerate(migrators):
        if hasattr(migrator, "name"):
            extra_name = "-%s" % migrator.name
        else:
            extra_name = ""

        print(
            "    %s%s: %d - gets %f seconds (%f percent)" % (
                migrator.__class__.__name__,
                extra_name,
                num_nodes[i],
                time_per_migrator[i],
                time_per_migrator[i] / max(tot_time_per_migrator, 1) * 100,
            ),
            flush=True,
        )

    for mg_ind, migrator in enumerate(migrators):
        if hasattr(migrator, "name"):
            assert isinstance(migrator.name, str)
            migrator_name = migrator.name.lower().replace(" ", "")
        else:
            migrator_name = migrator.__class__.__name__.lower()

        mmctx = MigratorContext(session=mctx, migrator=migrator)
        migrator.bind_to_ctx(mmctx)

        good_prs = 0
        _mg_start = time.time()
        effective_graph = mmctx.effective_graph
        time_per = time_per_migrator[mg_ind]

        if hasattr(migrator, "name"):
            extra_name = "-%s" % migrator.name
        else:
            extra_name = ""

        print(
            "\n========================================"
            "========================================"
            "\n"
            "========================================"
            "========================================",
            flush=True,
        )
        print(
            "Running migrations for %s%s: %d\n" % (
                migrator.__class__.__name__,
                extra_name,
                len(effective_graph.nodes),
            ),
            flush=True,
        )

        possible_nodes = list(migrator.order(effective_graph, mctx.graph))

        # version debugging info
        if isinstance(migrator, Version):
            LOGGER.info("possible version migrations:")
            for node_name in possible_nodes:
                with effective_graph.nodes[node_name]["payload"] as attrs:
                    LOGGER.info(
                        "    node|curr|new|attempts: %s|%s|%s|%d",
                        node_name,
                        attrs.get("version"),
                        attrs.get("new_version"),
                        (attrs.get("new_version_attempts", {}).get(
                            attrs.get("new_version", ""),
                            0,
                        )),
                    )

        for node_name in possible_nodes:
            with mctx.graph.nodes[node_name]["payload"] as attrs:
                base_branches = migrator.get_possible_feedstock_branches(attrs)
                orig_branch = attrs.get("branch", "master")

                # Don't let CI timeout, break ahead of the timeout so we make certain
                # to write to the repo
                # TODO: convert these env vars
                _now = time.time()
                if ((_now - int(env.get("START_TIME", time.time())) > int(
                        env.get("TIMEOUT", 600)))
                        or good_prs >= migrator.pr_limit
                        or (_now - _mg_start) > time_per):
                    break

                fctx = FeedstockContext(
                    package_name=node_name,
                    feedstock_name=attrs["feedstock_name"],
                    attrs=attrs,
                )

                try:
                    for base_branch in base_branches:
                        attrs["branch"] = base_branch
                        if migrator.filter(attrs):
                            continue

                        print("\n", flush=True, end="")
                        LOGGER.info(
                            "%s%s IS MIGRATING %s:%s",
                            migrator.__class__.__name__.upper(),
                            extra_name,
                            fctx.package_name,
                            base_branch,
                        )
                        try:
                            # Don't bother running if we are at zero
                            if mctx.gh_api_requests_left == 0:
                                break
                            migrator_uid, pr_json = run(
                                feedstock_ctx=fctx,
                                migrator=migrator,
                                rerender=migrator.rerender,
                                protocol="https",
                                hash_type=attrs.get("hash_type", "sha256"),
                                base_branch=base_branch,
                            )
                            # if migration successful
                            if migrator_uid:
                                d = frozen_to_json_friendly(migrator_uid)
                                # if we have the PR already do nothing
                                if d["data"] in [
                                        existing_pr["data"]
                                        for existing_pr in attrs.get(
                                            "PRed", [])
                                ]:
                                    pass
                                else:
                                    if not pr_json:
                                        pr_json = {
                                            "state": "closed",
                                            "head": {
                                                "ref": "<this_is_not_a_branch>"
                                            },
                                        }
                                    d["PR"] = pr_json
                                    attrs.setdefault("PRed", []).append(d)
                                attrs.update(
                                    {
                                        "smithy_version": mctx.smithy_version,
                                        "pinning_version":
                                        mctx.pinning_version,
                                    }, )

                        except github3.GitHubError as e:
                            if e.msg == "Repository was archived so is read-only.":
                                attrs["archived"] = True
                            else:
                                LOGGER.critical(
                                    "GITHUB ERROR ON FEEDSTOCK: %s",
                                    fctx.feedstock_name,
                                )
                                if is_github_api_limit_reached(e, mctx.gh):
                                    break
                        except URLError as e:
                            LOGGER.exception("URLError ERROR")
                            attrs["bad"] = {
                                "exception":
                                str(e),
                                "traceback":
                                str(traceback.format_exc()).split("\n"),
                                "code":
                                getattr(e, "code"),
                                "url":
                                getattr(e, "url"),
                            }

                            pre_key = "pre_pr_migrator_status"
                            if pre_key not in attrs:
                                attrs[pre_key] = {}
                            attrs[pre_key][migrator_name] = sanitize_string(
                                "bot error (%s): %s: %s" % (
                                    '<a href="' +
                                    os.getenv("CIRCLE_BUILD_URL", "") +
                                    '">bot CI job</a>',
                                    base_branch,
                                    str(traceback.format_exc()),
                                ), )
                        except Exception as e:
                            LOGGER.exception("NON GITHUB ERROR")
                            # we don't set bad for rerendering errors
                            if ("conda smithy rerender -c auto --no-check-uptodate"
                                    not in str(e)):
                                attrs["bad"] = {
                                    "exception":
                                    str(e),
                                    "traceback":
                                    str(traceback.format_exc()).split("\n", ),
                                }

                            pre_key = "pre_pr_migrator_status"
                            if pre_key not in attrs:
                                attrs[pre_key] = {}
                            attrs[pre_key][migrator_name] = sanitize_string(
                                "bot error (%s): %s: %s" % (
                                    '<a href="' +
                                    os.getenv("CIRCLE_BUILD_URL", "") +
                                    '">bot CI job</a>',
                                    base_branch,
                                    str(traceback.format_exc()),
                                ), )
                        else:
                            if migrator_uid:
                                # On successful PR add to our counter
                                good_prs += 1
                finally:
                    # reset branch
                    attrs["branch"] = orig_branch

                    # Write graph partially through
                    if not args.dry_run:
                        dump_graph(mctx.graph)

                    eval_cmd(f"rm -rf {mctx.rever_dir}/*")
                    LOGGER.info(os.getcwd())
                    for f in glob.glob("/tmp/*"):
                        if f not in temp:
                            try:
                                eval_cmd(f"rm -rf {f}")
                            except Exception:
                                pass

                if mctx.gh_api_requests_left == 0:
                    break

        print("\n", flush=True)

    LOGGER.info("API Calls Remaining: %d", mctx.gh_api_requests_left)
    LOGGER.info("Done")
Пример #5
0
def run(
    feedstock_ctx: FeedstockContext,
    migrator: Migrator,
    protocol: str = "ssh",
    pull_request: bool = True,
    rerender: bool = True,
    fork: bool = True,
    base_branch: str = "master",
    **kwargs: typing.Any,
) -> Tuple["MigrationUidTypedDict", dict]:
    """For a given feedstock and migration run the migration

    Parameters
    ----------
    feedstock_ctx: FeedstockContext
        The node attributes
    migrator: Migrator instance
        The migrator to run on the feedstock
    protocol : str, optional
        The git protocol to use, defaults to ``ssh``
    pull_request : bool, optional
        If true issue pull request, defaults to true
    rerender : bool
        Whether to rerender
    fork : bool
        If true create a fork, defaults to true
    base_branch : str, optional
        The base branch to which the PR will be targeted. Defaults to "master".
    kwargs: dict
        The key word arguments to pass to the migrator

    Returns
    -------
    migrate_return: MigrationUidTypedDict
        The migration return dict used for tracking finished migrations
    pr_json: dict
        The PR json object for recreating the PR as needed
    """
    # get the repo
    # TODO: stop doing this.
    migrator.attrs = feedstock_ctx.attrs  # type: ignore

    branch_name = migrator.remote_branch(
        feedstock_ctx) + "_h" + uuid4().hex[0:6]

    if hasattr(migrator, "name"):
        assert isinstance(migrator.name, str)
        migrator_name = migrator.name.lower().replace(" ", "")
    else:
        migrator_name = migrator.__class__.__name__.lower()

    # TODO: run this in parallel
    feedstock_dir, repo = get_repo(
        ctx=migrator.ctx.session,
        fctx=feedstock_ctx,
        branch=branch_name,
        feedstock=feedstock_ctx.feedstock_name,
        protocol=protocol,
        pull_request=pull_request,
        fork=fork,
        base_branch=base_branch,
    )
    if not feedstock_dir or not repo:
        LOGGER.critical(
            "Failed to migrate %s, %s",
            feedstock_ctx.package_name,
            feedstock_ctx.attrs.get("bad"),
        )
        return False, False

    recipe_dir = os.path.join(feedstock_dir, "recipe")

    # migrate the feedstock
    migrator.run_pre_piggyback_migrations(recipe_dir, feedstock_ctx.attrs,
                                          **kwargs)

    # TODO - make a commit here if the repo changed

    migrate_return = migrator.migrate(recipe_dir, feedstock_ctx.attrs,
                                      **kwargs)

    if not migrate_return:
        LOGGER.critical(
            "Failed to migrate %s, %s",
            feedstock_ctx.package_name,
            feedstock_ctx.attrs.get("bad"),
        )
        eval_cmd(f"rm -rf {feedstock_dir}")
        return False, False

    # TODO - commit main migration here

    migrator.run_post_piggyback_migrations(recipe_dir, feedstock_ctx.attrs,
                                           **kwargs)

    # TODO commit post migration here

    # rerender, maybe
    diffed_files: typing.List[str] = []
    with indir(feedstock_dir), env.swap(RAISE_SUBPROC_ERROR=False):
        msg = migrator.commit_message(feedstock_ctx)  # noqa
        try:
            eval_cmd("git add --all .")
            eval_cmd(f"git commit -am '{msg}'")
        except CalledProcessError as e:
            LOGGER.info(
                "could not commit to feedstock - "
                "likely no changes - error is '%s'" % (repr(e)), )
        if rerender:
            head_ref = eval_cmd("git rev-parse HEAD").strip()
            LOGGER.info("Rerendering the feedstock")

            try:
                eval_cmd(
                    "conda smithy rerender -c auto --no-check-uptodate",
                    timeout=300,
                )
                make_rerender_comment = False
            except Exception as e:
                # I am trying this bit of code to force these errors
                # to be surfaced in the logs at the right time.
                print(f"RERENDER ERROR: {e}", flush=True)
                if not isinstance(migrator, Version):
                    raise
                else:
                    # for check solvable or automerge, we always raise rerender errors
                    if feedstock_ctx.attrs["conda-forge.yml"].get(
                            "bot", {}).get(
                                "check_solvable",
                                False,
                            ) or (feedstock_ctx.attrs["conda-forge.yml"].get(
                                "bot", {}).get(
                                    "automerge",
                                    False,
                                )):
                        raise
                    else:
                        make_rerender_comment = True

            # If we tried to run the MigrationYaml and rerender did nothing (we only
            # bumped the build number and dropped a yaml file in migrations) bail
            # for instance platform specific migrations
            gdiff = eval_cmd(f"git diff --name-only {head_ref.strip()}...HEAD")

            diffed_files = [
                _ for _ in gdiff.split()
                if not (_.startswith("recipe") or _.startswith("migrators")
                        or _.startswith("README"))
            ]
        else:
            make_rerender_comment = False

    if (feedstock_ctx.feedstock_name != "conda-forge-pinning"
            and base_branch == "master" and
        ((
            migrator.check_solvable
            # we always let stuff in cycles go
            and feedstock_ctx.attrs["name"] not in getattr(
                migrator, "cycles", set())
            # we always let stuff at the top go
            and feedstock_ctx.attrs["name"] not in getattr(
                migrator, "top_level", set())
            # for solveability always assume automerge is on.
            and
            (feedstock_ctx.attrs["conda-forge.yml"].get("bot", {}).get(
                "automerge", True)))
         or feedstock_ctx.attrs["conda-forge.yml"].get("bot", {}).get(
             "check_solvable",
             False,
         ))):
        solvable, errors, _ = is_recipe_solvable(
            feedstock_dir,
            build_platform=feedstock_ctx.attrs["conda-forge.yml"].get(
                "build_platform",
                None,
            ),
        )
        if not solvable:
            _solver_err_str = "not solvable ({}): {}: {}".format(
                ('<a href="' + os.getenv("CIRCLE_BUILD_URL", "") +
                 '">bot CI job</a>'),
                base_branch,
                sorted(set(errors)),
            )

            if isinstance(migrator, Version):
                _new_ver = feedstock_ctx.attrs["new_version"]
                if _new_ver in feedstock_ctx.attrs["new_version_errors"]:
                    feedstock_ctx.attrs["new_version_errors"][
                        _new_ver] += "\n\nsolver error - {}".format(
                            _solver_err_str, )
                else:
                    feedstock_ctx.attrs["new_version_errors"][
                        _new_ver] = _solver_err_str
                feedstock_ctx.attrs["new_version_errors"][
                    _new_ver] = sanitize_string(
                        feedstock_ctx.attrs["new_version_errors"][_new_ver], )
                # remove part of a try for solver errors to make those slightly
                # higher priority
                feedstock_ctx.attrs["new_version_attempts"][_new_ver] -= 0.8

            pre_key = "pre_pr_migrator_status"
            if pre_key not in feedstock_ctx.attrs:
                feedstock_ctx.attrs[pre_key] = {}
            feedstock_ctx.attrs[pre_key][migrator_name] = sanitize_string(
                _solver_err_str, )
            eval_cmd(f"rm -rf {feedstock_dir}")
            return False, False

    # TODO: Better annotation here
    pr_json: typing.Union[MutableMapping, None, bool]
    if (isinstance(migrator, MigrationYaml) and not diffed_files
            and feedstock_ctx.attrs["name"] != "conda-forge-pinning"):
        # spoof this so it looks like the package is done
        pr_json = {
            "state": "closed",
            "merged_at": "never issued",
            "id": str(uuid4()),
        }
    else:
        # push up
        try:
            # TODO: remove this hack, but for now this is the only way to get
            # the feedstock dir into pr_body
            feedstock_ctx.feedstock_dir = feedstock_dir
            pr_json = push_repo(
                session_ctx=migrator.ctx.session,
                fctx=feedstock_ctx,
                feedstock_dir=feedstock_dir,
                body=migrator.pr_body(feedstock_ctx),
                repo=repo,
                title=migrator.pr_title(feedstock_ctx),
                head=f"{migrator.ctx.github_username}:{branch_name}",
                branch=branch_name,
                base_branch=base_branch,
            )

        # This shouldn't happen too often any more since we won't double PR
        except github3.GitHubError as e:
            if e.msg != "Validation Failed":
                raise
            else:
                print(f"Error during push {e}")
                # If we just push to the existing PR then do nothing to the json
                pr_json = False
                ljpr = False

    if pr_json and pr_json["state"] != "closed" and make_rerender_comment:
        comment_on_pr(
            pr_json,
            """\
Hi! This feedstock was not able to be rerendered after the version update changes. I
have pushed the version update changes anyways and am trying to rerender again with this
comment. Hopefully you all can fix this!

@conda-forge-admin rerender""",
            repo,
        )

    if pr_json:
        ljpr = LazyJson(
            os.path.join(migrator.ctx.session.prjson_dir,
                         str(pr_json["id"]) + ".json"), )
        ljpr.update(**pr_json)

        # from .dynamo_models import PRJson
        # PRJson.dump(pr_json)
    else:
        ljpr = False

    # If we've gotten this far then the node is good
    feedstock_ctx.attrs["bad"] = False
    LOGGER.info("Removing feedstock dir")
    eval_cmd(f"rm -rf {feedstock_dir}")
    return migrate_return, ljpr
Пример #6
0
def _run_migrator(migrator, mctx, temp, time_per, dry_run):
    if hasattr(migrator, "name"):
        assert isinstance(migrator.name, str)
        migrator_name = migrator.name.lower().replace(" ", "")
    else:
        migrator_name = migrator.__class__.__name__.lower()

    mmctx = MigratorContext(session=mctx, migrator=migrator)
    migrator.bind_to_ctx(mmctx)

    good_prs = 0
    _mg_start = time.time()
    effective_graph = mmctx.effective_graph

    if hasattr(migrator, "name"):
        extra_name = "-%s" % migrator.name
    else:
        extra_name = ""

    print(
        "Running migrations for %s%s: %d\n"
        % (
            migrator.__class__.__name__,
            extra_name,
            len(effective_graph.nodes),
        ),
        flush=True,
    )

    possible_nodes = list(migrator.order(effective_graph, mctx.graph))

    # version debugging info
    if isinstance(migrator, Version):
        LOGGER.info("possible version migrations:")
        for node_name in possible_nodes:
            with effective_graph.nodes[node_name]["payload"] as attrs:
                LOGGER.info(
                    "    node|curr|new|attempts: %s|%s|%s|%f",
                    node_name,
                    attrs.get("version"),
                    attrs.get("new_version"),
                    (
                        attrs.get("new_version_attempts", {}).get(
                            attrs.get("new_version", ""),
                            0,
                        )
                    ),
                )

    for node_name in possible_nodes:
        with mctx.graph.nodes[node_name]["payload"] as attrs:
            # Don't let CI timeout, break ahead of the timeout so we make certain
            # to write to the repo
            # TODO: convert these env vars
            _now = time.time()
            if (
                (
                    _now - int(env.get("START_TIME", time.time()))
                    > int(env.get("TIMEOUT", 600))
                )
                or good_prs >= migrator.pr_limit
                or (_now - _mg_start) > time_per
            ):
                break

            base_branches = migrator.get_possible_feedstock_branches(attrs)
            if "branch" in attrs:
                has_attrs_branch = True
                orig_branch = attrs.get("branch")
            else:
                has_attrs_branch = False
                orig_branch = None

            fctx = FeedstockContext(
                package_name=node_name,
                feedstock_name=attrs["feedstock_name"],
                attrs=attrs,
            )

            # map main to current default branch
            base_branches = [
                br if br != "main" else fctx.default_branch for br in base_branches
            ]

            try:
                for base_branch in base_branches:
                    attrs["branch"] = base_branch
                    if migrator.filter(attrs):
                        continue

                    print("\n", flush=True, end="")
                    sys.stderr.flush()
                    sys.stdout.flush()
                    LOGGER.info(
                        "%s%s IS MIGRATING %s:%s",
                        migrator.__class__.__name__.upper(),
                        extra_name,
                        fctx.package_name,
                        base_branch,
                    )
                    try:
                        # Don't bother running if we are at zero
                        if mctx.gh_api_requests_left == 0:
                            break
                        migrator_uid, pr_json = run(
                            feedstock_ctx=fctx,
                            migrator=migrator,
                            rerender=migrator.rerender,
                            protocol="https",
                            hash_type=attrs.get("hash_type", "sha256"),
                            base_branch=base_branch,
                        )
                        # if migration successful
                        if migrator_uid:
                            d = frozen_to_json_friendly(migrator_uid)
                            # if we have the PR already do nothing
                            if d["data"] in [
                                existing_pr["data"]
                                for existing_pr in attrs.get("PRed", [])
                            ]:
                                pass
                            else:
                                if not pr_json:
                                    pr_json = {
                                        "state": "closed",
                                        "head": {"ref": "<this_is_not_a_branch>"},
                                    }
                                d["PR"] = pr_json
                                attrs.setdefault("PRed", []).append(d)
                            attrs.update(
                                {
                                    "smithy_version": mctx.smithy_version,
                                    "pinning_version": mctx.pinning_version,
                                },
                            )

                    except github3.GitHubError as e:
                        if e.msg == "Repository was archived so is read-only.":
                            attrs["archived"] = True
                        else:
                            LOGGER.critical(
                                "GITHUB ERROR ON FEEDSTOCK: %s",
                                fctx.feedstock_name,
                            )
                            if is_github_api_limit_reached(e, mctx.gh):
                                break
                    except URLError as e:
                        LOGGER.exception("URLError ERROR")
                        attrs["bad"] = {
                            "exception": str(e),
                            "traceback": str(traceback.format_exc()).split("\n"),
                            "code": getattr(e, "code"),
                            "url": getattr(e, "url"),
                        }

                        _set_pre_pr_migrator_fields(
                            attrs,
                            migrator_name,
                            sanitize_string(
                                "bot error (%s): %s: %s"
                                % (
                                    '<a href="'
                                    + os.getenv("CIRCLE_BUILD_URL", "")
                                    + '">bot CI job</a>',
                                    base_branch,
                                    str(traceback.format_exc()),
                                ),
                            ),
                        )
                    except Exception as e:
                        LOGGER.exception("NON GITHUB ERROR")
                        # we don't set bad for rerendering errors
                        if (
                            "conda smithy rerender -c auto --no-check-uptodate"
                            not in str(e)
                        ):
                            attrs["bad"] = {
                                "exception": str(e),
                                "traceback": str(traceback.format_exc()).split(
                                    "\n",
                                ),
                            }

                        _set_pre_pr_migrator_fields(
                            attrs,
                            migrator_name,
                            sanitize_string(
                                "bot error (%s): %s: %s"
                                % (
                                    '<a href="'
                                    + os.getenv("CIRCLE_BUILD_URL", "")
                                    + '">bot CI job</a>',
                                    base_branch,
                                    str(traceback.format_exc()),
                                ),
                            ),
                        )
                    else:
                        if migrator_uid:
                            # On successful PR add to our counter
                            good_prs += 1
            finally:
                # reset branch
                if has_attrs_branch:
                    attrs["branch"] = orig_branch

                # do this but it is crazy
                gc.collect()

                # Write graph partially through
                if not dry_run:
                    dump_graph(mctx.graph)

                eval_cmd(f"rm -rf {mctx.rever_dir}/*")
                LOGGER.info(os.getcwd())
                for f in glob.glob("/tmp/*"):
                    if f not in temp:
                        try:
                            eval_cmd(f"rm -rf {f}")
                        except Exception:
                            pass

            if mctx.gh_api_requests_left == 0:
                break

    return good_prs