Example #1
0
async def test_analyzer(tmp_path: Path, session: ClientSession) -> None:
    repo = setup_python_repo(tmp_path)
    actor = Actor("Someone", "*****@*****.**")

    factory = Factory(Configuration(), session)
    analyzer = factory.create_python_analyzer(tmp_path)
    results = await analyzer.analyze()

    assert results == [
        PythonFrozenUpdate(path=tmp_path / "requirements", applied=False)
    ]

    # Ensure that the tree is restored to the previous contents.
    assert not repo.is_dirty()

    # If the repo is dirty, analysis will fail.
    subprocess.run(["make", "update-deps"], cwd=str(tmp_path), check=True)
    assert repo.is_dirty()
    factory = Factory(Configuration(), session)
    analyzer = factory.create_python_analyzer(tmp_path)
    with pytest.raises(UncommittedChangesError):
        results = await analyzer.analyze()

    # Commit the changed dependencies and remove the pre-commit configuration
    # file.  Analysis should now return no changes.
    repo.index.add(str(tmp_path / "requirements"))
    repo.index.commit("Update dependencies", author=actor, committer=actor)
    factory = Factory(Configuration(), session)
    analyzer = factory.create_python_analyzer(tmp_path)
    results = await analyzer.analyze()
    assert results == []
Example #2
0
def main(ctx: click.Context, config_path: str) -> None:
    """Command-line interface for neophile."""
    ctx.ensure_object(dict)
    assert isinstance(ctx.obj, dict)
    if os.path.exists(config_path):
        ctx.obj["config"] = Configuration.from_file(config_path)
    else:
        ctx.obj["config"] = Configuration()
Example #3
0
async def test_pr_push_failure(tmp_path: Path, session: ClientSession) -> None:
    setup_repo(tmp_path)
    config = Configuration(github_user="******", github_token="some-token")
    update = HelmUpdate(
        path=tmp_path / "Chart.yaml",
        applied=False,
        name="gafaelfawr",
        current="1.0.0",
        latest="2.0.0",
    )
    push_error = PushInfo(PushInfo.ERROR, None, "", None, summary="Some error")
    user = {"name": "Someone", "email": "*****@*****.**"}

    with aioresponses() as mock_responses:
        mock_responses.get("https://api.github.com/user", payload=user)
        mock_responses.get(
            "https://api.github.com/repos/foo/bar",
            payload={"default_branch": "master"},
        )
        pattern = re.compile(
            r"https://api.github.com/repos/foo/bar/pulls\?.*base=master.*")
        mock_responses.get(pattern, payload=[])
        pr = PullRequester(tmp_path, config, session)
        with patch.object(Remote, "push") as mock:
            mock.return_value = [push_error]
            with pytest.raises(PushError) as excinfo:
                await pr.make_pull_request([update])

    assert "Some error" in str(excinfo.value)
Example #4
0
async def test_analyzer(session: ClientSession) -> None:
    data_path = Path(__file__).parent.parent / "data" / "kubernetes"

    with aioresponses() as mock:
        register_mock_github_tags(
            mock, "lsst-sqre", "sqrbot-jr", ["0.6.0", "0.6.1", "0.7.0"]
        )
        register_mock_github_tags(
            mock, "lsst-sqre", "sqrbot", ["20170114", "0.6.1", "0.7.0"]
        )
        factory = Factory(Configuration(), session)
        analyzer = factory.create_kustomize_analyzer(data_path)
        results = await analyzer.analyze()

    assert results == [
        KustomizeUpdate(
            path=data_path / "sqrbot-jr" / "kustomization.yaml",
            applied=False,
            url="github.com/lsst-sqre/sqrbot-jr.git//manifests/base?ref=0.6.0",
            owner="lsst-sqre",
            repo="sqrbot-jr",
            current="0.6.0",
            latest="0.7.0",
        ),
    ]
