Beispiel #1
0
class LaunchpadTestCase(unit.TestCase):
    def setUp(self):
        super().setUp()
        self._project = self._make_snapcraft_project()
        self.lpc = LaunchpadClient(
            project=self._project, build_id="id", user="******", architectures=[]
        )

    @mock.patch("launchpadlib.launchpad.Launchpad.login_with")
    def test_login(self, mock_login):
        self.lpc.login()
        self.assertThat(self.lpc.user, Equals("user"))
        mock_login.assert_called_with(
            "snapcraft remote-build {}".format(snapcraft.__version__),
            "production",
            mock.ANY,
            credentials_file=mock.ANY,
            version="devel",
        )

    @mock.patch("launchpadlib.launchpad.Launchpad.login_with")
    def test_login_connection_issues(self, mock_login):
        mock_login.side_effect = ConnectionRefusedError()
        self.assertRaises(errors.LaunchpadHttpsError, self.lpc.login)
        mock_login.side_effect = TimeoutError()
        self.assertRaises(errors.LaunchpadHttpsError, self.lpc.login)

    @mock.patch("snapcraft.internal.remote_build.LaunchpadClient.login")
    def test_load_connection_issues(self, mock_login):
        self.lpc._lp = LaunchpadImpl()

        # ConnectionRefusedError should surface.
        self.lpc._lp._load_mock.side_effect = ConnectionRefusedError
        self.assertRaises(ConnectionRefusedError, self.lpc._lp_load_url, "foo")

        # Load URL should work OK after single connection reset.
        self.lpc._lp._load_mock.side_effect = [ConnectionResetError, None]
        self.lpc._lp_load_url(url="foo")
        mock_login.assert_called()

        # Load URL should fail with two connection resets.
        self.lpc._lp._load_mock.side_effect = [
            ConnectionResetError,
            ConnectionResetError,
        ]
        self.assertRaises(ConnectionResetError, self.lpc._lp_load_url, "foo")
        mock_login.assert_called()

    def test_create_snap(self):
        self.lpc._lp = LaunchpadImpl()
        self.lpc._create_snap()
        self.lpc._lp.snaps.new_mock.assert_called_with(
            auto_build=False,
            auto_build_archive="/ubuntu/+archive/primary",
            auto_build_pocket="Updates",
            git_path="master",
            git_repository_url="https://[email protected]/~user/+git/id/",
            name="id",
            owner="/~user",
        )

    def test_create_snap_with_archs(self):
        self.lpc._lp = LaunchpadImpl()
        self.lpc.architectures = ["arch1", "arch2"]
        self.lpc._create_snap()
        self.lpc._lp.snaps.new_mock.assert_called_with(
            auto_build=False,
            auto_build_archive="/ubuntu/+archive/primary",
            auto_build_pocket="Updates",
            git_path="master",
            git_repository_url="https://[email protected]/~user/+git/id/",
            name="id",
            owner="/~user",
            processors=["/+processors/arch1", "/+processors/arch2"],
        )

    def test_delete_snap(self):
        self.lpc._lp = LaunchpadImpl()
        self.lpc._delete_snap()
        self.lpc._lp.snaps.getByName_mock.assert_called_with(name="id", owner="/~user")

    def test_start_build(self):
        self.lpc._lp = LaunchpadImpl()
        self.lpc.start_build()

    @mock.patch(
        "tests.unit.remote_build.test_launchpad.SnapImpl.requestBuilds",
        return_value=SnapBuildReqImpl(
            status="Failed", error_message="snapcraft.yaml not found..."
        ),
    )
    def test_start_build_error(self, mock_rb):
        self.lpc._lp = LaunchpadImpl()

        raised = self.assertRaises(errors.RemoteBuilderError, self.lpc.start_build)
        self.assertThat(str(raised), Contains("snapcraft.yaml not found..."))

    @mock.patch(
        "tests.unit.remote_build.test_launchpad.SnapImpl.requestBuilds",
        return_value=SnapBuildReqImpl(status="Pending", error_message=""),
    )
    def test_start_build_error_timeout(self, mock_rb):
        self.lpc._lp = LaunchpadImpl()
        raised = self.assertRaises(
            errors.RemoteBuilderNotReadyError, self.lpc.start_build, timeout=0
        )
        self.assertThat(str(raised), Contains("is not ready"))

    @mock.patch("snapcraft.internal.remote_build.LaunchpadClient._download_file")
    def test_monitor_build(self, mock_download_file):
        open("test_i386.txt", "w").close()
        open("test_i386.1.txt", "w").close()
        self.lpc._lp = LaunchpadImpl()

        self.lpc.start_build()
        self.lpc.monitor_build(interval=0)
        mock_download_file.assert_has_calls(
            [
                mock.call(url="url_for/snap_file_i386.snap", dst="snap_file_i386.snap"),
                mock.call(
                    url="url_for/build_log_file_1", gunzip=True, dst="test_i386.2.txt"
                ),
                mock.call(
                    url="url_for/build_log_file_2", gunzip=True, dst="test_amd64.txt"
                ),
            ]
        )

    @mock.patch("snapcraft.internal.remote_build.LaunchpadClient._download_file")
    @mock.patch(
        "tests.unit.remote_build.test_launchpad.BuildImpl.getFileUrls", return_value=[]
    )
    @mock.patch("logging.Logger.error")
    def test_monitor_build_error(self, mock_log, mock_urls, mock_download_file):
        self.lpc._lp = LaunchpadImpl()
        self.lpc.start_build()
        self.lpc.monitor_build(interval=0)
        mock_download_file.assert_has_calls(
            [
                mock.call(
                    url="url_for/build_log_file_2", gunzip=True, dst="test_amd64.txt"
                )
            ]
        )
        mock_log.assert_called_with("Build failed for arch 'amd64'.")

    def test_get_build_status(self):
        self.lpc._lp = LaunchpadImpl()
        self.lpc.start_build()
        build_status = self.lpc.get_build_status()
        self.assertThat(
            build_status,
            Equals({"i386": "Successfully built", "amd64": "Failed to build"}),
        )

    def _make_snapcraft_project(self):
        yaml = textwrap.dedent(
            """\
            name: test
            base: core18
            version: "1.0"
            summary: test
            description: test
            confinement: strict
            grade: stable
            """
        )
        snapcraft_yaml_file_path = self.make_snapcraft_yaml(yaml)
        project = snapcraft.project.Project(
            snapcraft_yaml_file_path=snapcraft_yaml_file_path
        )
        return project

    def test_git_repository_creation(self):
        source_testdir = self.useFixture(TestDir())
        source_testdir.create_file("foo")
        repo_dir = source_testdir.path
        self.assertFalse(os.path.exists(os.path.join(repo_dir, ".git")))
        self.lpc._gitify_repository(repo_dir)
        self.assertTrue(os.path.exists(os.path.join(repo_dir, ".git")))

    @mock.patch("snapcraft.internal.sources.Git.push", return_value=None)
    def test_push_source_tree(self, mock_push):
        self.lpc._lp = LaunchpadImpl()
        source_testdir = self.useFixture(TestDir())
        source_testdir.create_file("foo")
        repo_dir = source_testdir.path
        self.lpc.push_source_tree(repo_dir)
        mock_push.assert_called_with(
            "https://*****:*****@git.launchpad.net/~user/+git/id/",
            "HEAD:master",
            force=True,
        )

    @mock.patch(
        "snapcraft.internal.sources.Git.push",
        side_effect=SnapcraftPullError(
            command="git push HEAD:master https://user:access-token@url", exit_code=128
        ),
    )
    def test_push_source_tree_error(self, mock_push):
        self.lpc._lp = LaunchpadImpl()
        source_testdir = self.useFixture(TestDir())
        source_testdir.create_file("foo")
        repo_dir = source_testdir.path

        self.assertRaises(
            errors.LaunchpadGitPushError, self.lpc.push_source_tree, repo_dir
        )

        mock_push.assert_called_with(
            "https://*****:*****@git.launchpad.net/~user/+git/id/",
            "HEAD:master",
            force=True,
        )
