Skip to content

Commit 09ba50e

Browse files
authored
Devops 792 gha deploy to s3 cloudfront cert (#1)
* Initial commit
1 parent f25c943 commit 09ba50e

11 files changed

+1168
-1
lines changed

README.md

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,141 @@
1-
# github-actions-deploy-serverless-website
1+
# Deploy serverless website
2+
3+
GitHub action to deploy anything into a bucket, adding the options to add a CDN and use a Domain (if hosted in Route53) with certificates.
4+
5+
This action will copy the files from the defined folder into an S3 bucket, defining the content type and serving ALL OF THEM PUBLICLY.
6+
7+
## Requirements
8+
9+
1. Files to publish
10+
2. An AWS Account
11+
3. If domain and cert wanted, registered domain in AWS.
12+
13+
### 1. Files to publish
14+
Will grab everything defined in `aws_spa_source_folder` and push it to a bucket.
15+
Define `aws_spa_root_object` if different than `index.html`
16+
17+
### 2. An AWS account
18+
You'll need [Access Keys](https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html) from an [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/)
19+
20+
### 3. CERTIFICATES - Only for AWS Managed domains with Route53
21+
If `domain_name` is defined, we will look up for a certificate with the name of that domain (eg. `example.com`). We expect that certificate to contain both `example.com` and `*.example.com`.
22+
23+
Setting `create_root_cert` to `true` will create this certificate with both `example.com` and `*.example.com` for you, and validate them. (DNS validation).
24+
25+
Setting `create_sub_cert` to `true` will create a certificate **just for the subdomain**, and validate it.
26+
27+
> :warning: Be very careful here! **Created certificates are fully managed by Terraform**. Therefor **they will be destroyed upon stack destruction**.
28+
29+
30+
## Example usage
31+
32+
Create `.github/workflow/deploy.yaml` with the following to build on push.
33+
34+
### Example usage
35+
```yaml
36+
name: Basic deploy
37+
on:
38+
push:
39+
branches: [ main ]
40+
41+
jobs:
42+
Deploy-SPA:
43+
runs-on: ubuntu-latest
44+
45+
steps:
46+
- name: Create deploy-bucket-only
47+
uses: bitovi/[email protected]
48+
with:
49+
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID_SANDBOX}}
50+
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY_SANDBOX}}
51+
aws_default_region: us-east-1
52+
53+
tf_action: 'apply'
54+
tf_state_bucket_destroy: true
55+
56+
aws_spa_cdn_enabled: true
57+
58+
aws_r53_domain_name: examplex.com
59+
aws_r53_sub_domain_name: spa
60+
```
61+
62+
## Customizing
63+
64+
### Inputs
65+
1. [Action defaults](#action-defaults-inputs)
66+
1. [AWS](#aws-inputs)
67+
1. [Terraform options](#terraform-options-inputs)
68+
1. [SPA Settings](#spa-settings-inputs)
69+
1. [Certificate](#certificate-inputs)
70+
71+
The following inputs can be used as `step.with` keys
72+
<br/>
73+
<br/>
74+
75+
#### **Action defaults Inputs**
76+
| Name | Type | Description |
77+
|------------------|---------|------------------------------------|
78+
| `checkout` | Boolean | Set to `false` if the code is already checked out. (Default is `true`). |
79+
<hr/>
80+
<br/>
81+
82+
#### **AWS Inputs**
83+
| Name | Type | Description |
84+
|------------------|---------|------------------------------------|
85+
| `aws_access_key_id` | String | AWS access key ID |
86+
| `aws_secret_access_key` | String | AWS secret access key |
87+
| `aws_default_region` | String | AWS default region. Defaults to `us-east-1` |
88+
| `aws_role_to_assume` | String | AWS Role to assume. Default is empty. |
89+
| `aws_resource_identifier` | String | Set to override the AWS resource identifier for the deployment. Defaults to `${GITHUB_ORG_NAME}-${GITHUB_REPO_NAME}-${GITHUB_BRANCH_NAME}`. Use with destroy to destroy specific resources. |
90+
| `additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to all provisioned resources.|
91+
<hr/>
92+
<br/>
93+
94+
#### **Terraform options inputs**
95+
| Name | Type | Description |
96+
|------------------|---------|------------------------------------|
97+
| `tf_action` | String | Option to run Terraform `apply` / `destroy` action. Will run `plan` if nothing defined. |
98+
| `tf_plan_show_details` | Boolean | Set to true to show a detailed output from Terraform plan. |
99+
| `tf_state_bucket` | String | AWS S3 bucket name to use for Terraform state. Defaults to `${org}-${repo}-{branch}-tf-state` |
100+
| `tf_state_bucket_destroy` | Boolean | Force purge and deletion of S3 bucket defined if terraform destroy action succeded. |
101+
<hr/>
102+
<br/>
103+
104+
#### **SPA Settings inputs**
105+
| Name | Type | Description |
106+
|------------------|---------|------------------------------------|
107+
| `aws_spa_source_folder` | String | Source folder for files to be published. Will ignore any hidden file. Defaults to root folder of the calling repo if nothing defined. |
108+
| `aws_spa_root_object` | Boolean | Root object to be served as entry-point. Defaults to `index.html`. |
109+
| `aws_spa_website_bucket_name` | String | AWS S3 bucket name to use for the public files. Defaults to `${org}-${repo}-{branch}-sp` |
110+
| `aws_spa_cdn_enabled` | Boolean | Enable or disables the use of CDN. Defaults to `false`. |
111+
<hr/>
112+
<br/>
113+
114+
#### **Certificate Inputs**
115+
| Name | Type | Description |
116+
|------------------|---------|------------------------------------|
117+
| `aws_r53_domain_name` | String | Define the root domain name for the application. e.g. `bitovi.com`. |
118+
| `aws_r53_sub_domain_name` | String | Define the sub-domain part of the URL. Defaults to `${GITHUB_ORG_NAME}-${GITHUB_REPO_NAME}-${GITHUB_BRANCH_NAME}`. |
119+
| `aws_r53_root_domain_deploy` | Boolean | Deploy application to root domain. Will create root and www records. Default is `false`. |
120+
| `aws_r53_cert_arn` | String | Define the certificate ARN to use for the application. |
121+
| `aws_r53_create_root_cert` | Boolean | Generates and manage the root cert for the application. Default is `false`. |
122+
| `aws_r53_create_sub_cert` | Boolean | Generates and manage the sub-domain certificate for the application. Default is `false`. |
123+
<hr/>
124+
<br/>
125+
126+
127+
## Note about resource identifiers
128+
129+
Most resources will contain the tag `${GITHUB_ORG_NAME}-${GITHUB_REPO_NAME}-${GITHUB_BRANCH_NAME}`, some of them, even the resource name after.
130+
We limit this to a 60 characters string because some AWS resources have a length limit and short it if needed.
131+
132+
We use the kubernetes style for this. For example, kubernetes -> k(# of characters)s -> k8s. And so you might see some compressions are made.
133+
134+
For some specific resources, we have a 32 characters limit. If the identifier length exceeds this number after compression, we remove the middle part and replace it for a hash made up from the string itself.
135+
136+
## Contributing
137+
We would love for you to contribute to [bitovi/github-actions-deploy-docker-to-ec2](https://github.com/bitovi/github-actions-deploy-docker-to-ec2).
138+
Would you like to see additional features? [Create an issue](https://github.com/bitovi/github-actions-deploy-docker-to-ec2/issues/new) or a [Pull Requests](https://github.com/bitovi/github-actions-deploy-docker-to-ec2/pulls). We love discussing solutions!
139+
140+
## License
141+
The scripts and documentation in this project are released under the [MIT License](https://github.com/bitovi/github-actions-deploy-docker-to-ec2/blob/main/LICENSE).

action.yaml

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
name: 'Deploy Docker to AWS (EC2)'
2+
description: 'Deploy a Docker app to an AWS Virtual Machine (EC2) with Docker Compose'
3+
branding:
4+
icon: upload-cloud
5+
color: red
6+
inputs:
7+
# Checkout
8+
checkout:
9+
description: 'Specifies if this action should checkout the code'
10+
required: false
11+
default: 'true'
12+
13+
# AWS
14+
aws_access_key_id:
15+
description: 'AWS access key ID'
16+
required: true
17+
aws_secret_access_key:
18+
description: 'AWS secret access key'
19+
required: true
20+
aws_default_region:
21+
description: 'AWS default region'
22+
default: us-east-1
23+
required: false
24+
aws_role_to_assume:
25+
description: 'AWS Role to assume.'
26+
required: false
27+
aws_resource_identifier:
28+
description: 'Set to override the AWS resource identifier for the deployment. Defaults to `${org}-{repo}-{branch}`. Use with destroy to destroy specific resources.'
29+
required: false
30+
aws_additional_tags:
31+
description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`'
32+
required: false
33+
34+
# Terraform options
35+
tf_action:
36+
desctiption: 'Option to run Terraform apply / destroy option. Will run plan if nothing defined.'
37+
required: false
38+
tf_plan_show_details:
39+
description: 'Set to true to show a detailed output from Terraform plan.'
40+
required: false
41+
tf_state_bucket:
42+
description: 'AWS S3 bucket to use for Terraform state. Defaults to `${org}-${repo}-{branch}-tf-state`'
43+
required: false
44+
tf_state_bucket_destroy:
45+
description: 'Force purge and deletion of S3 bucket defined if terraform destroy action succeded.'
46+
required: false
47+
48+
# SPA Settings
49+
aws_spa_source_folder:
50+
description: 'Source folder for files to be published. Will ignore any hidden file. Defaults to root folder of the calling repo if nothing defined.'
51+
required: true
52+
aws_spa_root_object:
53+
description: 'Root object to be served as entry-point. Defaults to `index.html`'
54+
required: false
55+
aws_spa_website_bucket_name:
56+
description: ' AWS S3 bucket name to use for the public files. Defaults to `${org}-${repo}-{branch}-sp`'
57+
required: false
58+
aws_spa_cdn_enabled:
59+
description: 'Enable or disables the use of CDN. Defaults to `false`.'
60+
required: false
61+
62+
# AWS Route53 Domains abd Certificates
63+
aws_r53_domain_name:
64+
description: 'Define the root domain name for the application. e.g. `bitovi.com`'
65+
required: false
66+
aws_r53_sub_domain_name:
67+
description: 'Define the sub-domain part of the URL. Defaults to `${GITHUB_ORG_NAME}-${GITHUB_REPO_NAME}-${GITHUB_BRANCH_NAME}`.'
68+
aws_r53_root_domain_deploy:
69+
description: 'Deploy application to root domain. Will create root and www records. Default is `false`.'
70+
required: false
71+
aws_r53_cert_arn:
72+
description: Define the certificate ARN to use for the application.'
73+
required: false
74+
aws_r53_create_root_cert:
75+
description: 'Generates and manage the root cert for the application. Default is `false`.'
76+
required: false
77+
aws_r53_create_sub_cert:
78+
description: 'Generates and manage the sub-domain certificate for the application. Default is `false`.'
79+
required: false
80+
81+
82+
outputs:
83+
public_url:
84+
description: "The URL of the generated app"
85+
value: ${{ steps.apply.outputs.public_url }}
86+
87+
runs:
88+
using: 'composite'
89+
steps:
90+
- name: Checkout
91+
uses: actions/checkout@v3
92+
93+
- name: Configure AWS Credentials
94+
if: ${{ inputs.aws_access_key_id != '' }}
95+
uses: aws-actions/configure-aws-credentials@v2
96+
with:
97+
aws-access-key-id: ${{ inputs.aws_access_key_id }}
98+
aws-secret-access-key: ${{ inputs.aws_secret_access_key }}
99+
aws-region: ${{ inputs.aws_default_region }}
100+
role-to-assume: ${{ inputs.aws_role_to_assume }}
101+
102+
- name: Deploy with BitOps
103+
id: deploy
104+
shell: bash
105+
env:
106+
# AWS
107+
AWS_DEFAULT_REGION: ${{ inputs.aws_default_region }}
108+
AWS_RESOURCE_IDENTIFIER: ${{ inputs.aws_resource_identifier }}
109+
AWS_ADDITIONAL_TAGS: ${{ inputs.aws_additional_tags }}
110+
# TF
111+
TF_STATE_BUCKET: ${{ inputs.tf_state_bucket }}
112+
TF_STATE_BUCKET_DESTROY: ${{ inputs.tf_state_bucket_destroy }}
113+
# SPA
114+
AWS_SPA_SOURCE_FOLDER: ${{ inputs.aws_spa_source_folder }}
115+
AWS_SPA_ROOT_OBJECT: ${{ inputs.aws_spa_root_object }}
116+
AWS_SPA_WEBSITE_BUCKET_NAME: ${{ inputs.aws_spa_website_bucket_name }}
117+
AWS_SPA_CDN_ENABLED: ${{ inputs.aws_spa_cdn_enabled }}
118+
# AWS Route53 Domains abd Certificates
119+
AWS_R53_DOMAIN_NAME: ${{ inputs.aws_r53_domain_name }}
120+
AWS_R53_SUB_DOMAIN_NAME: ${{ inputs.aws_r53_sub_domain_name }}
121+
AWS_R53_ROOT_DOMAIN_DEPLOY: ${{ inputs.aws_r53_root_domain_deploy }}
122+
AWS_R53_CERT_ARN: ${{ inputs.aws_r53_cert_arn }}
123+
AWS_R53_CREATE_ROOT_CERT: ${{ inputs.aws_r53_create_root_cert }}
124+
AWS_R53_CREATE_SUB_CERT: ${{ inputs.aws_r53_create_sub_cert }}
125+
run: |
126+
echo "running scripts/generate_deploy.sh"
127+
$GITHUB_ACTION_PATH/scripts/generate_deploy.sh
128+
129+
- name: Setup Terraform
130+
uses: hashicorp/setup-terraform@v2
131+
132+
- name: Terraform Init
133+
id: init
134+
shell: bash
135+
run: terraform -chdir=$GITHUB_ACTION_PATH/terraform_code init
136+
137+
- name: Terraform Validate
138+
id: validate
139+
shell: bash
140+
run: terraform -chdir=$GITHUB_ACTION_PATH/terraform_code validate
141+
142+
- name: Terraform Plan
143+
if: ${{ success () && inputs.tf_show_plan_details == 'true' && inputs.tf_action != 'destroy' }}
144+
id: plan
145+
shell: bash
146+
run: terraform -chdir=$GITHUB_ACTION_PATH/terraform_code plan
147+
148+
- name: Terraform Show Plan Details
149+
if: ${{ success () && inputs.tf_show_plan_details == 'true' && inputs.tf_action != 'destroy' }}
150+
shell: bash
151+
run: |
152+
echo ${{ steps.plan.outputs.stdout }}
153+
echo ${{ steps.plan.outputs.stderr }}
154+
echo ${{ steps.plan.outputs.exitcode }}
155+
156+
- name: Terraform Apply
157+
id: apply
158+
if: ${{ success() && inputs.tf_action == 'apply' }}
159+
shell: bash
160+
run: |
161+
terraform -chdir=$GITHUB_ACTION_PATH/terraform_code apply -auto-approve
162+
163+
- name: Terraform Destroy
164+
if: ${{ success() && inputs.tf_action == 'destroy' }}
165+
shell: bash
166+
run: |
167+
terraform -chdir=$GITHUB_ACTION_PATH/terraform_code destroy -auto-approve
168+
169+
- name: Terraform Destroy
170+
if: ${{ success() && inputs.tf_action == 'destroy' && inputs.tf_state_bucket_destroy == 'true' }}
171+
shell: bash
172+
env:
173+
TF_STATE_BUCKET: ${{ inputs.tf_state_bucket }}
174+
run: |
175+
echo "running scripts/destroy_tf_state_bucket.sh"
176+
$GITHUB_ACTION_PATH/scripts/destroy_tf_state_bucket.sh

scripts/check_bucket_name.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
### S3 Buckets name must follow AWS rules. Info below.
6+
### https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
7+
8+
function checkBucket() {
9+
# check length of bucket name
10+
if [[ ${#1} -lt 3 || ${#1} -gt 63 ]]; then
11+
echo "::error::Bucket name must be between 3 and 63 characters long."
12+
exit 1
13+
fi
14+
15+
# check that bucket name consists only of lowercase letters, numbers, dots (.), and hyphens (-)
16+
if [[ ! $1 =~ ^[a-z0-9.-]+$ ]]; then
17+
echo "::error::Bucket name can only consist of lowercase letters, numbers, dots (.), and hyphens (-)."
18+
exit 1
19+
fi
20+
21+
# check that bucket name begins and ends with a letter or number
22+
if [[ ! $1 =~ ^[a-zA-Z0-9] ]]; then
23+
echo "::error::Bucket name must begin with a letter or number."
24+
exit 1
25+
fi
26+
if [[ ! $1 =~ [a-zA-Z0-9]$ ]]; then
27+
echo "::error::Bucket name must end with a letter or number."
28+
exit 1
29+
fi
30+
31+
# check that bucket name does not contain two adjacent periods
32+
if [[ $1 =~ \.\. ]]; then
33+
echo "::error::Bucket name cannot contain two adjacent periods."
34+
exit 1
35+
fi
36+
37+
# check that bucket name is not formatted as an IP address
38+
if [[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
39+
echo "::error::Bucket name cannot be formatted as an IP address."
40+
exit 1
41+
fi
42+
43+
# check that bucket name does not start with the prefix xn--
44+
if [[ $1 =~ ^xn-- ]]; then
45+
echo "::error::Bucket name cannot start with the prefix xn--."
46+
exit 1
47+
fi
48+
49+
# check that bucket name does not end with the suffix -s3alias
50+
if [[ $1 =~ -s3alias$ ]]; then
51+
echo "::error::Bucket name cannot end with the suffix -s3alias."
52+
exit 1
53+
fi
54+
}
55+
56+
checkBucket $1

0 commit comments

Comments
 (0)