Skip to content
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

Support custom ids #8216

Merged
merged 15 commits into from
Oct 13, 2024
Merged

Support custom ids #8216

merged 15 commits into from
Oct 13, 2024

Conversation

cloutierMat
Copy link
Contributor

@cloutierMat cloutierMat commented Oct 9, 2024

Motivation

This pr aims to introduce a mechanism by which we can define custom ids in order to be able to create a resource with deterministic ids.

A ResourceIdentifier base class will allow us to quickly create objects to manage ids. Each resource can implement their own generate method giving us much flexibility when it comes to the type of id created.

The MotoIdManager implements an interface over the registered custom ids. It also contains a method add_id_source that will allow us the possibility to further expand the behaviour of the id manager.

Changes

  • Implementation of the id manager
  • Integration of the ResourceIdentifier in apigateway and secretsmanager as to provide examples of how it can be used.
  • Added tests for implemented services.

Not in scope

Many services can benefit from this upgrade, but only 2 are implemented here to provide a sample of how to use. Follow PRs will spread the functionality to more services. This should help the review process over smaller PRs!

Copy link

codecov bot commented Oct 9, 2024

Codecov Report

Attention: Patch coverage is 99.27007% with 1 line in your changes missing coverage. Please review.

Project coverage is 94.54%. Comparing base (43410b9) to head (29ba736).
Report is 14 commits behind head on master.

Files with missing lines Patch % Lines
moto/utilities/id_generator.py 98.68% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           master    #8216    +/-   ##
========================================
  Coverage   94.53%   94.54%            
========================================
  Files        1155     1158     +3     
  Lines       99707   100089   +382     
========================================
+ Hits        94261    94629   +368     
- Misses       5446     5460    +14     
Flag Coverage Δ
servertests 28.88% <87.59%> (+0.03%) ⬆️
unittests 94.51% <99.27%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

self._lock = threading.RLock()
self._id_sources = []

self.add_id_source(self.get_custom_id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the rationale behind having multiple ID sources?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Allow for being expanded in the future, maybe adding a deterministic fallback. It might be a bit of a premature optimisation and I am not married to this solution.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Having a deterministic fallback would make sense here, considering moto_id_generator is the only place where LS can tweak the behaviour.

deployment_id = create_id()
# Since there are no unique values to a deployment, we will use the stage name for the deployment.
# We are also passing a list of deployment ids to generate to prevent overwriting deployments.
deployment_id = ApigwDeploymentIdentifier(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Having a list of ExistingId's essentially means that it's up to the user to call moto_id_manager.set_custom_id(..) before every Deployment is instantiated.

So in pseudo code:

moto_id_manager.set_custom_id(DeploymentIdentifier, 'suffix')
create_stage(name="mystage")
create_deployment()  # deployment_id = account.region.suffix.mystage
create_deployment()  # deployment_id = random identifier, because 'account.region.suffix.mystage' already exists

moto_id_manager.set_custom_id(DeploymentIdentifier, 'suffix2')
create_deployment()  # deployment_id = account.region.suffix2.mystage

Am I understanding the flow correctly?

I can't recall another resource where this is the case, but I'm sure this happens more often.. And it is slightly confusing if the actual behaviour differs for specific Resources.

Do you think it makes sense to implement this in a different way? Maybe make it an incremental generator somehow, so that the names become account.region.deployment1, account.region.deployment2, etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are correct.
However I don't see this as a big issue but rather a normal behavior.
The way it is currently implemented would guarantee the first run of your IAC will have the same result every time you restart. But subsequent deployment will create a new deployment in a non-destructive manner.

I can see the argument going both ways and if it ever becomes problematic we can review this behaviour, and add an auto-increment.

@cloutierMat cloutierMat marked this pull request as ready for review October 11, 2024 18:13
@cloutierMat cloutierMat requested a review from bblommers October 11, 2024 18:14
Copy link
Collaborator

@bblommers bblommers left a comment

Choose a reason for hiding this comment

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

LGTM! The actual implementation (writing a ResourceIdentifier class) is fairly simple, and there is plenty of docstrings to understand the actual implementation if people are curious.

There are a few nitpicks, but none of them blocking, so feel fre to address that in a followup PR.

Thank you @cloutierMat!

existing_ids: ExistingIds = None,
tags: Tags = None,
):
return GENERATED_ID
Copy link
Collaborator

Choose a reason for hiding this comment

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

Bit of a nitpick, and not blocking a merge, but calling this GENERIC_ID may be more fitting, as that's a better opposite to CUSTOM_ID

@pytest.mark.skipif(
not settings.TEST_DECORATOR_MODE, reason="Can't access the id manager in proxy mode"
)
def test_create_secret_custom_id(account_id, set_custom_id):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another non-blocking nitpick - the account_id fixture is only useful for tests that will also be run against AWS.

It's faster (and better readable IMO) to use the static ACCOUNT_ID directly.

def get_custom_id(
self, resource_identifier: ResourceIdentifier
) -> Union[str, None]:
# retrieves a custom_id for a resource. Returns None
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# retrieves a custom_id for a resource. Returns None
# retrieves a custom_id for a resource. Returns None _if_ ...

def __init__(self, account_id: str, region: str, name: str):
super().__init__(account_id, region, name)

def generate(
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be nice if we could move this method into the ResourceIdentifier-class with some sensible defaults, and only override it if necessary.

It's currently difficult to tell what is sensible/common of course, without implementing many more ResourceIdentifier-classes, so that can wait until we have a better grasp of what identifiers typically look like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I will keep that in mind when expanding support to other resources.

On one side it would make using the generate method a lot simpler if it explicitly defines the property name(s) to pass.

But we will see, it may be that most will require name and will return the same generate_str_id.

ps. I have addressed the remaining comments in #8230

@bblommers bblommers added this to the 5.0.17 milestone Oct 13, 2024
@bblommers bblommers merged commit 1ace1db into getmoto:master Oct 13, 2024
53 checks passed
@cloutierMat cloutierMat mentioned this pull request Oct 14, 2024
1 task
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