Repository: RenovoSolutions/cdk-library-certbot (language: python)

Issue:
Add trigger to immediately generate certs



Discussion / hints:

Fix patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index af1765b5..b6a3af9c 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -12,7 +12,7 @@
   "parserOptions": {
     "ecmaVersion": 2018,
     "sourceType": "module",
-    "project": "./tsconfig.eslint.json"
+    "project": "./tsconfig.dev.json"
   },
   "extends": [
     "plugin:import/typescript"
@@ -27,7 +27,7 @@
     "import/resolver": {
       "node": {},
       "typescript": {
-        "project": "./tsconfig.eslint.json"
+        "project": "./tsconfig.dev.json"
       }
     }
   },
diff --git a/.gitattributes b/.gitattributes
index 4e522c37..5885da6d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -15,6 +15,5 @@
 /.projen/tasks.json                  	linguist-generated
 /LICENSE                             	linguist-generated
 /package.json                        	linguist-generated
-/tsconfig.eslint.json                	linguist-generated
-/tsconfig.jest.json                  	linguist-generated
+/tsconfig.dev.json                   	linguist-generated
 /yarn.lock                           	linguist-generated
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 24974ef0..465d796a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,12 +38,11 @@ junit.xml
 /dist/version.txt
 !/.github/pull_request_template.md
 !/test/
+!/tsconfig.dev.json
 !/src/
 /lib
 /dist/
-!/tsconfig.jest.json
 !/.eslintrc.json
-!/tsconfig.eslint.json
 .jsii
 tsconfig.json
 !/API.md
diff --git a/.npmignore b/.npmignore
index ce52c2a4..7956cf2e 100644
--- a/.npmignore
+++ b/.npmignore
@@ -6,6 +6,7 @@ junit.xml
 /dist/changelog.md
 /dist/version.txt
 /test/
+/tsconfig.dev.json
 /src/
 !/lib/
 !/lib/**/*.js
@@ -17,9 +18,7 @@ dist
 /.idea/
 /.projenrc.js
 tsconfig.tsbuildinfo
-/tsconfig.jest.json
 /.eslintrc.json