Example #5
0
async def test_inventory_missing(session: ClientSession) -> None:
    """Missing and empty version lists should return None."""
    with aioresponses() as mock:
        register_mock_github_tags(mock, "foo", "bar", [])
        inventory = GitHubInventory(Configuration(), session)
        assert await inventory.inventory("foo", "bar") is None
        assert await inventory.inventory("foo", "nonexistent") is None
Example #6
0
async def test_inventory(session: ClientSession) -> None:
    tests = [
        {
            "tags": ["3.7.0", "3.8.0", "3.9.0", "3.8.1"],
            "latest": "3.9.0"
        },
        {
            "tags": ["v3.1.0", "v3.0.1", "v3.0.0", "v2.5.0"],
            "latest": "v3.1.0"
        },
        {
            "tags": ["4.3.20", "4.3.21-2"],
            "latest": "4.3.21-2"
        },
        {
            "tags": ["19.10b0", "19.3b0", "18.4a4"],
            "latest": "19.10b0"
        },
    ]

    for test in tests:
        with aioresponses() as mock:
            register_mock_github_tags(mock, "foo", "bar", test["tags"])
            inventory = GitHubInventory(Configuration(), session)
            latest = await inventory.inventory("foo", "bar")
        assert latest == test["latest"]
Example #7
0
async def test_no_updates(tmp_path: Path, session: ClientSession) -> None:
    data_path = Path(__file__).parent / "data" / "kubernetes" / "sqrbot-jr"
    tmp_repo_path = tmp_path / "tmp"
    tmp_repo_path.mkdir()
    tmp_repo = Repo.init(str(tmp_repo_path))
    shutil.copytree(str(data_path), str(tmp_repo_path / "sqrbot-jr"))
    actor = Actor("Someone", "*****@*****.**")
    tmp_repo.index.commit("Initial commit", author=actor, committer=actor)
    upstream_path = tmp_path / "upstream"
    create_upstream_git_repository(tmp_repo, upstream_path)
    config = Configuration(
        repositories=[GitHubRepository(owner="foo", repo="bar")],
        work_area=tmp_path / "work",
    )
    user = {"name": "Someone", "email": "*****@*****.**"}

    # Don't register any GitHub tag lists, so we shouldn't see any updates.
    with aioresponses() as mock:
        mock.get("https://api.github.com/user", payload=user)
        factory = Factory(config, session)
        processor = factory.create_processor()
        with patch_clone_from("foo", "bar", upstream_path):
            with patch.object(Remote, "push") as mock_push:
                await processor.process()

    assert mock_push.call_count == 0
    repo = Repo(str(tmp_path / "work" / "bar"))
    assert not repo.is_dirty()
    assert repo.head.ref.name == "master"
Example #8
0
async def test_analyzer_missing(session: ClientSession) -> None:
    """Test missing GitHub tags for all resources."""
    data_path = Path(__file__).parent.parent / "data" / "kubernetes"

    with aioresponses():
        factory = Factory(Configuration(), session)
        analyzer = factory.create_kustomize_analyzer(data_path)
        assert await analyzer.analyze() == []
Example #9
0
async def test_virtualenv(tmp_path: Path, session: ClientSession,
                          caplog: LogCaptureFixture) -> None:
    setup_python_repo(tmp_path, require_venv=True)

    factory = Factory(Configuration(), session)
    analyzer = factory.create_python_analyzer(tmp_path)
    assert await analyzer.analyze() == []
    assert "make update-deps failed" in caplog.records[0].msg
