Skip to content

Commit

Permalink
fix(remotebuild): use legacy launchpad credentials if they exist
Browse files Browse the repository at this point in the history
If the legacy remote-build credentials exist and the new credentials do
not, emit a deprecation warning and use the legacy credentials.

Signed-off-by: Callahan Kovacs <callahan.kovacs@canonical.com>
  • Loading branch information
mr-cal committed Jul 6, 2024
1 parent 4bb948b commit c1c70a2
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 4 deletions.
35 changes: 35 additions & 0 deletions snapcraft/services/remotebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,47 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Snapcraft Lifecycle Service."""

import pathlib

import craft_cli
import platformdirs
from craft_application import launchpad
from craft_application.services import remotebuild
from typing_extensions import override


class RemoteBuild(remotebuild.RemoteBuildService):
"""Snapcraft remote build service."""

RecipeClass = launchpad.models.SnapRecipe

__credentials_filepath: pathlib.Path | None = None

@property
@override
def credentials_filepath(self) -> pathlib.Path:
"""The filepath to the Launchpad credentials.
The legacy credentials are only loaded when they exist and the new credentials
do not exist. If the legacy credentials are loaded, emit a deprecation warning.
"""
# return early so the deprecation notice is emitted only once
if self.__credentials_filepath:
return self.__credentials_filepath

credentials_filepath = super().credentials_filepath
legacy_credentials_filepath = (
platformdirs.user_data_path("snapcraft") / "provider/launchpad/credentials"
)

if not credentials_filepath.exists() and legacy_credentials_filepath.exists():
craft_cli.emit.progress(
f"Warning: Using launchpad credentials from deprecated location {str(legacy_credentials_filepath)!r}.\n"
f"Credentials should be migrated to {str(credentials_filepath)!r}.",
permanent=True,
)
self.__credentials_filepath = legacy_credentials_filepath
else:
self.__credentials_filepath = credentials_filepath

return self.__credentials_filepath
26 changes: 23 additions & 3 deletions snapcraft_legacy/internal/remote_build/_launchpad.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2019 Canonical Ltd
# Copyright (C) 2019,2024 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
Expand All @@ -16,6 +16,8 @@

import gzip
import logging
import pathlib
import platformdirs
import os
import shutil
import time
Expand Down Expand Up @@ -111,7 +113,6 @@ def __init__(

self._cache_dir = self._create_cache_directory()
self._data_dir = self._create_data_directory()
self._credentials = os.path.join(self._data_dir, "credentials")

self._lp: Launchpad = self.login()
self.user = self._lp.me.name
Expand All @@ -122,6 +123,25 @@ def __init__(
def architectures(self) -> Sequence[str]:
return self._architectures

@property
def _credentials_filepath(self) -> pathlib.Path:
"""The filepath to the Launchpad credentials.
If the credentials file does not exist in the default location but exists in the
legacy location, emit a deprecation warning and return the legacy location.
"""
credentials_filepath = platformdirs.user_data_path("snapcraft") / "launchpad-credentials"
legacy_credentials_filepath = platformdirs.user_data_path("snapcraft") / "provider/launchpad/credentials"

if not credentials_filepath.exists() and legacy_credentials_filepath.exists():
logger.warning(
f"Warning: Using launchpad credentials from deprecated location {str(legacy_credentials_filepath)!r}.\n"
f"Credentials should be migrated to {str(credentials_filepath)!r}."
)
return legacy_credentials_filepath

return credentials_filepath

@architectures.setter
def architectures(self, architectures: Sequence[str]) -> None:
self._lp_processors: Optional[Sequence[str]] = None
Expand Down Expand Up @@ -256,7 +276,7 @@ def login(self) -> Launchpad:
"snapcraft remote-build {}".format(snapcraft_legacy.__version__),
"production",
self._cache_dir,
credentials_file=self._credentials,
credentials_file=self._credentials_filepath,
version="devel",
)
except (ConnectionRefusedError, TimeoutError):
Expand Down
28 changes: 27 additions & 1 deletion tests/legacy/unit/remote_build/test_launchpad.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2019 Canonical Ltd
# Copyright (C) 2019,2024 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
Expand All @@ -16,6 +16,8 @@

import textwrap
from datetime import datetime, timedelta, timezone
import pathlib
import pytest
from unittest import mock

import fixtures
Expand Down Expand Up @@ -535,3 +537,27 @@ def test_push_source_tree_error(self):
self.assertRaises(
errors.LaunchpadGitPushError, self.lpc.push_source_tree, repo_dir
)


@pytest.mark.parametrize(
("new_exists", "legacy_exists", "expected"),
[
(False, False, pathlib.Path("launchpad-credentials")),
(False, True, pathlib.Path("provider/launchpad/credentials")),
(True, False, pathlib.Path("launchpad-credentials")),
(True, True, pathlib.Path("launchpad-credentials")),
],
)
def test_credentials_filepaths(new_exists, legacy_exists, expected, mocker, new_dir):
"""Load legacy credentials only when they exist and the new ones do not."""
mocker.patch.object(LaunchpadClient, "__init__", lambda x: None)
mocker.patch("platformdirs.user_data_path", return_value=new_dir)
if new_exists:
(new_dir / "launchpad-credentials").touch()
if legacy_exists:
(new_dir / "provider/launchpad/credentials").mkdir(parents=True)
(new_dir / "provider/launchpad/credentials").touch()

credentials_filepath = LaunchpadClient()._credentials_filepath

assert credentials_filepath == new_dir / expected
46 changes: 46 additions & 0 deletions tests/unit/services/test_remotebuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2024 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 RemoteBuild service tests."""

import pathlib

import pytest


@pytest.mark.parametrize(
("new_exists", "legacy_exists", "expected"),
[
(False, False, pathlib.Path("launchpad-credentials")),
(False, True, pathlib.Path("provider/launchpad/credentials")),
(True, False, pathlib.Path("launchpad-credentials")),
(True, True, pathlib.Path("launchpad-credentials")),
],
)
def test_credentials_filepaths(
new_exists, legacy_exists, expected, remote_build_service, mocker, new_dir
):
"""Load legacy credentials only when they exist and the new ones do not."""
mocker.patch("platformdirs.user_data_path", return_value=new_dir)
if new_exists:
(new_dir / "launchpad-credentials").touch()
if legacy_exists:
(new_dir / "provider/launchpad/credentials").mkdir(parents=True)
(new_dir / "provider/launchpad/credentials").touch()

credentials_filepath = remote_build_service.credentials_filepath

assert credentials_filepath == new_dir / expected

0 comments on commit c1c70a2

Please sign in to comment.