Beispiel #2
0
def remote_build(
    recover: int,
    status: int,
    build_on: str,
    launchpad_accept_public_upload: bool,
    launchpad_user: str,
    package_all_sources: bool,
    echoer=echo,
) -> None:
    """Dispatch a snap for remote build.

    Command remote-build sends the current project to be built remotely. After the build
    is complete, packages for each architecture are retrieved and will be available in
    the local filesystem.

    If not specified in the snapcraft.yaml file, the list of architectures to build
    can be set using the --build-on option. If both are specified, an error will occur.

    Interrupted remote builds can be resumed using the --recover option, followed by
    the build number informed when the remote build was originally dispatched. The
    current state of the remote build for each architecture can be checked using the
    --status option.

    \b
    Examples:
        snapcraft remote-build --launchpad-user <user>
        snapcraft remote-build --launchpad-user <user> --build-on=amd64
        snapcraft remote-build --launchpad-user <user> --build-on=amd64,arm64,armhf,i386,ppc64el,s390x
        snapcraft remote-build --launchpad-user <user> --recover 47860738
        snapcraft remote-build --launchpad-user <user> --status 47860738
    """
    if not launchpad_accept_public_upload:
        raise errors.AcceptPublicUploadError()

    echo.warning(
        "snapcraft remote-build is experimental and is subject to change - use with caution."
    )

    project = get_project()

    # TODO: use project.is_legacy() when available.
    base = project.info.get_build_base()
    if base is None:
        raise errors.BaseRequiredError()

    # Use a hash of current working directory to distinguish between other
    # potential project builds occurring in parallel elsewhere.
    project_hash = project._get_project_directory_hash()
    build_id = f"snapcraft-{project.info.name}-{project_hash}"
    architectures = _determine_architectures(project, build_on)

    lp = LaunchpadClient(
        project=project,
        build_id=build_id,
        user=launchpad_user,
        architectures=architectures,
    )
    lp.login()

    if status:
        _print_status(lp)
    elif recover:
        # Recover from interrupted build.
        if not lp.has_outstanding_build():
            echo.info("No build found.")
            return

        echo.info("Recovering build...")
        _monitor_build(lp)
    elif lp.has_outstanding_build():
        # There was a previous build that hasn't finished.
        # Recover from interrupted build.
        echo.info("Found previously started build.")
        _print_status(lp)

        if not echo.confirm("Do you wish to recover this build?",
                            default=True):
            _clean_build(lp)
            _start_build(
                lp=lp,
                project=project,
                build_id=build_id,
                package_all_sources=package_all_sources,
            )
        _monitor_build(lp)
    else:
        _start_build(
            lp=lp,
            project=project,
            build_id=build_id,
            package_all_sources=package_all_sources,
        )
        _monitor_build(lp)