-/tsconfig.eslint.json
 !.jsii
 .functionbundle/*
 .venv/*
diff --git a/.projen/tasks.json b/.projen/tasks.json
index 3e48f890..e8c4f9dc 100644
--- a/.projen/tasks.json
+++ b/.projen/tasks.json
@@ -49,7 +49,7 @@
       "description": "compiles the test code",
       "steps": [
         {
-          "exec": "tsc --noEmit --project tsconfig.jest.json"
+          "exec": "tsc --noEmit --project tsconfig.dev.json"
         }
       ]
     },
diff --git a/.projenrc.js b/.projenrc.js
index b7ab74fe..cba569a4 100644
--- a/.projenrc.js
+++ b/.projenrc.js
@@ -18,7 +18,14 @@ const project = new AwsCdkConstructLibrary({
     '@aws-cdk/aws-sns-subscriptions',
     '@aws-cdk/aws-route53',
   ],
-  projectType: ProjectType.LIB,
+  keywords: [
+    'letsencrypt',
+    'certbot',
+    'cdk',
+    'aws-cdk',
+    'aws-cdk-construct',
+    'projen',
+  ],
   releaseToNpm: true,
   releaseWorkflow: true,
   npmAccess: NpmAccess.PUBLIC,
diff --git a/API.md b/API.md
index ca7eef95..3399ddb6 100644
--- a/API.md
+++ b/API.md
@@ -202,6 +202,32 @@ Default is 30 days
 
 ---
 
+##### `runOnDeploy`<sup>Optional</sup> <a name="@renovosolutions/cdk-library-certbot.ICertbotProps.property.runOnDeploy"></a>
+
+```typescript
+public readonly runOnDeploy: boolean;
+```
+
+- *Type:* `boolean`
+
+Whether or not to schedule a trigger to run the function after each deployment.
+
+---
+
+##### `runOnDeployWaitMinutes`<sup>Optional</sup> <a name="@renovosolutions/cdk-library-certbot.ICertbotProps.property.runOnDeployWaitMinutes"></a>
+
+```typescript
+public readonly runOnDeployWaitMinutes: number;
+```
+
+- *Type:* `number`
+
+How many minutes to wait before running the post deployment Lambda trigger.
+
+Defaults to 10 minutes
+
+---
+
 ##### `schedule`<sup>Optional</sup> <a name="@renovosolutions/cdk-library-certbot.ICertbotProps.property.schedule"></a>
 
 ```typescript
diff --git a/README.md b/README.md
index 1d299436..46f05b75 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,43 @@
 
 A CDK Construct Library to automate the creation and renewal of Let's Encrypt certificates.
 
-This library creates a Lambda function that utilizes Certbot to create certificates. Upon completion of the scheduled creation those certs are imported to AWS Certificate Manager (ACM) and uploaded to S3 and the email used for the certs is sent a notification from SNS. The function is also assigned, by default, an every Sunday trigger to check if there is under 30 days remaining on the certificates that have been imported to ACM and if so it attempts to re-issues new certificates.
+## Features
+- Creates a lambda function that utilizes Certbot to request a certificate from Let's Encrypt
+- Uploads the resulting certificate data to S3 for later retrieval
+- Imports the certificate to AWS Certificate Manager for tracking expiration
+- Creates a trigger to re-run and re-new if the cert will expire in the next 30 days (customizable)
 
-This construct will create all required components but optionally allows the users to pass their own S3 bucket, SNS topic, enable Lambda insights, and other customization of items like schedule, lambda architecture, reissue days. See the [API](API.md) documentation for full details. You can also see example usage in this repo [example/example.ts](example/example.ts)
+## API Doc
+See [API](API.md)
+
+## References
+Original [gist](# Modified from original gist https://gist.github.com/arkadiyt/5d764c32baa43fc486ca16cb8488169a) that was modified for the Lambda code
+
+## Examples
+### Typescript
+```
+import * as cdk from '@aws-cdk/core';
+import { Certbot } from '@renovosolutions/cdk-library-certbot';
+import { Architecture } from '@aws-cdk/aws-lambda';
+
+export class CdkExampleCertsStack extends cdk.Stack {
+  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
+    super(scope, id, props);
+
+    let domains = [
+      'example.com',
+      'www.example.com'
+    ]
+
+    new Certbot(this, 'cert', {
+      letsencryptDomains: domains.join(','),
+      letsencryptEmail: 'webmaster+letsencrypt@example.com',
+      hostedZoneNames: [
+        'example.com'
+      ],
+      architecture: Architecture.ARM_64
+    })
+  }
+}
+
+```
diff --git a/example/example.ts b/example/example.ts
deleted file mode 100644
index 79eb37ac..00000000
--- a/example/example.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as cdk from '@aws-cdk/core';
-import { Certbot } from '@renovosolutions/cdk-library-certbot';
-import { Architecture } from '@aws-cdk/aws-lambda';
-
-export class CdkExampleCertsStack extends cdk.Stack {
-  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
-    super(scope, id, props);
-
-    let domains = [
-      'example.com',
-      'www.example.com'
-    ]
-
-    new Certbot(this, 'cert', {
-      letsencryptDomains: domains.join(','),
-      letsencryptEmail: 'webmaster+letsencrypt@example.com',
-      hostedZoneNames: [
-        'example.com'
-      ],
-      architecture: Architecture.ARM_64
-    })
-  }
-}
diff --git a/package.json b/package.json
index 9031366d..d79e2972 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "jsii-pacmak": "^1.39.0",
     "json-schema": "^0.3.0",
     "npm-check-updates": "^11",
-    "projen": "^0.29.10",
+    "projen": "^0.30.2",
     "standard-version": "^9",
     "ts-jest": "^27.0.6",
     "typescript": "^4.4.4"
@@ -83,7 +83,12 @@
   },
   "bundledDependencies": [],
   "keywords": [
-    "cdk"
+    "aws-cdk",
+    "aws-cdk-construct",
+    "cdk",
+    "certbot",
+    "letsencrypt",
+    "projen"
   ],
   "main": "lib/index.js",
   "license": "Apache-2.0",
@@ -126,7 +131,7 @@
     "preset": "ts-jest",
     "globals": {
       "ts-jest": {
-        "tsconfig": "tsconfig.jest.json"
+        "tsconfig": "tsconfig.dev.json"
       }
     }
   },
diff --git a/src/index.ts b/src/index.ts
index 1424a7db..28cd0844 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -66,6 +66,14 @@ export interface ICertbotProps {
    * The schedule for the certificate check trigger. Defaults to once every Sunday.
    */
   schedule?: events.Schedule;