Example #10
0
async def test_pr_update(tmp_path: Path, session: ClientSession,
                         mock_push: Mock) -> None:
    """Test updating an existing PR."""
    repo = setup_repo(tmp_path)
    config = Configuration(
        github_email="*****@*****.**",
        github_token="some-token",
        github_user="******",
    )
    update = HelmUpdate(
        path=tmp_path / "Chart.yaml",
        applied=False,
        name="gafaelfawr",
        current="1.0.0",
        latest="2.0.0",
    )
    user = {"name": "Someone", "email": "*****@*****.**"}
    updated_pr = False

    def check_pr_update(url: str, **kwargs: Any) -> CallbackResult:
        change = "Update gafaelfawr Helm chart from 1.0.0 to 2.0.0"
        assert json.loads(kwargs["data"]) == {
            "title": CommitMessage.title,
            "body": f"- {change}\n",
        }

        nonlocal updated_pr
        updated_pr = True
        return CallbackResult(status=200)

    with aioresponses() as mock_responses:
        mock_responses.get("https://api.github.com/user", payload=user)
        mock_responses.get("https://api.github.com/repos/foo/bar", payload={})
        pattern = re.compile(
            r"https://api.github.com/repos/foo/bar/pulls\?.*base=master.*")
        mock_responses.get(pattern, payload=[{"number": 1234}])
        mock_responses.patch(
            "https://api.github.com/repos/foo/bar/pulls/1234",
            callback=check_pr_update,
        )
        repository = Repository(tmp_path)
        repository.switch_branch()
        update.apply()
        pr = PullRequester(tmp_path, config, session)
        await pr.make_pull_request([update])

    assert mock_push.call_args_list == [
        call("u/neophile:u/neophile", force=True)
    ]
    assert not repo.is_dirty()
    assert repo.head.ref.name == "u/neophile"
    commit = repo.head.commit
    assert commit.author.name == "Someone"
    assert commit.author.email == "*****@*****.**"
    assert commit.committer.name == "Someone"
    assert commit.committer.email == "*****@*****.**"
Example #11
0
async def test_inventory_semantic(session: ClientSession) -> None:
    tags = ["1.19.0", "1.18.0", "1.15.1", "20171120-1"]

    with aioresponses() as mock:
        register_mock_github_tags(mock, "foo", "bar", tags)
        inventory = GitHubInventory(Configuration(), session)
        latest = await inventory.inventory("foo", "bar")
        assert latest == "20171120-1"
        latest = await inventory.inventory("foo", "bar", semantic=True)
        assert latest == "1.19.0"
Example #12
0
async def test_analyzer_update(tmp_path: Path, session: ClientSession) -> None:
    repo = setup_python_repo(tmp_path)

    factory = Factory(Configuration(), session)
    analyzer = factory.create_python_analyzer(tmp_path)
    results = await analyzer.update()

    assert results == [
        PythonFrozenUpdate(path=tmp_path / "requirements", applied=True)
    ]
    assert repo.is_dirty()
Example #13
0
async def test_get_github_repo(tmp_path: Path, session: ClientSession) -> None:
    repo = Repo.init(str(tmp_path))

    config = Configuration(github_user="******", github_token="some-token")
    pr = PullRequester(tmp_path, config, session)

    remote = Remote.create(repo, "origin", "[email protected]:foo/bar.git")
    assert pr._get_github_repo() == GitHubRepository(owner="foo", repo="bar")

    remote.set_url("https://github.com/foo/bar.git")
    assert pr._get_github_repo() == GitHubRepository(owner="foo", repo="bar")

    remote.set_url("ssh://[email protected]/foo/bar")
    assert pr._get_github_repo() == GitHubRepository(owner="foo", repo="bar")
