Repository: getmoto/moto

Issue:
Moto ECR mock throws away user-provided registry IDs
The Moto ECR mock does not respect registry ID parameters with any ECR APIs - instead it uses a hard-coded default (the account ID itself). This causes incorrect state/output when test authors need to mock behavior around specific registry IDs.

Example code - just a create_repository call with a specific, non-default registry ID:

```
client = boto3.client('ecr', region_name='us-west-2')
client.create_repository(
        registryId='222222222222',
        repositoryName='myRepoName'
    )
print(client.describe_repositories())
```

Example output showing that the repository was created under the wrong registry ID:

```
{'repositories': [{'repositoryArn': 'arn:aws:ecr:us-west-2:123456789012:repository/myRepoName', 'registryId': '123456789012', 'repositoryName': 'myRepoName', 'repositoryUri': '123456789012.dkr.ecr.us-west-2.amazonaws.com/myRepoName', 'createdAt': datetime.datetime(2021, 12, 21, 0, 53, 14, tzinfo=tzlocal()), 'imageTagMutability': 'MUTABLE', 'imageScanningConfiguration': {'scanOnPush': False}, 'encryptionConfiguration': {'encryptionType': 'AES256'}}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'amazon.com'}, 'RetryAttempts': 0}}
```

The output says the registry ID is 123456789012 which is the default account ID. My input of registry ID of 222222222222 was ignored.

Reading the source code this seems like it's by design - we hard-code the registry ID here: https://github.com/spulec/moto/blame/43269fc8af7872aebb648233c551053d499503b4/moto/ecr/models.py#L75

In addition we don't even accept a registry ID for create_repository: https://github.com/spulec/moto/blame/43269fc8af7872aebb648233c551053d499503b4/moto/ecr/models.py#L407-L414

This is with Moto v2.2.19 (latest I believe) and Boto3 v1.20.21.


Discussion:
Worth noting that our tests were previously working with Moto v1.3.16, so this seems to be a regression... but reading through the same code in 1.3.16 we seem to have the same issues, we don't actually ingest registry ID anywhere. Still trying to figure out what changed.

It is odd that we have all this code that does sanity checks/filters on registry ID, like this: https://github.com/spulec/moto/blame/43269fc8af7872aebb648233c551053d499503b4/moto/ecr/models.py#L395-L398 when it's impossible to actually use a non-default registry ID... maybe I'm missing something about how registry IDs are set?
Ah figured it out, this was always broken even in 1.3.16, there is just very poor enforcement of registry ID across the board. In 1.3.16 it was possible to create a repository with a unique registry ID, put an image there, and then call describe_images while passing in that registry ID - and this would all work behind the scenes, even though the registry ID itself was never saved, and the repository was technically invalid. This is because in the describe_images call in 1.3.16, we matched a repository by name without applying a registry ID filter: 

https://github.com/spulec/moto/blob/63ce647123755e4c4693a89f52c254596004c098/moto/ecr/models.py#L275-L280

However in 2.2.19 describe_images now delegates to a new method called _get_repository which actually performs this registry ID filter and throws because our registry ID was thrown away:

https://github.com/spulec/moto/blob/43269fc8af7872aebb648233c551053d499503b4/moto/ecr/models.py#L364-L370

The issue is still the same, under the hood the registry IDs have always been ignored and hard-coded, we just now throw on certain API calls if users actually pass in custom registry IDs, whereas before we silently passed(!!!). Registry ID support in general seems half-baked - there are no ways to pass in registry IDs but there are multiple attempts to enforce matching registry IDs on read APIs.
Thanks for raising this @sichinumi, and for doing the investigation - that will make it much quicker to fix it. 
Raising is as a bug.

Resolving patch:
diff --git a/moto/ecr/models.py b/moto/ecr/models.py
--- a/moto/ecr/models.py
+++ b/moto/ecr/models.py
@@ -66,12 +66,13 @@ def __init__(
         self,
         region_name,
         repository_name,
+        registry_id,
         encryption_config,
         image_scan_config,
         image_tag_mutablility,
     ):
         self.region_name = region_name
-        self.registry_id = DEFAULT_REGISTRY_ID
+        self.registry_id = registry_id or DEFAULT_REGISTRY_ID
         self.arn = (
             f"arn:aws:ecr:{region_name}:{self.registry_id}:repository/{repository_name}"
         )
@@ -190,6 +191,7 @@ def create_from_cloudformation_json(
             # RepositoryName is optional in CloudFormation, thus create a random
             # name if necessary
             repository_name=resource_name,
+            registry_id=None,
             encryption_config=encryption_config,
             image_scan_config=image_scan_config,
             image_tag_mutablility=image_tag_mutablility,
@@ -406,6 +408,7 @@ def describe_repositories(self, registry_id=None, repository_names=None):
     def create_repository(
         self,
         repository_name,
+        registry_id,
         encryption_config,
         image_scan_config,
         image_tag_mutablility,
@@ -417,6 +420,7 @@ def create_repository(
         repository = Repository(
             region_name=self.region_name,
             repository_name=repository_name,
+            registry_id=registry_id,
             encryption_config=encryption_config,
             image_scan_config=image_scan_config,
             image_tag_mutablility=image_tag_mutablility,
diff --git a/moto/ecr/responses.py b/moto/ecr/responses.py
--- a/moto/ecr/responses.py
+++ b/moto/ecr/responses.py
@@ -24,6 +24,7 @@ def _get_param(self, param, if_none=None):
 
     def create_repository(self):
         repository_name = self._get_param("repositoryName")
+        registry_id = self._get_param("registryId")
         encryption_config = self._get_param("encryptionConfiguration")
         image_scan_config = self._get_param("imageScanningConfiguration")
         image_tag_mutablility = self._get_param("imageTagMutability")
@@ -31,6 +32,7 @@ def create_repository(self):
 
         repository = self.ecr_backend.create_repository(
             repository_name=repository_name,
+            registry_id=registry_id,
             encryption_config=encryption_config,
             image_scan_config=image_scan_config,
             image_tag_mutablility=image_tag_mutablility,


Test patch:
diff --git a/tests/test_ecr/test_ecr_boto3.py b/tests/test_ecr/test_ecr_boto3.py
--- a/tests/test_ecr/test_ecr_boto3.py
+++ b/tests/test_ecr/test_ecr_boto3.py
@@ -116,6 +116,37 @@ def test_create_repository_with_non_default_config():
     )
 
 
+@mock_ecr
+def test_create_repository_in_different_account():
+    # given
+    client = boto3.client("ecr", region_name="us-east-1")
+    repo_name = "test-repo"
+
+    # when passing in a custom registry ID
+    response = client.create_repository(
+        registryId="222222222222", repositoryName=repo_name
+    )
+
+    # then we should persist this ID
+    repo = response["repository"]
+    repo.should.have.key("registryId").equals("222222222222")
+    repo.should.have.key("repositoryArn").equals(
+        "arn:aws:ecr:us-east-1:222222222222:repository/test-repo"
+    )
+
+    # then this repo should be returned with the correct ID
+    repo = client.describe_repositories()["repositories"][0]
+    repo.should.have.key("registryId").equals("222222222222")
+
+    # then we can search for repos with this ID
+    response = client.describe_repositories(registryId="222222222222")
+    response.should.have.key("repositories").length_of(1)
+
+    # then this repo is not found when searching for a different ID
+    response = client.describe_repositories(registryId=ACCOUNT_ID)
+    response.should.have.key("repositories").length_of(0)
+
+
 @mock_ecr
 def test_create_repository_with_aws_managed_kms():
     # given