+  /**
+   * Whether or not to schedule a trigger to run the function after each deployment
+   */
+  runOnDeploy?: boolean;
+  /**
+   * How many minutes to wait before running the post deployment Lambda trigger. Defaults to 10 minutes
+   */
+  runOnDeployWaitMinutes?: number;
 }
 
 export class Certbot extends cdk.Construct {
@@ -101,6 +109,7 @@ export class Certbot extends cdk.Construct {
     props.timeout = (props.timeout === undefined) ? cdk.Duration.seconds(180) : props.timeout;
     props.architecture = (props.architecture === undefined) ? lambda.Architecture.X86_64 : props.architecture;
     props.schedule = (props.schedule === undefined ) ? events.Schedule.cron({ minute: '0', hour: '0', weekDay: '1' }) : props.schedule;
+    props.runOnDeploy = (props.runOnDeploy === undefined ) ? true : props.runOnDeploy;
     props.enableInsights = (props.enableInsights === undefined) ? false : props.enableInsights;
     props.insightsARN = (props.insightsARN === undefined) ? 'arn:aws:lambda:' + cdk.Stack.of(this).region + ':580247275435:layer:LambdaInsightsExtension:14' : props.insightsARN;
 
@@ -229,5 +238,26 @@ export class Certbot extends cdk.Construct {
       schedule: props.schedule,
       targets: [new targets.LambdaFunction(this.handler)],
     });
+
+    if (props.runOnDeploy) {
+      const dateToCron = (date:Date) => {
+        const minutesToAdd = props.runOnDeployWaitMinutes || 10;
+        const future = new Date(date.getTime() + minutesToAdd * 60000);
+        const minutes = future.getMinutes();
+        const hours = future.getHours();
+        const days = future.getDate();
+        const months = future.getMonth() + 1;
+        const dayOfWeek = future.getDay();
+
+        return `${minutes} ${hours} ${days} ${months} ${dayOfWeek}`;
+      };
+
+      const oneTimeSchedule = events.Schedule.expression(dateToCron(new Date()));
+
+      new events.Rule(this, 'triggerImmediate', {
+        schedule: oneTimeSchedule,
+        targets: [new targets.LambdaFunction(this.handler)],
+      });
+    }
   }
 }
diff --git a/tsconfig.eslint.json b/tsconfig.dev.json
similarity index 96%
rename from tsconfig.eslint.json
rename to tsconfig.dev.json
index 00655d0b..2e29dbaa 100644
--- a/tsconfig.eslint.json
+++ b/tsconfig.dev.json
@@ -2,6 +2,7 @@
   "compilerOptions": {
     "alwaysStrict": true,
     "declaration": true,
+    "esModuleInterop": true,
     "experimentalDecorators": true,
     "inlineSourceMap": true,
     "inlineSources": true,
diff --git a/tsconfig.jest.json b/tsconfig.jest.json
deleted file mode 100644
index 00655d0b..00000000
--- a/tsconfig.jest.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
-  "compilerOptions": {
-    "alwaysStrict": true,
-    "declaration": true,
-    "experimentalDecorators": true,
-    "inlineSourceMap": true,
-    "inlineSources": true,
-    "lib": [
-      "es2019"
-    ],
-    "module": "CommonJS",
-    "noEmitOnError": false,
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitAny": true,
-    "noImplicitReturns": true,
-    "noImplicitThis": true,
-    "noUnusedLocals": true,
-    "noUnusedParameters": true,
-    "resolveJsonModule": true,
-    "strict": true,
-    "strictNullChecks": true,
-    "strictPropertyInitialization": true,
-    "stripInternal": true,
-    "target": "ES2019"
-  },
-  "include": [
-    ".projenrc.js",
-    "src/**/*.ts",
-    "test/**/*.ts"
-  ],
-  "exclude": [
-    "node_modules"
-  ],
-  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"."
-}
diff --git a/yarn.lock b/yarn.lock
index b2f9d013..14bf5907 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1719,11 +1719,6 @@ ansi-regex@^2.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
   integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
 
-ansi-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
-  integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
-
 ansi-regex@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
@@ -2776,9 +2771,9 @@ ecc-jsbn@~0.1.1:
     safer-buffer "^2.1.0"
 
 electron-to-chromium@^1.3.867:
-  version "1.3.869"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.869.tgz#c319171803d796639b1af3ddbf07a5a087037ccb"
-  integrity sha512-mcoyZ2jd691yAx0OHqqGw+aPCcRYVDw/bqPy660aBfcQkUcWcfAVsbL4M9WeevflKH4MfpKCtnOngVLM7HzSzA==
+  version "1.3.870"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.870.tgz#c15c921e66a46985181b261f8093b91c2abb6604"
+  integrity sha512-PiJMshfq6PL+i1V+nKLwhHbCKeD8eAz8rvO9Cwk/7cChOHJBtufmjajLyYLsSRHguRFiOCVx3XzJLeZsIAYfSA==
 
 emittery@^0.8.1:
   version "0.8.1"