Example #14
0
async def test_pr(tmp_path: Path, session: ClientSession,
                  mock_push: Mock) -> None:
    repo = setup_repo(tmp_path)
    config = Configuration(github_user="******", github_token="some-token")
    update = HelmUpdate(
        path=tmp_path / "Chart.yaml",
        applied=False,
        name="gafaelfawr",
        current="1.0.0",
        latest="2.0.0",
    )
    payload = {"name": "Someone", "email": "*****@*****.**"}

    with aioresponses() as mock_responses:
        mock_responses.get("https://api.github.com/user", payload=payload)
        mock_responses.get(
            "https://api.github.com/repos/foo/bar",
            payload={"default_branch": "main"},
        )
        pattern = re.compile(
            r"https://api.github.com/repos/foo/bar/pulls\?.*base=main.*")
        mock_responses.get(pattern, payload=[])
        mock_responses.post(
            "https://api.github.com/repos/foo/bar/pulls",
            payload={},
            status=201,
        )
        repository = Repository(tmp_path)
        repository.switch_branch()
        update.apply()
        pr = PullRequester(tmp_path, config, session)
        await pr.make_pull_request([update])

    assert mock_push.call_args_list == [
        call("u/neophile:u/neophile", force=True)
    ]
    assert not repo.is_dirty()
    assert repo.head.ref.name == "u/neophile"
    commit = repo.head.commit
    assert commit.author.name == "Someone"
    assert commit.author.email == "*****@*****.**"
    assert commit.committer.name == "Someone"
    assert commit.committer.email == "*****@*****.**"
    change = "Update gafaelfawr Helm chart from 1.0.0 to 2.0.0"
    assert commit.message == f"{CommitMessage.title}\n\n- {change}\n"
    assert "tmp-neophile" not in [r.name for r in repo.remotes]
Example #15
0
async def test_get_authenticated_remote(tmp_path: Path,
                                        session: ClientSession) -> None:
    repo = Repo.init(str(tmp_path))

    config = Configuration(github_user="******", github_token="some-token")
    pr = PullRequester(tmp_path, config, session)

    remote = Remote.create(repo, "origin", "https://github.com/foo/bar")
    url = pr._get_authenticated_remote()
    assert url == "https://*****:*****@github.com/foo/bar"

    remote.set_url("https://[email protected]:8080/foo/bar")
    url = pr._get_authenticated_remote()
    assert url == "https://*****:*****@github.com:8080/foo/bar"

    remote.set_url("[email protected]:bar/foo")
    url = pr._get_authenticated_remote()
    assert url == "https://*****:*****@github.com/bar/foo"

    remote.set_url("ssh://*****:*****@github.com/baz/stuff")
    url = pr._get_authenticated_remote()
    assert url == "https://*****:*****@github.com/baz/stuff"
Example #16
0
async def test_processor(tmp_path: Path, session: ClientSession) -> None:
    tmp_repo = setup_python_repo(tmp_path / "tmp", require_venv=True)
    upstream_path = tmp_path / "upstream"
    create_upstream_git_repository(tmp_repo, upstream_path)
    config = Configuration(
        repositories=[GitHubRepository(owner="foo", repo="bar")],
        work_area=tmp_path / "work",
    )
    user = {"name": "Someone", "email": "*****@*****.**"}
    push_result = [PushInfo(PushInfo.NEW_HEAD, None, "", None)]
    created_pr = False

    def check_pr_post(url: str, **kwargs: Any) -> CallbackResult:
        changes = [
            "Update frozen Python dependencies",
            "Update ambv/black pre-commit hook from 19.10b0 to 20.0.0",
        ]
        body = "- " + "\n- ".join(changes) + "\n"
        assert json.loads(kwargs["data"]) == {
            "title": CommitMessage.title,
            "body": body,
            "head": "u/neophile",
            "base": "main",
            "maintainer_can_modify": True,
            "draft": False,
        }

        repo = Repo(str(tmp_path / "work" / "bar"))
        assert repo.head.ref.name == "u/neophile"
        yaml = YAML()
        data = yaml.load(tmp_path / "work" / "bar" / ".pre-commit-config.yaml")
        assert data["repos"][2]["rev"] == "20.0.0"
        commit = repo.head.commit
        assert commit.author.name == "Someone"
        assert commit.author.email == "*****@*****.**"
        assert commit.message == f"{CommitMessage.title}\n\n{body}"

        nonlocal created_pr
        created_pr = True
        return CallbackResult(status=201)

    with aioresponses() as mock:
        register_mock_github_tags(mock, "ambv", "black", ["20.0.0", "19.10b0"])
        mock.get("https://api.github.com/user", payload=user)
        mock.get(
            "https://api.github.com/repos/foo/bar",
            payload={"default_branch": "main"},
        )
        pattern = re.compile(r"https://api.github.com/repos/foo/bar/pulls\?.*")
        mock.get(pattern, payload=[])
        mock.post(
            "https://api.github.com/repos/foo/bar/pulls",
            callback=check_pr_post,
        )

        # Unfortunately, the mock_push fixture can't be used here because we
        # want to use git.Remote.push in create_upstream_git_repository.
        factory = Factory(config, session)
        processor = factory.create_processor()
        with patch_clone_from("foo", "bar", upstream_path):
            with patch.object(Remote, "push") as mock_push:
                mock_push.return_value = push_result
                await processor.process()

    assert mock_push.call_args_list == [
        call("u/neophile:u/neophile", force=True)
    ]
    assert created_pr
    repo = Repo(str(tmp_path / "work" / "bar"))
    assert not repo.is_dirty()
    assert repo.head.ref.name == "master"
    assert "u/neophile" not in [h.name for h in repo.heads]
    assert "tmp-neophile" not in [r.name for r in repo.remotes]
