Skip to content

Commit

Permalink
feat: exposing schedule as part of backup configuration (#67)
Browse files Browse the repository at this point in the history
* feat: exposing schedule as part of backup configuration

* chore: self mutation

Signed-off-by: github-actions <github-actions@github.com>

---------

Signed-off-by: github-actions <github-actions@github.com>
Co-authored-by: github-actions <github-actions@github.com>
  • Loading branch information
stefanfreitag and github-actions authored Feb 26, 2023
1 parent 90a8d6c commit 133fea3
Show file tree
Hide file tree
Showing 20 changed files with 2,785 additions and 26 deletions.
1 change: 0 additions & 1 deletion .gitignore

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

1 change: 0 additions & 1 deletion .npmignore

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

2 changes: 1 addition & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ project.devContainer.name='cdk-dev-environment';
project.devContainer.addDockerImage(DevEnvironmentDockerImage.fromFile('.gitpod.Dockerfile'));
project.devContainer.addVscodeExtensions('esbenp.prettier-vscode', 'dbaeumer.vscode-eslint');

const common_exclude = ['.history/', '.venv', '.idea', 'test/integ.*/'];
const common_exclude = ['.history/', '.venv', '.idea'];
project.npmignore.exclude(...common_exclude);
project.gitignore.exclude(...common_exclude);

Expand Down
22 changes: 20 additions & 2 deletions API.md

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

22 changes: 20 additions & 2 deletions API.md.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ const archiverProperties: ArchiverProperties = { ... }
| --- | --- | --- |
| <code><a href="#azure-devops-repository-archiver.ArchiverProperties.property.backupConfigurations">backupConfigurations</a></code> | <code><a href="#azure-devops-repository-archiver.BackupConfiguration">BackupConfiguration</a>[]</code> | Contains details on the git repositories to be backed up. |
| <code><a href="#azure-devops-repository-archiver.ArchiverProperties.property.notificationEvents">notificationEvents</a></code> | <code>aws-cdk-lib.aws_s3.EventType[]</code> | S3 events that will trigger a message to the SNS topic. |
| <code><a href="#azure-devops-repository-archiver.ArchiverProperties.property.retentionDays">retentionDays</a></code> | <code>aws-cdk-lib.aws_logs.RetentionDays</code> | Number of days to keep the Cloudwatch logs. |
| <code><a href="#azure-devops-repository-archiver.ArchiverProperties.property.retentionDays">retentionDays</a></code> | <code>aws-cdk-lib.aws_logs.RetentionDays</code> | The number of days to keep the Cloudwatch logs. |

---

Expand Down Expand Up @@ -222,12 +222,14 @@ public readonly retentionDays: RetentionDays;
- *Type:* aws-cdk-lib.aws_logs.RetentionDays
- *Default:* RetentionDays.ONE_MONTH

Number of days to keep the Cloudwatch logs.
The number of days to keep the Cloudwatch logs.

---

### BackupConfiguration <a name="BackupConfiguration" id="azure-devops-repository-archiver.BackupConfiguration"></a>

A backup configuration defining - the repositories to backup, and - the backup interval All repositories that are part of a backup configuration are belonging to the same Azure DevOps organization and project.

#### Initializer <a name="Initializer" id="azure-devops-repository-archiver.BackupConfiguration.Initializer"></a>

```typescript
Expand All @@ -244,6 +246,7 @@ const backupConfiguration: BackupConfiguration = { ... }
| <code><a href="#azure-devops-repository-archiver.BackupConfiguration.property.projectName">projectName</a></code> | <code>string</code> | The name of the Azure DevOps project. |
| <code><a href="#azure-devops-repository-archiver.BackupConfiguration.property.repositoryNames">repositoryNames</a></code> | <code>string[]</code> | The names of the git repositories to backup. |
| <code><a href="#azure-devops-repository-archiver.BackupConfiguration.property.secretArn">secretArn</a></code> | <code>string</code> | ARN of the secret containing the token for accessing the git repositories of the Azure DevOps organization. |
| <code><a href="#azure-devops-repository-archiver.BackupConfiguration.property.schedule">schedule</a></code> | <code>aws-cdk-lib.aws_events.Schedule</code> | The schedule allows to define the frequency of backups. |

---

Expand Down Expand Up @@ -295,5 +298,20 @@ ARN of the secret containing the token for accessing the git repositories of the

---

##### `schedule`<sup>Optional</sup> <a name="schedule" id="azure-devops-repository-archiver.BackupConfiguration.property.schedule"></a>

```typescript
public readonly schedule: Schedule;
```

- *Type:* aws-cdk-lib.aws_events.Schedule
- *Default:* Schedule.expression('cron(0 0 ? * 1 *)')

The schedule allows to define the frequency of backups.

If not defined, a weekly backup is configured.

---



6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ In the S3 bucket the backups are placed in a "directory" structure like
| ...
```


## Features

The S3 bucket is configured as below
Expand All @@ -51,6 +50,9 @@ The CodeBuild projects are configured as below
- Logging to CloudWatch
- Configurable retention period. Default is one month.
- Encryption using customer-managed KMS key
- Schedule based execution.
- The default schedule is one week and can be overriden as part of the backup
configuration.

## Prerequisites

Expand All @@ -71,7 +73,7 @@ aws secretsmanager create-secret --name repository_archiver --description "Secre
```javascript
"dependencies": {
[...],
"azure-devops-repository-archiver": "0.0.23",
"azure-devops-repository-archiver": "1.4.0",
},
```

Expand Down
7 changes: 3 additions & 4 deletions src/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ import { ArchiverProperties } from './archiverProperties';
import { BackupConfiguration } from './backupConfiguration';

/**
* Every week
* The default backup intervall for the git repositories is "every week".
*/
const DEFAULT_CRON_EXPRESSION = 'cron(0 0 ? * 1 *)';

const DEFAULT_SCHEDULE = events.Schedule.expression('cron(0 0 ? * 1 *)');
export class Archiver extends Construct {
props: ArchiverProperties;

Expand Down Expand Up @@ -218,7 +217,7 @@ export class Archiver extends Construct {
'ScheduleRule-' + element.organizationName + '-' + element.projectName,
{
enabled: true,
schedule: events.Schedule.expression(DEFAULT_CRON_EXPRESSION),
schedule: element.schedule ? element.schedule : DEFAULT_SCHEDULE,
targets: [new eventsTargets.CodeBuildProject(project)],
description:
'Trigger for backing up Azure DevOps git repositories of organization ' +
Expand Down
2 changes: 1 addition & 1 deletion src/archiverProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BackupConfiguration } from './backupConfiguration';

export interface ArchiverProperties {
/**
* Number of days to keep the Cloudwatch logs.
* The number of days to keep the Cloudwatch logs.
* @default RetentionDays.ONE_MONTH
*
* @type {RetentionDays}
Expand Down
20 changes: 20 additions & 0 deletions src/backupConfiguration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import { Schedule } from 'aws-cdk-lib/aws-events';

/**
* A backup configuration defining
* - the repositories to backup, and
* - the backup interval
* All repositories that are part of a backup configuration are belonging to the
* same Azure DevOps organization and project.
* @export
* @interface BackupConfiguration
*/
export interface BackupConfiguration {
/**
* The name of the Azure DevOps organization.
Expand All @@ -22,6 +33,15 @@ export interface BackupConfiguration {
*/
readonly repositoryNames: string[];

/**
* The schedule allows to define the frequency of backups.
* If not defined, a weekly backup is configured.
* @default Schedule.expression('cron(0 0 ? * 1 *)')
* @type {Schedule}
* @memberof BackupConfiguration
*/
readonly schedule?: Schedule;

/**
* ARN of the secret containing the token for accessing the git repositories
* of the Azure DevOps organization.
Expand Down
52 changes: 51 additions & 1 deletion test/archiver.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as cdk from 'aws-cdk-lib/';
import * as assertions from 'aws-cdk-lib/assertions';
import { Schedule } from 'aws-cdk-lib/aws-events';
import { CfnKey } from 'aws-cdk-lib/aws-kms';
import * as logs from 'aws-cdk-lib/aws-logs';
import { CfnBucket, EventType } from 'aws-cdk-lib/aws-s3';
Expand Down Expand Up @@ -323,7 +324,7 @@ describe('Logging settings', () => {
});
});

describe('Cloudwatch rules', () => {
describe('Default Cloudwatch rule setup', () => {


let stack: cdk.Stack;
Expand Down Expand Up @@ -355,6 +356,15 @@ describe('Cloudwatch rules', () => {
test('Expected number of rules created', () => {
assertions.Template.fromStack(stack).resourceCountIs('AWS::Events::Rule', 2);
});

test('Default schedule', () => {
backupConfigurations.forEach(() => {
assertions.Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'cron(0 0 ? * 1 *)',
});
});
});

test('Description of rules', () => {
backupConfigurations.forEach((configuration) => {
assertions.Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
Expand All @@ -368,3 +378,43 @@ describe('Cloudwatch rules', () => {
});
});
});

describe('Custom Cloudwatch rule setup', () => {


let stack: cdk.Stack;
const backupConfigurations = [
{
organizationName: 'organization-a',
projectName: 'project-b',
repositoryNames: ['repository-c'],
schedule: Schedule.expression('cron(0 0 ? * 2 *)'),
secretArn: 'secret-arn',
},
{
organizationName: 'organization-a',
projectName: 'project-d',
repositoryNames: ['repository-c'],
schedule: Schedule.expression('cron(0 0 ? * 2 *)'),
secretArn: 'secret-arn',
},
];

beforeEach(() => {
const app = new cdk.App();
stack = new cdk.Stack(app, 'stack', {});
new Archiver(stack, 'archiver', {
retentionDays: logs.RetentionDays.ONE_WEEK,
backupConfigurations,
});
});

test('Cusstom schedule', () => {
backupConfigurations.forEach(() => {
assertions.Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'cron(0 0 ? * 2 *)',
});
});
});

});
51 changes: 40 additions & 11 deletions test/integ.archiver.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha';
import {
AssertionsProvider,
ExpectedResult,
IntegTest,
} from '@aws-cdk/integ-tests-alpha';
import { App, Stack, StackProps } from 'aws-cdk-lib';
import { Archiver } from '../src/';

class StackUnderTest extends Stack {
archiver: Archiver;
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
this.archiver = new Archiver( this, 'archiver', {
this.archiver = new Archiver(this, 'archiver', {
backupConfigurations: [],
});
}
Expand All @@ -15,22 +19,47 @@ class StackUnderTest extends Stack {
// Beginning of the test suite
const app = new App();

const stack = new StackUnderTest(app, 'empty-backup-configuration', {
} );
const stack = new StackUnderTest(app, 'empty-backup-configuration', {});
const integ = new IntegTest(app, 'MyTestCase', {
regions: ['eu-central-1'],
testCases: [
stack,
],
testCases: [stack],
});

const output = integ.assertions.awsApiCall('S3', 'getBucketVersioning', {
Bucket: stack.archiver.bucket.bucketName,
});

output.expect(ExpectedResult.objectLike({
Status: 'Enabled',
}));
output.expect(
ExpectedResult.objectLike({
Status: 'Enabled',
}),
);

app.synth();
const output2 = integ.assertions.awsApiCall('S3', 'getBucketEncryption', {
Bucket: stack.archiver.bucket.bucketName,
});

const assertionProvider = output2.node.tryFindChild(
'SdkProvider',
) as AssertionsProvider;
assertionProvider.addPolicyStatementFromSdkCall(
's3',
'GetEncryptionConfiguration',
[stack.archiver.bucket.bucketArn],
);
console.log(output2);
output2.expect(
ExpectedResult.objectLike({
ServerSideEncryptionConfiguration: {
Rules: [
{
ApplyServerSideEncryptionByDefault: {
SSEAlgorithm: 'AES256',
},
},
],
},
}),
);

app.synth();
Loading

0 comments on commit 133fea3

Please sign in to comment.