├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── pull-request-lint.yml │ ├── upgrade-main.yml │ ├── upgrade-cdkv1.yml │ ├── build.yml │ ├── release.yml │ └── release-cdkv1.yml ├── .gitpod.yml ├── images ├── s3-bucket.png └── lambda-logs.png ├── src ├── index.ts ├── integ.default.ts ├── integ.python.ts ├── lambda-python.ts ├── lambda-bash.ts └── main.ts ├── docker.d ├── Dockerfile ├── main.sh ├── entrypoint.sh ├── bootstrap ├── function.sh └── s3_sync.py ├── assets ├── requirements.txt └── main.py ├── .npmignore ├── .projen ├── files.json ├── deps.json └── tasks.json ├── .mergify.yml ├── tsconfig.dev.json ├── .gitattributes ├── .gitignore ├── .projenrc.js ├── package.json ├── README.md ├── .eslintrc.json ├── LICENSE ├── test └── certbot-lambda.test.ts └── API.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: neilkuan 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: yarn install -------------------------------------------------------------------------------- /images/s3-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neilkuan/cdk-certbot-dns-route53/HEAD/images/s3-bucket.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './main'; 2 | export * from './lambda-bash'; 3 | export * from './lambda-python'; -------------------------------------------------------------------------------- /images/lambda-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neilkuan/cdk-certbot-dns-route53/HEAD/images/lambda-logs.png -------------------------------------------------------------------------------- /docker.d/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/lambda/python:3.12 2 | 3 | RUN pip3 install certbot-dns-route53 awscli 4 | 5 | COPY bootstrap /var/runtime/bootstrap 6 | COPY function.sh /var/task/function.sh 7 | COPY main.sh /var/task/main.sh 8 | RUN chmod +x /var/runtime/bootstrap /var/task/function.sh /var/task/main.sh 9 | 10 | WORKDIR /var/task 11 | CMD [ "function.sh.handler" ] -------------------------------------------------------------------------------- /assets/requirements.txt: -------------------------------------------------------------------------------- 1 | acme==2.8.0 2 | boto3==1.34.2 3 | botocore==1.34.2 4 | certbot==2.8.0 5 | certbot-dns-route53==2.8.0 6 | certifi==2023.11.17 7 | cffi==1.16.0 8 | charset-normalizer==3.3.2 9 | ConfigArgParse==1.7 10 | configobj==5.0.8 11 | cryptography==41.0.7 12 | distro==1.8.0 13 | idna==3.6 14 | jmespath==1.0.1 15 | josepy==1.14.0 16 | parsedatetime==2.6 17 | pycparser==2.21 18 | pyOpenSSL==23.3.0 19 | pyRFC3339==1.1 20 | python-dateutil==2.8.2 21 | pytz==2023.3.post1 22 | requests==2.31.0 23 | s3transfer==0.9.0 24 | six==1.16.0 25 | urllib3==2.0.7 -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | cdk.context.json 3 | yarn-error.log 4 | cdk.out 5 | images 6 | assets/venv/** 7 | assets/layers/** 8 | /.projen/ 9 | /test-reports/ 10 | junit.xml 11 | /coverage/ 12 | permissions-backup.acl 13 | /dist/changelog.md 14 | /dist/version.txt 15 | /.mergify.yml 16 | /test/ 17 | /tsconfig.dev.json 18 | /src/ 19 | !/lib/ 20 | !/lib/**/*.js 21 | !/lib/**/*.d.ts 22 | dist 23 | /tsconfig.json 24 | /.github/ 25 | /.vscode/ 26 | /.idea/ 27 | /.projenrc.js 28 | tsconfig.tsbuildinfo 29 | /.eslintrc.json 30 | !.jsii 31 | /.gitattributes 32 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'neilkuan') 18 | steps: 19 | - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/build.yml", 8 | ".github/workflows/pull-request-lint.yml", 9 | ".github/workflows/release-cdkv1.yml", 10 | ".github/workflows/release.yml", 11 | ".github/workflows/upgrade-cdkv1.yml", 12 | ".github/workflows/upgrade-main.yml", 13 | ".gitignore", 14 | ".mergify.yml", 15 | ".projen/deps.json", 16 | ".projen/files.json", 17 | ".projen/tasks.json", 18 | "LICENSE", 19 | "tsconfig.dev.json" 20 | ], 21 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 22 | } 23 | -------------------------------------------------------------------------------- /docker.d/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DOMAIN_NAME=$DOMAIN_NAME 3 | MAIL=$EMAIL 4 | 5 | echo "start to renew cert from route53" 6 | certbot certonly --agree-tos \ 7 | --dns-route53 \ 8 | -d ${DOMAIN_NAME} -m ${MAIL} --no-eff-email --config-dir /tmp --work-dir /tmp --logs-dir /tmp 9 | sleep 30 10 | TODAY=$(date +"%Y-%m-%d") 11 | 12 | if [ -z "$CUSTOM_PREFIX_DIRECTORY" ]; then 13 | echo "CUSTOM_PREFIX_DIRECTORY is not set" 14 | echo "S3_PATH is set to \$TODAY" 15 | export S3_PATH="${TODAY}/" 16 | elif [ $CUSTOM_PREFIX_DIRECTORY == "/" ]; then 17 | echo "S3_PATH is set to /" 18 | export S3_PATH="" 19 | else 20 | echo "S3_PATH CUSTOM_PREFIX_DIRECTORY is set" 21 | export S3_PATH="${CUSTOM_PREFIX_DIRECTORY}/" 22 | fi 23 | 24 | 25 | echo "sync file to S3 bucket s3://${BUCKET_NAME}/${S3_PATH}" 26 | aws s3 sync /tmp/live/ s3://${BUCKET_NAME}/${S3_PATH} 27 | -------------------------------------------------------------------------------- /docker.d/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DOMAIN_NAME=$DOMAIN_NAME 3 | MAIL=$EMAIL 4 | 5 | echo "start to renew cert from route53" 6 | certbot certonly --agree-tos \ 7 | --dns-route53 \ 8 | -d ${DOMAIN_NAME} -m ${MAIL} --no-eff-email --config-dir /tmp --work-dir /tmp --logs-dir /tmp 9 | sleep 30 10 | TODAY=$(date +"%Y-%m-%d") 11 | 12 | if [ -z "$CUSTOM_PREFIX_DIRECTORY" ]; then 13 | echo "CUSTOM_PREFIX_DIRECTORY is not set" 14 | echo "S3_PATH is set to \$TODAY" 15 | export S3_PATH="${TODAY}/" 16 | elif [ $CUSTOM_PREFIX_DIRECTORY == "/" ]; then 17 | echo "S3_PATH is set to /" 18 | export S3_PATH="" 19 | else 20 | echo "S3_PATH CUSTOM_PREFIX_DIRECTORY is set" 21 | export S3_PATH="${CUSTOM_PREFIX_DIRECTORY}/" 22 | fi 23 | 24 | 25 | echo "sync file to S3 bucket s3://${BUCKET_NAME}/${S3_PATH}" 26 | aws s3 sync /tmp/live/ s3://${BUCKET_NAME}/${S3_PATH} 27 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v6 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | requireScope: false 31 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | queue_conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | - status-success=package-js 11 | - status-success=package-python 12 | merge_method: squash 13 | commit_message_template: |- 14 | {{ title }} (#{{ number }}) 15 | 16 | {{ body }} 17 | pull_request_rules: 18 | - name: Automatic merge on approval and successful build 19 | actions: 20 | delete_head_branch: {} 21 | queue: 22 | name: default 23 | conditions: 24 | - "#approved-reviews-by>=1" 25 | - -label~=(do-not-merge) 26 | - status-success=build 27 | - status-success=package-js 28 | - status-success=package-python 29 | merge_queue: 30 | max_parallel_checks: 1 31 | -------------------------------------------------------------------------------- /docker.d/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euo pipefail 4 | 5 | # Initialization - load function handler 6 | source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh" 7 | 8 | # Processing 9 | while true 10 | do 11 | HEADERS="$(mktemp)" 12 | # Get an event. The HTTP request will block until one is received 13 | EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") 14 | 15 | # Extract request ID by scraping response headers received above 16 | REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) 17 | 18 | # Run the handler function from the script and pass in the event data as argument 19 | # IMPORTANT NOTE: -f3 gets the "handler" part from function.sh.handler 20 | RESPONSE=$($(echo "$_HANDLER" | cut -d. -f3) "$EVENT_DATA") 21 | 22 | # Send the response 23 | curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE" 24 | done -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2020" 12 | ], 13 | "module": "CommonJS", 14 | "noEmitOnError": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": true, 25 | "stripInternal": true, 26 | "target": "ES2020" 27 | }, 28 | "include": [ 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.js" 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /docker.d/function.sh: -------------------------------------------------------------------------------- 1 | function handler() { 2 | EVENT_DATA=$1 3 | 4 | export EVENT_DATA 5 | 6 | RESPONSE="$(./main.sh)" 7 | 8 | # send to cloudwatch logs 9 | echo "$RESPONSE" 1>&2; 10 | } 11 | 12 | # 13 | # Always execute the user handler function in all request types. 14 | # The user function(i.e main.sh) will determine the conditional execution. 15 | # 16 | function onEvent() { 17 | EVENT_DATA=$1 18 | echo "onEvent ==> $EVENT_DATA" 1>&2; 19 | requestType=$(getRequestType $1) 20 | 21 | case $requestType in 22 | 'Create') onCreate $1 ;; 23 | 'Update') onUpdate $1 ;; 24 | 'Delete') onDelete $1 ;; 25 | esac 26 | } 27 | 28 | function onCreate() { 29 | invokeLambda $1 30 | } 31 | 32 | function onUpdate() { 33 | invokeLambda $1 34 | } 35 | function onDelete() { 36 | invokeLambda $1 37 | } 38 | 39 | function invokeLambda() { 40 | echo $1 | jq '. | @base64' | aws lambda invoke \ 41 | --payload file:///dev/stdin \ 42 | --function-name ${LAMBDA_FUNCTION_ARN} /dev/stdout 43 | } 44 | 45 | function getRequestType() { 46 | echo $1 | jq -r .RequestType 47 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.eslintrc.json linguist-generated 6 | /.gitattributes linguist-generated 7 | /.github/pull_request_template.md linguist-generated 8 | /.github/workflows/auto-approve.yml linguist-generated 9 | /.github/workflows/build.yml linguist-generated 10 | /.github/workflows/pull-request-lint.yml linguist-generated 11 | /.github/workflows/release-cdkv1.yml linguist-generated 12 | /.github/workflows/release.yml linguist-generated 13 | /.github/workflows/upgrade-cdkv1.yml linguist-generated 14 | /.github/workflows/upgrade-main.yml linguist-generated 15 | /.gitignore linguist-generated 16 | /.mergify.yml linguist-generated 17 | /.npmignore linguist-generated 18 | /.projen/** linguist-generated 19 | /.projen/deps.json linguist-generated 20 | /.projen/files.json linguist-generated 21 | /.projen/tasks.json linguist-generated 22 | /API.md linguist-generated 23 | /LICENSE linguist-generated 24 | /package.json linguist-generated 25 | /tsconfig.dev.json linguist-generated 26 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /src/integ.default.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 3 | import * as r53 from 'aws-cdk-lib/aws-route53'; 4 | import * as s3 from 'aws-cdk-lib/aws-s3'; 5 | import { CertbotDnsRoute53Job } from '.'; 6 | 7 | const devEnv = { 8 | account: process.env.CDK_DEFAULT_ACCOUNT, 9 | region: process.env.CDK_DEFAULT_REGION, 10 | }; 11 | 12 | const app = new cdk.App(); 13 | 14 | const stack = new cdk.Stack(app, 'lambda-certbot-dev', { env: devEnv }); 15 | 16 | new CertbotDnsRoute53Job(stack, 'Demo', { 17 | certbotOptions: { 18 | domainName: stack.node.tryGetContext('DOMAIN') ?? '*.example.com', 19 | email: stack.node.tryGetContext('EMAIL') ?? 'user@example.com', 20 | customPrefixDirectory: '/', 21 | }, 22 | zone: r53.HostedZone.fromHostedZoneAttributes(stack, 'myZone', { 23 | zoneName: stack.node.tryGetContext('ZONENAME') ?? 'example.com', 24 | hostedZoneId: stack.node.tryGetContext('HOSTZONEID') ?? 'mockId', 25 | }), 26 | destinationBucket: s3.Bucket.fromBucketName(stack, 'myBucket', stack.node.tryGetContext('BUCKETNAME') ?? 'mybucket'), 27 | architecture: lambda.Architecture.X86_64, 28 | }); 29 | 30 | app.synth(); -------------------------------------------------------------------------------- /src/integ.python.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 3 | import * as r53 from 'aws-cdk-lib/aws-route53'; 4 | import * as s3 from 'aws-cdk-lib/aws-s3'; 5 | import { CertbotDnsRoute53JobPython } from '.'; 6 | 7 | const devEnv = { 8 | account: process.env.CDK_DEFAULT_ACCOUNT, 9 | region: process.env.CDK_DEFAULT_REGION, 10 | }; 11 | 12 | const app = new cdk.App(); 13 | 14 | const stack = new cdk.Stack(app, 'lambda-certbot-dev-python', { env: devEnv }); 15 | 16 | new CertbotDnsRoute53JobPython(stack, 'Demo', { 17 | certbotOptions: { 18 | domainName: stack.node.tryGetContext('DOMAIN'), 19 | email: stack.node.tryGetContext('EMAIL'), 20 | customPrefixDirectory: '/', 21 | }, 22 | zone: r53.HostedZone.fromHostedZoneAttributes(stack, 'myZone', { 23 | zoneName: stack.node.tryGetContext('ZONENAME'), 24 | hostedZoneId: stack.node.tryGetContext('HOSTZONEID'), 25 | }), 26 | destinationBucket: new s3.Bucket(stack, 'myBucket123', { 27 | removalPolicy: cdk.RemovalPolicy.DESTROY, 28 | }), 29 | architecture: lambda.Architecture.ARM_64, 30 | enabledLambdaFunctionUrl: true, 31 | // enabledLambdaFunctionUrl: false, 32 | }); 33 | 34 | app.synth(); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | cdk.context.json 35 | yarn-error.log 36 | cdk.out 37 | assets/venv/** 38 | assets/layers/** 39 | venv/** 40 | /test-reports/ 41 | junit.xml 42 | /coverage/ 43 | !/.github/workflows/build.yml 44 | /dist/changelog.md 45 | /dist/version.txt 46 | !/.github/workflows/release.yml 47 | !/.github/workflows/release-cdkv1.yml 48 | !/.mergify.yml 49 | !/.github/workflows/upgrade-main.yml 50 | !/.github/workflows/upgrade-cdkv1.yml 51 | !/.github/pull_request_template.md 52 | !/test/ 53 | !/tsconfig.dev.json 54 | !/src/ 55 | /lib 56 | /dist/ 57 | !/.eslintrc.json 58 | .jsii 59 | tsconfig.json 60 | !/API.md 61 | !/.projenrc.js 62 | -------------------------------------------------------------------------------- /docker.d/s3_sync.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | from botocore.exceptions import ClientError 4 | from datetime import datetime, timezone, timedelta 5 | s3resource = boto3.resource('s3') 6 | bucket_name = os.getenv('BUCKET_NAME') 7 | domain_name = os.getenv('DOMAIN_NAME') 8 | 9 | # what time is it ?? 10 | def what_time_is(): 11 | time = datetime.now() 12 | date_time = str(time).split(' ')[0] 13 | return date_time 14 | 15 | def sync_certificat_to_s3(): 16 | try: 17 | domain_list = domain_name.split('.')[-2:] 18 | domain_name_dir = '.'.join(domain_list) 19 | print('your domain is '+ domain_name_dir) 20 | print(what_time_is()) 21 | print(f'Start to sync fullchain.pem to {bucket_name} !!!'.format(bucket_name)) 22 | s3resource.Object(bucket_name, f'{what_time_is()}/fullchain.pem'.format(what_time_is())).put(Body=open(f'/tmp/live/{domain_name_dir}/fullchain.pem'.format(domain_name_dir), 'rb')) 23 | 24 | print(f'Start to sync privkey.pem to {bucket_name} !!!'.format(bucket_name)) 25 | s3resource.Object(bucket_name, f'{what_time_is()}/privkey.pem'.format(what_time_is())).put(Body=open(f'/tmp/live/{domain_name_dir}/privkey.pem'.format(domain_name_dir), 'rb')) 26 | 27 | print('Sync Completed !!!') 28 | except ClientError as err: 29 | print(err) 30 | 31 | if __name__ == '__main__': 32 | sync_certificat_to_s3() -------------------------------------------------------------------------------- /src/lambda-python.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as lambdaPython from '@aws-cdk/aws-lambda-python-alpha'; 3 | import { CfnOutput, Duration } from 'aws-cdk-lib'; 4 | import * as iam from 'aws-cdk-lib/aws-iam'; 5 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 6 | import * as logs from 'aws-cdk-lib/aws-logs'; 7 | import { Construct } from 'constructs'; 8 | 9 | export interface LambdaFunctionProps { 10 | readonly timeout: Duration; 11 | readonly environment?: { [key: string]: string }; 12 | readonly role?: iam.IRole; 13 | readonly architecture: lambda.Architecture; 14 | } 15 | 16 | 17 | export class LambdaPythonFunction extends Construct { 18 | readonly handler: lambdaPython.PythonFunction; 19 | constructor(scope: Construct, id: string, props: LambdaFunctionProps) { 20 | super(scope, id); 21 | this.handler = new lambdaPython.PythonFunction(this, 'PythonFunction', { 22 | entry: path.join(__dirname, '../assets/'), 23 | handler: 'handler', 24 | index: 'main.py', 25 | runtime: lambda.Runtime.PYTHON_3_12, 26 | timeout: props.timeout ?? Duration.seconds(60), 27 | logGroup: new logs.LogGroup(this, 'LogGroup', { 28 | retention: logs.RetentionDays.ONE_DAY, 29 | }), 30 | environment: props.environment, 31 | architecture: props.architecture, 32 | role: props.role, 33 | memorySize: 1024, 34 | bundling: { 35 | platform: props.architecture.dockerPlatform, 36 | }, 37 | }); 38 | new CfnOutput(this, 'LogGroupOutput', { value: this.handler.logGroup.logGroupName }); 39 | } 40 | } -------------------------------------------------------------------------------- /.projenrc.js: -------------------------------------------------------------------------------- 1 | const { awscdk } = require('projen'); 2 | const project = new awscdk.AwsCdkConstructLibrary({ 3 | author: 'Neil Kuan', 4 | authorAddress: 'guan840912@gmail.com', 5 | cdkVersion: '2.214.1', 6 | /** 7 | * we default release the main branch(cdkv2) with major version 2. 8 | */ 9 | majorVersion: 2, 10 | defaultReleaseBranch: 'main', 11 | /** 12 | * we also release the cdkv1 branch with major version 1. 13 | */ 14 | releaseBranches: { 15 | cdkv1: { npmDistTag: 'cdkv1', majorVersion: 1 }, 16 | }, 17 | defaultReleaseBranch: 'main', 18 | name: 'cdk-certbot-dns-route53', 19 | repositoryUrl: 'https://github.com/neilkuan/cdk-certbot-dns-route53.git', 20 | description: 'Create Cron Job Via Lambda, to update certificate and put it to S3 Bucket.', 21 | keywords: ['aws', 'cdk', 'certbot'], 22 | depsUpgradeOptions: { 23 | ignoreProjen: false, 24 | workflowOptions: { 25 | labels: ['auto-approve', 'auto-merge'], 26 | }, 27 | exclude: ['mock-fs'], 28 | }, 29 | autoApproveOptions: { 30 | secret: 'GITHUB_TOKEN', 31 | allowedUsernames: ['neilkuan'], 32 | }, 33 | publishToPypi: { 34 | distName: 'cdk-certbot-dns-route53', 35 | module: 'cdk_certbot_dns_route53', 36 | }, 37 | catalog: { 38 | announce: true, 39 | twitter: 'neilkuan', 40 | }, 41 | gitignore: [ 42 | 'cdk.context.json', 43 | 'yarn-error.log', 44 | 'cdk.out', 45 | 'assets/venv/**', 46 | 'assets/layers/**', 47 | 'venv/**', 48 | ], 49 | npmignore: [ 50 | 'cdk.context.json', 51 | 'yarn-error.log', 52 | 'cdk.out', 53 | 'images', 54 | 'assets/venv/**', 55 | 'assets/layers/**', 56 | ], 57 | deps: [ 58 | '@aws-cdk/aws-lambda-python-alpha@2.214.1-alpha.0', 59 | ], 60 | bundledDependencies: [ 61 | '@aws-cdk/aws-lambda-python-alpha@2.214.1-alpha.0', 62 | ], 63 | peerDependencies: [ 64 | '@aws-cdk/aws-lambda-python-alpha@2.214.1-alpha.0', 65 | ], 66 | devDeps: [ 67 | 'ts-jest@29.1.2', 68 | 'jsii-rosetta@5.0.x', 69 | ], 70 | minNodeVersion: '20.10.0', 71 | workflowNodeVersion: '20.10.0', 72 | typescriptVersion: '^5', 73 | jsiiVersion: '5.9.x', 74 | }); 75 | project.synth(); 76 | -------------------------------------------------------------------------------- /src/lambda-bash.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { CfnOutput, Duration } from 'aws-cdk-lib'; 4 | import * as iam from 'aws-cdk-lib/aws-iam'; 5 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 6 | import * as logs from 'aws-cdk-lib/aws-logs'; 7 | import { Construct } from 'constructs'; 8 | 9 | export interface BashExecFunctionProps { 10 | /** 11 | * The path of the shell script to be executed. 12 | */ 13 | readonly script: string; 14 | 15 | /** 16 | * The path of your custom dockerfile. 17 | */ 18 | readonly dockerfile?: string; 19 | 20 | /** 21 | * Lambda environment variables. 22 | */ 23 | readonly environment?: { [key: string]: string }; 24 | 25 | /** 26 | * The function execution time (in seconds) after which Lambda terminates the function. 27 | * Because the execution time affects cost, set this value based on the function's expected execution time. 28 | * @default - Duration.seconds(60) 29 | * */ 30 | readonly timeout?: Duration; 31 | 32 | /** 33 | * Custom lambda execution role. 34 | * 35 | * @default - auto generated role. 36 | */ 37 | readonly role?: iam.IRole; 38 | 39 | /** 40 | * Custom lambda Image Architecture. 41 | * 42 | * @default - lambda.Architecture.X86_64 43 | */ 44 | readonly architecture?: lambda.Architecture; 45 | } 46 | 47 | export class BashExecFunction extends Construct { 48 | readonly handler: lambda.DockerImageFunction; 49 | constructor(scope: Construct, id: string, props: BashExecFunctionProps) { 50 | super(scope, id); 51 | 52 | const dockerDirPath = path.join(__dirname, '../docker.d'); 53 | const scriptPath = props.script; 54 | 55 | // copy the user script to the docker.d directory as main.sh so we can bundle it up into a new docker image 56 | const mainFile = path.join(dockerDirPath, '/main.sh'); 57 | fs.copyFileSync(scriptPath, mainFile); 58 | 59 | this.handler = new lambda.DockerImageFunction(this, 'BashExecFunction', { 60 | code: lambda.DockerImageCode.fromImageAsset(dockerDirPath, { 61 | }), 62 | timeout: props.timeout ?? Duration.seconds(60), 63 | logGroup: new logs.LogGroup(this, 'LogGroup', { 64 | retention: logs.RetentionDays.ONE_DAY, 65 | }), 66 | environment: props.environment, 67 | role: props.role, 68 | architecture: props.architecture, 69 | memorySize: 1024, 70 | }); 71 | new CfnOutput(this, 'LogGroupOutput', { value: this.handler.logGroup.logGroupName }); 72 | } 73 | } -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@stylistic/eslint-plugin", 5 | "version": "^2", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@types/jest", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@types/node", 14 | "version": "^20", 15 | "type": "build" 16 | }, 17 | { 18 | "name": "@typescript-eslint/eslint-plugin", 19 | "version": "^8", 20 | "type": "build" 21 | }, 22 | { 23 | "name": "@typescript-eslint/parser", 24 | "version": "^8", 25 | "type": "build" 26 | }, 27 | { 28 | "name": "commit-and-tag-version", 29 | "version": "^12", 30 | "type": "build" 31 | }, 32 | { 33 | "name": "eslint-import-resolver-typescript", 34 | "type": "build" 35 | }, 36 | { 37 | "name": "eslint-plugin-import", 38 | "type": "build" 39 | }, 40 | { 41 | "name": "eslint", 42 | "version": "^9", 43 | "type": "build" 44 | }, 45 | { 46 | "name": "jest", 47 | "type": "build" 48 | }, 49 | { 50 | "name": "jest-junit", 51 | "version": "^16", 52 | "type": "build" 53 | }, 54 | { 55 | "name": "jsii-diff", 56 | "type": "build" 57 | }, 58 | { 59 | "name": "jsii-docgen", 60 | "version": "^10.5.0", 61 | "type": "build" 62 | }, 63 | { 64 | "name": "jsii-pacmak", 65 | "type": "build" 66 | }, 67 | { 68 | "name": "jsii-rosetta", 69 | "version": "5.9.x", 70 | "type": "build" 71 | }, 72 | { 73 | "name": "jsii", 74 | "version": "5.9.x", 75 | "type": "build" 76 | }, 77 | { 78 | "name": "projen", 79 | "type": "build" 80 | }, 81 | { 82 | "name": "ts-jest", 83 | "type": "build" 84 | }, 85 | { 86 | "name": "typescript", 87 | "version": "^5", 88 | "type": "build" 89 | }, 90 | { 91 | "name": "aws-cdk-lib", 92 | "version": "^2.214.1", 93 | "type": "peer" 94 | }, 95 | { 96 | "name": "constructs", 97 | "version": "^10.0.5", 98 | "type": "peer" 99 | }, 100 | { 101 | "name": "@aws-cdk/aws-lambda-python-alpha", 102 | "version": "2.214.1-alpha.0", 103 | "type": "runtime" 104 | } 105 | ], 106 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 107 | } 108 | -------------------------------------------------------------------------------- /assets/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import boto3 4 | import sys 5 | from certbot.main import main 6 | import json 7 | sys.path.append('/var/task') 8 | def handler(events, contexts): 9 | events = events 10 | contexts = contexts 11 | DOMAIN_NAME=os.getenv('DOMAIN_NAME') 12 | MAIL=os.getenv('EMAIL') 13 | BUCKET_NAME = os.getenv('BUCKET_NAME') 14 | 15 | print('start to renew cert from route53...') 16 | try: 17 | main(['certonly', '--agree-tos', 18 | '--dns-route53', 19 | '-d', DOMAIN_NAME, 20 | '-m', MAIL, 21 | '--no-eff-email', 22 | '--config-dir', '/tmp', 23 | '--work-dir', '/tmp', 24 | '--logs-dir', '/tmp', 25 | ]) 26 | except Exception as e: 27 | print(e) 28 | return json.dumps({ 29 | "message": f'Execute certbot command fail {e}' 30 | }) 31 | 32 | today = datetime.date.today() 33 | CUSTOM_PREFIX_DIRECTORY= os.getenv('CUSTOM_PREFIX_DIRECTORY') 34 | s3_path = '' 35 | if CUSTOM_PREFIX_DIRECTORY == None: 36 | print('CUSTOM_PREFIX_DIRECTORY is not set') 37 | print(f'S3_PATH is set to {today}') 38 | s3_path = f'{today}/' 39 | elif CUSTOM_PREFIX_DIRECTORY == '/': 40 | print('S3_PATH is set to /') 41 | s3_path = '' 42 | else: 43 | print('S3_PATH CUSTOM_PREFIX_DIRECTORY is set') 44 | s3_path = f'{CUSTOM_PREFIX_DIRECTORY}/' 45 | print('sync file to S3 bucket s3://{BUCKET_NAME}/{S3_PATH}') 46 | 47 | client = boto3.client('s3') 48 | domain_path = domain_path_helper(DOMAIN_NAME) 49 | dirs = os.listdir(f'/tmp/live/{domain_path}') 50 | for file_name in dirs: 51 | try: 52 | client.upload_file('/tmp/live/' + domain_path + '/' + file_name, BUCKET_NAME, s3_path + domain_path + '/' + file_name) 53 | except Exception as e: 54 | print(e) 55 | 56 | return json.dumps({ 57 | "message": f'{e}' 58 | }) 59 | if s3_path == '': 60 | print(f'find your certificates in your bucket s3://{BUCKET_NAME}/{domain_path}') 61 | return json.dumps({ 62 | "message": f'find your certificates in your bucket s3://{BUCKET_NAME}/{domain_path}' 63 | }) 64 | else: 65 | print(f'find your certificates in your bucket s3://{BUCKET_NAME}/{s3_path}/{domain_path}') 66 | return json.dumps({ 67 | "message": f'find your certificates in your bucket s3://{BUCKET_NAME}/{s3_path}/{domain_path}' 68 | }) 69 | 70 | 71 | 72 | def domain_path_helper(domain: str): 73 | d = domain.split('.') 74 | try: 75 | d.remove('*') 76 | except: 77 | pass 78 | r = '.'.join(d) 79 | return r 80 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 * * * 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v5 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: 20.10.0 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-main" workflow* 81 | branch: github-actions/upgrade-main 82 | title: "chore(deps): upgrade dependencies" 83 | labels: auto-approve,auto-merge 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-cdkv1.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-cdkv1 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 * * * 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v5 19 | with: 20 | ref: cdkv1 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: 20.10.0 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: cdkv1 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-cdkv1" workflow* 81 | branch: github-actions/upgrade-cdkv1 82 | title: "chore(deps): upgrade dependencies" 83 | labels: auto-approve,auto-merge 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-cdkv1" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-certbot-dns-route53", 3 | "description": "Create Cron Job Via Lambda, to update certificate and put it to S3 Bucket.", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/neilkuan/cdk-certbot-dns-route53.git" 7 | }, 8 | "scripts": { 9 | "build": "npx projen build", 10 | "bump": "npx projen bump", 11 | "clobber": "npx projen clobber", 12 | "compat": "npx projen compat", 13 | "compile": "npx projen compile", 14 | "default": "npx projen default", 15 | "docgen": "npx projen docgen", 16 | "eject": "npx projen eject", 17 | "eslint": "npx projen eslint", 18 | "package": "npx projen package", 19 | "package-all": "npx projen package-all", 20 | "package:js": "npx projen package:js", 21 | "package:python": "npx projen package:python", 22 | "post-compile": "npx projen post-compile", 23 | "post-upgrade": "npx projen post-upgrade", 24 | "pre-compile": "npx projen pre-compile", 25 | "release": "npx projen release", 26 | "release:cdkv1": "npx projen release:cdkv1", 27 | "test": "npx projen test", 28 | "test:watch": "npx projen test:watch", 29 | "unbump": "npx projen unbump", 30 | "upgrade": "npx projen upgrade", 31 | "watch": "npx projen watch", 32 | "projen": "npx projen" 33 | }, 34 | "author": { 35 | "name": "Neil Kuan", 36 | "email": "guan840912@gmail.com", 37 | "organization": false 38 | }, 39 | "devDependencies": { 40 | "@stylistic/eslint-plugin": "^2", 41 | "@types/jest": "^27", 42 | "@types/node": "^20", 43 | "@typescript-eslint/eslint-plugin": "^8", 44 | "@typescript-eslint/parser": "^8", 45 | "aws-cdk-lib": "2.214.1", 46 | "commit-and-tag-version": "^12", 47 | "constructs": "10.0.5", 48 | "eslint": "^9", 49 | "eslint-import-resolver-typescript": "^2.7.1", 50 | "eslint-plugin-import": "^2.32.0", 51 | "jest": "^27", 52 | "jest-junit": "^16", 53 | "jsii": "5.9.x", 54 | "jsii-diff": "^1.121.0", 55 | "jsii-docgen": "^10.5.0", 56 | "jsii-pacmak": "^1.121.0", 57 | "jsii-rosetta": "5.9.x", 58 | "projen": "^0.98.30", 59 | "ts-jest": "^27", 60 | "typescript": "^5" 61 | }, 62 | "peerDependencies": { 63 | "aws-cdk-lib": "^2.214.1", 64 | "constructs": "^10.0.5" 65 | }, 66 | "dependencies": { 67 | "@aws-cdk/aws-lambda-python-alpha": "2.214.1-alpha.0" 68 | }, 69 | "keywords": [ 70 | "aws", 71 | "cdk", 72 | "certbot" 73 | ], 74 | "engines": { 75 | "node": ">= 20.10.0" 76 | }, 77 | "main": "lib/index.js", 78 | "license": "Apache-2.0", 79 | "publishConfig": { 80 | "access": "public" 81 | }, 82 | "version": "0.0.0", 83 | "jest": { 84 | "coverageProvider": "v8", 85 | "testMatch": [ 86 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 87 | "/@(src|test)/**/__tests__/**/*.ts?(x)" 88 | ], 89 | "clearMocks": true, 90 | "collectCoverage": true, 91 | "coverageReporters": [ 92 | "json", 93 | "lcov", 94 | "clover", 95 | "cobertura", 96 | "text" 97 | ], 98 | "coverageDirectory": "coverage", 99 | "coveragePathIgnorePatterns": [ 100 | "/node_modules/" 101 | ], 102 | "testPathIgnorePatterns": [ 103 | "/node_modules/" 104 | ], 105 | "watchPathIgnorePatterns": [ 106 | "/node_modules/" 107 | ], 108 | "reporters": [ 109 | "default", 110 | [ 111 | "jest-junit", 112 | { 113 | "outputDirectory": "test-reports" 114 | } 115 | ] 116 | ], 117 | "preset": "ts-jest", 118 | "globals": { 119 | "ts-jest": { 120 | "tsconfig": "tsconfig.dev.json" 121 | } 122 | } 123 | }, 124 | "types": "lib/index.d.ts", 125 | "stability": "stable", 126 | "jsii": { 127 | "outdir": "dist", 128 | "targets": { 129 | "python": { 130 | "distName": "cdk-certbot-dns-route53", 131 | "module": "cdk_certbot_dns_route53" 132 | } 133 | }, 134 | "tsc": { 135 | "outDir": "lib", 136 | "rootDir": "src" 137 | } 138 | }, 139 | "awscdkio": { 140 | "twitter": "neilkuan", 141 | "announce": true 142 | }, 143 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version](https://badge.fury.io/js/cdk-certbot-dns-route53.svg)](https://badge.fury.io/js/cdk-certbot-dns-route53) 2 | [![PyPI version](https://badge.fury.io/py/cdk-certbot-dns-route53.svg)](https://badge.fury.io/py/cdk-certbot-dns-route53) 3 | [![Release](https://github.com/neilkuan/cdk-certbot-dns-route53/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/neilkuan/cdk-certbot-dns-route53/actions/workflows/release.yml) 4 | 5 | ![Downloads](https://img.shields.io/badge/-DOWNLOADS:-brightgreen?color=gray) 6 | ![npm](https://img.shields.io/npm/dt/cdk-certbot-dns-route53?label=npm&color=orange) 7 | ![PyPI](https://img.shields.io/pypi/dm/cdk-certbot-dns-route53?label=pypi&color=blue) 8 | 9 | # cdk-certbot-dns-route53 10 | **cdk-certbot-dns-route53** is a CDK construct library that allows you to create [Certbot](https://github.com/certbot/certbot) Lambda Function on AWS with CDK, and setting schedule cron job to renew certificate to store on S3 Bucket. 11 | 12 | ## Install 13 | ```bash 14 | Use the npm dist tag to opt in CDKv1 or CDKv2: 15 | 16 | // for CDKv2 17 | npm install cdk-certbot-dns-route53 18 | or 19 | npm install cdk-certbot-dns-route53@latest 20 | 21 | // for CDKv1 22 | npm install cdk-certbot-dns-route53@cdkv1 23 | ``` 24 | 25 | 💡💡💡 please click [here](https://github.com/neilkuan/cdk-certbot-dns-route53/tree/cdkv1#readme), if you are using aws-cdk v1.x.x version.💡💡💡 26 | 27 | ```ts 28 | import * as r53 from 'aws-cdk-lib/aws-route53'; 29 | import * as s3 from 'aws-cdk-lib/aws-s3'; 30 | import * as cdk from 'aws-cdk-lib'; 31 | import { CertbotDnsRoute53Job } from 'cdk-certbot-dns-route53'; 32 | 33 | const devEnv = { 34 | account: process.env.CDK_DEFAULT_ACCOUNT, 35 | region: process.env.CDK_DEFAULT_REGION, 36 | }; 37 | 38 | const app = new cdk.App(); 39 | 40 | const stack = new cdk.Stack(app, 'lambda-certbot-dev', { env: devEnv }); 41 | 42 | new CertbotDnsRoute53Job(stack, 'Demo', { 43 | certbotOptions: { 44 | domainName: '*.example.com', 45 | email: 'user@example.com', 46 | }, 47 | zone: r53.HostedZone.fromHostedZoneAttributes(stack, 'myZone', { 48 | zoneName: 'example.com', 49 | hostedZoneId: 'mockId', 50 | }), 51 | destinationBucket: s3.Bucket.fromBucketName(stack, 'myBucket', 'mybucket'), 52 | }); 53 | ``` 54 | 55 | 56 | ### You can define Lambda Image Architecture now. 2022/04/19 57 | ```ts 58 | import * as r53 from 'aws-cdk-lib/aws-route53'; 59 | import * as s3 from 'aws-cdk-lib/aws-s3'; 60 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 61 | import * as cdk from 'aws-cdk-lib'; 62 | import { CertbotDnsRoute53Job } from 'cdk-certbot-dns-route53'; 63 | 64 | const mockApp = new cdk.App(); 65 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 66 | const bucket = new s3.Bucket(stack, 'testingBucket'); 67 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 68 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 69 | }); 70 | new CertbotDnsRoute53Job(stack, 'Testtask', { 71 | certbotOptions: { 72 | domainName: 'example.com', 73 | email: 'user@example.com', 74 | customPrefixDirectory: '/', 75 | }, 76 | zone, 77 | destinationBucket: bucket, 78 | schedule: events.Schedule.cron({ month: '2' }), 79 | architecture: lambda.Architecture.ARM_64, // <- like this way. 80 | }); 81 | 82 | ``` 83 | 84 | ### Example: Invoke Lambda Function log. 85 | ![](./images/lambda-logs.png) 86 | 87 | ### Example: Renew certificate to store on S3 Bucket 88 | ![](./images/s3-bucket.png) 89 | 90 | 91 | ### Support Python Lambda Runtime. 2023/12/17 92 | > Support enabled Lambda Function Url. 93 | ```ts 94 | import * as r53 from 'aws-cdk-lib/aws-route53'; 95 | import * as s3 from 'aws-cdk-lib/aws-s3'; 96 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 97 | import * as cdk from 'aws-cdk-lib'; 98 | import { CertbotDnsRoute53JobPython } from 'cdk-certbot-dns-route53'; 99 | 100 | const mockApp = new cdk.App(); 101 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 102 | const bucket = new s3.Bucket(stack, 'testingBucket'); 103 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 104 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 105 | }); 106 | new CertbotDnsRoute53JobPython(stack, 'Testtask', { 107 | certbotOptions: { 108 | domainName: 'example.com', 109 | email: 'user@example.com', 110 | customPrefixDirectory: '/', 111 | }, 112 | zone, 113 | destinationBucket: bucket, 114 | schedule: events.Schedule.cron({ month: '2' }), 115 | enabledLambdaFunctionUrl: true, 116 | }); 117 | 118 | ``` -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import", 11 | "@stylistic" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module", 17 | "project": "./tsconfig.dev.json" 18 | }, 19 | "extends": [ 20 | "plugin:import/typescript" 21 | ], 22 | "settings": { 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [ 25 | ".ts", 26 | ".tsx" 27 | ] 28 | }, 29 | "import/resolver": { 30 | "node": {}, 31 | "typescript": { 32 | "project": "./tsconfig.dev.json", 33 | "alwaysTryTypes": true 34 | } 35 | } 36 | }, 37 | "ignorePatterns": [ 38 | "*.js", 39 | "*.d.ts", 40 | "node_modules/", 41 | "*.generated.ts", 42 | "coverage", 43 | "!.projenrc.js" 44 | ], 45 | "rules": { 46 | "@stylistic/indent": [ 47 | "error", 48 | 2 49 | ], 50 | "@stylistic/quotes": [ 51 | "error", 52 | "single", 53 | { 54 | "avoidEscape": true 55 | } 56 | ], 57 | "@stylistic/comma-dangle": [ 58 | "error", 59 | "always-multiline" 60 | ], 61 | "@stylistic/comma-spacing": [ 62 | "error", 63 | { 64 | "before": false, 65 | "after": true 66 | } 67 | ], 68 | "@stylistic/no-multi-spaces": [ 69 | "error", 70 | { 71 | "ignoreEOLComments": false 72 | } 73 | ], 74 | "@stylistic/array-bracket-spacing": [ 75 | "error", 76 | "never" 77 | ], 78 | "@stylistic/array-bracket-newline": [ 79 | "error", 80 | "consistent" 81 | ], 82 | "@stylistic/object-curly-spacing": [ 83 | "error", 84 | "always" 85 | ], 86 | "@stylistic/object-curly-newline": [ 87 | "error", 88 | { 89 | "multiline": true, 90 | "consistent": true 91 | } 92 | ], 93 | "@stylistic/object-property-newline": [ 94 | "error", 95 | { 96 | "allowAllPropertiesOnSameLine": true 97 | } 98 | ], 99 | "@stylistic/keyword-spacing": [ 100 | "error" 101 | ], 102 | "@stylistic/brace-style": [ 103 | "error", 104 | "1tbs", 105 | { 106 | "allowSingleLine": true 107 | } 108 | ], 109 | "@stylistic/space-before-blocks": [ 110 | "error" 111 | ], 112 | "@stylistic/member-delimiter-style": [ 113 | "error" 114 | ], 115 | "@stylistic/semi": [ 116 | "error", 117 | "always" 118 | ], 119 | "@stylistic/max-len": [ 120 | "error", 121 | { 122 | "code": 150, 123 | "ignoreUrls": true, 124 | "ignoreStrings": true, 125 | "ignoreTemplateLiterals": true, 126 | "ignoreComments": true, 127 | "ignoreRegExpLiterals": true 128 | } 129 | ], 130 | "@stylistic/quote-props": [ 131 | "error", 132 | "consistent-as-needed" 133 | ], 134 | "@stylistic/key-spacing": [ 135 | "error" 136 | ], 137 | "@stylistic/no-multiple-empty-lines": [ 138 | "error" 139 | ], 140 | "@stylistic/no-trailing-spaces": [ 141 | "error" 142 | ], 143 | "curly": [ 144 | "error", 145 | "multi-line", 146 | "consistent" 147 | ], 148 | "@typescript-eslint/no-require-imports": "error", 149 | "import/no-extraneous-dependencies": [ 150 | "error", 151 | { 152 | "devDependencies": [ 153 | "**/test/**", 154 | "**/build-tools/**" 155 | ], 156 | "optionalDependencies": false, 157 | "peerDependencies": true 158 | } 159 | ], 160 | "import/no-unresolved": [ 161 | "error" 162 | ], 163 | "import/order": [ 164 | "warn", 165 | { 166 | "groups": [ 167 | "builtin", 168 | "external" 169 | ], 170 | "alphabetize": { 171 | "order": "asc", 172 | "caseInsensitive": true 173 | } 174 | } 175 | ], 176 | "import/no-duplicates": [ 177 | "error" 178 | ], 179 | "no-shadow": [ 180 | "off" 181 | ], 182 | "@typescript-eslint/no-shadow": "error", 183 | "@typescript-eslint/no-floating-promises": "error", 184 | "no-return-await": [ 185 | "off" 186 | ], 187 | "@typescript-eslint/return-await": "error", 188 | "dot-notation": [ 189 | "error" 190 | ], 191 | "no-bitwise": [ 192 | "error" 193 | ], 194 | "@typescript-eslint/member-ordering": [ 195 | "error", 196 | { 197 | "default": [ 198 | "public-static-field", 199 | "public-static-method", 200 | "protected-static-field", 201 | "protected-static-method", 202 | "private-static-field", 203 | "private-static-method", 204 | "field", 205 | "constructor", 206 | "method" 207 | ] 208 | } 209 | ] 210 | }, 211 | "overrides": [ 212 | { 213 | "files": [ 214 | ".projenrc.js" 215 | ], 216 | "rules": { 217 | "@typescript-eslint/no-require-imports": "off", 218 | "import/no-extraneous-dependencies": "off" 219 | } 220 | } 221 | ] 222 | } 223 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | outputs: 13 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 14 | env: 15 | CI: "true" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v5 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v5 24 | with: 25 | node-version: 20.10.0 26 | - name: Install dependencies 27 | run: yarn install --check-files 28 | - name: build 29 | run: npx projen build 30 | - name: Find mutations 31 | id: self_mutation 32 | run: |- 33 | git add . 34 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 35 | shell: bash 36 | working-directory: ./ 37 | - name: Upload patch 38 | if: steps.self_mutation.outputs.self_mutation_happened 39 | uses: actions/upload-artifact@v4.6.2 40 | with: 41 | name: repo.patch 42 | path: repo.patch 43 | overwrite: true 44 | - name: Fail build on mutation 45 | if: steps.self_mutation.outputs.self_mutation_happened 46 | run: |- 47 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 48 | cat repo.patch 49 | exit 1 50 | - name: Backup artifact permissions 51 | run: cd dist && getfacl -R . > permissions-backup.acl 52 | continue-on-error: true 53 | - name: Upload artifact 54 | uses: actions/upload-artifact@v4.6.2 55 | with: 56 | name: build-artifact 57 | path: dist 58 | overwrite: true 59 | self-mutation: 60 | needs: build 61 | runs-on: ubuntu-latest 62 | permissions: 63 | contents: write 64 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v5 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | ref: ${{ github.event.pull_request.head.ref }} 71 | repository: ${{ github.event.pull_request.head.repo.full_name }} 72 | - name: Download patch 73 | uses: actions/download-artifact@v5 74 | with: 75 | name: repo.patch 76 | path: ${{ runner.temp }} 77 | - name: Apply patch 78 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 79 | - name: Set git identity 80 | run: |- 81 | git config user.name "github-actions[bot]" 82 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 83 | - name: Push changes 84 | env: 85 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 86 | run: |- 87 | git add . 88 | git commit -s -m "chore: self mutation" 89 | git push origin "HEAD:$PULL_REQUEST_REF" 90 | package-js: 91 | needs: build 92 | runs-on: ubuntu-latest 93 | permissions: 94 | contents: read 95 | if: ${{ !needs.build.outputs.self_mutation_happened }} 96 | steps: 97 | - uses: actions/setup-node@v5 98 | with: 99 | node-version: 20.10.0 100 | - name: Download build artifacts 101 | uses: actions/download-artifact@v5 102 | with: 103 | name: build-artifact 104 | path: dist 105 | - name: Restore build artifact permissions 106 | run: cd dist && setfacl --restore=permissions-backup.acl 107 | continue-on-error: true 108 | - name: Checkout 109 | uses: actions/checkout@v5 110 | with: 111 | ref: ${{ github.event.pull_request.head.ref }} 112 | repository: ${{ github.event.pull_request.head.repo.full_name }} 113 | path: .repo 114 | - name: Install Dependencies 115 | run: cd .repo && yarn install --check-files --frozen-lockfile 116 | - name: Extract build artifact 117 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 118 | - name: Move build artifact out of the way 119 | run: mv dist dist.old 120 | - name: Create js artifact 121 | run: cd .repo && npx projen package:js 122 | - name: Collect js artifact 123 | run: mv .repo/dist dist 124 | package-python: 125 | needs: build 126 | runs-on: ubuntu-latest 127 | permissions: 128 | contents: read 129 | if: ${{ !needs.build.outputs.self_mutation_happened }} 130 | steps: 131 | - uses: actions/setup-node@v5 132 | with: 133 | node-version: 20.10.0 134 | - uses: actions/setup-python@v6 135 | with: 136 | python-version: 3.x 137 | - name: Download build artifacts 138 | uses: actions/download-artifact@v5 139 | with: 140 | name: build-artifact 141 | path: dist 142 | - name: Restore build artifact permissions 143 | run: cd dist && setfacl --restore=permissions-backup.acl 144 | continue-on-error: true 145 | - name: Checkout 146 | uses: actions/checkout@v5 147 | with: 148 | ref: ${{ github.event.pull_request.head.ref }} 149 | repository: ${{ github.event.pull_request.head.repo.full_name }} 150 | path: .repo 151 | - name: Install Dependencies 152 | run: cd .repo && yarn install --check-files --frozen-lockfile 153 | - name: Extract build artifact 154 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 155 | - name: Move build artifact out of the way 156 | run: mv dist dist.old 157 | - name: Create python artifact 158 | run: cd .repo && npx projen package:python 159 | - name: Collect python artifact 160 | run: mv .repo/dist dist 161 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v5 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions[bot]" 30 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v5 33 | with: 34 | node-version: 20.10.0 35 | - name: Install dependencies 36 | run: yarn install --check-files --frozen-lockfile 37 | - name: release 38 | run: npx projen release 39 | - name: Check if version has already been tagged 40 | id: check_tag_exists 41 | run: |- 42 | TAG=$(cat dist/releasetag.txt) 43 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 44 | cat $GITHUB_OUTPUT 45 | - name: Check for new commits 46 | id: git_remote 47 | run: |- 48 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 49 | cat $GITHUB_OUTPUT 50 | shell: bash 51 | - name: Backup artifact permissions 52 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 53 | run: cd dist && getfacl -R . > permissions-backup.acl 54 | continue-on-error: true 55 | - name: Upload artifact 56 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 57 | uses: actions/upload-artifact@v4.6.2 58 | with: 59 | name: build-artifact 60 | path: dist 61 | overwrite: true 62 | release_github: 63 | name: Publish to GitHub Releases 64 | needs: 65 | - release 66 | - release_npm 67 | - release_pypi 68 | runs-on: ubuntu-latest 69 | permissions: 70 | contents: write 71 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 72 | steps: 73 | - uses: actions/setup-node@v5 74 | with: 75 | node-version: 20.10.0 76 | - name: Download build artifacts 77 | uses: actions/download-artifact@v5 78 | with: 79 | name: build-artifact 80 | path: dist 81 | - name: Restore build artifact permissions 82 | run: cd dist && setfacl --restore=permissions-backup.acl 83 | continue-on-error: true 84 | - name: Release 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 87 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_SHA 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 88 | release_npm: 89 | name: Publish to npm 90 | needs: release 91 | runs-on: ubuntu-latest 92 | permissions: 93 | id-token: write 94 | contents: read 95 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 96 | steps: 97 | - uses: actions/setup-node@v5 98 | with: 99 | node-version: 20.10.0 100 | - name: Download build artifacts 101 | uses: actions/download-artifact@v5 102 | with: 103 | name: build-artifact 104 | path: dist 105 | - name: Restore build artifact permissions 106 | run: cd dist && setfacl --restore=permissions-backup.acl 107 | continue-on-error: true 108 | - name: Checkout 109 | uses: actions/checkout@v5 110 | with: 111 | path: .repo 112 | - name: Install Dependencies 113 | run: cd .repo && yarn install --check-files --frozen-lockfile 114 | - name: Extract build artifact 115 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 116 | - name: Move build artifact out of the way 117 | run: mv dist dist.old 118 | - name: Create js artifact 119 | run: cd .repo && npx projen package:js 120 | - name: Collect js artifact 121 | run: mv .repo/dist dist 122 | - name: Release 123 | env: 124 | NPM_DIST_TAG: latest 125 | NPM_REGISTRY: registry.npmjs.org 126 | NPM_CONFIG_PROVENANCE: "true" 127 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 128 | run: npx -p publib@latest publib-npm 129 | release_pypi: 130 | name: Publish to PyPI 131 | needs: release 132 | runs-on: ubuntu-latest 133 | permissions: 134 | contents: read 135 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 136 | steps: 137 | - uses: actions/setup-node@v5 138 | with: 139 | node-version: 20.10.0 140 | - uses: actions/setup-python@v6 141 | with: 142 | python-version: 3.x 143 | - name: Download build artifacts 144 | uses: actions/download-artifact@v5 145 | with: 146 | name: build-artifact 147 | path: dist 148 | - name: Restore build artifact permissions 149 | run: cd dist && setfacl --restore=permissions-backup.acl 150 | continue-on-error: true 151 | - name: Checkout 152 | uses: actions/checkout@v5 153 | with: 154 | path: .repo 155 | - name: Install Dependencies 156 | run: cd .repo && yarn install --check-files --frozen-lockfile 157 | - name: Extract build artifact 158 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 159 | - name: Move build artifact out of the way 160 | run: mv dist dist.old 161 | - name: Create python artifact 162 | run: cd .repo && npx projen package:python 163 | - name: Collect python artifact 164 | run: mv .repo/dist dist 165 | - name: Release 166 | env: 167 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 168 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 169 | run: npx -p publib@latest publib-pypi 170 | -------------------------------------------------------------------------------- /.github/workflows/release-cdkv1.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: release-cdkv1 4 | on: 5 | push: 6 | branches: 7 | - cdkv1 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v5 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions[bot]" 30 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v5 33 | with: 34 | node-version: 20.10.0 35 | - name: Install dependencies 36 | run: yarn install --check-files --frozen-lockfile 37 | - name: release:cdkv1 38 | run: npx projen release:cdkv1 39 | - name: Check if version has already been tagged 40 | id: check_tag_exists 41 | run: |- 42 | TAG=$(cat dist/releasetag.txt) 43 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 44 | cat $GITHUB_OUTPUT 45 | - name: Check for new commits 46 | id: git_remote 47 | run: |- 48 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 49 | cat $GITHUB_OUTPUT 50 | shell: bash 51 | - name: Backup artifact permissions 52 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 53 | run: cd dist && getfacl -R . > permissions-backup.acl 54 | continue-on-error: true 55 | - name: Upload artifact 56 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 57 | uses: actions/upload-artifact@v4.6.2 58 | with: 59 | name: build-artifact 60 | path: dist 61 | overwrite: true 62 | release_github: 63 | name: Publish to GitHub Releases 64 | needs: 65 | - release 66 | - release_npm 67 | - release_pypi 68 | runs-on: ubuntu-latest 69 | permissions: 70 | contents: write 71 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 72 | steps: 73 | - uses: actions/setup-node@v5 74 | with: 75 | node-version: 20.10.0 76 | - name: Download build artifacts 77 | uses: actions/download-artifact@v5 78 | with: 79 | name: build-artifact 80 | path: dist 81 | - name: Restore build artifact permissions 82 | run: cd dist && setfacl --restore=permissions-backup.acl 83 | continue-on-error: true 84 | - name: Release 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 87 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_SHA 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 88 | release_npm: 89 | name: Publish to npm 90 | needs: release 91 | runs-on: ubuntu-latest 92 | permissions: 93 | id-token: write 94 | contents: read 95 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 96 | steps: 97 | - uses: actions/setup-node@v5 98 | with: 99 | node-version: 20.10.0 100 | - name: Download build artifacts 101 | uses: actions/download-artifact@v5 102 | with: 103 | name: build-artifact 104 | path: dist 105 | - name: Restore build artifact permissions 106 | run: cd dist && setfacl --restore=permissions-backup.acl 107 | continue-on-error: true 108 | - name: Checkout 109 | uses: actions/checkout@v5 110 | with: 111 | path: .repo 112 | - name: Install Dependencies 113 | run: cd .repo && yarn install --check-files --frozen-lockfile 114 | - name: Extract build artifact 115 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 116 | - name: Move build artifact out of the way 117 | run: mv dist dist.old 118 | - name: Create js artifact 119 | run: cd .repo && npx projen package:js 120 | - name: Collect js artifact 121 | run: mv .repo/dist dist 122 | - name: Release 123 | env: 124 | NPM_DIST_TAG: cdkv1 125 | NPM_REGISTRY: registry.npmjs.org 126 | NPM_CONFIG_PROVENANCE: "true" 127 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 128 | run: npx -p publib@latest publib-npm 129 | release_pypi: 130 | name: Publish to PyPI 131 | needs: release 132 | runs-on: ubuntu-latest 133 | permissions: 134 | contents: read 135 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 136 | steps: 137 | - uses: actions/setup-node@v5 138 | with: 139 | node-version: 20.10.0 140 | - uses: actions/setup-python@v6 141 | with: 142 | python-version: 3.x 143 | - name: Download build artifacts 144 | uses: actions/download-artifact@v5 145 | with: 146 | name: build-artifact 147 | path: dist 148 | - name: Restore build artifact permissions 149 | run: cd dist && setfacl --restore=permissions-backup.acl 150 | continue-on-error: true 151 | - name: Checkout 152 | uses: actions/checkout@v5 153 | with: 154 | path: .repo 155 | - name: Install Dependencies 156 | run: cd .repo && yarn install --check-files --frozen-lockfile 157 | - name: Extract build artifact 158 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 159 | - name: Move build artifact out of the way 160 | run: mv dist dist.old 161 | - name: Create python artifact 162 | run: cd .repo && npx projen package:python 163 | - name: Collect python artifact 164 | run: mv .repo/dist dist 165 | - name: Release 166 | env: 167 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 168 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 169 | run: npx -p publib@latest publib-pypi 170 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as cdk from 'aws-cdk-lib'; 3 | import * as events from 'aws-cdk-lib/aws-events'; 4 | import * as target from 'aws-cdk-lib/aws-events-targets'; 5 | import * as iam from 'aws-cdk-lib/aws-iam'; 6 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 7 | import * as r53 from 'aws-cdk-lib/aws-route53'; 8 | import * as s3 from 'aws-cdk-lib/aws-s3'; 9 | import { Construct } from 'constructs'; 10 | import { BashExecFunction } from './lambda-bash'; 11 | import { LambdaPythonFunction } from './lambda-python'; 12 | 13 | export interface CertbotOptions { 14 | /** 15 | * the domain must host on route53 like example.com. 16 | * 17 | * @example - `*.example.com` or `a.example.com` . 18 | */ 19 | readonly domainName: string; 20 | 21 | /** 22 | * Email address for important account notifications. 23 | */ 24 | readonly email: string; 25 | 26 | /** 27 | * Custom prefix directory on s3 bucket object path. 28 | * @default - `s3://YOUR_BUCKET_NAME/2021-01-01/your.domain.name/` 29 | * 30 | * @example - customPrefixDirectory: '/' -> `s3://YOUR_BUCKET_NAME/your.domain.name/` 31 | * 32 | * @example - customPrefixDirectory: 'abc' -> `s3://YOUR_BUCKET_NAME/abc/your.domain.name/` 33 | */ 34 | readonly customPrefixDirectory?: string; 35 | } 36 | 37 | export interface CertbotDnsRoute53JobProps { 38 | /** 39 | * run the Job with defined schedule 40 | * @default - no schedule 41 | */ 42 | readonly schedule?: events.Schedule; 43 | 44 | /** 45 | * The S3 bucket to store certificate. 46 | */ 47 | readonly destinationBucket: s3.IBucket; 48 | 49 | /** 50 | * The HostZone on route53 to dns-01 challenge. 51 | */ 52 | readonly zone: r53.IHostedZone; 53 | 54 | /** 55 | * certbot cmd options. 56 | */ 57 | readonly certbotOptions: CertbotOptions; 58 | 59 | /** 60 | * Custom lambda Image Architecture. 61 | * 62 | * @default - lambda.Architecture.X86_64 63 | */ 64 | readonly architecture?: lambda.Architecture; 65 | 66 | /** 67 | * Enabled Lambda Function URL 68 | * @default - false 69 | */ 70 | readonly enabledLambdaFunctionUrl?: boolean; 71 | 72 | /** 73 | * Options to add a url to a Lambda function 74 | * @default - authType: lambda.FunctionUrlAuthType.NONE 75 | */ 76 | readonly functionUrlOptions?: lambda.FunctionUrlOptions; 77 | } 78 | 79 | export class CertbotDnsRoute53Job extends Construct { 80 | constructor(scope: Construct, id: string, props: CertbotDnsRoute53JobProps ) { 81 | super(scope, id); 82 | const certOptions = { 83 | BUCKET_NAME: props.destinationBucket.bucketName, 84 | EMAIL: props.certbotOptions.email, 85 | DOMAIN_NAME: props.certbotOptions.domainName, 86 | CUSTOM_PREFIX_DIRECTORY: props.certbotOptions.customPrefixDirectory!, 87 | }; 88 | 89 | const lambdaFun = new BashExecFunction(this, 'certbotDnsRoute53JobLambda', { 90 | script: path.join(__dirname, '../docker.d/entrypoint.sh'), 91 | timeout: cdk.Duration.minutes(5), 92 | architecture: props.architecture ?? lambda.Architecture.X86_64, 93 | environment: { 94 | ...certOptions, 95 | }, 96 | }); 97 | 98 | props.destinationBucket.grantReadWrite(lambdaFun.handler.role!); 99 | const route53PolicyJsonList = [{ 100 | Effect: 'Allow', 101 | Action: [ 102 | 'route53:ListHostedZones', 103 | 'route53:GetChange', 104 | ], 105 | Resource: [ 106 | '*', 107 | ], 108 | }, 109 | { 110 | Effect: 'Allow', 111 | Action: [ 112 | 'route53:ChangeResourceRecordSets', 113 | ], 114 | Resource: [ 115 | `arn:${new cdk.ScopedAws(this).partition}:route53:::hostedzone/${props.zone.hostedZoneId}`, 116 | ], 117 | }]; 118 | route53PolicyJsonList.forEach( 119 | e => { 120 | lambdaFun.handler.role!.addToPrincipalPolicy(iam.PolicyStatement.fromJson(e)); 121 | }, 122 | ); 123 | 124 | 125 | if (props.schedule) { 126 | new events.Rule(this, 'ScheduleRule', { 127 | schedule: props.schedule, 128 | targets: [ 129 | new target.LambdaFunction(lambdaFun.handler), 130 | ], 131 | }); 132 | }; 133 | 134 | if (props.enabledLambdaFunctionUrl) { 135 | const url = new lambda.FunctionUrl(this, 'LambdaFunctionUrl', { 136 | function: lambdaFun.handler, 137 | authType: lambda.FunctionUrlAuthType.NONE, 138 | ...props.functionUrlOptions, 139 | }); 140 | 141 | new cdk.CfnOutput(this, 'lambdaFunctionUrl', { 142 | value: url.url, 143 | }); 144 | }; 145 | } 146 | } 147 | 148 | export interface CertbotOptions { 149 | /** 150 | * the domain must host on route53 like example.com. 151 | * 152 | * @example - `*.example.com` or `a.example.com` . 153 | */ 154 | readonly domainName: string; 155 | 156 | /** 157 | * Email address for important account notifications. 158 | */ 159 | readonly email: string; 160 | 161 | /** 162 | * Custom prefix directory on s3 bucket object path. 163 | * @default - `s3://YOUR_BUCKET_NAME/2021-01-01/your.domain.name/` 164 | * 165 | * @example - customPrefixDirectory: '/' -> `s3://YOUR_BUCKET_NAME/your.domain.name/` 166 | * 167 | * @example - customPrefixDirectory: 'abc' -> `s3://YOUR_BUCKET_NAME/abc/your.domain.name/` 168 | */ 169 | readonly customPrefixDirectory?: string; 170 | } 171 | 172 | 173 | export class CertbotDnsRoute53JobPython extends Construct { 174 | constructor(scope: Construct, id: string, props: CertbotDnsRoute53JobProps ) { 175 | super(scope, id); 176 | const certOptions = { 177 | BUCKET_NAME: props.destinationBucket.bucketName, 178 | EMAIL: props.certbotOptions.email, 179 | DOMAIN_NAME: props.certbotOptions.domainName, 180 | CUSTOM_PREFIX_DIRECTORY: props.certbotOptions.customPrefixDirectory!, 181 | }; 182 | 183 | const lambdaFun = new LambdaPythonFunction(this, 'certbotDnsRoute53JobPythonLambda', { 184 | timeout: cdk.Duration.minutes(5), 185 | architecture: props.architecture ?? lambda.Architecture.X86_64, 186 | environment: { 187 | ...certOptions, 188 | }, 189 | }); 190 | 191 | props.destinationBucket.grantReadWrite(lambdaFun.handler.role!); 192 | const route53PolicyJsonList = [{ 193 | Effect: 'Allow', 194 | Action: [ 195 | 'route53:ListHostedZones', 196 | 'route53:GetChange', 197 | ], 198 | Resource: [ 199 | '*', 200 | ], 201 | }, 202 | { 203 | Effect: 'Allow', 204 | Action: [ 205 | 'route53:ChangeResourceRecordSets', 206 | ], 207 | Resource: [ 208 | `arn:${new cdk.ScopedAws(this).partition}:route53:::hostedzone/${props.zone.hostedZoneId}`, 209 | ], 210 | }]; 211 | route53PolicyJsonList.forEach( 212 | e => { 213 | lambdaFun.handler.role!.addToPrincipalPolicy(iam.PolicyStatement.fromJson(e)); 214 | }, 215 | ); 216 | 217 | 218 | if (props.schedule) { 219 | new events.Rule(this, 'ScheduleRule', { 220 | schedule: props.schedule, 221 | targets: [ 222 | new target.LambdaFunction(lambdaFun.handler), 223 | ], 224 | }); 225 | }; 226 | 227 | if (props.enabledLambdaFunctionUrl) { 228 | const url = new lambda.FunctionUrl(this, 'LambdaFunctionUrl', { 229 | function: lambdaFun.handler, 230 | authType: lambda.FunctionUrlAuthType.NONE, 231 | ...props.functionUrlOptions, 232 | }); 233 | 234 | new cdk.CfnOutput(this, 'lambdaFunctionUrl', { 235 | value: url.url, 236 | }); 237 | }; 238 | } 239 | } -------------------------------------------------------------------------------- /.projen/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "name": "build", 5 | "description": "Full release build", 6 | "steps": [ 7 | { 8 | "spawn": "default" 9 | }, 10 | { 11 | "spawn": "pre-compile" 12 | }, 13 | { 14 | "spawn": "compile" 15 | }, 16 | { 17 | "spawn": "post-compile" 18 | }, 19 | { 20 | "spawn": "test" 21 | }, 22 | { 23 | "spawn": "package" 24 | } 25 | ] 26 | }, 27 | "bump": { 28 | "name": "bump", 29 | "description": "Bumps version based on latest git tag and generates a changelog entry", 30 | "env": { 31 | "OUTFILE": "package.json", 32 | "CHANGELOG": "dist/changelog.md", 33 | "BUMPFILE": "dist/version.txt", 34 | "RELEASETAG": "dist/releasetag.txt", 35 | "RELEASE_TAG_PREFIX": "", 36 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 37 | }, 38 | "steps": [ 39 | { 40 | "builtin": "release/bump-version" 41 | } 42 | ], 43 | "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" 44 | }, 45 | "clobber": { 46 | "name": "clobber", 47 | "description": "hard resets to HEAD of origin and cleans the local repo", 48 | "env": { 49 | "BRANCH": "$(git branch --show-current)" 50 | }, 51 | "steps": [ 52 | { 53 | "exec": "git checkout -b scratch", 54 | "name": "save current HEAD in \"scratch\" branch" 55 | }, 56 | { 57 | "exec": "git checkout $BRANCH" 58 | }, 59 | { 60 | "exec": "git fetch origin", 61 | "name": "fetch latest changes from origin" 62 | }, 63 | { 64 | "exec": "git reset --hard origin/$BRANCH", 65 | "name": "hard reset to origin commit" 66 | }, 67 | { 68 | "exec": "git clean -fdx", 69 | "name": "clean all untracked files" 70 | }, 71 | { 72 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 73 | } 74 | ], 75 | "condition": "git diff --exit-code > /dev/null" 76 | }, 77 | "compat": { 78 | "name": "compat", 79 | "description": "Perform API compatibility check against latest version", 80 | "steps": [ 81 | { 82 | "exec": "jsii-diff npm:$(node -p \"require('./package.json').name\") -k --ignore-file .compatignore || (echo \"\nUNEXPECTED BREAKING CHANGES: add keys such as 'removed:constructs.Node.of' to .compatignore to skip.\n\" && exit 1)" 83 | } 84 | ] 85 | }, 86 | "compile": { 87 | "name": "compile", 88 | "description": "Only compile", 89 | "steps": [ 90 | { 91 | "exec": "jsii --silence-warnings=reserved-word" 92 | } 93 | ] 94 | }, 95 | "default": { 96 | "name": "default", 97 | "description": "Synthesize project files", 98 | "steps": [ 99 | { 100 | "exec": "node .projenrc.js" 101 | } 102 | ] 103 | }, 104 | "docgen": { 105 | "name": "docgen", 106 | "description": "Generate API.md from .jsii manifest", 107 | "steps": [ 108 | { 109 | "exec": "jsii-docgen -o API.md" 110 | } 111 | ] 112 | }, 113 | "eject": { 114 | "name": "eject", 115 | "description": "Remove projen from the project", 116 | "env": { 117 | "PROJEN_EJECTING": "true" 118 | }, 119 | "steps": [ 120 | { 121 | "spawn": "default" 122 | } 123 | ] 124 | }, 125 | "eslint": { 126 | "name": "eslint", 127 | "description": "Runs eslint against the codebase", 128 | "env": { 129 | "ESLINT_USE_FLAT_CONFIG": "false", 130 | "NODE_NO_WARNINGS": "1" 131 | }, 132 | "steps": [ 133 | { 134 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools .projenrc.js", 135 | "receiveArgs": true 136 | } 137 | ] 138 | }, 139 | "install": { 140 | "name": "install", 141 | "description": "Install project dependencies and update lockfile (non-frozen)", 142 | "steps": [ 143 | { 144 | "exec": "yarn install --check-files" 145 | } 146 | ] 147 | }, 148 | "install:ci": { 149 | "name": "install:ci", 150 | "description": "Install project dependencies using frozen lockfile", 151 | "steps": [ 152 | { 153 | "exec": "yarn install --check-files --frozen-lockfile" 154 | } 155 | ] 156 | }, 157 | "package": { 158 | "name": "package", 159 | "description": "Creates the distribution package", 160 | "steps": [ 161 | { 162 | "spawn": "package:js", 163 | "condition": "node -e \"if (!process.env.CI) process.exit(1)\"" 164 | }, 165 | { 166 | "spawn": "package-all", 167 | "condition": "node -e \"if (process.env.CI) process.exit(1)\"" 168 | } 169 | ] 170 | }, 171 | "package-all": { 172 | "name": "package-all", 173 | "description": "Packages artifacts for all target languages", 174 | "steps": [ 175 | { 176 | "spawn": "package:js" 177 | }, 178 | { 179 | "spawn": "package:python" 180 | } 181 | ] 182 | }, 183 | "package:js": { 184 | "name": "package:js", 185 | "description": "Create js language bindings", 186 | "steps": [ 187 | { 188 | "exec": "jsii-pacmak -v --target js" 189 | } 190 | ] 191 | }, 192 | "package:python": { 193 | "name": "package:python", 194 | "description": "Create python language bindings", 195 | "steps": [ 196 | { 197 | "exec": "jsii-pacmak -v --target python" 198 | } 199 | ] 200 | }, 201 | "post-compile": { 202 | "name": "post-compile", 203 | "description": "Runs after successful compilation", 204 | "steps": [ 205 | { 206 | "spawn": "docgen" 207 | } 208 | ] 209 | }, 210 | "post-upgrade": { 211 | "name": "post-upgrade", 212 | "description": "Runs after upgrading dependencies" 213 | }, 214 | "pre-compile": { 215 | "name": "pre-compile", 216 | "description": "Prepare the project for compilation" 217 | }, 218 | "release": { 219 | "name": "release", 220 | "description": "Prepare a release from \"main\" branch", 221 | "env": { 222 | "RELEASE": "true", 223 | "MAJOR": "2" 224 | }, 225 | "steps": [ 226 | { 227 | "exec": "rm -fr dist" 228 | }, 229 | { 230 | "spawn": "bump" 231 | }, 232 | { 233 | "spawn": "build" 234 | }, 235 | { 236 | "spawn": "unbump" 237 | }, 238 | { 239 | "exec": "git diff --ignore-space-at-eol --exit-code" 240 | } 241 | ] 242 | }, 243 | "release:cdkv1": { 244 | "name": "release:cdkv1", 245 | "description": "Prepare a release from \"cdkv1\" branch", 246 | "env": { 247 | "RELEASE": "true", 248 | "MAJOR": "1" 249 | }, 250 | "steps": [ 251 | { 252 | "exec": "rm -fr dist" 253 | }, 254 | { 255 | "spawn": "bump" 256 | }, 257 | { 258 | "spawn": "build" 259 | }, 260 | { 261 | "spawn": "unbump" 262 | }, 263 | { 264 | "exec": "git diff --ignore-space-at-eol --exit-code" 265 | } 266 | ] 267 | }, 268 | "test": { 269 | "name": "test", 270 | "description": "Run tests", 271 | "steps": [ 272 | { 273 | "exec": "jest --passWithNoTests --updateSnapshot", 274 | "receiveArgs": true 275 | }, 276 | { 277 | "spawn": "eslint" 278 | } 279 | ] 280 | }, 281 | "test:watch": { 282 | "name": "test:watch", 283 | "description": "Run jest in watch mode", 284 | "steps": [ 285 | { 286 | "exec": "jest --watch" 287 | } 288 | ] 289 | }, 290 | "unbump": { 291 | "name": "unbump", 292 | "description": "Restores version to 0.0.0", 293 | "env": { 294 | "OUTFILE": "package.json", 295 | "CHANGELOG": "dist/changelog.md", 296 | "BUMPFILE": "dist/version.txt", 297 | "RELEASETAG": "dist/releasetag.txt", 298 | "RELEASE_TAG_PREFIX": "", 299 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 300 | }, 301 | "steps": [ 302 | { 303 | "builtin": "release/reset-version" 304 | } 305 | ] 306 | }, 307 | "upgrade": { 308 | "name": "upgrade", 309 | "description": "upgrade dependencies", 310 | "env": { 311 | "CI": "0" 312 | }, 313 | "steps": [ 314 | { 315 | "exec": "npx npm-check-updates@18 --upgrade --target=minor --peer --no-deprecated --dep=dev,peer,prod,optional --filter=@types/jest,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,jsii-pacmak,projen,ts-jest" 316 | }, 317 | { 318 | "exec": "yarn install --check-files" 319 | }, 320 | { 321 | "exec": "yarn upgrade @stylistic/eslint-plugin @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser commit-and-tag-version eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii projen ts-jest typescript aws-cdk-lib constructs @aws-cdk/aws-lambda-python-alpha" 322 | }, 323 | { 324 | "exec": "npx projen" 325 | }, 326 | { 327 | "spawn": "post-upgrade" 328 | } 329 | ] 330 | }, 331 | "watch": { 332 | "name": "watch", 333 | "description": "Watch & compile in the background", 334 | "steps": [ 335 | { 336 | "exec": "jsii -w --silence-warnings=reserved-word" 337 | } 338 | ] 339 | } 340 | }, 341 | "env": { 342 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 343 | }, 344 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 345 | } 346 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/certbot-lambda.test.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import * as assertions from 'aws-cdk-lib/assertions'; 3 | import * as events from 'aws-cdk-lib/aws-events'; 4 | import * as lambda from 'aws-cdk-lib/aws-lambda'; 5 | import * as r53 from 'aws-cdk-lib/aws-route53'; 6 | import * as s3 from 'aws-cdk-lib/aws-s3'; 7 | import { CertbotDnsRoute53Job, CertbotDnsRoute53JobPython } from '../src/index'; 8 | const devEnv = { 9 | account: '1234567890xx', 10 | region: 'ap-northeast-1', 11 | }; 12 | 13 | const mock = { 14 | zoneId: 'XXXXXXXXXXXXX', 15 | zoneName: 'example.com', 16 | }; 17 | 18 | test('only create certbot lambda.', () => { 19 | const mockApp = new cdk.App(); 20 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 21 | const bucket = new s3.Bucket(stack, 'testingBucket'); 22 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 23 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 24 | }); 25 | new CertbotDnsRoute53Job(stack, 'Testtask', { 26 | certbotOptions: { 27 | domainName: 'example.com', 28 | email: 'user@example.com', 29 | }, 30 | zone, 31 | destinationBucket: bucket, 32 | }); 33 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 34 | Environment: { 35 | Variables: { 36 | BUCKET_NAME: { 37 | Ref: 'testingBucket9FA8E574', 38 | }, 39 | EMAIL: 'user@example.com', 40 | DOMAIN_NAME: 'example.com', 41 | }, 42 | }, 43 | }); 44 | assertions.Template.fromStack(stack).findResources('AWS::IAM::Role'); 45 | }); 46 | 47 | test('create certbot lambda schedule rule.', () => { 48 | const mockApp = new cdk.App(); 49 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 50 | const bucket = new s3.Bucket(stack, 'testingBucket'); 51 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 52 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 53 | }); 54 | new CertbotDnsRoute53Job(stack, 'Testtask', { 55 | certbotOptions: { 56 | domainName: 'example.com', 57 | email: 'user@example.com', 58 | }, 59 | zone, 60 | destinationBucket: bucket, 61 | schedule: events.Schedule.cron({ month: '2' }), 62 | }); 63 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 64 | Environment: { 65 | Variables: { 66 | BUCKET_NAME: { 67 | Ref: 'testingBucket9FA8E574', 68 | }, 69 | EMAIL: 'user@example.com', 70 | DOMAIN_NAME: 'example.com', 71 | }, 72 | }, 73 | }); 74 | assertions.Template.fromStack(stack).findResources('AWS::IAM::Role'); 75 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { 76 | ScheduleExpression: 'cron(* * * 2 ? *)', 77 | State: 'ENABLED', 78 | Targets: [ 79 | { 80 | Arn: { 81 | 'Fn::GetAtt': [ 82 | 'TesttaskcertbotDnsRoute53JobLambdaBashExecFunction83E58B7B', 83 | 'Arn', 84 | ], 85 | }, 86 | Id: 'Target0', 87 | }, 88 | ], 89 | }); 90 | }); 91 | 92 | test('test right customPrefixDirectory.', () => { 93 | const mockApp = new cdk.App(); 94 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 95 | const bucket = new s3.Bucket(stack, 'testingBucket'); 96 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 97 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 98 | }); 99 | new CertbotDnsRoute53Job(stack, 'Testtask', { 100 | certbotOptions: { 101 | domainName: 'example.com', 102 | email: 'user@example.com', 103 | customPrefixDirectory: 'abc', 104 | }, 105 | zone, 106 | destinationBucket: bucket, 107 | schedule: events.Schedule.cron({ month: '2' }), 108 | }); 109 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 110 | Environment: { 111 | Variables: { 112 | BUCKET_NAME: { 113 | Ref: 'testingBucket9FA8E574', 114 | }, 115 | EMAIL: 'user@example.com', 116 | DOMAIN_NAME: 'example.com', 117 | CUSTOM_PREFIX_DIRECTORY: 'abc', 118 | }, 119 | }, 120 | }); 121 | }); 122 | 123 | test('test right / path customPrefixDirectory.', () => { 124 | const mockApp = new cdk.App(); 125 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 126 | const bucket = new s3.Bucket(stack, 'testingBucket'); 127 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 128 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 129 | }); 130 | new CertbotDnsRoute53Job(stack, 'Testtask', { 131 | certbotOptions: { 132 | domainName: 'example.com', 133 | email: 'user@example.com', 134 | customPrefixDirectory: '/', 135 | }, 136 | zone, 137 | destinationBucket: bucket, 138 | schedule: events.Schedule.cron({ month: '2' }), 139 | }); 140 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 141 | Environment: { 142 | Variables: { 143 | BUCKET_NAME: { 144 | Ref: 'testingBucket9FA8E574', 145 | }, 146 | EMAIL: 'user@example.com', 147 | DOMAIN_NAME: 'example.com', 148 | CUSTOM_PREFIX_DIRECTORY: '/', 149 | }, 150 | }, 151 | }); 152 | }); 153 | 154 | test('test define right Lambda Image Architecture x86_64', () => { 155 | const mockApp = new cdk.App(); 156 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 157 | const bucket = new s3.Bucket(stack, 'testingBucket'); 158 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 159 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 160 | }); 161 | new CertbotDnsRoute53Job(stack, 'Testtask', { 162 | certbotOptions: { 163 | domainName: 'example.com', 164 | email: 'user@example.com', 165 | customPrefixDirectory: '/', 166 | }, 167 | zone, 168 | destinationBucket: bucket, 169 | schedule: events.Schedule.cron({ month: '2' }), 170 | architecture: lambda.Architecture.X86_64, 171 | }); 172 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 173 | Architectures: [ 174 | 'x86_64', 175 | ], 176 | Environment: { 177 | Variables: { 178 | BUCKET_NAME: { 179 | Ref: 'testingBucket9FA8E574', 180 | }, 181 | EMAIL: 'user@example.com', 182 | DOMAIN_NAME: 'example.com', 183 | CUSTOM_PREFIX_DIRECTORY: '/', 184 | }, 185 | }, 186 | }); 187 | }); 188 | 189 | test('test define right Lambda Image Architecture arm64', () => { 190 | const mockApp = new cdk.App(); 191 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 192 | const bucket = new s3.Bucket(stack, 'testingBucket'); 193 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 194 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 195 | }); 196 | new CertbotDnsRoute53Job(stack, 'Testtask', { 197 | certbotOptions: { 198 | domainName: 'example.com', 199 | email: 'user@example.com', 200 | customPrefixDirectory: '/', 201 | }, 202 | zone, 203 | destinationBucket: bucket, 204 | schedule: events.Schedule.cron({ month: '2' }), 205 | architecture: lambda.Architecture.ARM_64, 206 | }); 207 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 208 | Architectures: [ 209 | 'arm64', 210 | ], 211 | Environment: { 212 | Variables: { 213 | BUCKET_NAME: { 214 | Ref: 'testingBucket9FA8E574', 215 | }, 216 | EMAIL: 'user@example.com', 217 | DOMAIN_NAME: 'example.com', 218 | CUSTOM_PREFIX_DIRECTORY: '/', 219 | }, 220 | }, 221 | }); 222 | }); 223 | 224 | test('test enabled LambdaFunctionUrl', () => { 225 | const mockApp = new cdk.App(); 226 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 227 | const bucket = new s3.Bucket(stack, 'testingBucket'); 228 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 229 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 230 | }); 231 | new CertbotDnsRoute53Job(stack, 'Testtask', { 232 | certbotOptions: { 233 | domainName: 'example.com', 234 | email: 'user@example.com', 235 | customPrefixDirectory: '/', 236 | }, 237 | zone, 238 | destinationBucket: bucket, 239 | schedule: events.Schedule.cron({ month: '2' }), 240 | architecture: lambda.Architecture.ARM_64, 241 | enabledLambdaFunctionUrl: true, 242 | functionUrlOptions: { 243 | authType: lambda.FunctionUrlAuthType.AWS_IAM, 244 | }, 245 | }); 246 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 247 | Architectures: [ 248 | 'arm64', 249 | ], 250 | Environment: { 251 | Variables: { 252 | BUCKET_NAME: { 253 | Ref: 'testingBucket9FA8E574', 254 | }, 255 | EMAIL: 'user@example.com', 256 | DOMAIN_NAME: 'example.com', 257 | CUSTOM_PREFIX_DIRECTORY: '/', 258 | }, 259 | }, 260 | }); 261 | 262 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 263 | Architectures: [ 264 | 'arm64', 265 | ], 266 | Environment: { 267 | Variables: { 268 | BUCKET_NAME: { 269 | Ref: 'testingBucket9FA8E574', 270 | }, 271 | EMAIL: 'user@example.com', 272 | DOMAIN_NAME: 'example.com', 273 | CUSTOM_PREFIX_DIRECTORY: '/', 274 | }, 275 | }, 276 | }); 277 | 278 | 279 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Url', { 280 | AuthType: 'AWS_IAM', 281 | TargetFunctionArn: { 282 | 'Fn::GetAtt': [ 283 | 'TesttaskcertbotDnsRoute53JobLambdaBashExecFunction83E58B7B', 284 | 'Arn', 285 | ], 286 | }, 287 | }); 288 | 289 | }); 290 | test('test define right Lambda Image Architecture arm64', () => { 291 | const mockApp = new cdk.App(); 292 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 293 | const bucket = new s3.Bucket(stack, 'testingBucket'); 294 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 295 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 296 | }); 297 | new CertbotDnsRoute53Job(stack, 'Testtask', { 298 | certbotOptions: { 299 | domainName: 'example.com', 300 | email: 'user@example.com', 301 | customPrefixDirectory: '/', 302 | }, 303 | zone, 304 | destinationBucket: bucket, 305 | schedule: events.Schedule.cron({ month: '2' }), 306 | architecture: lambda.Architecture.ARM_64, 307 | }); 308 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 309 | Architectures: [ 310 | 'arm64', 311 | ], 312 | Environment: { 313 | Variables: { 314 | BUCKET_NAME: { 315 | Ref: 'testingBucket9FA8E574', 316 | }, 317 | EMAIL: 'user@example.com', 318 | DOMAIN_NAME: 'example.com', 319 | CUSTOM_PREFIX_DIRECTORY: '/', 320 | }, 321 | }, 322 | }); 323 | }); 324 | 325 | test('test PythonLambdaFunction', () => { 326 | const mockApp = new cdk.App(); 327 | const stack = new cdk.Stack(mockApp, 'teststack', { env: devEnv }); 328 | const bucket = new s3.Bucket(stack, 'testingBucket'); 329 | const zone = r53.HostedZone.fromHostedZoneAttributes(stack, 'zone', { 330 | zoneName: mock.zoneName, hostedZoneId: mock.zoneId, 331 | }); 332 | new CertbotDnsRoute53JobPython(stack, 'Testtask', { 333 | certbotOptions: { 334 | domainName: 'example.com', 335 | email: 'user@example.com', 336 | customPrefixDirectory: '/', 337 | }, 338 | zone, 339 | destinationBucket: bucket, 340 | schedule: events.Schedule.cron({ month: '2' }), 341 | enabledLambdaFunctionUrl: true, 342 | functionUrlOptions: { 343 | authType: lambda.FunctionUrlAuthType.AWS_IAM, 344 | }, 345 | }); 346 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 347 | Environment: { 348 | Variables: { 349 | BUCKET_NAME: { 350 | Ref: 'testingBucket9FA8E574', 351 | }, 352 | EMAIL: 'user@example.com', 353 | DOMAIN_NAME: 'example.com', 354 | CUSTOM_PREFIX_DIRECTORY: '/', 355 | }, 356 | }, 357 | }); 358 | 359 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { 360 | Environment: { 361 | Variables: { 362 | BUCKET_NAME: { 363 | Ref: 'testingBucket9FA8E574', 364 | }, 365 | EMAIL: 'user@example.com', 366 | DOMAIN_NAME: 'example.com', 367 | CUSTOM_PREFIX_DIRECTORY: '/', 368 | }, 369 | }, 370 | }); 371 | 372 | 373 | assertions.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Url', { 374 | AuthType: 'AWS_IAM', 375 | TargetFunctionArn: { 376 | 'Fn::GetAtt': [ 377 | 'TesttaskcertbotDnsRoute53JobPythonLambdaPythonFunctionD27DC536', 378 | 'Arn', 379 | ], 380 | }, 381 | }); 382 | 383 | }); -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### BashExecFunction 6 | 7 | #### Initializers 8 | 9 | ```typescript 10 | import { BashExecFunction } from 'cdk-certbot-dns-route53' 11 | 12 | new BashExecFunction(scope: Construct, id: string, props: BashExecFunctionProps) 13 | ``` 14 | 15 | | **Name** | **Type** | **Description** | 16 | | --- | --- | --- | 17 | | scope | constructs.Construct | *No description.* | 18 | | id | string | *No description.* | 19 | | props | BashExecFunctionProps | *No description.* | 20 | 21 | --- 22 | 23 | ##### `scope`Required 24 | 25 | - *Type:* constructs.Construct 26 | 27 | --- 28 | 29 | ##### `id`Required 30 | 31 | - *Type:* string 32 | 33 | --- 34 | 35 | ##### `props`Required 36 | 37 | - *Type:* BashExecFunctionProps 38 | 39 | --- 40 | 41 | #### Methods 42 | 43 | | **Name** | **Description** | 44 | | --- | --- | 45 | | toString | Returns a string representation of this construct. | 46 | 47 | --- 48 | 49 | ##### `toString` 50 | 51 | ```typescript 52 | public toString(): string 53 | ``` 54 | 55 | Returns a string representation of this construct. 56 | 57 | #### Static Functions 58 | 59 | | **Name** | **Description** | 60 | | --- | --- | 61 | | isConstruct | Checks if `x` is a construct. | 62 | 63 | --- 64 | 65 | ##### `isConstruct` 66 | 67 | ```typescript 68 | import { BashExecFunction } from 'cdk-certbot-dns-route53' 69 | 70 | BashExecFunction.isConstruct(x: any) 71 | ``` 72 | 73 | Checks if `x` is a construct. 74 | 75 | Use this method instead of `instanceof` to properly detect `Construct` 76 | instances, even when the construct library is symlinked. 77 | 78 | Explanation: in JavaScript, multiple copies of the `constructs` library on 79 | disk are seen as independent, completely different libraries. As a 80 | consequence, the class `Construct` in each copy of the `constructs` library 81 | is seen as a different class, and an instance of one class will not test as 82 | `instanceof` the other class. `npm install` will not create installations 83 | like this, but users may manually symlink construct libraries together or 84 | use a monorepo tool: in those cases, multiple copies of the `constructs` 85 | library can be accidentally installed, and `instanceof` will behave 86 | unpredictably. It is safest to avoid using `instanceof`, and using 87 | this type-testing method instead. 88 | 89 | ###### `x`Required 90 | 91 | - *Type:* any 92 | 93 | Any object. 94 | 95 | --- 96 | 97 | #### Properties 98 | 99 | | **Name** | **Type** | **Description** | 100 | | --- | --- | --- | 101 | | node | constructs.Node | The tree node. | 102 | | handler | aws-cdk-lib.aws_lambda.DockerImageFunction | *No description.* | 103 | 104 | --- 105 | 106 | ##### `node`Required 107 | 108 | ```typescript 109 | public readonly node: Node; 110 | ``` 111 | 112 | - *Type:* constructs.Node 113 | 114 | The tree node. 115 | 116 | --- 117 | 118 | ##### `handler`Required 119 | 120 | ```typescript 121 | public readonly handler: DockerImageFunction; 122 | ``` 123 | 124 | - *Type:* aws-cdk-lib.aws_lambda.DockerImageFunction 125 | 126 | --- 127 | 128 | 129 | ### CertbotDnsRoute53Job 130 | 131 | #### Initializers 132 | 133 | ```typescript 134 | import { CertbotDnsRoute53Job } from 'cdk-certbot-dns-route53' 135 | 136 | new CertbotDnsRoute53Job(scope: Construct, id: string, props: CertbotDnsRoute53JobProps) 137 | ``` 138 | 139 | | **Name** | **Type** | **Description** | 140 | | --- | --- | --- | 141 | | scope | constructs.Construct | *No description.* | 142 | | id | string | *No description.* | 143 | | props | CertbotDnsRoute53JobProps | *No description.* | 144 | 145 | --- 146 | 147 | ##### `scope`Required 148 | 149 | - *Type:* constructs.Construct 150 | 151 | --- 152 | 153 | ##### `id`Required 154 | 155 | - *Type:* string 156 | 157 | --- 158 | 159 | ##### `props`Required 160 | 161 | - *Type:* CertbotDnsRoute53JobProps 162 | 163 | --- 164 | 165 | #### Methods 166 | 167 | | **Name** | **Description** | 168 | | --- | --- | 169 | | toString | Returns a string representation of this construct. | 170 | 171 | --- 172 | 173 | ##### `toString` 174 | 175 | ```typescript 176 | public toString(): string 177 | ``` 178 | 179 | Returns a string representation of this construct. 180 | 181 | #### Static Functions 182 | 183 | | **Name** | **Description** | 184 | | --- | --- | 185 | | isConstruct | Checks if `x` is a construct. | 186 | 187 | --- 188 | 189 | ##### `isConstruct` 190 | 191 | ```typescript 192 | import { CertbotDnsRoute53Job } from 'cdk-certbot-dns-route53' 193 | 194 | CertbotDnsRoute53Job.isConstruct(x: any) 195 | ``` 196 | 197 | Checks if `x` is a construct. 198 | 199 | Use this method instead of `instanceof` to properly detect `Construct` 200 | instances, even when the construct library is symlinked. 201 | 202 | Explanation: in JavaScript, multiple copies of the `constructs` library on 203 | disk are seen as independent, completely different libraries. As a 204 | consequence, the class `Construct` in each copy of the `constructs` library 205 | is seen as a different class, and an instance of one class will not test as 206 | `instanceof` the other class. `npm install` will not create installations 207 | like this, but users may manually symlink construct libraries together or 208 | use a monorepo tool: in those cases, multiple copies of the `constructs` 209 | library can be accidentally installed, and `instanceof` will behave 210 | unpredictably. It is safest to avoid using `instanceof`, and using 211 | this type-testing method instead. 212 | 213 | ###### `x`Required 214 | 215 | - *Type:* any 216 | 217 | Any object. 218 | 219 | --- 220 | 221 | #### Properties 222 | 223 | | **Name** | **Type** | **Description** | 224 | | --- | --- | --- | 225 | | node | constructs.Node | The tree node. | 226 | 227 | --- 228 | 229 | ##### `node`Required 230 | 231 | ```typescript 232 | public readonly node: Node; 233 | ``` 234 | 235 | - *Type:* constructs.Node 236 | 237 | The tree node. 238 | 239 | --- 240 | 241 | 242 | ### CertbotDnsRoute53JobPython 243 | 244 | #### Initializers 245 | 246 | ```typescript 247 | import { CertbotDnsRoute53JobPython } from 'cdk-certbot-dns-route53' 248 | 249 | new CertbotDnsRoute53JobPython(scope: Construct, id: string, props: CertbotDnsRoute53JobProps) 250 | ``` 251 | 252 | | **Name** | **Type** | **Description** | 253 | | --- | --- | --- | 254 | | scope | constructs.Construct | *No description.* | 255 | | id | string | *No description.* | 256 | | props | CertbotDnsRoute53JobProps | *No description.* | 257 | 258 | --- 259 | 260 | ##### `scope`Required 261 | 262 | - *Type:* constructs.Construct 263 | 264 | --- 265 | 266 | ##### `id`Required 267 | 268 | - *Type:* string 269 | 270 | --- 271 | 272 | ##### `props`Required 273 | 274 | - *Type:* CertbotDnsRoute53JobProps 275 | 276 | --- 277 | 278 | #### Methods 279 | 280 | | **Name** | **Description** | 281 | | --- | --- | 282 | | toString | Returns a string representation of this construct. | 283 | 284 | --- 285 | 286 | ##### `toString` 287 | 288 | ```typescript 289 | public toString(): string 290 | ``` 291 | 292 | Returns a string representation of this construct. 293 | 294 | #### Static Functions 295 | 296 | | **Name** | **Description** | 297 | | --- | --- | 298 | | isConstruct | Checks if `x` is a construct. | 299 | 300 | --- 301 | 302 | ##### `isConstruct` 303 | 304 | ```typescript 305 | import { CertbotDnsRoute53JobPython } from 'cdk-certbot-dns-route53' 306 | 307 | CertbotDnsRoute53JobPython.isConstruct(x: any) 308 | ``` 309 | 310 | Checks if `x` is a construct. 311 | 312 | Use this method instead of `instanceof` to properly detect `Construct` 313 | instances, even when the construct library is symlinked. 314 | 315 | Explanation: in JavaScript, multiple copies of the `constructs` library on 316 | disk are seen as independent, completely different libraries. As a 317 | consequence, the class `Construct` in each copy of the `constructs` library 318 | is seen as a different class, and an instance of one class will not test as 319 | `instanceof` the other class. `npm install` will not create installations 320 | like this, but users may manually symlink construct libraries together or 321 | use a monorepo tool: in those cases, multiple copies of the `constructs` 322 | library can be accidentally installed, and `instanceof` will behave 323 | unpredictably. It is safest to avoid using `instanceof`, and using 324 | this type-testing method instead. 325 | 326 | ###### `x`Required 327 | 328 | - *Type:* any 329 | 330 | Any object. 331 | 332 | --- 333 | 334 | #### Properties 335 | 336 | | **Name** | **Type** | **Description** | 337 | | --- | --- | --- | 338 | | node | constructs.Node | The tree node. | 339 | 340 | --- 341 | 342 | ##### `node`Required 343 | 344 | ```typescript 345 | public readonly node: Node; 346 | ``` 347 | 348 | - *Type:* constructs.Node 349 | 350 | The tree node. 351 | 352 | --- 353 | 354 | 355 | ### LambdaPythonFunction 356 | 357 | #### Initializers 358 | 359 | ```typescript 360 | import { LambdaPythonFunction } from 'cdk-certbot-dns-route53' 361 | 362 | new LambdaPythonFunction(scope: Construct, id: string, props: LambdaFunctionProps) 363 | ``` 364 | 365 | | **Name** | **Type** | **Description** | 366 | | --- | --- | --- | 367 | | scope | constructs.Construct | *No description.* | 368 | | id | string | *No description.* | 369 | | props | LambdaFunctionProps | *No description.* | 370 | 371 | --- 372 | 373 | ##### `scope`Required 374 | 375 | - *Type:* constructs.Construct 376 | 377 | --- 378 | 379 | ##### `id`Required 380 | 381 | - *Type:* string 382 | 383 | --- 384 | 385 | ##### `props`Required 386 | 387 | - *Type:* LambdaFunctionProps 388 | 389 | --- 390 | 391 | #### Methods 392 | 393 | | **Name** | **Description** | 394 | | --- | --- | 395 | | toString | Returns a string representation of this construct. | 396 | 397 | --- 398 | 399 | ##### `toString` 400 | 401 | ```typescript 402 | public toString(): string 403 | ``` 404 | 405 | Returns a string representation of this construct. 406 | 407 | #### Static Functions 408 | 409 | | **Name** | **Description** | 410 | | --- | --- | 411 | | isConstruct | Checks if `x` is a construct. | 412 | 413 | --- 414 | 415 | ##### `isConstruct` 416 | 417 | ```typescript 418 | import { LambdaPythonFunction } from 'cdk-certbot-dns-route53' 419 | 420 | LambdaPythonFunction.isConstruct(x: any) 421 | ``` 422 | 423 | Checks if `x` is a construct. 424 | 425 | Use this method instead of `instanceof` to properly detect `Construct` 426 | instances, even when the construct library is symlinked. 427 | 428 | Explanation: in JavaScript, multiple copies of the `constructs` library on 429 | disk are seen as independent, completely different libraries. As a 430 | consequence, the class `Construct` in each copy of the `constructs` library 431 | is seen as a different class, and an instance of one class will not test as 432 | `instanceof` the other class. `npm install` will not create installations 433 | like this, but users may manually symlink construct libraries together or 434 | use a monorepo tool: in those cases, multiple copies of the `constructs` 435 | library can be accidentally installed, and `instanceof` will behave 436 | unpredictably. It is safest to avoid using `instanceof`, and using 437 | this type-testing method instead. 438 | 439 | ###### `x`Required 440 | 441 | - *Type:* any 442 | 443 | Any object. 444 | 445 | --- 446 | 447 | #### Properties 448 | 449 | | **Name** | **Type** | **Description** | 450 | | --- | --- | --- | 451 | | node | constructs.Node | The tree node. | 452 | | handler | @aws-cdk/aws-lambda-python-alpha.PythonFunction | *No description.* | 453 | 454 | --- 455 | 456 | ##### `node`Required 457 | 458 | ```typescript 459 | public readonly node: Node; 460 | ``` 461 | 462 | - *Type:* constructs.Node 463 | 464 | The tree node. 465 | 466 | --- 467 | 468 | ##### `handler`Required 469 | 470 | ```typescript 471 | public readonly handler: PythonFunction; 472 | ``` 473 | 474 | - *Type:* @aws-cdk/aws-lambda-python-alpha.PythonFunction 475 | 476 | --- 477 | 478 | 479 | ## Structs 480 | 481 | ### BashExecFunctionProps 482 | 483 | #### Initializer 484 | 485 | ```typescript 486 | import { BashExecFunctionProps } from 'cdk-certbot-dns-route53' 487 | 488 | const bashExecFunctionProps: BashExecFunctionProps = { ... } 489 | ``` 490 | 491 | #### Properties 492 | 493 | | **Name** | **Type** | **Description** | 494 | | --- | --- | --- | 495 | | script | string | The path of the shell script to be executed. | 496 | | architecture | aws-cdk-lib.aws_lambda.Architecture | Custom lambda Image Architecture. | 497 | | dockerfile | string | The path of your custom dockerfile. | 498 | | environment | {[ key: string ]: string} | Lambda environment variables. | 499 | | role | aws-cdk-lib.aws_iam.IRole | Custom lambda execution role. | 500 | | timeout | aws-cdk-lib.Duration | The function execution time (in seconds) after which Lambda terminates the function. | 501 | 502 | --- 503 | 504 | ##### `script`Required 505 | 506 | ```typescript 507 | public readonly script: string; 508 | ``` 509 | 510 | - *Type:* string 511 | 512 | The path of the shell script to be executed. 513 | 514 | --- 515 | 516 | ##### `architecture`Optional 517 | 518 | ```typescript 519 | public readonly architecture: Architecture; 520 | ``` 521 | 522 | - *Type:* aws-cdk-lib.aws_lambda.Architecture 523 | - *Default:* lambda.Architecture.X86_64 524 | 525 | Custom lambda Image Architecture. 526 | 527 | --- 528 | 529 | ##### `dockerfile`Optional 530 | 531 | ```typescript 532 | public readonly dockerfile: string; 533 | ``` 534 | 535 | - *Type:* string 536 | 537 | The path of your custom dockerfile. 538 | 539 | --- 540 | 541 | ##### `environment`Optional 542 | 543 | ```typescript 544 | public readonly environment: {[ key: string ]: string}; 545 | ``` 546 | 547 | - *Type:* {[ key: string ]: string} 548 | 549 | Lambda environment variables. 550 | 551 | --- 552 | 553 | ##### `role`Optional 554 | 555 | ```typescript 556 | public readonly role: IRole; 557 | ``` 558 | 559 | - *Type:* aws-cdk-lib.aws_iam.IRole 560 | - *Default:* auto generated role. 561 | 562 | Custom lambda execution role. 563 | 564 | --- 565 | 566 | ##### `timeout`Optional 567 | 568 | ```typescript 569 | public readonly timeout: Duration; 570 | ``` 571 | 572 | - *Type:* aws-cdk-lib.Duration 573 | - *Default:* Duration.seconds(60) 574 | 575 | The function execution time (in seconds) after which Lambda terminates the function. 576 | 577 | Because the execution time affects cost, set this value based on the function's expected execution time. 578 | 579 | --- 580 | 581 | ### CertbotDnsRoute53JobProps 582 | 583 | #### Initializer 584 | 585 | ```typescript 586 | import { CertbotDnsRoute53JobProps } from 'cdk-certbot-dns-route53' 587 | 588 | const certbotDnsRoute53JobProps: CertbotDnsRoute53JobProps = { ... } 589 | ``` 590 | 591 | #### Properties 592 | 593 | | **Name** | **Type** | **Description** | 594 | | --- | --- | --- | 595 | | certbotOptions | CertbotOptions | certbot cmd options. | 596 | | destinationBucket | aws-cdk-lib.aws_s3.IBucket | The S3 bucket to store certificate. | 597 | | zone | aws-cdk-lib.aws_route53.IHostedZone | The HostZone on route53 to dns-01 challenge. | 598 | | architecture | aws-cdk-lib.aws_lambda.Architecture | Custom lambda Image Architecture. | 599 | | enabledLambdaFunctionUrl | boolean | Enabled Lambda Function URL. | 600 | | functionUrlOptions | aws-cdk-lib.aws_lambda.FunctionUrlOptions | Options to add a url to a Lambda function. | 601 | | schedule | aws-cdk-lib.aws_events.Schedule | run the Job with defined schedule. | 602 | 603 | --- 604 | 605 | ##### `certbotOptions`Required 606 | 607 | ```typescript 608 | public readonly certbotOptions: CertbotOptions; 609 | ``` 610 | 611 | - *Type:* CertbotOptions 612 | 613 | certbot cmd options. 614 | 615 | --- 616 | 617 | ##### `destinationBucket`Required 618 | 619 | ```typescript 620 | public readonly destinationBucket: IBucket; 621 | ``` 622 | 623 | - *Type:* aws-cdk-lib.aws_s3.IBucket 624 | 625 | The S3 bucket to store certificate. 626 | 627 | --- 628 | 629 | ##### `zone`Required 630 | 631 | ```typescript 632 | public readonly zone: IHostedZone; 633 | ``` 634 | 635 | - *Type:* aws-cdk-lib.aws_route53.IHostedZone 636 | 637 | The HostZone on route53 to dns-01 challenge. 638 | 639 | --- 640 | 641 | ##### `architecture`Optional 642 | 643 | ```typescript 644 | public readonly architecture: Architecture; 645 | ``` 646 | 647 | - *Type:* aws-cdk-lib.aws_lambda.Architecture 648 | - *Default:* lambda.Architecture.X86_64 649 | 650 | Custom lambda Image Architecture. 651 | 652 | --- 653 | 654 | ##### `enabledLambdaFunctionUrl`Optional 655 | 656 | ```typescript 657 | public readonly enabledLambdaFunctionUrl: boolean; 658 | ``` 659 | 660 | - *Type:* boolean 661 | - *Default:* false 662 | 663 | Enabled Lambda Function URL. 664 | 665 | --- 666 | 667 | ##### `functionUrlOptions`Optional 668 | 669 | ```typescript 670 | public readonly functionUrlOptions: FunctionUrlOptions; 671 | ``` 672 | 673 | - *Type:* aws-cdk-lib.aws_lambda.FunctionUrlOptions 674 | - *Default:* authType: lambda.FunctionUrlAuthType.NONE 675 | 676 | Options to add a url to a Lambda function. 677 | 678 | --- 679 | 680 | ##### `schedule`Optional 681 | 682 | ```typescript 683 | public readonly schedule: Schedule; 684 | ``` 685 | 686 | - *Type:* aws-cdk-lib.aws_events.Schedule 687 | - *Default:* no schedule 688 | 689 | run the Job with defined schedule. 690 | 691 | --- 692 | 693 | ### CertbotOptions 694 | 695 | #### Initializer 696 | 697 | ```typescript 698 | import { CertbotOptions } from 'cdk-certbot-dns-route53' 699 | 700 | const certbotOptions: CertbotOptions = { ... } 701 | ``` 702 | 703 | #### Properties 704 | 705 | | **Name** | **Type** | **Description** | 706 | | --- | --- | --- | 707 | | domainName | string | the domain must host on route53 like example.com. | 708 | | email | string | Email address for important account notifications. | 709 | | customPrefixDirectory | string | Custom prefix directory on s3 bucket object path. | 710 | 711 | --- 712 | 713 | ##### `domainName`Required 714 | 715 | ```typescript 716 | public readonly domainName: string; 717 | ``` 718 | 719 | - *Type:* string 720 | 721 | the domain must host on route53 like example.com. 722 | 723 | --- 724 | 725 | *Example* 726 | 727 | ```typescript 728 | - `*.example.com` or `a.example.com` . 729 | ``` 730 | 731 | 732 | ##### `email`Required 733 | 734 | ```typescript 735 | public readonly email: string; 736 | ``` 737 | 738 | - *Type:* string 739 | 740 | Email address for important account notifications. 741 | 742 | --- 743 | 744 | ##### `customPrefixDirectory`Optional 745 | 746 | ```typescript 747 | public readonly customPrefixDirectory: string; 748 | ``` 749 | 750 | - *Type:* string 751 | - *Default:* `s3://YOUR_BUCKET_NAME/2021-01-01/your.domain.name/` 752 | 753 | Custom prefix directory on s3 bucket object path. 754 | 755 | --- 756 | 757 | *Example* 758 | 759 | ```typescript 760 | - customPrefixDirectory: 'abc' -> `s3://YOUR_BUCKET_NAME/abc/your.domain.name/` 761 | ``` 762 | 763 | 764 | ### LambdaFunctionProps 765 | 766 | #### Initializer 767 | 768 | ```typescript 769 | import { LambdaFunctionProps } from 'cdk-certbot-dns-route53' 770 | 771 | const lambdaFunctionProps: LambdaFunctionProps = { ... } 772 | ``` 773 | 774 | #### Properties 775 | 776 | | **Name** | **Type** | **Description** | 777 | | --- | --- | --- | 778 | | architecture | aws-cdk-lib.aws_lambda.Architecture | *No description.* | 779 | | timeout | aws-cdk-lib.Duration | *No description.* | 780 | | environment | {[ key: string ]: string} | *No description.* | 781 | | role | aws-cdk-lib.aws_iam.IRole | *No description.* | 782 | 783 | --- 784 | 785 | ##### `architecture`Required 786 | 787 | ```typescript 788 | public readonly architecture: Architecture; 789 | ``` 790 | 791 | - *Type:* aws-cdk-lib.aws_lambda.Architecture 792 | 793 | --- 794 | 795 | ##### `timeout`Required 796 | 797 | ```typescript 798 | public readonly timeout: Duration; 799 | ``` 800 | 801 | - *Type:* aws-cdk-lib.Duration 802 | 803 | --- 804 | 805 | ##### `environment`Optional 806 | 807 | ```typescript 808 | public readonly environment: {[ key: string ]: string}; 809 | ``` 810 | 811 | - *Type:* {[ key: string ]: string} 812 | 813 | --- 814 | 815 | ##### `role`Optional 816 | 817 | ```typescript 818 | public readonly role: IRole; 819 | ``` 820 | 821 | - *Type:* aws-cdk-lib.aws_iam.IRole 822 | 823 | --- 824 | 825 | 826 | 827 | --------------------------------------------------------------------------------