-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(codeartifact): add L2 constructs for Repository & Domain (#54)
* feat(codeartifact): initial implementation of L2 constructs for Domain & Repository * linting * refactor imports * add readme * Apply suggestions from code review Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * Apply suggestions from code review Co-authored-by: Roman <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * refactor: use encryptionKey (IKey) instead of encryptionKeyArn * chore: add JSDOc * chore: add more JSDoc * fix: tests * chore: remove explicit rendertags * add todo * chore: add jsdoc to repository * chore: more jsdoc * add tests, fix validation * chore: remove unnneeded code... * fix: lazy rendering of policy doc * feat: implement upstreams * Apply suggestions from code review Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * refactor(Repository): move grant etc into base class * move grant methods to IDomain * chore: expose all resource attributes for domain & repository * chore: make upstreams lazy * ran full build locally * Update integration test snapshots * remove `ITaggableV2` implementation, bc it'snot needed * linting * Apply suggestions from code review Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * Apply suggestions from code review Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * Apply suggestions from code review Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * add some @default label * Update src/aws-codeartifact/domain.ts Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * refactor cfresource creating methods * Update src/aws-codeartifact/repository.ts Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * Apply suggestions from code review Co-authored-by: Kenta Goto (k.goto) <[email protected]> Signed-off-by: Benjamin Genz <[email protected]> * refactor: addToResourcePolicy & token comparison * ran build * remove some unneeded comment * update snapshots * failed integ tests * integrate change request * more review findings * do not cfreate encryption key by default, but use managed one * fix tests * update integ test stuff * ran build again to fix API.md --------- Signed-off-by: Benjamin Genz <[email protected]> Co-authored-by: Kenta Goto (k.goto) <[email protected]> Co-authored-by: Roman <[email protected]> Co-authored-by: bweigel <[email protected]> Co-authored-by: Thorsten Hoeger <[email protected]>
- Loading branch information
1 parent
1817c14
commit f19707e
Showing
17 changed files
with
3,452 additions
and
246 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
Constructs for the AWS CodeArtifact service | ||
|
||
# CDK Constructs for CodeArtifact Service | ||
|
||
## Overview | ||
|
||
The `Domain` and `Repository` constructs simplify the creation and management of AWS CodeArtifact domains and repositories within AWS CDK | ||
applications. These constructs allow users to manage private repositories for software packages and define domains to group repositories, | ||
facilitating secure sharing and version control across teams. | ||
|
||
## Usage | ||
|
||
Import the `Domain` and `Repository` constructs and create a new CodeArtifact domain & repository within your AWS CDK stack. | ||
|
||
```ts | ||
import { App, Stack } from 'aws-cdk-lib'; | ||
import { Domain, Repository } from '@open-constructs/aws-cdk/aws-codeartifact'; | ||
|
||
const app = new App(); | ||
const stack = new Stack(app, 'CodeArtifactDomainStack'); | ||
|
||
const domain = new Domain(stack, 'MyDomain', { | ||
domainName: 'my-domain', | ||
}); | ||
|
||
const repository = new Repository(this, 'MyRepo', { | ||
domain: domain, | ||
repositoryName: 'my-repo', | ||
}); | ||
``` | ||
|
||
### Importing existing resources | ||
|
||
If you need to manage an existing CodeArtifact repository, you can import it into your CDK stack. Since the domain is implicit in the ARN of the repository it will be automatically imported as well. | ||
|
||
```ts | ||
import { Repository } from '@open-constructs/aws-cdk/aws-codeartifact'; | ||
|
||
const existingRepo = Repository.fromRepositoryArn(stack, 'ImportedRepo', 'arn:aws:codeartifact:us-east-1:123456789012:repository/my-domain/my-repo'); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
import { Annotations, ArnFormat, IResource, Lazy, Resource, Stack, Token, TokenComparison } from 'aws-cdk-lib'; | ||
import { CfnDomain, CfnDomainProps } from 'aws-cdk-lib/aws-codeartifact'; | ||
import { | ||
AddToResourcePolicyResult, | ||
Grant, | ||
GrantWithResourceOptions, | ||
IGrantable, | ||
PolicyDocument, | ||
PolicyStatement, | ||
principalIsOwnedResource, | ||
} from 'aws-cdk-lib/aws-iam'; | ||
import { IKey } from 'aws-cdk-lib/aws-kms'; | ||
import { Construct } from 'constructs'; | ||
|
||
/** | ||
* Represents a CodeArtifact Domain | ||
*/ | ||
export interface IDomain extends IResource { | ||
/** | ||
* The ARN of the Domain | ||
* | ||
* @attribute | ||
*/ | ||
readonly domainArn: string; | ||
|
||
/** | ||
* The name of the Domain | ||
* | ||
* @attribute | ||
*/ | ||
readonly domainName: string; | ||
|
||
/** | ||
* The ARN of the key used to encrypt the Domain | ||
* | ||
* @attribute | ||
*/ | ||
readonly domainEncryptionKey?: string; | ||
|
||
/** | ||
* 12-digit account number of the AWS account that owns the domain that contains the Domain. | ||
* | ||
* @attribute | ||
*/ | ||
readonly domainOwner: string; | ||
|
||
/** | ||
* The KMS key used to encrypt the Domain | ||
*/ | ||
readonly encryptionKey?: IKey; | ||
|
||
/** | ||
* Adds a statement to the Codeartifact domain resource policy. | ||
* @param statement The policy statement to add | ||
*/ | ||
addToResourcePolicy(statement: PolicyStatement): AddToResourcePolicyResult; | ||
|
||
/** | ||
* Grants permissions to the specified grantee on this CodeArtifact domain. | ||
* | ||
* It handles both same-environment and cross-environment scenarios: | ||
* - For same-environment grants, it adds the permissions to the principal or resource. | ||
* - For cross-environment grants, it adds the permissions to both the principal and the resource. | ||
* | ||
* @param grantee - The principal to grant permissions to. | ||
* @param actions - The actions to grant. These should be valid CodeArtifact actions. | ||
*/ | ||
grant(grantee: IGrantable, ...actions: string[]): Grant; | ||
|
||
/** | ||
* Grants contribute permissions to the specified grantee on this CodeArtifact domain. | ||
* | ||
* @param grantee - The principal to grant contribute permissions to. | ||
*/ | ||
grantContribute(grantee: IGrantable): Grant; | ||
} | ||
|
||
/** | ||
* A new or imported CodeArtifact Domain. | ||
*/ | ||
abstract class DomainBase extends Resource implements IDomain { | ||
/** | ||
* The ARN (Amazon Resource Name) of the CodeArtifact domain. | ||
*/ | ||
public abstract readonly domainArn: string; | ||
|
||
/** | ||
* The name of the CodeArtifact domain. | ||
*/ | ||
public abstract readonly domainName: string; | ||
|
||
/** | ||
* The AWS KMS encryption key associated with the domain, if any. | ||
*/ | ||
public abstract readonly encryptionKey?: IKey; | ||
|
||
/** | ||
* The ARN of the key used to encrypt the Domain | ||
*/ | ||
public abstract readonly domainEncryptionKey?: string; | ||
|
||
/** | ||
* The AWS account ID that owns the domain. | ||
*/ | ||
public abstract readonly domainOwner: string; | ||
|
||
protected abstract policy?: PolicyDocument; | ||
|
||
/** | ||
* Adds a statement to the Codeartifact domain resource policy. | ||
* @param statement The policy statement to add | ||
*/ | ||
public addToResourcePolicy(statement: PolicyStatement): AddToResourcePolicyResult { | ||
if (!Resource.isOwnedResource(this)) { | ||
Annotations.of(this).addWarningV2( | ||
'NoResourcePolicyStatementAdded', | ||
`No statements added to imported resource ${this.domainArn}.`, | ||
); | ||
return { statementAdded: false }; | ||
} | ||
|
||
if (!this.policy) { | ||
this.policy = new PolicyDocument(); | ||
} | ||
this.policy.addStatements(statement); | ||
return { statementAdded: true, policyDependable: this.policy }; | ||
} | ||
|
||
private isCrossEnvironmentGrantee(grantee: IGrantable): boolean { | ||
if (!principalIsOwnedResource(grantee.grantPrincipal)) { | ||
return false; | ||
} | ||
const thisStack = Stack.of(this); | ||
const identityStack = Stack.of(grantee.grantPrincipal); | ||
return ( | ||
Token.compareStrings(thisStack.region, identityStack.region) === TokenComparison.DIFFERENT || | ||
Token.compareStrings(thisStack.account, identityStack.account) === TokenComparison.DIFFERENT | ||
); | ||
} | ||
|
||
/** | ||
* Grants permissions to the specified grantee on this CodeArtifact domain. | ||
* | ||
* It handles both same-environment and cross-environment scenarios: | ||
* - For same-environment grants, it adds the permissions to the principal or resource. | ||
* - For cross-environment grants, it adds the permissions to both the principal and the resource. | ||
* | ||
* @param grantee - The principal to grant permissions to. | ||
* @param actions - The actions to grant. These should be valid CodeArtifact actions. | ||
*/ | ||
public grant(grantee: IGrantable, ...actions: string[]): Grant { | ||
const crossEnvironment = this.isCrossEnvironmentGrantee(grantee); | ||
const grantOptions: GrantWithResourceOptions = { | ||
grantee, | ||
actions, | ||
resource: this, | ||
resourceArns: [this.domainArn], | ||
resourceSelfArns: crossEnvironment ? undefined : ['*'], | ||
}; | ||
if (!crossEnvironment) { | ||
return Grant.addToPrincipalOrResource(grantOptions); | ||
} else { | ||
return Grant.addToPrincipalAndResource({ | ||
...grantOptions, | ||
resourceArns: [this.domainArn], | ||
resourcePolicyPrincipal: grantee.grantPrincipal, | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Grants contribute permissions to the specified grantee on this CodeArtifact domain. | ||
* | ||
* @param grantee - The principal to grant contribute permissions to. | ||
*/ | ||
public grantContribute(grantee: IGrantable) { | ||
return this.grant( | ||
grantee, | ||
'codeartifact:CreateRepository', | ||
'codeartifact:DescribeDomain', | ||
'codeartifact:GetAuthorizationToken', | ||
'codeartifact:GetDomainPermissionsPolicy', | ||
'codeartifact:ListRepositoriesInDomain', | ||
'sts:GetServiceBearerToken', | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Interface representing the attributes of a CodeArtifact domain. | ||
*/ | ||
export interface DomainAttributes { | ||
/** | ||
* The ARN (Amazon Resource Name) of the CodeArtifact domain. | ||
*/ | ||
readonly domainArn: string; | ||
|
||
/** | ||
* The name of the CodeArtifact domain. | ||
*/ | ||
readonly domainName: string; | ||
|
||
/** | ||
* The AWS KMS encryption key associated with the domain, if any. | ||
*/ | ||
readonly encryptionKey?: IKey; | ||
|
||
/** | ||
* The AWS account ID that owns the domain. | ||
*/ | ||
readonly domainOwner: string; | ||
} | ||
|
||
/** | ||
* Construction properties for `Domain`. | ||
*/ | ||
export interface DomainProps { | ||
/** | ||
* The name of the Domain | ||
*/ | ||
readonly domainName: string; | ||
/** | ||
* The key used to encrypt the Domain | ||
* | ||
* @default - An AWS managed KMS key is used | ||
*/ | ||
readonly encryptionKey?: IKey; | ||
} | ||
|
||
/** | ||
* Deploys a CodeArtifact domain. | ||
*/ | ||
export class Domain extends DomainBase implements IDomain { | ||
/** | ||
* Creates a Domain object from existing domain attributes. | ||
* | ||
* @param scope The parent construct. | ||
* @param id The construct id. | ||
* @param attrs The attributes of the domain to import. | ||
*/ | ||
public static fromDomainAttributes(scope: Construct, id: string, attrs: DomainAttributes): IDomain { | ||
class Import extends DomainBase { | ||
public readonly domainArn = attrs.domainArn; | ||
public readonly domainName = attrs.domainName; | ||
public readonly encryptionKey = attrs.encryptionKey; | ||
public readonly domainOwner = attrs.domainOwner; | ||
public readonly domainEncryptionKey = attrs.encryptionKey?.keyArn; | ||
protected readonly policy?: PolicyDocument | undefined = undefined; | ||
} | ||
|
||
return new Import(scope, id); | ||
} | ||
|
||
/** | ||
* Creates an IDomain object from an existing CodeArtifact domain ARN. | ||
* | ||
* @param scope The parent construct. | ||
* @param id The construct id. | ||
* @param domainArn - The ARN (Amazon Resource Name) of the existing CodeArtifact domain. | ||
*/ | ||
public static fromDomainArn(scope: Construct, id: string, domainArn: string): IDomain { | ||
const domainResourceArnParts = Stack.of(scope).splitArn(domainArn, ArnFormat.SLASH_RESOURCE_NAME); | ||
if ( | ||
domainResourceArnParts.resource !== 'domain' || | ||
domainResourceArnParts.account === undefined || | ||
domainResourceArnParts.resourceName === undefined | ||
) { | ||
throw new Error(`Expected a domain ARN, but got ${domainArn}`); | ||
} | ||
return Domain.fromDomainAttributes(scope, id, { | ||
domainArn, | ||
domainName: domainResourceArnParts.resourceName, | ||
domainOwner: domainResourceArnParts.account, | ||
}); | ||
} | ||
|
||
/** | ||
* (internal) The CloudFormation resource representing this CodeArtifact domain. | ||
*/ | ||
protected cfnResource: CfnDomain; | ||
/** | ||
* The properties used to create the CloudFormation resource for this domain. | ||
*/ | ||
private cfnResourceProps: CfnDomainProps; | ||
|
||
/** | ||
* The ARN (Amazon Resource Name) of this CodeArtifact domain. | ||
*/ | ||
readonly domainArn: string; | ||
/** | ||
* The name of this CodeArtifact domain. | ||
*/ | ||
readonly domainName: string; | ||
/** | ||
* The AWS KMS encryption key associated with this domain, if any. | ||
*/ | ||
readonly encryptionKey?: IKey; | ||
/** | ||
* The AWS account ID that owns this domain. | ||
*/ | ||
readonly domainOwner: string; | ||
|
||
/** | ||
* The ARN of the key used to encrypt the Domain | ||
*/ | ||
readonly domainEncryptionKey?: string; | ||
|
||
protected policy?: PolicyDocument; | ||
|
||
constructor(scope: Construct, id: string, props: DomainProps) { | ||
super(scope, id); | ||
|
||
const encryptionKey = props.encryptionKey; | ||
|
||
this.cfnResourceProps = { | ||
domainName: props.domainName, | ||
encryptionKey: encryptionKey?.keyArn, | ||
permissionsPolicyDocument: Lazy.any({ produce: () => this.policy?.toJSON() }), | ||
}; | ||
this.cfnResource = this.createResource(this, 'Resource'); | ||
|
||
this.domainName = this.cfnResource.attrName; | ||
this.domainArn = this.cfnResource.attrArn; | ||
this.encryptionKey = encryptionKey; | ||
this.domainOwner = this.cfnResource.attrOwner; | ||
this.domainEncryptionKey = this.cfnResource.attrEncryptionKey; | ||
} | ||
|
||
protected createResource(scope: Construct, id: string): CfnDomain { | ||
return new CfnDomain(scope, id, this.cfnResourceProps); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './domain'; | ||
export * from './repository'; |
Oops, something went wrong.