Skip to content

Commit 7e31eb6

Browse files
authored
feat(imagebuilder-alpha): add support for Lifecycle Policy Construct (#36152)
### Issue aws/aws-cdk-rfcs#789 This change adds a new alpha module for EC2 Image Builder L2 Constructs (@aws-cdk/aws-imagebuilder-alpha), as outlined in aws/aws-cdk-rfcs#789. This PR specifically implements the LifecyclePolicy construct. ### Description of changes This change implements the ImageRecipe construct, which is a higher-level construct of [CfnLifecyclePolicy](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_imagebuilder.CfnLifecyclePolicy.html). #### Example ```ts const graduatedPolicy = new imagebuilder.LifecyclePolicy(this, 'GraduatedPolicy', { resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE, details: [ { // First: Deprecate images after 30 days action: { type: imagebuilder.LifecyclePolicyActionType.DEPRECATE, includeAmis: true }, filter: { age: Duration.days(30), retainAtLeast: 5 } }, { // Second: Disable images after 60 days action: { type: imagebuilder.LifecyclePolicyActionType.DISABLE, includeAmis: true }, filter: { age: Duration.days(60), retainAtLeast: 3 } }, { // Finally: Delete images after 90 days action: { type: imagebuilder.LifecyclePolicyActionType.DELETE, includeAmis: true, includeSnapshots: true }, filter: { age: Duration.days(90), retainAtLeast: 1 } } ], resourceSelection: { tags: { Environment: 'production' } } }); ``` ### Describe any new or updated permissions being added N/A - new L2 construct in alpha module ### Description of how you validated changes Validated with unit tests and integration tests. Manually verified generated CFN templates as well. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 6f0ac51 commit 7e31eb6

File tree

41 files changed

+6212
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+6212
-3
lines changed

packages/@aws-cdk/aws-imagebuilder-alpha/README.md

Lines changed: 340 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const manualPipeline = new imagebuilder.ImagePipeline(this, 'ManualPipeline', {
8686
});
8787

8888
// Grant Lambda function permission to trigger the pipeline
89-
manualPipeline.grantStartExecution(role);
89+
manualPipeline.grantStartExecution(lambdaRole);
9090
```
9191

9292
##### Automated Pipeline Scheduling
@@ -283,7 +283,7 @@ const automationRole = new iam.Role(this, 'AutomationRole', {
283283
});
284284

285285
existingPipelineByName.grantStartExecution(automationRole);
286-
existingPipelineByArn.grantRead(role);
286+
existingPipelineByArn.grantRead(lambdaRole);
287287
```
288288

289289
### Image Recipe
@@ -1324,3 +1324,341 @@ const testContainerWorkflow = imagebuilder.AwsManagedWorkflow.testContainer(this
13241324
// Distribution workflows
13251325
const distributeContainerWorkflow = imagebuilder.AwsManagedWorkflow.distributeContainer(this, 'DistributeContainer');
13261326
```
1327+
1328+
### Lifecycle Policy
1329+
1330+
Lifecycle policies help you manage the retention and cleanup of Image Builder resources automatically. These policies define rules for deprecating or deleting old image versions, managing AMI snapshots, and controlling resource costs by removing unused images based on age, count, or other criteria.
1331+
1332+
#### Lifecycle Policy Basic Usage
1333+
1334+
Create a lifecycle policy to automatically delete old AMI images after 30 days:
1335+
1336+
```ts
1337+
const lifecyclePolicy = new imagebuilder.LifecyclePolicy(this, 'MyLifecyclePolicy', {
1338+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1339+
details: [
1340+
{
1341+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1342+
filter: { ageFilter: { age: Duration.days(30) } }
1343+
}
1344+
],
1345+
resourceSelection: {
1346+
tags: { Environment: 'development' }
1347+
}
1348+
});
1349+
```
1350+
1351+
Create a lifecycle policy to keep only the 10 most recent container images:
1352+
1353+
```ts
1354+
const containerLifecyclePolicy = new imagebuilder.LifecyclePolicy(this, 'ContainerLifecyclePolicy', {
1355+
resourceType: imagebuilder.LifecyclePolicyResourceType.CONTAINER_IMAGE,
1356+
details: [
1357+
{
1358+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1359+
filter: { countFilter: { count: 10 } }
1360+
}
1361+
],
1362+
resourceSelection: {
1363+
tags: { Application: 'web-app' }
1364+
}
1365+
});
1366+
```
1367+
1368+
#### Lifecycle Policy Resource Selection
1369+
1370+
##### Tag-Based Resource Selection
1371+
1372+
Apply lifecycle policies to images with specific tags:
1373+
1374+
```ts
1375+
const tagBasedPolicy = new imagebuilder.LifecyclePolicy(this, 'TagBasedPolicy', {
1376+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1377+
details: [
1378+
{
1379+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1380+
filter: { ageFilter: { age: Duration.days(90) } }
1381+
}
1382+
],
1383+
resourceSelection: {
1384+
tags: {
1385+
Environment: 'staging',
1386+
Team: 'backend'
1387+
}
1388+
}
1389+
});
1390+
```
1391+
1392+
##### Recipe-Based Resource Selection
1393+
1394+
Apply lifecycle policies to specific image or container recipes:
1395+
1396+
```ts
1397+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'MyImageRecipe', {
1398+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
1399+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
1400+
)
1401+
});
1402+
1403+
const containerRecipe = new imagebuilder.ContainerRecipe(this, 'MyContainerRecipe', {
1404+
baseImage: imagebuilder.BaseContainerImage.fromDockerHub('amazonlinux', 'latest'),
1405+
targetRepository: imagebuilder.Repository.fromEcr(
1406+
ecr.Repository.fromRepositoryName(this, 'Repository', 'my-container-repo')
1407+
)
1408+
});
1409+
1410+
const recipeBasedPolicy = new imagebuilder.LifecyclePolicy(this, 'RecipeBasedPolicy', {
1411+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1412+
details: [
1413+
{
1414+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1415+
filter: { countFilter: { count: 5 } }
1416+
}
1417+
],
1418+
resourceSelection: {
1419+
recipes: [imageRecipe, containerRecipe]
1420+
}
1421+
});
1422+
```
1423+
1424+
#### Lifecycle Policy Rules
1425+
1426+
##### Age-Based Rules
1427+
1428+
Delete images older than a specific time period:
1429+
1430+
```ts
1431+
const ageBasedPolicy = new imagebuilder.LifecyclePolicy(this, 'AgeBasedPolicy', {
1432+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1433+
details: [
1434+
{
1435+
action: {
1436+
type: imagebuilder.LifecyclePolicyActionType.DELETE,
1437+
includeAmis: true,
1438+
includeSnapshots: true
1439+
},
1440+
filter: {
1441+
ageFilter: {
1442+
age: Duration.days(60),
1443+
retainAtLeast: 3 // Always keep at least 3 images
1444+
}
1445+
}
1446+
}
1447+
],
1448+
resourceSelection: {
1449+
tags: { Environment: 'testing' }
1450+
}
1451+
});
1452+
```
1453+
1454+
##### Count-Based Rules
1455+
1456+
Keep only a specific number of the most recent images:
1457+
1458+
```ts
1459+
const countBasedPolicy = new imagebuilder.LifecyclePolicy(this, 'CountBasedPolicy', {
1460+
resourceType: imagebuilder.LifecyclePolicyResourceType.CONTAINER_IMAGE,
1461+
details: [
1462+
{
1463+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1464+
filter: { countFilter: { count: 15 } } // Keep only the 15 most recent images
1465+
}
1466+
],
1467+
resourceSelection: {
1468+
tags: { Application: 'microservice' }
1469+
}
1470+
});
1471+
```
1472+
1473+
##### Multiple Lifecycle Rules
1474+
1475+
Implement a graduated approach with multiple actions:
1476+
1477+
```ts
1478+
const graduatedPolicy = new imagebuilder.LifecyclePolicy(this, 'GraduatedPolicy', {
1479+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1480+
details: [
1481+
{
1482+
// First: Deprecate images after 30 days
1483+
action: {
1484+
type: imagebuilder.LifecyclePolicyActionType.DEPRECATE,
1485+
includeAmis: true
1486+
},
1487+
filter: {
1488+
ageFilter: {
1489+
age: Duration.days(30),
1490+
retainAtLeast: 5
1491+
}
1492+
}
1493+
},
1494+
{
1495+
// Second: Disable images after 60 days
1496+
action: {
1497+
type: imagebuilder.LifecyclePolicyActionType.DISABLE,
1498+
includeAmis: true
1499+
},
1500+
filter: {
1501+
ageFilter: {
1502+
age: Duration.days(60),
1503+
retainAtLeast: 3
1504+
}
1505+
}
1506+
},
1507+
{
1508+
// Finally: Delete images after 90 days
1509+
action: {
1510+
type: imagebuilder.LifecyclePolicyActionType.DELETE,
1511+
includeAmis: true,
1512+
includeSnapshots: true
1513+
},
1514+
filter: {
1515+
ageFilter: {
1516+
age: Duration.days(90),
1517+
retainAtLeast: 1
1518+
}
1519+
}
1520+
}
1521+
],
1522+
resourceSelection: {
1523+
tags: { Environment: 'production' }
1524+
}
1525+
});
1526+
```
1527+
1528+
#### Lifecycle Policy Exclusion Rules
1529+
1530+
##### AMI Exclusion Rules
1531+
1532+
Exclude specific AMIs from lifecycle actions based on various criteria:
1533+
1534+
```ts
1535+
const excludeAmisPolicy = new imagebuilder.LifecyclePolicy(this, 'ExcludeAmisPolicy', {
1536+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1537+
details: [
1538+
{
1539+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1540+
filter: { ageFilter: { age: Duration.days(30) } },
1541+
exclusionRules: {
1542+
amiExclusionRules: {
1543+
isPublic: true, // Exclude public AMIs
1544+
lastLaunched: Duration.days(7), // Exclude AMIs launched in last 7 days
1545+
regions: ['us-west-2', 'eu-west-1'], // Exclude AMIs in specific regions
1546+
sharedAccounts: ['123456789012'], // Exclude AMIs shared with specific accounts
1547+
tags: {
1548+
Protected: 'true',
1549+
Environment: 'production'
1550+
}
1551+
}
1552+
}
1553+
}
1554+
],
1555+
resourceSelection: {
1556+
tags: { Team: 'infrastructure' }
1557+
}
1558+
});
1559+
```
1560+
1561+
##### Image Exclusion Rules
1562+
1563+
Exclude Image Builder images with protective tags:
1564+
1565+
```ts
1566+
const excludeImagesPolicy = new imagebuilder.LifecyclePolicy(this, 'ExcludeImagesPolicy', {
1567+
resourceType: imagebuilder.LifecyclePolicyResourceType.CONTAINER_IMAGE,
1568+
details: [
1569+
{
1570+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1571+
filter: { countFilter: { count: 20 } },
1572+
exclusionRules: {
1573+
imageExclusionRules: {
1574+
tags: {
1575+
DoNotDelete: 'true',
1576+
Critical: 'baseline'
1577+
}
1578+
}
1579+
}
1580+
}
1581+
],
1582+
resourceSelection: {
1583+
tags: { Application: 'frontend' }
1584+
}
1585+
});
1586+
```
1587+
1588+
#### Advanced Lifecycle Configuration
1589+
1590+
##### Custom Execution Roles
1591+
1592+
Provide your own IAM execution role with specific permissions:
1593+
1594+
```ts
1595+
const executionRole = new iam.Role(this, 'LifecycleExecutionRole', {
1596+
assumedBy: new iam.ServicePrincipal('imagebuilder.amazonaws.com'),
1597+
managedPolicies: [
1598+
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/EC2ImageBuilderLifecycleExecutionPolicy')
1599+
]
1600+
});
1601+
1602+
const customRolePolicy = new imagebuilder.LifecyclePolicy(this, 'CustomRolePolicy', {
1603+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1604+
executionRole: executionRole,
1605+
details: [
1606+
{
1607+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1608+
filter: { ageFilter: { age: Duration.days(45) } }
1609+
}
1610+
],
1611+
resourceSelection: {
1612+
tags: { Environment: 'development' }
1613+
}
1614+
});
1615+
```
1616+
1617+
##### Lifecycle Policy Status
1618+
1619+
Control whether the lifecycle policy is active:
1620+
1621+
```ts
1622+
const disabledPolicy = new imagebuilder.LifecyclePolicy(this, 'DisabledPolicy', {
1623+
lifecyclePolicyName: 'my-disabled-policy',
1624+
description: 'A lifecycle policy that is temporarily disabled',
1625+
status: imagebuilder.LifecyclePolicyStatus.DISABLED,
1626+
resourceType: imagebuilder.LifecyclePolicyResourceType.AMI_IMAGE,
1627+
details: [
1628+
{
1629+
action: { type: imagebuilder.LifecyclePolicyActionType.DELETE },
1630+
filter: { ageFilter: { age: Duration.days(30) } }
1631+
}
1632+
],
1633+
resourceSelection: {
1634+
tags: { Environment: 'testing' }
1635+
},
1636+
tags: {
1637+
Owner: 'DevOps',
1638+
CostCenter: 'Engineering'
1639+
}
1640+
});
1641+
```
1642+
1643+
##### Importing Lifecycle Policies
1644+
1645+
Reference lifecycle policies created outside of CDK:
1646+
1647+
```ts
1648+
// Import by name
1649+
const importedByName = imagebuilder.LifecyclePolicy.fromLifecyclePolicyName(
1650+
this,
1651+
'ImportedByName',
1652+
'existing-lifecycle-policy'
1653+
);
1654+
1655+
// Import by ARN
1656+
const importedByArn = imagebuilder.LifecyclePolicy.fromLifecyclePolicyArn(
1657+
this,
1658+
'ImportedByArn',
1659+
'arn:aws:imagebuilder:us-east-1:123456789012:lifecycle-policy/my-policy'
1660+
);
1661+
1662+
importedByName.grantRead(lambdaRole);
1663+
importedByArn.grant(lambdaRole, 'imagebuilder:PutLifecyclePolicy');
1664+
```

packages/@aws-cdk/aws-imagebuilder-alpha/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './distribution-configuration';
66
export * from './image-pipeline';
77
export * from './image-recipe';
88
export * from './infrastructure-configuration';
9+
export * from './lifecycle-policy';
910
export * from './workflow';
1011

1112
export * from './base-image';

0 commit comments

Comments
 (0)