Skip to content

fix(no-ticket): keep credentials.ini sections and save missing API keys - #533

Merged
cloudsmith-iduffy merged 6 commits into
masterfrom
iduffy/fix-logout-credentials-corruption
Nov 2, 2026
Merged

cloudsmith-iduffy merged 6 commits into
masterfrom
iduffy/fix-logout-credentials-corruption

Conversation

@cloudsmith-iduffy

@cloudsmith-iduffy cloudsmith-iduffy commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Description

This PR fixes two bugs in CredentialsReader._set_api_key. The first breaks credentials.ini on logout. The second loses a new API key without an error.

cloudsmith logout can break credentials.ini so badly that no later command runs. The pattern in CredentialsReader._set_api_key used \s* after the =. \s also matches a newline, so an empty api_key = line swallowed the blank line and the next section header.

The common way to hit it is to run logout twice. Start with:

[default]
api_key = defaultkey5678

[profile:staging]
api_key = stagingkey1234

The first logout is fine and leaves both keys empty. The second logout turns the file into:

[default]
api_key =
api_key =

After that, every command fails with DuplicateOptionError: ... option 'api_key' in section 'default' already exists, including logout itself, so the user has to fix the file by hand. login and the --save-config options (tokens create, tokens refresh, auth --request-api-key) go through the same function through update_api_key, so they did the same damage when they wrote a key after an empty one.

With this change, the second logout leaves the file as:

[default]
api_key =

[profile:staging]
api_key =

The change is in _set_api_key only:

  • The pattern is now ^api_key[ \t]*[=:].*$, so it cannot cross a line. It also accepts :, which configparser reads as a separator. Before, logout said it removed the credentials but left an api_key: ... line in place. This is the same entry pattern that _set_section_value in the same file uses.
  • re.sub gets a callable instead of a template string, so a backslash in a key is written as it is.
  • The three methods have type hints.

_set_section_value is not a drop-in replacement for the clear. It edits one section and adds the key when the section has none, which would change what logout writes.

Second fix: a missing key was never saved

When credentials.ini has no api_key line in [default], update_api_key changed nothing. create_config_files still printed Credentials config file: ... UPDATED, so login and the --save-config options lost the new key without telling you. For example, a file with only:

[default]

stayed like that. Now it becomes:

[default]
api_key = NEWKEY

When a key is written, _set_api_key now also calls _set_section_value for [default]. That replaces the key there, or adds it when it is missing. When the file has no [default] section, it creates one. The clear path does not call it, so logout never adds a key line.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • Other (please describe)

Additional Notes

  • Tests are in the new cloudsmith_cli/cli/tests/test_config.py. They call CredentialsReader on a file in tmp_path, because the logout command tests mock the reader. One test clears a two-key file twice: the first call makes the file that has the empty key, and the second call is the call that broke it. The second test covers the update_api_key path after an empty key. The third test covers a [default] section that has no key. Each test failed before its fix.
  • CRLF files: the file is read in text mode, so \r\n becomes \n before the pattern runs and no \r is left behind. The file is written back with the platform line ending. The CRLF case of the clear test compares bytes and has the windows marker. It runs on every platform.
  • The windows marker line in pyproject.toml is the same as the one in iduffy/fix-non-ascii-output-crash, which adds the marker and a windows-latest CI job. Whichever PR merges second keeps the line with no conflict. pytest runs with --strict-markers, so this PR has to register the marker itself.
  • Not changed: update_api_key writes the new key into every section, not only the active profile. So tokens create --save-config for the default profile also replaces the key in each [profile:NAME] section, and logout clears every section. Logout scopes the keyring step to the active profile but not the file step, and create_config_files has no profile to pass. The intended behaviour is not clear from the code, tests, or docs, so I left it for a separate change.
  • A file that an earlier version already broke still fails to parse. You must put the missing section header back by hand.

🤖 Generated with Claude Code

The api_key pattern used "\s*" after "=". "\s" also matches a newline,
so an empty api_key consumed the next line that is not blank. On the
second logout, this removed the next section header, and each later
command failed with DuplicateOptionError.

Match only spaces and tabs, accept ":" as configparser does, and give
re.sub a callable so the key is never read as a template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 23, 2026 21:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused fix preserves existing behavior, covers the reported corruption paths with regression tests, and has no unresolved findings.

No open issues 🎉

What changed in this PR

Fixes credentials-file corruption when logout clears an already-empty API key or login updates one, preserving section headers and the existing all-section behavior.

Changes:

  • Restricts API-key replacement to one line, supports : separators, and preserves literal backslashes.
  • Adds regression tests for repeated clears, updates after empty keys, and LF/CRLF input.
  • Documents the fix and registers the Windows test marker.
File Description
pyproject.toml Registers the Windows test marker.
cloudsmith_cli/​cli/​tests/​test_config.py Tests section preservation during credential edits.
cloudsmith_cli/​cli/​config.py Fixes API-key replacement and adds type hints.
CHANGELOG.md Documents the corruption fix and affected commands.

Review effort: Balanced

@cloudsmith-iduffy
cloudsmith-iduffy enabled auto-merge (squash) October 24, 2026 17:43
When the [default] section had no api_key line, update_api_key changed
nothing, but login and --save-config showed UPDATED. Add the key to
[default], and create the section when it does not exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudsmith-iduffy cloudsmith-iduffy changed the title fix(no-ticket): keep credentials.ini sections when logout clears a key fix(no-ticket): keep credentials.ini sections and save missing API keys Oct 24, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudsmith-iduffy

Copy link
Copy Markdown
Collaborator Author

@copilot resolve the merge conflicts on this branch.

…redentials-corruption

# Conflicts:
#	CHANGELOG.md
#	cloudsmith_cli/cli/config.py

Co-authored-by: cloudsmith-iduffy <178375997+cloudsmith-iduffy@users.noreply.github.com>
auto-merge was automatically disabled October 30, 2026 10:39

Head branch was pushed to by a user without write access

Copilot AI commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts on this branch.

Resolved the conflicts and merged master in commit 1265346. The merge is published.

* origin/master:
  fix(no-ticket): set a default timeout on each API request (#538)
  fix(no-ticket): print the result count when a list is empty (#545)
  fix(no-ticket): escape the args in the terraformrc block (#543)
  fix(no-ticket): show the dependencies help for list deps (#546)

# Conflicts:
#	CHANGELOG.md
@cloudsmith-iduffy
cloudsmith-iduffy enabled auto-merge (squash) November 2, 2026 00:22
@cloudsmith-iduffy
cloudsmith-iduffy merged commit 3a13aa7 into master Nov 2, 2026
19 checks passed
@cloudsmith-iduffy
cloudsmith-iduffy deleted the iduffy/fix-logout-credentials-corruption branch November 2, 2026 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants