Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/release-notes/snapcraft-9-0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ Snapcraft 9.0.0
build packages with versioned dependencies couldn't be resolved.
- `craft-parts#1492 <https://github.com/canonical/craft-parts/issues/1492>`__ The
:ref:`craft_parts_poetry_plugin` didn't work for core26 snaps.
- `#6301 <https://github.com/canonical/snapcraft/issues/6301>`__ Using
``export-login`` on a credentials file that already exists failed with an
internal error.


Contributors
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies = [
"setuptools>=69.0,<80.9.0",
"snap-helpers",
"tabulate",
"tomli>=2.4.0",
"typing-extensions",
"validators>=0.28.3",
]
Expand Down
10 changes: 10 additions & 0 deletions snapcraft/commands/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ def run(self, parsed_args: argparse.Namespace) -> None:
# This is sensitive-- it should only be accessible by the owner
private_open = functools.partial(os.open, mode=0o600)

# If the file already exists from a previous export, it was made
# owner-read-only by the chmod below. The opener's mode only applies
# when creating a new file, so an existing read-only file would fail
# to open for writing. Restore owner write access first. Limit this
# to regular files so an unusual login_file (e.g. a directory) isn't
# mutated unexpectedly.
login_path = pathlib.Path(parsed_args.login_file)
if login_path.is_file():
login_path.chmod(stat.S_IRUSR | stat.S_IWUSR)

with open(
parsed_args.login_file, "w", opener=private_open, encoding="utf-8"
) as login_fd:
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/commands/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,33 @@ def test_export_login_file(project_path, emitter, fake_store_login, fake_app_con
assert login_file.read_text() == "secret"


def test_export_login_file_overwrite(
project_path, emitter, fake_store_login, fake_app_config
):
"""Re-exporting to an existing read-only credentials file should succeed."""
cmd = commands.StoreExportLoginCommand(fake_app_config)
namespace = argparse.Namespace(
login_file="target_file",
snaps=None,
channels=None,
acls=None,
expires=None,
experimental_login=False,
)

cmd.run(namespace)

login_file = project_path / "target_file"
# The first run leaves the file owner-read-only.
assert login_file.stat().st_mode & 0o777 == 0o400

# A second run must not fail with PermissionError.
cmd.run(namespace)

assert login_file.read_text() == "secret"
assert login_file.stat().st_mode & 0o777 == 0o400


def test_export_login_with_params(emitter, fake_store_login, fake_app_config):
cmd = commands.StoreExportLoginCommand(fake_app_config)

Expand Down
2 changes: 2 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading