-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding workspace/account logic for python #81
Conversation
@@ -441,6 +442,14 @@ def default_config(host=None, | |||
access_token = os.getenv("PULUMI_ACCESS_TOKEN") | |||
if not host: | |||
host = os.getenv("PULUMI_BACKEND_URL") | |||
|
|||
account, backend_url = workspace.get_current_account(False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check whether access token or host because this is probing the file system and we should avoid it if we have what we need?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will check if either accessToken or backend URL is missing before loading account
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets mark this and the workspace_models.py as linguist-generated=false in .gitattributes
CHANGELOG_PENDING.md
Outdated
@@ -4,6 +4,8 @@ | |||
- All SDK now automatically read in configuration environment variables | |||
- Go SDK also automatically picks up configuration from CLI Pulumi accounts | |||
[#76](https://github.com/pulumi/esc-sdk/pull/76) | |||
- Adding CLI Pulumi accounts loading logic into Python SDK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Adding CLI Pulumi accounts loading logic into Python SDK | |
- Adds support for reading credentials from disk to Python SDK |
@@ -0,0 +1,89 @@ | |||
# Copyright 2025, Pulumi Corporation. All rights reserved. | |||
|
|||
"""Recreating Pulumi workspace and account logic for python SDK.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also recommend adding a reference to the go implementation here so we know what this is implementing and make it clear that we are porting over some existing code.
"""Recreating Pulumi workspace and account logic for python SDK.""" | |
"""Pulumi workspace and account logic for python SDK.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, will link to ESC CLI code
# Otherwise, use the current user's home dir + .pulumi | ||
home_dir = pathlib.Path.home() | ||
|
||
return str(home_dir / ".pulumi") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return str(home_dir / ".pulumi") | |
return str(home_dir.joinpath(".pulumi")) |
def get_esc_bookkeeping_dir() -> str: | ||
""" | ||
Returns the path of the '.esc' folder inside Pulumi home dir. | ||
""" | ||
return get_pulumi_path(".esc") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is that directory for? I don't see it on my machine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one appears if you login via esc cli, not the pulumi one. It is a single-value file like this
{
"name": "https://api.pulumi.com/"
}
that allows you to override current account. So if your current account in normal credentials file is moolumi.com
, and you use pulumi CLI, that's what it will use. But if using esc CLI you then login to boolumi.com
, it will use that as default in ESC CLI. See this method as go reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay sweet can we just add a comment to that effect?
""" | ||
Returns the path to the esc credentials file on disk. | ||
""" | ||
return str(pathlib.Path(dir) / "credentials.json") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return str(pathlib.Path(dir) / "credentials.json") | |
return str(pathlib.Path(dir).joinpath("credentials.json")) |
@@ -0,0 +1,41 @@ | |||
# Copyright 2025, Pulumi Corporation. All rights reserved. | |||
|
|||
"""Pulumi workspace models.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reference the file/code this is porting
"lastValidatedAt": "2026-03-17T11:19:28.392525488-06:00" | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} | |
@@ -0,0 +1,3 @@ | |||
{ | |||
"name": "https://api.boolumi.com" | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} | |
"lastValidatedAt": "2026-03-17T11:19:28.392525488-06:00" | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} | |
self.assertTrue(self.config.api_key['Authorization'], "pul-fake-token-boo") | ||
|
||
if __name__ == '__main__': | ||
unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unittest.main() | |
unittest.main() | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the thorough review, fixed up these issues!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mostly small things, looks good otherwise
|
||
return str(home_dir.joinpath(".pulumi")) | ||
|
||
def get_pulumi_path(*elem: str) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this method is only called with ".esc" and with no args. can the no args call be replaced with get_pulumi_home_dir()
? If so, then I don't think this method has any purpose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point, I was porting over method by method, and in CLI there's more uses for it. But in this case, I don't expect more usage, so can eliminate this for sure.
""" | ||
creds_file = get_path_to_creds_file(get_esc_bookkeeping_dir()) | ||
try: | ||
with open(creds_file, 'r') as f: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's be consistent with using double quotes, here and elsewhere
try: | ||
with open(creds_file, 'r') as f: | ||
data = json.loads(f.read()) | ||
return data['name'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should add a case for if name
doesn't exist. whether that's a conditional here or caught as an except ...
, either is fine to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, no reason to print "unexpected error" when it is rather expected. I'll make it return None on KeyError
accounts: Dict[str, Account] | ||
|
||
@classmethod | ||
def from_json(self, dataJson: str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Account.from_json reads from a dict, but this reads from a json string? maybe we can name this from_file
and pass the file? (json.loads can accept a file so we can skip the extra f.read() step)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, not great that they are different. I think it would be better to keep the methods the same, both from_json
s receiving dict
with values, and we can follow the same pattern of data = json.loads(f.read())
on file loading
def from_json(self, dataJson: str): | ||
data = json.loads(dataJson) | ||
accounts: Dict[str, Account] = {} | ||
accounts_dict = data.get("accounts") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems more natural to me, but what you have is fine
if "accounts" in data:
for ...
Also, should we take into account if the creds file is in the wrong format? I assume this will throw an ambiguous attribute error, so if we can have a helpful error that might be better. (do we give a helpful error in pulumi/pulumi for the equivalent?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ty, I've no idea what's natural or not in python 😅
Hmm, creds files are not meant to be edited by hand, so I think it's fine to just throw an ambiguous "unexpected error". But I think it might be worth adding a test that makes sure that's what happens and SDK doesn't break.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer a clearer error but not a huge deal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In most scenarios it won't throw an error at all, just return None, it's only if you screw us the schema in very specific ways (like for example adding "accounts" key, but making it a string and not an object). I just think it would be overkill to try to catch all potential errors on bad deserializations
print(f"An unexpected error occured: {e}") | ||
return None | ||
|
||
def get_current_account(shared) -> tuple[Account, str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is shared for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Shared
is true, we would ignore the ESC override and use the account selected in main credentials file - same thing as with the path method, not strictly necessary in this implementation, I was just keeping it as in the CLI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we're not using it, i would opt to remove it. but if you want to keep it to match the CLI, i would at least document it
Summary
Testing
Note: I'm not a Python expert, so please correct any conventions I missed