Skip to content

Commit

Permalink
cli: move remote build out of legacy (#3919)
Browse files Browse the repository at this point in the history
- move the CLI logic out of legacy
- export --build-for and hide --build-on
- stop the emitter when calling confirm_with_user's prompt

Signed-off-by: Sergio Schvezov <sergio.schvezov@canonical.com>
  • Loading branch information
sergiusens authored Sep 23, 2022
1 parent 3a54031 commit 686d697
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 117 deletions.
2 changes: 1 addition & 1 deletion snapcraft/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
commands.StageCommand,
commands.PrimeCommand,
commands.PackCommand,
commands.RemoteBuildCommand,
commands.SnapCommand, # hidden (legacy compatibility)
commands.StoreLegacyRemoteBuildCommand,
commands.PluginsCommand,
commands.ListPluginsCommand,
],
Expand Down
4 changes: 2 additions & 2 deletions snapcraft/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
StoreLegacyMetricsCommand,
StoreLegacyPromoteCommand,
StoreLegacyRegisterKeyCommand,
StoreLegacyRemoteBuildCommand,
StoreLegacySetDefaultTrackCommand,
StoreLegacySignBuildCommand,
StoreLegacyUploadMetadataCommand,
Expand All @@ -59,6 +58,7 @@
StoreNamesCommand,
StoreRegisterCommand,
)
from .remote import RemoteBuildCommand
from .status import (
StoreListRevisionsCommand,
StoreListTracksCommand,
Expand All @@ -80,6 +80,7 @@
"PluginsCommand",
"PrimeCommand",
"PullCommand",
"RemoteBuildCommand",
"SnapCommand",
"StageCommand",
"StoreCloseCommand",
Expand All @@ -94,7 +95,6 @@
"StoreLegacyPromoteCommand",
"StoreLegacyPushCommand",
"StoreLegacyRegisterKeyCommand",
"StoreLegacyRemoteBuildCommand",
"StoreLegacySetDefaultTrackCommand",
"StoreLegacySignBuildCommand",
"StoreLegacyUploadMetadataCommand",
Expand Down
49 changes: 0 additions & 49 deletions snapcraft/commands/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,55 +180,6 @@ def fill_parser(self, parser: "argparse.ArgumentParser") -> None:
)


#########
# Build #
#########


class StoreLegacyRemoteBuildCommand(LegacyBaseCommand):
"""Command passthrough for the remote-build command."""

name = "remote-build"
help_msg = "Dispatch a snap for remote build"
overview = textwrap.dedent(
"""
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."""
)

@overrides
def fill_parser(self, parser: "argparse.ArgumentParser") -> None:
parser.add_argument(
"--recover", action="store_true", help="recover an interrupted build"
)
parser.add_argument(
"--status", action="store_true", help="display remote build status"
)
parser.add_argument(
"--build-on",
metavar="arch",
nargs="+",
help="architecture to build on",
)
parser.add_argument(
"--build-id", metavar="build-id", help="specific build id to retrieve"
)
parser.add_argument(
"--launchpad-accept-public-upload",
action="store_true",
help="acknowledge that uploaded code will be publicly available.",
)


##############
# Assertions #
##############
Expand Down
118 changes: 118 additions & 0 deletions snapcraft/commands/remote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2022 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Snapcraft remote build command."""

import argparse
import os
import textwrap

from craft_cli import BaseCommand, emit
from craft_cli.helptexts import HIDDEN
from overrides import overrides

from snapcraft.legacy_cli import run_legacy
from snapcraft.parts.lifecycle import get_snap_project, process_yaml
from snapcraft.utils import confirm_with_user
from snapcraft_legacy.internal.remote_build.errors import AcceptPublicUploadError

_CONFIRMATION_PROMPT = (
"All data sent to remote builders will be publicly available. "
"Are you sure you want to continue?"
)


class RemoteBuildCommand(BaseCommand):
"""Command passthrough for the remote-build command."""

name = "remote-build"
help_msg = "Dispatch a snap for remote build"
overview = textwrap.dedent(
"""
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."""
)

@overrides
def fill_parser(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--recover", action="store_true", help="recover an interrupted build"
)
parser.add_argument(
"--status", action="store_true", help="display remote build status"
)
parser_target = parser.add_mutually_exclusive_group()
parser_target.add_argument(
"--build-on",
metavar="arch",
nargs="+",
help=HIDDEN,
)
parser_target.add_argument(
"--build-for",
metavar="arch",
nargs="+",
help="architecture to build for",
)
parser.add_argument(
"--build-id", metavar="build-id", help="specific build id to retrieve"
)
parser.add_argument(
"--launchpad-accept-public-upload",
action="store_true",
help="acknowledge that uploaded code will be publicly available.",
)

@overrides
def run(self, parsed_args):
if os.getenv("SUDO_USER") and os.geteuid() == 0:
emit.message(
"Running with 'sudo' may cause permission errors and is discouraged."
)

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

if parsed_args.build_on:
emit.message("Use --build-for instead of --build-on")
parsed_args.build_for = parsed_args.build_on

if not parsed_args.launchpad_accept_public_upload and not confirm_with_user(
_CONFIRMATION_PROMPT
):
raise AcceptPublicUploadError()

snap_project = get_snap_project()
# TODO proper core22 support would mean we need to load the project
# yaml_data = process_yaml(snap_project.project_file)
# for now, only log explicitly that we are falling back to legacy to
# remote build for core22
process_yaml(snap_project.project_file)

emit.debug(
"core22 not yet supported in new code base: re-executing into legacy for remote-build"
)
run_legacy()
4 changes: 3 additions & 1 deletion snapcraft/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ def confirm_with_user(prompt_text, default=False) -> bool:

choices = " [Y/n]: " if default else " [y/N]: "

reply = str(input(prompt_text + choices)).lower().strip()
with emit.pause():
reply = str(input(prompt_text + choices)).lower().strip()

if reply and reply[0] == "y":
return True

Expand Down
36 changes: 12 additions & 24 deletions snapcraft_legacy/cli/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import time
from typing import List

Expand Down Expand Up @@ -47,6 +46,14 @@ def remotecli():
required=False,
help="Set architectures to build on.",
)
@click.option(
"--build-for",
metavar="<arch-list>",
type=str,
nargs=1,
required=False,
help="Set architectures to build for.",
)
@click.option(
"--build-id",
metavar="<build-id>",
Expand Down Expand Up @@ -82,11 +89,11 @@ def remote_build(
recover: bool,
status: bool,
build_on: str,
build_for: str,
build_id: str,
launchpad_accept_public_upload: bool,
launchpad_timeout: int,
package_all_sources: bool,
echoer=echo,
) -> None:
"""Dispatch a snap for remote build.
Expand All @@ -112,17 +119,11 @@ def remote_build(
snapcraft remote-build --status
snapcraft remote-build --status --build-id snapcraft-my-snap-b98a6bd3
"""
if os.getenv("SUDO_USER") and os.geteuid() == 0:
echo.warning(
"Running with 'sudo' may cause permission errors and is discouraged."
)

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

project = get_project()

if build_for:
build_on = build_for

try:
project._get_build_base()
except RuntimeError:
Expand Down Expand Up @@ -172,8 +173,6 @@ def remote_build(
# Otherwise clean running build before we start a new one.
_clean_build(lp)

_check_launchpad_acceptance(launchpad_accept_public_upload)

_start_build(
lp=lp,
project=project,
Expand All @@ -184,17 +183,6 @@ def remote_build(
_monitor_build(lp)


def _check_launchpad_acceptance(launchpad_accept_public_upload):
if not (
launchpad_accept_public_upload
or echo.confirm(
"All data sent to remote builders will be publicly available. Are you sure you want to continue?",
default=True,
)
):
raise errors.AcceptPublicUploadError()


def _clean_build(lp: LaunchpadClient):
echo.info("Cleaning existing builds and artifacts...")
lp.cleanup()
Expand Down
40 changes: 0 additions & 40 deletions tests/legacy/unit/commands/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,6 @@ def setUp(self):
)
)

@mock.patch("snapcraft_legacy.cli.remote.echo.confirm")
def test_remote_build_prompts(self, mock_confirm):
result = self.run_command(["remote-build"])

self.mock_lc_init.assert_called_once_with(
project=mock.ANY,
architectures=mock.ANY,
deadline=mock.ANY,
build_id="snapcraft-test-snap-fakehash123",
)
self.mock_lc.start_build.assert_called_once()
self.mock_lc.cleanup.assert_called_once()
self.assertThat(result.output, Contains("Building snap package for i386."))
self.assertThat(result.exit_code, Equals(0))
mock_confirm.assert_called_once_with(
"All data sent to remote builders will be publicly available. Are you sure you want to continue?",
default=True,
)

@mock.patch("snapcraft_legacy.cli.remote.echo.confirm")
def test_remote_build_with_accept_option_doesnt_prompt(self, mock_confirm):
result = self.run_command(["remote-build", "--launchpad-accept-public-upload"])
Expand All @@ -82,13 +63,6 @@ def test_remote_build_with_accept_option_doesnt_prompt(self, mock_confirm):
self.assertThat(result.exit_code, Equals(0))
mock_confirm.assert_not_called()

@mock.patch("snapcraft_legacy.cli.remote.echo.confirm")
def test_remote_build_without_acceptance_raises(self, mock_confirm):
mock_confirm.return_value = False
self.assertRaises(
errors.AcceptPublicUploadError, self.run_command, ["remote-build"]
)

def test_remote_build_with_build_id(self):
result = self.run_command(
[
Expand Down Expand Up @@ -139,20 +113,6 @@ def test_remote_build_invalid_user_arch(self):
self.mock_lc.start_build.assert_not_called()
self.mock_lc.cleanup.assert_not_called()

@mock.patch("snapcraft_legacy.cli.remote.echo")
def test_remote_build_sudo_errors(self, mock_echo):
self.useFixture(fixtures.EnvironmentVariable("SUDO_USER", "testuser"))
self.useFixture(fixtures.MockPatch("os.geteuid", return_value=0))

self.run_command(["remote-build", "--launchpad-accept-public-upload"])
mock_echo.assert_has_calls(
[
mock.call.warning(
"Running with 'sudo' may cause permission errors and is discouraged."
)
]
)

@mock.patch("snapcraft_legacy.cli.remote.echo")
def test_remote_build_recover_doesnt_prompt(self, mock_echo):
result = self.run_command(["remote-build", "--recover"])
Expand Down
Loading

0 comments on commit 686d697

Please sign in to comment.