Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,23 @@
]
]
}
},
"DummyRepoArn": {
"Value": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":ecr:us-east-1:123456789012:repository/DUMMY_ARN"
]
]
}
},
"IsDummy": {
"Value": "true"
}
},
"Parameters": {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ new CfnOutput(lookupStack, 'RepositoryUri', {
value: lookupRepo.repositoryUri,
});

const dummyRepo = ecr.Repository.fromLookup(lookupStack, 'DummyRepo', {
repositoryArn: 'arn:aws:ecr:us-east-1:123456789012:repository/does-not-exist-repo',
mustExist: false,
});

new CfnOutput(lookupStack, 'DummyRepoArn', { value: dummyRepo.repositoryArn });
new CfnOutput(lookupStack, 'IsDummy', { value: String(ecr.Repository.isLookupDummy(dummyRepo)) });

new IntegTest(app, 'EcrRepoLookupTest', {
enableLookups: true,
stackUpdateWorkflow: false,
Expand Down
17 changes: 17 additions & 0 deletions packages/aws-cdk-lib/aws-ecr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,23 @@ const repositoryFromLookup = ecr.Repository.fromLookup(this, 'ImportedRepoByLook
});
```

If the target repository is not found in your account when using `Repository.fromLookup()`, an error will be thrown.
To prevent the error in the case, you can receive a dummy repository without the error
by setting `mustExist` to `false`. The dummy repository has a `repositoryArn` of
`arn:{partition}:ecr:us-east-1:123456789012:repository/DUMMY_ARN`. You can check if the
repository is a dummy repository by using the `Repository.isLookupDummy()` method.

```ts
const dummy = ecr.Repository.fromLookup(this, 'ImportedRepoByLookup', {
repositoryArn: 'arn:aws:ecr:us-east-1:123456789012:repository/does-not-exist-repo',
mustExist: false,
});

if (ecr.Repository.isLookupDummy(dummy)) {
Copy link
Contributor

@rix0rrr rix0rrr Apr 3, 2025

Choose a reason for hiding this comment

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

I think I'd rather have a new lookup function that returns IRepository | undefined.


That new lookup function would also be a perfect place to attach disclaimers that I'm 99% sure most users will not expect, such as:

  • The result of this lookup is cached in context at the time of synth. Therefore:
    • You must have access to all environments you want to deploy this to at synth time
    • The lookup does not run at deploy time, so if the repository gets created/deleted between synth time and deploy time, the behavior will be not what you expect.
    • After one successful synth, if a user regrets the decision and want to change it (i.e., externally delete/create the resource), the user has clear the context entry before trying again.

I'm sure there's more that I can't think of right now.

Are we 100% confident offering this feature will lead to a good user experience? Because I'm concerned it will end up being a footgun.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm... You have a point, maybe we can't be 100% sure...
I'll drop this PR for now. If I get another good idea I might reopen it.

// alternative process
}
```

## CloudWatch event rules

You can publish repository events to a CloudWatch event rule with `onEvent`:
Expand Down
45 changes: 38 additions & 7 deletions packages/aws-cdk-lib/aws-ecr/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Arn,
ValidationError,
UnscopedValidationError,
ArnComponents,
} from '../../core';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
import { AutoDeleteImagesProvider } from '../../custom-resource-handlers/dist/aws-ecr/auto-delete-images-provider.generated';
Expand Down Expand Up @@ -620,19 +621,43 @@ export interface RepositoryLookupOptions {
* @default - Do not filter on repository ARN
*/
readonly repositoryArn?: string;

/**
* Whether to throw an error if the repository was not found.
*
* If it is set to `false` and the repository was not found, a dummy
* repository with the arn 'arn:{partition}:ecr:us-east-1:123456789012:repository/DUMMY_ARN'
* will be returned. You can check if the repository is a dummy repository by using the
* `Repository.isLookupDummy()` method.
*
* @default true
*/
readonly mustExist?: boolean;
}

export interface RepositoryAttributes {
readonly repositoryName: string;
readonly repositoryArn: string;
}

const dummyArnComponents: ArnComponents = {
service: 'ecr',
region: 'us-east-1',
account: '123456789012',
resource: 'repository',
resourceName: 'DUMMY_ARN',
};

/**
* Define an ECR repository
*/
export class Repository extends RepositoryBase {
/**
* Lookup an existing repository
*
* If you set `mustExist` to `false` in `options` and the repository was not found,
* this method will return a dummy repository with the arn 'arn:{partition}:ecr:us-east-1:123456789012:repository/DUMMY_ARN'.
* You can check if the repository is a dummy repository by using the `Repository.isLookupDummy()` method.
*/
public static fromLookup(scope: Construct, id: string, options: RepositoryLookupOptions): IRepository {
if (Token.isUnresolved(options.repositoryName) || Token.isUnresolved(options.repositoryArn)) {
Expand All @@ -659,15 +684,10 @@ export class Repository extends RepositoryBase {
} as cxschema.CcApiContextQuery,
dummyValue: [
{
Arn: Stack.of(scope).formatArn({
service: 'ecr',
region: 'us-east-1',
account: '123456789012',
resource: 'repository',
resourceName: 'DUMMY_ARN',
}),
Arn: Stack.of(scope).formatArn(dummyArnComponents),
},
],
mustExist: options.mustExist,
}).value;

const repository = response[0];
Expand All @@ -679,6 +699,17 @@ export class Repository extends RepositoryBase {
});
}

/**
* Checks if the repository returned by the `Repository.fromLookup()` method is a dummy repository,
* i.e., a repository that was not found.
*
* This method can only be used if the `mustExist` option is set to `false` in the `options`
* for the `Repository.fromLookup()` method.
*/
public static isLookupDummy(repository: IRepository): boolean {
return repository.repositoryArn === Stack.of(repository).formatArn(dummyArnComponents);
}

/**
* Import a repository
*/
Expand Down
Loading