Skip to content

Add config file support for config resolution#729

Open
ubaskota wants to merge 1 commit into
smithy-lang:developfrom
ubaskota:config_file_support
Open

Add config file support for config resolution#729
ubaskota wants to merge 1 commit into
smithy-lang:developfrom
ubaskota:config_file_support

Conversation

@ubaskota

Copy link
Copy Markdown
Contributor

Issue #, if available:

Description of changes:
Add support for parsing and merging AWS config and credentials files (~/.aws/config and ~/.aws/credentials) as part of the config resolution pipeline.

Changes:

  • Custom line-by-line parser that handles inline comments, sub-properties, and property continuations
  • Standardizer that normalizes section prefixes, handles [default] vs [profile default] precedence, and separates
    profiles/sso-sessions/services
  • ParsedConfigFile class for querying the merged result
  • Async config loading with path resolution from environment variables and defaults
  • Added support to run conformance tests (config-file-parser-tests.json and config-file-location-tests.json)+ unit tests for the query layer and file I/O edge cases

Testing:

  • Verified that the newly added and existing tests pass
  • Manually tested against a real ~/.aws/config file to verify end-to-end parsing behavior

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@ubaskota ubaskota requested a review from a team as a code owner June 24, 2026 18:56
)

return (
os.path.expanduser(os.path.expandvars(config_path)),

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.

[Trying to reason about this, genuine question]

This method strikes me as a little odd. If someone passes both the credentials and config path, we don't take them literally and just expand the user for them.

I don't know if this needs to be a public method. We could have all of this logic inline in load_config, or make it private, no?

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.

[change requested]

To keep the public facing interface smaller, could we move this logic inline to load_config?

Also, for now can we get rid of the expandvars? We shouldn't expand environment vars if other SDKs don't, and I don't see it in the specification.

)

return (
os.path.expanduser(os.path.expandvars(config_path)),

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.

[change requested]

To keep the public facing interface smaller, could we move this logic inline to load_config?

Also, for now can we get rid of the expandvars? We shouldn't expand environment vars if other SDKs don't, and I don't see it in the specification.

"""Read file content asynchronously.

Returns None if the file doesn't exist or can't be opened.
Per the SEP: inaccessible files are treated as empty.

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.

[Change requested]
There's a couple places we reference "SEP", which we shouldn't do in public code. Can you reword this?

)


async def load_config(

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.

[discussion]

While I like this as an entry point, there's a few things I'd want to consider changing, but you're more into this than me so maybe I'm missing something.

First, the ParsedConfigFile is a strange name for an object that holds two parsed files (config + credentials). Maybe we should rename to MergedConfig?

Second (and this one is more spitballing), how would you feel about accepting a path object instead of a string so we don't need to normalize?

def parse_content(content: str) -> RawParsedSections:
"""Parse config file content from a string into raw sections.

Note: This function is public only for direct use by unit tests

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.

[Still thinking about how to fix this one, but commenting cause I think it's right]

If this should be internal, we should make it an internal method. If that makes it harder to test, that's a sign that we should reconsider our testing strategy. This probably looks like writing to temp files as the config files for testing.

raise ConfigParseError(f"Line {line_num}: Section definition must end with ']'")
inner = stripped[1:bracket_end].strip()
if not inner:
raise ConfigParseError(f"Line {line_num}: Section name cannot be empty")

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.

[Discussion for posterity, no change requested]

I feel weird commenting this because I like what you did here better than the specification, but the spec does say that this is wrong for sso-session profiles:

If the session name is empty, the section and all following properties must be ignored.

Service profiles have a similar line:

If the services configuration name is empty, the section and all following properties MUST be ignored.

I'm not asking you to change this for two reasons:

  1. If we ever want to conform with the specification, we can always come back and update this code
  2. It's a better customer experience to let them know we are ignoring their profile's content.

Maybe there's something I'm missing as to why that was part of the spec, but for now, let's leave it as is unless you (or anyone else) disagrees. Also, depending on how you read the word "ignored", we could consider raising an error ignoring this since those values would never be used...

from smithy_aws_core.config.exceptions import ConfigParseError

# Type aliases
type PropertyValue = str | dict[str, str]

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.

[Discussion]

Instead of having ProfileProperties, a ProfileMap could have a Profile; each Profile could have ScalarProperties and GroupedProperties. The union feels a bit awkward to have to consider while coding around the StandardizedOutput object.

What do you think?

try:
content = await asyncio.to_thread(Path(path).read_text, encoding="utf-8")
return content
except (FileNotFoundError, PermissionError, OSError, UnicodeDecodeError):

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.

[Suggestion]

For usability, maybe we should add a warning on PermissionError and OSError so the user knows that we couldn't read from the requested file. Also, a UnicodeDecodeError should raise and not silently fail.

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants