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

feat(codeartifact): add L2 constructs for Repository & Domain #54

Merged
merged 49 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5655af9
feat(codeartifact): initial implementation of L2 constructs for Domai…
bweigel Sep 18, 2024
efc0d64
linting
bweigel Sep 18, 2024
dc636c3
refactor imports
bweigel Sep 18, 2024
2a284fa
add readme
bweigel Sep 18, 2024
fb760db
Apply suggestions from code review
bweigel Sep 21, 2024
cb3e9e8
Apply suggestions from code review
bweigel Sep 21, 2024
e41346c
refactor: use encryptionKey (IKey) instead of encryptionKeyArn
Sep 21, 2024
3b7ef3f
chore: add JSDOc
Sep 21, 2024
e12b799
chore: add more JSDoc
Sep 21, 2024
d227615
fix: tests
Sep 21, 2024
cdcbd0c
chore: remove explicit rendertags
Sep 21, 2024
50c8922
add todo
Sep 21, 2024
1d6153e
chore: add jsdoc to repository
Sep 21, 2024
9a3e773
chore: more jsdoc
Sep 21, 2024
513a052
add tests, fix validation
Sep 21, 2024
a6f8ebc
chore: remove unnneeded code...
Sep 21, 2024
b1454a7
fix: lazy rendering of policy doc
Sep 21, 2024
993b815
feat: implement upstreams
Sep 21, 2024
a300c4b
Apply suggestions from code review
bweigel Sep 22, 2024
b2e42b6
refactor(Repository): move grant etc into base class
Sep 22, 2024
6f88edc
move grant methods to IDomain
bweigel Sep 25, 2024
d918a3f
Merge branch 'main' into codeartifact
hoegertn Sep 25, 2024
6a90738
chore: expose all resource attributes for domain & repository
bweigel Sep 25, 2024
d4f3f30
chore: make upstreams lazy
bweigel Sep 25, 2024
caf856c
ran full build locally
bweigel Sep 25, 2024
f2e8079
Update integration test snapshots
bweigel Sep 25, 2024
ba1096a
remove `ITaggableV2` implementation, bc it'snot needed
bweigel Sep 25, 2024
f6cbbbe
linting
bweigel Sep 25, 2024
b47758b
Apply suggestions from code review
bweigel Sep 25, 2024
1644e9f
Apply suggestions from code review
bweigel Sep 26, 2024
93ce292
Apply suggestions from code review
bweigel Sep 26, 2024
153a598
add some @default label
bweigel Sep 26, 2024
5062a60
Update src/aws-codeartifact/domain.ts
bweigel Sep 26, 2024
e602dff
refactor cfresource creating methods
bweigel Sep 26, 2024
f56d8ab
Update src/aws-codeartifact/repository.ts
bweigel Sep 26, 2024
4ff6eeb
Apply suggestions from code review
bweigel Sep 26, 2024
98d51d6
refactor: addToResourcePolicy & token comparison
bweigel Sep 26, 2024
2a7de7f
ran build
bweigel Sep 26, 2024
1a54c66
remove some unneeded comment
bweigel Sep 26, 2024
b927e1a
update snapshots
bweigel Sep 26, 2024
0b9f899
failed integ tests
bweigel Sep 26, 2024
b260e3d
integrate change request
Oct 6, 2024
4737e6d
more review findings
Oct 6, 2024
e3b3a9d
do not cfreate encryption key by default, but use managed one
Oct 6, 2024
24d19e2
fix tests
Oct 6, 2024
c26440c
update integ test stuff
Oct 6, 2024
c810f96
Merge branch 'main' into codeartifact
bweigel Oct 6, 2024
2349268
ran build again to fix API.md
Oct 6, 2024
e5b39ea
Merge branch 'main' into codeartifact
hoegertn Oct 9, 2024
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
2,363 changes: 1,994 additions & 369 deletions API.md

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions src/aws-codeartifact/README.md
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');
```
339 changes: 339 additions & 0 deletions src/aws-codeartifact/domain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
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, Key } 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;
go-to-k marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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 {
const stack = Stack.of(this);

if (!Resource.isOwnedResource(this)) {
Annotations.of(stack).addWarningV2(
bweigel marked this conversation as resolved.
Show resolved Hide resolved
'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.SAME ||
Token.compareStrings(thisStack.account, identityStack.account) === TokenComparison.SAME
);
bweigel marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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 - A new KMS key will be created
*/
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 ??
new Key(this, 'Key', {
description: `Key for CodeArtifact Domain ${props.domainName}`,
alias: `codeartifact-domain/${props.domainName}`,
});
bweigel marked this conversation as resolved.
Show resolved Hide resolved

this.cfnResourceProps = {
domainName: props.domainName,
bweigel marked this conversation as resolved.
Show resolved Hide resolved
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 = encryptionKey.keyArn;
}

protected createResource(scope: Construct, id: string): CfnDomain {
return new CfnDomain(scope, id, this.cfnResourceProps);
}
}
2 changes: 2 additions & 0 deletions src/aws-codeartifact/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './domain';
export * from './repository';
Loading
Loading