Example #17
0
async def test_allow_expressions(tmp_path: Path,
                                 session: ClientSession) -> None:
    tmp_repo = setup_kubernetes_repo(tmp_path / "tmp")
    upstream_path = tmp_path / "upstream"
    create_upstream_git_repository(tmp_repo, upstream_path)
    config = Configuration(
        allow_expressions=True,
        cache_enabled=False,
        repositories=[GitHubRepository(owner="foo", repo="bar")],
        work_area=tmp_path / "work",
    )
    user = {"name": "Someone", "email": "*****@*****.**"}
    push_result = [PushInfo(PushInfo.NEW_HEAD, None, "", None)]
    created_pr = False

    def check_pr_post(url: str, **kwargs: Any) -> CallbackResult:
        assert json.loads(kwargs["data"]) == {
            "title": CommitMessage.title,
            "body": "- Update gafaelfawr Helm chart from 1.3.1 to v1.4.0\n",
            "head": "u/neophile",
            "base": "main",
            "maintainer_can_modify": True,
            "draft": False,
        }

        nonlocal created_pr
        created_pr = True
        return CallbackResult(status=201)

    with aioresponses() as mock:
        register_mock_helm_repository(
            mock,
            "https://kubernetes-charts.storage.googleapis.com/index.yaml",
            {
                "elasticsearch": ["1.26.2"],
                "kibana": ["3.0.1"]
            },
        )
        register_mock_helm_repository(
            mock,
            "https://kiwigrid.github.io/index.yaml",
            {"fluentd-elasticsearch": ["3.0.0"]},
        )
        register_mock_helm_repository(
            mock,
            "https://lsst-sqre.github.io/charts/index.yaml",
            {"gafaelfawr": ["1.3.1", "v1.4.0"]},
        )
        mock.get("https://api.github.com/user", payload=user)
        mock.get(
            "https://api.github.com/repos/foo/bar",
            payload={"default_branch": "main"},
        )
        pattern = re.compile(r"https://api.github.com/repos/foo/bar/pulls\?.*")
        mock.get(pattern, payload=[])
        mock.post(
            "https://api.github.com/repos/foo/bar/pulls",
            callback=check_pr_post,
        )

        # Unfortunately, the mock_push fixture can't be used here because we
        # want to use git.Remote.push in create_upstream_git_repository.
        factory = Factory(config, session)
        processor = factory.create_processor()
        with patch_clone_from("foo", "bar", upstream_path):
            with patch.object(Remote, "push") as mock_push:
                mock_push.return_value = push_result
                await processor.process()

    assert created_pr