@@ -3876,11 +3871,6 @@ is-fullwidth-code-point@^1.0.0:
   dependencies:
     number-is-nan "^1.0.0"
 
-is-fullwidth-code-point@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
-  integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
-
 is-fullwidth-code-point@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
@@ -4612,9 +4602,9 @@ jsii-diff@^1.39.0:
     yargs "^16.2.0"
 
 jsii-docgen@^3.7.6:
-  version "3.7.6"
-  resolved "https://registry.yarnpkg.com/jsii-docgen/-/jsii-docgen-3.7.6.tgz#b87097d8b5c6bcd75d848369bfc19a66159c3ed5"
-  integrity sha512-Ledb/jnZLjnh/5k8ZtZAP/PTc5BmM+r49w9kLlfgnzqV9AAi15nvVdUy0wcjeM6Fn7Pr+OaAJXWpx1LKPutbqg==
+  version "3.7.7"
+  resolved "https://registry.yarnpkg.com/jsii-docgen/-/jsii-docgen-3.7.7.tgz#35cb57ecc5d86c4b6e24a1766eb878802225eb67"
+  integrity sha512-XBmKeEeeflxTgoBlVZG1F6l09hBnGdJ8DhDmoE/60XYqu0rjZFcADRJxOHNslPJ5rpYEoSbL9BDidaL+I5fc8w==
   dependencies:
     "@jsii/spec" "^1.39.0"
     case "^1.6.3"
@@ -5744,10 +5734,10 @@ progress@^2.0.0, progress@^2.0.3:
   resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
   integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
 
-projen@^0.29.10:
-  version "0.29.17"
-  resolved "https://registry.yarnpkg.com/projen/-/projen-0.29.17.tgz#19622be20aff044f7648dfaecea2ee86a914a26f"
-  integrity sha512-BjPur+KOh8Tqbvhp/+FOfONWXP+i7nB+o0g64eYhlJYvpGw85lGGs3boFZJIC8ZFuzEG1hMx5+YxPp8M5U/hcA==
+projen@^0.30.2:
+  version "0.30.2"
+  resolved "https://registry.yarnpkg.com/projen/-/projen-0.30.2.tgz#8912a3684a802d660eebba07569abbd82fa6465b"
+  integrity sha512-zubNg8khVSEsdx0f9Hu0uv+IFaUoYNG4kSAqpo5D96RRkrvCxSjcjyhCxc+RnhXbz31/zClJFyBWA9C+r9foFA==
   dependencies:
     "@iarna/toml" "^2.2.5"
     chalk "^4.1.2"
@@ -6372,15 +6362,7 @@ string-width@^1.0.1:
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
-"string-width@^1.0.2 || 2":
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
-  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
-  dependencies:
-    is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^4.0.0"
-
-string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -6436,13 +6418,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   dependencies:
     ansi-regex "^2.0.0"
 
-strip-ansi@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
-  integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
-  dependencies:
-    ansi-regex "^3.0.0"
-
 strip-ansi@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@@ -6990,11 +6965,11 @@ which@^2.0.1, which@^2.0.2:
     isexe "^2.0.0"
 
 wide-align@^1.1.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
-  integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
+  integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
   dependencies:
-    string-width "^1.0.2 || 2"
+    string-width "^1.0.2 || 2 || 3 || 4"
 
 widest-line@^3.1.0:
   version "3.1.0"


Test patch:
diff --git a/test/certbot.test.ts b/test/certbot.test.ts
index 46d2b749..a49b7be8 100644
--- a/test/certbot.test.ts
+++ b/test/certbot.test.ts
@@ -18,7 +18,7 @@ test('Default', () => {
   });
 
   expectCDK(stack).to(countResources('AWS::Lambda::Function', 1));
-  expectCDK(stack).to(countResources('AWS::Events::Rule', 1));
+  expectCDK(stack).to(countResources('AWS::Events::Rule', 2)); // one for ongoing checks and one for immediate creation
   expectCDK(stack).to(countResources('AWS::S3::Bucket', 1));
   expectCDK(stack).to(countResources('AWS::IAM::ManagedPolicy', 3)); // acm, sns, and r53
   expectCDK(stack).to(countResources('AWS::IAM::Policy', 1)); // 1 inline policy for granting bucket write