├── .github
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .gitmodules
├── .travis.yml
├── .yardopts
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE
├── LICENSE.txt
├── NOTICE
├── README.md
├── Rakefile
├── VERSION
├── aws-rails-provisioner.gemspec
├── bin
└── aws-rails-provisioner
├── buildspecs
├── buildspec-db.yml
└── buildspec-ecr.yml
├── doc-src
└── templates
│ └── default
│ └── layout
│ └── html
│ ├── footer.erb
│ └── layout.erb
├── examples
├── multi_service.yml
├── no_db.yml
├── single_service.yml
└── tiny.yml
├── lib
├── aws-rails-provisioner.rb
└── aws-rails-provisioner
│ ├── build.rb
│ ├── cdk_builder.rb
│ ├── cdk_code_builder.rb
│ ├── cdk_deployer.rb
│ ├── code_build.rb
│ ├── db_cluster.rb
│ ├── errors.rb
│ ├── fargate.rb
│ ├── migration.rb
│ ├── parser.rb
│ ├── scaling.rb
│ ├── service.rb
│ ├── services.rb
│ ├── subnet_selection.rb
│ ├── utils.rb
│ ├── version.rb
│ ├── view.rb
│ ├── views
│ ├── fargate_stack.rb
│ ├── init_stack.rb
│ ├── pipeline_stack.rb
│ └── project.rb
│ └── vpc.rb
├── spec
├── cdk_code_builder_spec.rb
├── fixtures
│ ├── cdk
│ │ ├── multi_service
│ │ │ ├── cdk-sample-init-stack.ts
│ │ │ ├── cdk-sample.ts
│ │ │ ├── rails-foo-fargate-stack.ts
│ │ │ ├── rails-foo-pipeline-stack.ts
│ │ │ ├── rails-no-db-fargate-stack.ts
│ │ │ └── rails-no-db-pipeline-stack.ts
│ │ ├── no_db
│ │ │ ├── cdk-sample-init-stack.ts
│ │ │ ├── cdk-sample.ts
│ │ │ ├── rails-no-db-fargate-stack.ts
│ │ │ └── rails-no-db-pipeline-stack.ts
│ │ └── single_service
│ │ │ ├── cdk-sample-init-stack.ts
│ │ │ ├── cdk-sample.ts
│ │ │ ├── rails-foo-fargate-stack.ts
│ │ │ └── rails-foo-pipeline-stack.ts
│ └── yml
│ │ ├── multi_service.yml
│ │ ├── no_db.yml
│ │ └── single_service.yml
└── spec_helper.rb
└── templates
├── fargate_stack.mustache
├── init_stack.mustache
├── pipeline_stack.mustache
└── project.mustache
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | *Issue #, if available:*
2 |
3 | *Description of changes:*
4 |
5 |
6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .ruby-version
2 | .yardoc/
3 | Gemfile.lock
4 | doc
5 |
6 | aws-rails-provisioner.yml
7 | cdk-sample/
8 | package-lock.json
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "tasks/release"]
2 | path = tasks/release
3 | url = https://github.com/aws/aws-sdk-ruby-release-tools.git
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | rvm:
4 | - 2.0
5 | - 2.1
6 | - 2.2
7 | - 2.3
8 | - 2.4
9 | - 2.5
10 | - 2.6
11 | - 2.7
12 | - jruby-9.1
13 | - jruby-9.2
14 |
15 | sudo: false
16 |
17 | script: bundle exec rake release:test
18 |
19 | bundler_args: --without docs release repl
20 |
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --title 'AWS Rails Provisioner'
2 | --template-path doc-src/templates
3 | --plugin sitemap
4 | --hide-api private
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Unreleased Changes
2 | ------------------
3 |
4 | 0.0.2.rc4 (2020-11-23)
5 | ------------------
6 |
7 | * Issue - Use `ParameterGroup` as `ClusterParameterGroup` was removed. (#14)
8 |
9 | 0.0.2.rc3 (2020-05-27)
10 | ------------------
11 |
12 | * Issue - Run `cdk bootstrap` automatically.
13 |
14 | * Issue - Use docker image asset instead of repo from ECR (fixes failed builds).
15 |
16 | * Issue - Upgrade postgresql DB engine family.
17 |
18 | * Issue - Fix `--profile` option being ignored.
19 |
20 | 0.0.1.rc2 (2019-10-14)
21 | -----------------
22 |
23 | * Issue - Update `LoadBalancerFargate` to `ApplicationLoadBalancerFargate`
24 |
25 | * Issue - Remove CDK default test file
26 |
27 | 0.0.0.rc1 (2019-07-10)
28 | ------------------
29 |
30 | * Initial preview release of `aws-rails-provisioner` gem.
31 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check [existing open](https://github.com/awslabs/aws-rails-provisioner/issues), or [recently closed](https://github.com/awslabs/aws-rails-provisioner/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *master* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-rails-provisioner/labels/help%20wanted) issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](https://github.com/awslabs/aws-rails-provisioner/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
62 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
5 | gem 'rake', require: false
6 |
7 | group :docs do
8 | gem 'yard'
9 | gem 'yard-sitemap', '~> 1.0'
10 | end
11 |
12 | group :test do
13 | gem 'rspec'
14 | end
15 |
16 | group :release do
17 | gem 'octokit'
18 | end
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | aws-rails-provisioner
2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This tool is deprecated
2 |
3 | This tool is deprecated. We cannot guarantee that it will work.
4 |
5 | This documentation will remain for historical purposes.
6 |
7 | ## aws-rails-provisioner
8 |
9 | [](https://travis-ci.org/awslabs/aws-rails-provisioner)
10 |
11 | A tool for defining and deploying containerized Ruby on Rails applications on AWS.
12 |
13 | `aws-rails-provisioner` is a command line tool using your configurations defined in `aws-rails-provisioner.yml` file to generate [AWS CDK](https://github.com/awslabs/aws-cdk) stacks on your behalf, provisioning required AWS resources for running your containerized Ruby on Rails applications on AWS (current supported platform: AWS Fargate) within a few commands.
14 |
15 |
16 | ---
17 | 
18 | > This tool is under developer preview (beta) stage, with active development, releases might lack features and might have future breaking changes.
19 | ---
20 |
21 |
22 | ## Links of Interest
23 |
24 | * [Change Log](./CHANGELOG.md)
25 | * [Issues](https://github.com/awslabs/aws-rails-provisioner/issues)
26 | * [License](http://aws.amazon.com/apache2.0/)
27 | * [Documentation](https://docs.aws.amazon.com/awsrailsprovisioner/api/)
28 |
29 | ## Getting Started
30 |
31 | ### Prerequisites
32 |
33 | Before using the `aws-rails-provisioner` gem, you need to have:
34 |
35 | * a [Ruby on Rails](https://rubyonrails.org/) application with a Dockerfile
36 | * install or update the [AWS CDK Toolkit] from npm (requires [Node.js ≥ 8.11.x](https://nodejs.org/en/download)):
37 |
38 | ```bash
39 | $ npm i -g aws-cdk
40 | ```
41 | * docker daemon vailable for building images
42 |
43 | ### Install aws-rails-provisioner
44 |
45 | `aws-rails-provisioner` gem is available from RubyGems, currently with [preview versions](https://rubygems.org/gems/aws-rails-provisioner).
46 |
47 | ```
48 | gem install aws-rails-provisioner --pre
49 | ```
50 |
51 | ### Define aws-rails-provisioner.yml
52 |
53 | `aws-rails-provisioner.yml` is a configuration file defining how `aws-rails-provisioner` bootstraps required AWS resources for your Ruby on Rails applications.
54 |
55 | ```
56 | version: '0'
57 |
58 | vpc:
59 | max_az: 2
60 | enable_dns: true
61 | services:
62 | my_rails_foo:
63 | source_path: ./path/to/my_rails_foo # relative path from `aws-rails-provisioner.yml`
64 | fargate:
65 | desired_count: 3
66 | memory: 512
67 | cpu: 256
68 | envs:
69 | PORT: 80
70 | RAILS_LOG_TO_STDOUT: true
71 | public: true
72 | db_cluster:
73 | engine: aurora-postgresql
74 | db_name: app_development
75 | scaling:
76 | max_capacity: 5
77 | on_cpu:
78 | target_util_percent: 80
79 | scale_in_cool_down: 300
80 | on_requests:
81 | requests_per_target: 1000
82 | my_another_rails:
83 | ...
84 | ```
85 |
86 | See `./examples` for more `aws-rails-provisioner` examples (see `tiny.yml` for a minimal `aws-rails-provisioner.yml` configuration example). The full configuration options are documented [here](https://docs.aws.amazon.com/awsrailsprovisioner/api/).
87 |
88 | ### Build and Deploy
89 |
90 | Once `aws-rails-provisioner.yml` is defined, run the build command. This will boostrap AWS CDK stacks and define all required AWS resources and connections in CDK code.
91 |
92 | ```
93 | aws-rails-provisioner build
94 | ```
95 |
96 | By default, it defines a VPC with public and private subnets, an Amazon RDS Database Cluster, an ECS cluster with AWS Fargate services containing application images. When building with `--with-cicd` option a CICD stack will be defined automatically (including a Rails data migration step).
97 |
98 | ```
99 | aws-rails-provisioner build --with-cicd
100 | ```
101 |
102 | After the build completes, run the deploy command to run CDK code that deploys all defined AWS resources:
103 |
104 | ```
105 | aws-rails-provisioner deploy
106 | ```
107 |
108 | Instead of deploying everything all at once, you can deploy stack by stack, application by application:
109 |
110 | ```
111 | # only deploys the stack creates VPC and ECS cluster
112 | aws-rails-provisioner deploy --init
113 |
114 | # deploys fargate service and database cluster when defined
115 | aws-rails-provisioner deploy --fargate
116 |
117 | # deploy CICD stack
118 | aws-rails-provisioner deploy --cicd
119 |
120 | # deploy only :rails_foo application
121 | aws-rails-provisioner deploy --fargate --service my_rails_foo
122 | ```
123 |
124 | After the deployment completes, your applications are now running on AWS Fargate fronted with AWS Application LoadBalancer. You can view your website using the public IP address of the network interface's load balancer.
125 |
126 | > Note: for applications with databases, rails db migration is needed; the CICD stack contains a migration phase by default, running DB migration commands insides private subnets, connected to the DB Cluster.
127 |
128 | ### CICD
129 |
130 | When `--with-cicd` is enabled at build, a CICD stack is created. Once deployment completes, an AWS CodePipeline is available with source, build, migration, and deploy phases for your Rails application. You need to commit your Rails application to the CodeCommit source repository in the pipeline with `buildspec` files to activate the pipeline. Sample `buildspec`s are availble under `./buildspecs` handling application image builds and rails migrations.
131 |
132 | Full `aws-rails-provisioner` command line options see:
133 |
134 | ```
135 | aws-rails-provisioner -h
136 | ```
137 |
138 | ## Contributing
139 |
140 | We welcome community contributions and pull requests. See [CONTRIBUTING](./CONTRIBUTING.md) for details.
141 |
142 | ## Getting Help
143 |
144 | Please use these community resources for getting help. We use the GitHub issues for tracking bugs and feature requests.
145 |
146 | * Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/aws-rails-provisioner) and tag it with `aws-rails-provisioner`
147 | * If it turns out that you may have found a bug, or want to submit a feature request, please open an [issue](https://github.com/awslabs/aws-cdk/issues/new)
148 |
149 | ## License
150 |
151 | The `aws-rails-provisioner` is distributed under [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). See [LICENSE](./LICENSE.txt) for more information.
152 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rspec/core/rake_task'
2 |
3 | $REPO_ROOT = File.dirname(__FILE__)
4 | $LOAD_PATH.unshift(File.join($REPO_ROOT, 'lib'))
5 | $VERSION = ENV['VERSION'] || File.read(File.join($REPO_ROOT, 'VERSION')).strip
6 |
7 | task 'test:coverage:clear' do
8 | sh("rm -rf #{File.join($REPO_ROOT, 'coverage')}")
9 | end
10 |
11 | desc 'run unit tests'
12 | RSpec::Core::RakeTask.new do |t|
13 | t.rspec_opts = "-I #{$REPO_ROOT}/lib -I #{$REPO_ROOT}/spec"
14 | t.pattern = "#{$REPO_ROOT}/spec"
15 | end
16 |
17 | task :spec => 'test:coverage:clear'
18 | task :default => :spec
19 | task 'release:test' => :spec
20 |
21 | Dir.glob('**/*.rake').each do |task_file|
22 | load task_file
23 | end
24 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 0.0.2.rc4
2 |
--------------------------------------------------------------------------------
/aws-rails-provisioner.gemspec:
--------------------------------------------------------------------------------
1 | version = File.read(File.expand_path('../VERSION', __FILE__)).strip
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "aws-rails-provisioner"
5 | spec.version = version
6 | spec.authors = ["Amazon Web Services"]
7 | spec.email = ["mamuller@amazon.com", "alexwoo@amazon.com"]
8 |
9 | spec.summary = "Deploy a Ruby on Rails application on AWS."
10 | spec.description = "Define and deploy containerized Ruby on Rails Applications on AWS."
11 | spec.homepage = "http://github.com/awslabs/aws-rails-provisioner"
12 | spec.license = "Apache-2.0"
13 |
14 | spec.require_paths = ["lib"]
15 | spec.files = Dir['lib/**/*.rb', 'templates/*']
16 | spec.bindir = 'bin'
17 | spec.executables << 'aws-rails-provisioner'
18 |
19 | spec.add_dependency('aws-sdk-rds', '~> 1')
20 | spec.add_dependency('aws-sdk-secretsmanager', '~> 1')
21 | spec.add_dependency('mustache', '~> 1')
22 | end
23 |
--------------------------------------------------------------------------------
/bin/aws-rails-provisioner:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'optparse'
5 | require 'fileutils'
6 |
7 | # load the `aws-rails-provisioner` gem
8 | $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
9 | require 'aws-rails-provisioner'
10 |
11 | # setup default options
12 | options = {
13 | file_path: 'aws-rails-provisioner.yml'
14 | }
15 | options[ARGV[0].to_sym] = {}
16 |
17 | OptionParser.new do |opts|
18 |
19 | opts.banner = "Usage: aws-rails-provisioner [command] [options]"
20 | opts.separator("")
21 | opts.separator("Commands:")
22 | opts.separator(" build: generate CDK stacks from aws-rails-provisioner.yml")
23 | opts.separator(" deploy: deploy generated CDK stacks")
24 | opts.separator("")
25 | opts.separator("Options:")
26 |
27 | svc_name_msg = 'Service name. Changes apply to all services '\
28 | 'configured under :services if not provided'
29 | opts.on('-s', '--service SERVICE_NAME', svc_name_msg) do |name|
30 | options[:service_name] = name.to_sym
31 | end
32 |
33 | opts.on('-p', '--profile AWS_PROFILE_NAME', 'Used the indicated AWS profile') do |profile|
34 | options[:profile] = profile
35 | end
36 |
37 | file_path_msg = 'File path to `aws-rails-provisioner.yml`, default to `aws-rails-provisioner.yml`'\
38 | ' at current directory'
39 | opts.on('-f', '--file YAML_FILE_PATH', file_path_msg) do |path|
40 | options[:file_path] = path
41 | end
42 |
43 | dir_msg = "Directory where generated code lives, defaults to `cdk-sample`"
44 | opts.on('-d', '--cdk-directory DIR_PATH', dir_msg) do |dir|
45 | options[:cdk_dir] = dir
46 | end
47 |
48 | # Build
49 | cicd_msg = "Enable CICD stack generation, use with `build`"
50 | opts.on('--with-cicd', cicd_msg) do
51 | if options[:build].nil?
52 | raise Aws::RailsProvisioner::Errors::InvalidCommandOption.new('build', '--with-cicd')
53 | end
54 | options[:build][:enable_cicd] = true
55 | end
56 |
57 | # need to consider scenarios for dryrun
58 | #opts.on('--dryrun') do
59 | #options[:dryrun] = true
60 | #end
61 |
62 | # Deploy
63 | init_msg = "Deploy the stack that creates VPC and ECS cluster"
64 | opts.on('--init', init_msg) do
65 | if options[:deploy].nil?
66 | raise Aws::RailsProvisioner::Errors::InvalidCommandOption.new('deploy', '--init')
67 | end
68 | options[:deploy][:init] = true
69 | end
70 |
71 | fargate_msg = "Deploy the stack that creates Fargate service and DBCluster (if specificed)"
72 | opts.on('--fargate', fargate_msg) do
73 | if options[:deploy].nil?
74 | raise Aws::RailsProvisioner::Errors::InvalidCommandOption.new('deploy', '--fargate')
75 | end
76 | options[:deploy][:fargate] = true
77 | end
78 |
79 | cicd_deploy_msg = "Deploy the stack that builds CICD flow"
80 | opts.on('--cicd', cicd_deploy_msg) do
81 | if options[:deploy].nil?
82 | raise Aws::RailsProvisioner::Errors::InvalidCommandOption.new('deploy', '--cicd')
83 | end
84 | options[:deploy][:cicd] = true
85 | end
86 |
87 | opts.on('-h', '--help', 'Show help') do
88 | puts opts
89 | exit
90 | end
91 |
92 | end.parse!
93 |
94 | config = Aws::RailsProvisioner::Utils.parse(options[:file_path])
95 | cdk_dir = options[:cdk_dir] || config[:cdk_dir] || 'cdk-sample'
96 | config[:cdk_dir] = cdk_dir
97 |
98 | # prepare codebuilder params
99 | if options[:build]
100 | options[:build][:profile] = options[:profile]
101 | options[:build][:file_path] = options[:file_path]
102 | if svc_name = options[:service_name]
103 | config[:services][svc_name].merge!(options[:build])
104 | else
105 | config[:services].each do |_, svc|
106 | svc.merge!(options[:build])
107 | end
108 | end
109 | end
110 | code_builder = Aws::RailsProvisioner::CDKCodeBuilder.new(config)
111 |
112 | if options[:build]
113 | Aws::RailsProvisioner::CDKBuilder.new(
114 | cdk_dir: cdk_dir,
115 | source_files: code_builder.source_files,
116 | default_stack: code_builder.default_stack,
117 | default_test: code_builder.default_test,
118 | services: code_builder.services
119 | ).run
120 | elsif options[:deploy]
121 | deploy_params = {
122 | cdk_dir: cdk_dir,
123 | services: code_builder.services,
124 | stack_prefix: code_builder.stack_prefix,
125 | service_name: options[:service_name]
126 | }
127 | if options[:profile]
128 | deploy_params[:profile] = options[:profile]
129 | end
130 | deploy_params.merge!(options[:deploy])
131 | deployer = Aws::RailsProvisioner::CDKDeployer.new(deploy_params)
132 | deployer.run
133 | end
134 |
--------------------------------------------------------------------------------
/buildspecs/buildspec-db.yml:
--------------------------------------------------------------------------------
1 | version: 0.2
2 |
3 | phases:
4 | install:
5 | commands:
6 | - gem install bundler
7 | - bundler install
8 | pre_build:
9 | commands:
10 | - DATABASE_URL=$DATABASE_URL RAILS_ENV=$RAILS_ENV bundle exec rake db:migrate
11 |
--------------------------------------------------------------------------------
/buildspecs/buildspec-ecr.yml:
--------------------------------------------------------------------------------
1 | version: 0.2
2 |
3 | phases:
4 | install:
5 | commands:
6 | - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
7 | pre_build:
8 | commands:
9 | - TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION
10 | - echo $TAG
11 | - docker build -t $REPO_NAME:$TAG .
12 | - docker tag $REPO_NAME:$TAG $REPO_PREFIX:$TAG
13 | post_build:
14 | commands:
15 | - docker push $REPO_PREFIX:$TAG
16 | - CONTAINER_NAME='FargateTaskContainer'
17 | - URI=$REPO_PREFIX:$TAG
18 | - echo Writing image definitions file...
19 | - printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $URI > imagedefinitions.json
20 | artifacts:
21 | files: imagedefinitions.json
22 |
--------------------------------------------------------------------------------
/doc-src/templates/default/layout/html/footer.erb:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/doc-src/templates/default/layout/html/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= erb(:headers) %>
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
22 |
23 |
24 |
25 | <%= yieldall %>
26 |
27 |
28 | <%= erb(:footer) %>
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/multi_service.yml:
--------------------------------------------------------------------------------
1 | version: '0'
2 |
3 | vpc:
4 | max_azs: 2
5 | enable_dns: true
6 | services:
7 | rails_foo:
8 | source_path: /path/to/rails_foo
9 | fargate:
10 | desired_count: 1
11 | public: true
12 | envs:
13 | PORT: 80
14 | db_cluster:
15 | engine: aurora-mysql
16 | db_name: app_development
17 | scaling:
18 | max_capacity: 2
19 | on_cpu:
20 | target_util_percent: 80
21 | scale_in_cool_down: 300
22 | rails_no_db:
23 | source_path: /path/to/no_db
24 | fargate:
25 | desired_count: 1
26 | public: true
27 | envs:
28 | PORT: 80
29 |
--------------------------------------------------------------------------------
/examples/no_db.yml:
--------------------------------------------------------------------------------
1 | version: '0'
2 | vpc:
3 | max_azs: 2
4 | services:
5 | rails_no_db:
6 | source_path: /path/to/rails
7 | fargate:
8 | desired_count: 1
9 | task:
10 | environment:
11 | RAILS_LOG_TO_STDOUT: true
12 |
--------------------------------------------------------------------------------
/examples/single_service.yml:
--------------------------------------------------------------------------------
1 | version: '0'
2 |
3 | vpc:
4 | max_azs: 2
5 | enable_dns: true
6 | services:
7 | rails_foo:
8 | source_path: /path/to/rails_foo
9 | fargate:
10 | desired_count: 5
11 | public: true
12 | db_cluster:
13 | engine: aurora-postgresql
14 | db_name: app_development
15 | cicd:
16 | pipeline_name: RailsFoo
17 |
--------------------------------------------------------------------------------
/examples/tiny.yml:
--------------------------------------------------------------------------------
1 | version: '0'
2 |
3 | vpc:
4 | enable_dns: true
5 | services:
6 | my_rails:
7 | source_path: ./path/to/my/rails
8 | fargate:
9 | public: true
10 | db_cluster: # skip if no db
11 | engine: aurora-postgresql
12 | db_name: app_development
13 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner.rb:
--------------------------------------------------------------------------------
1 | # services
2 | require 'aws-sdk-rds'
3 |
4 | # code gen helpers
5 | require_relative 'aws-rails-provisioner/cdk_builder'
6 | require_relative 'aws-rails-provisioner/cdk_deployer'
7 | require_relative 'aws-rails-provisioner/cdk_code_builder'
8 |
9 | # utils
10 | require_relative 'aws-rails-provisioner/services'
11 | require_relative 'aws-rails-provisioner/service'
12 | require_relative 'aws-rails-provisioner/utils'
13 | require_relative 'aws-rails-provisioner/errors'
14 |
15 | # init stack
16 | require_relative 'aws-rails-provisioner/vpc'
17 | require_relative 'aws-rails-provisioner/subnet_selection'
18 |
19 | # CICD stack
20 | require_relative 'aws-rails-provisioner/code_build'
21 | require_relative 'aws-rails-provisioner/build'
22 | require_relative 'aws-rails-provisioner/migration'
23 |
24 | # fargate stack
25 | require_relative 'aws-rails-provisioner/db_cluster'
26 | require_relative 'aws-rails-provisioner/fargate'
27 | require_relative 'aws-rails-provisioner/scaling'
28 |
29 | # views
30 | require_relative 'aws-rails-provisioner/view'
31 | require_relative 'aws-rails-provisioner/views/init_stack'
32 | require_relative 'aws-rails-provisioner/views/fargate_stack'
33 | require_relative 'aws-rails-provisioner/views/pipeline_stack'
34 | require_relative 'aws-rails-provisioner/views/project'
35 |
36 | # version
37 | require_relative 'aws-rails-provisioner/version'
38 |
39 | module Aws
40 | module RailsProvisioner
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/build.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class Build < Aws::RailsProvisioner::CodeBuild
3 |
4 | # An AWS CodeBuild Project that build, tag and
5 | # push image to AWS ECR Repo
6 | #
7 | # configuration for :build
8 | #
9 | # @param [Hash] options
10 | #
11 | # @option options [String] :project_name name for
12 | # the CodeBuild project, default to 'RailsProvisionerImageBuild'
13 | #
14 | # @option options [String] :description description for this
15 | # CodeBuild project, default to 'build, tag and push image to ECR'
16 | #
17 | # @option options [String] :buildspec buildspec.yml file path, default
18 | # to `buildspec-ecr.yml` under root directory, using template
19 | # under `buildspecs/`
20 | #
21 | # @option options [String] :build_image default to codebuild `ubuntu_14_04_docker_18_09_0`
22 | # full list of supported images see:
23 | # https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-codebuild.LinuxBuildImage.html
24 | #
25 | # @option options [Integer] :timeout number of minutes after which
26 | # CodeBuild stops the build if it’s not complete
27 | #
28 | def initialize(options = {})
29 | unless options[:description]
30 | options[:description] = 'build, tag and push image to ECR'
31 | end
32 | unless options[:buildspec]
33 | options[:buildspec] = 'buildspec-ecr.yml'
34 | end
35 | unless options[:build_image]
36 | options[:build_image] = 'ubuntu_14_04_docker_18_09_0'
37 | end
38 | super(options)
39 | end
40 |
41 | end
42 | end
43 |
44 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/cdk_builder.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class CDKBuilder
3 |
4 | def initialize(options = {})
5 | @source_files = options[:source_files]
6 | @default_stack = options[:default_stack]
7 | @default_test = options[:default_test]
8 | @cdk_dir = options[:cdk_dir]
9 | @services = options[:services]
10 | end
11 |
12 | def run
13 | _init_cdk
14 | # code to files
15 | files = @source_files
16 | files.each do |path, code|
17 | if File.exists?(path)
18 | puts "replacing #{path}"
19 | else
20 | puts "creating #{path}"
21 | end
22 | FileUtils.mkdir_p(File.dirname(path))
23 | File.open(path, 'w') do |f|
24 | f.write(code)
25 | end
26 | end
27 | _install_dependencies
28 | _npm_build
29 | end
30 |
31 | private
32 |
33 | def _init_cdk
34 | unless Dir.exist?(@cdk_dir)
35 | FileUtils.mkdir_p(@cdk_dir)
36 | unless @dryrun
37 | Dir.chdir(@cdk_dir) do
38 | `npm i -g aws-cdk`
39 | `cdk init app --language=typescript`
40 | end
41 |
42 | if File.exists?(@default_stack)
43 | FileUtils.rm_f(@default_stack)
44 | end
45 | if File.exists?(@default_test)
46 | FileUtils.rm_f(@default_test)
47 | end
48 | end
49 | end
50 | end
51 |
52 | def _install_dependencies
53 | pkgs = @services.inject(Set.new) do |set, svc|
54 | set.merge(svc.packages)
55 | set
56 | end
57 | Dir.chdir(@cdk_dir) do
58 | pkgs.each do |pkg|
59 | puts "installing pkg: #{pkg} ..."
60 | `npm install #{pkg}`
61 | end
62 | end
63 | end
64 |
65 | def _npm_build
66 | Dir.chdir(@cdk_dir) do
67 | puts "running npm run build ..."
68 | `npm run build --verbose`
69 | end
70 | end
71 |
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/cdk_code_builder.rb:
--------------------------------------------------------------------------------
1 | require 'set'
2 |
3 | module Aws::RailsProvisioner
4 | class CDKCodeBuilder
5 |
6 | # Generates CDK projects with separate stacks:
7 | #
8 | # * InitStack - VPC and Fargate Cluster
9 | # * FargetStack - Fargate Service, AutoScaling, ALB
10 | # * PipelineStack - CICD CodePipline (when enabled) that:
11 | # * Source: CodeCommit Repository when host rails applications
12 | # * Build : CodeBuild Project that takes care of image build, tag(commit id) and push to ECR
13 | # * Migration: CodeBuild Project that run migration inside DB subnet (optional)
14 | # * Deploy: CodeDeploy that deploys tagged(commit id) image from ECR to Fargate service
15 | #
16 | # @param [Hash] options
17 | #
18 | # @option options [String] :cdk_dir Directory path for
19 | # generated CDK code, defaults to "cdk-sample"
20 | #
21 | # @option options [Hash] :vpc VPC configurations
22 | # @see {Aws::RailsProvisioner::Vpc}
23 | #
24 | # @option options [Hash] :services
25 | #
26 | # @option options [Hash] :db_cluster DB cluster configurations
27 | # @see {Aws::RailsProvisioner::DBCluster}
28 | #
29 | # @option options [required, String] :source_path path to the directory containing
30 | # Rails Application source
31 | #
32 | # @option options [Hash] :fargate Fargate service configurations
33 | # @example: at `aws-rails-provisioner.yml`
34 | # fargate:
35 | # desired_count: 5
36 | # task:
37 | # environment:
38 | # RAILS_LOG_TO_STDOUT: true
39 | # @see {Aws::RailsProvisioner::Fargate}
40 | #
41 | # @option options [Hash] :scaling Scaling definitions for the Fargate service
42 | # @example: at `aws-rails-provisioner.yml`
43 | # scaling:
44 | # max_capacity: 7
45 | # on_cpu:
46 | # target_util_percent: 40
47 | # on_request:
48 | # requests_per_target: 20000
49 | # @see {Aws::RailsProvisioner::Scaling}
50 | #
51 | # @option options [Hash] :loadbalancer Application Loadbalancer that front
52 | # the Fargate service
53 | # @example: at `aws-rails-provisioner.yml`
54 | # loadbalancer:
55 | # internet_facing: true
56 | # @see {Aws::RailsProvisioner::LoadBalancer}
57 | #
58 | # @option options [Hash] :cicd configurations for CICD that covers:
59 | # source, build, database migration with code pipline
60 | # @see {Aws::RailsProvisioner::Pipeline}
61 | #
62 | def initialize(options = {})
63 | @cdk_dir = options[:cdk_dir] || "cdk-sample"
64 | @stack_prefix = _stack_prefix
65 |
66 | # init, vpc & fargate cluster
67 | @vpc = options[:vpc] || {}
68 |
69 | # fargate services defs
70 | # including related db, alb, scaling, cicd etc.
71 | @services = Aws::RailsProvisioner::ServiceEnumerator.new(options[:services] || {})
72 |
73 | # npm cdk service packages need to be installed
74 | @packages = Set.new
75 | end
76 |
77 | # @return [Enumerable]
78 | attr_reader :services
79 |
80 | # @return [Set]
81 | attr_reader :packages
82 |
83 | # @return [String]
84 | attr_reader :cdk_dir
85 |
86 | # @api private
87 | # @return [String]
88 | attr_reader :stack_prefix
89 |
90 | def source_files
91 | Enumerator.new do |y|
92 | y.yield("#{@cdk_dir}/lib/#{@cdk_dir}-init-stack.ts", init_stack)
93 | @services.each do |svc|
94 | y.yield("#{@cdk_dir}/lib/#{svc.path_prefix}-fargate-stack.ts",
95 | svc.fargate_stack)
96 | y.yield("#{@cdk_dir}/lib/#{svc.path_prefix}-pipeline-stack.ts",
97 | svc.pipeline_stack) if svc.enable_cicd
98 | @packages.merge(svc.packages)
99 | end
100 |
101 | y.yield("#{@cdk_dir}/bin/#{@cdk_dir}.ts", project)
102 | end
103 | end
104 |
105 | def default_stack
106 | # CDK init cmd default empty stack
107 | "#{@cdk_dir}/lib/#{@cdk_dir}-stack.ts"
108 | end
109 |
110 | def default_test
111 | # CDK init cmd default test file
112 | "#{@cdk_dir}/test/#{@cdk_dir}.test.ts"
113 | end
114 |
115 | # @api private
116 | def project
117 | Aws::RailsProvisioner::Views::Project.new(
118 | stack_prefix: @stack_prefix,
119 | path_prefix: @cdk_dir,
120 | services: @services
121 | ).render
122 | end
123 |
124 | # @api private
125 | def init_stack
126 | init = Aws::RailsProvisioner::Views::InitStack.new(
127 | vpc: @vpc,
128 | stack_prefix: @stack_prefix
129 | )
130 | @packages.merge(init.packages)
131 | init.render
132 | end
133 |
134 | private
135 |
136 | def _stack_prefix
137 | dir = @cdk_dir.dup
138 | dir.split('-').map do |part|
139 | part[0] = part[0].upcase
140 | part
141 | end.join
142 | end
143 |
144 | end
145 | end
146 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/cdk_deployer.rb:
--------------------------------------------------------------------------------
1 | require 'open3'
2 |
3 | module Aws::RailsProvisioner
4 | class CDKDeployer
5 |
6 | def initialize(options = {})
7 | @cdk_dir = options[:cdk_dir]
8 | @services = options[:services]
9 | @stack_prefix = options[:stack_prefix]
10 |
11 | both = options[:fargate].nil? && options[:cicd].nil?
12 | @fargate_stack = both || !!options[:fargate]
13 | @pipeline_stack = both || !!options[:cicd]
14 |
15 | @init_stack_only = !!options[:init]
16 |
17 | @svc_name = options[:service_name]
18 | @profile = options[:profile]
19 | end
20 |
21 | def run
22 | Dir.chdir(@cdk_dir) do
23 | `cdk bootstrap`
24 | deploy_init_stack
25 | unless @init_stack_only
26 | if @svc_name
27 | deploy_svc(@services[@svc_name].stack_prefix)
28 | else
29 | @services.each do |svc|
30 | deploy_svc(svc.stack_prefix)
31 | end
32 | end
33 | end
34 | end
35 | end
36 |
37 | private
38 |
39 | def deploy_init_stack
40 | opts = @profile ? " --profile #{@profile}" : ''
41 | # disable prompts in deploy command
42 | opts += " --require-approval never"
43 | cmd = "cdk deploy #{@stack_prefix}InitStack#{opts}"
44 | Open3.popen3(cmd) do |_, stdout, stderr, _|
45 | puts stdout.read
46 | puts stderr.read
47 | end
48 | end
49 |
50 | def deploy_svc(stack_prefix)
51 | opts = @profile ? " --profile #{@profile}" : ''
52 | # disable prompts in deploy command
53 | opts += " --require-approval never"
54 | if @fargate_stack
55 | `cdk deploy #{stack_prefix}FargateStack#{opts}`
56 | end
57 | if @pipeline_stack
58 | `cdk deploy #{stack_prefix}PipelineStack#{opts}`
59 | end
60 | end
61 |
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/code_build.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class CodeBuild
3 |
4 | def initialize(options = {})
5 | @project_name = options[:project_name]
6 | @description = options[:description]
7 | @buildspec = options[:buildspec]
8 | @image = options[:build_image].upcase
9 | @timeout = options[:timeout]
10 | end
11 |
12 | # @return [String]
13 | attr_reader :project_name
14 |
15 | # @return [String]
16 | attr_reader :description
17 |
18 | # @return [String]
19 | attr_reader :buildspec
20 |
21 | # @return [Integer]
22 | attr_reader :timeout
23 |
24 | # @return [String]
25 | attr_reader :image
26 |
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/db_cluster.rb:
--------------------------------------------------------------------------------
1 | require 'aws-sdk-rds'
2 |
3 | module Aws::RailsProvisioner
4 | class DBCluster
5 |
6 | # Configuration value under :db_cluster
7 | #
8 | # @param [Hash] options
9 | #
10 | # @option options [String] :username DB username, default
11 | # to `SERVICE_NAMEDBAdminUser`
12 | #
13 | # @option options [required, String] :engine provide the engine for
14 | # the database cluster: `aurora`, `aurora-mysql` or `aurora-postgresql`
15 | #
16 | # @option options [String] :engine_version version of the database to start,
17 | # when not provided, default for the engine is used.
18 | #
19 | # @option options [String] :instance_type type of instance to start
20 | # for the replicas, if not provided, a default :instance_type will
21 | # be populated responding to the type of engine provided.
22 | #
23 | # @option options [String] :instance_subnet where to place the instances
24 | # within the VPC, default to `isolated` subnet (recommend)
25 | #
26 | # @option options [Hash] :backup backup config for DB databases
27 | #
28 | # @example: at `aws-rails-provisioner.yml`
29 | # backup:
30 | # retention_days: 7
31 | # preferred_window: '01:00-02:00'
32 | # @see {Aws::RailsProvisioner::DBCluster::BackUp}
33 | #
34 | # @option options [String] :cluster_identifier An optional identifier for
35 | # the cluster, If not supplied, a name is automatically generated
36 | #
37 | # @option options [required, String] :db_name name for the DB inside the cluster
38 | #
39 | # @option options [String] :removal_policy policy to apply when
40 | # the cluster and its instances are removed from the stack or replaced during an update.
41 | # `retain`, `destroy` available, default to `retain`
42 | #
43 | # @option options [String] :instance_identifier every replica is named
44 | # by appending the replica number to this string, default to 'Instance'
45 | #
46 | # @option options [Integer] :instances how many replicas/instances
47 | # to create, defaults to 2
48 | #
49 | # @option options [Integer] :port if not provided, default value is
50 | # based on :engine
51 | #
52 | # @option options [String] :kms_key_arn the he KMS key arn for storage encryption
53 | #
54 | # @option options [String] :preferred_maintenance_window daily time
55 | # range in 24-hours UTC format in which backups preferably execute
56 | #
57 | # @option oprions [Hash] :parameter_group default value is based on engine
58 | # following example shows default for aurora postgres
59 | #
60 | # @example: at `aws-rails-provisioner.yml`
61 | # parameter_group:
62 | # family: 'aurora-postgresql11'
63 | # description: 'created by AWS RailsProvisioner'
64 | # parameters:
65 | # key: value
66 | # @see {Aws::RailsProvisioner::DBCluster::ParameterGroup}
67 | #
68 | # @see AWS CDK DatabaseClusterProps
69 | def initialize(options = {})
70 | @username = options[:username] || 'DBAdminUser'
71 |
72 | @engine = _engine_type(options[:engine])
73 | @engine_version = options[:engine_version]
74 | @postgres = @engine == 'AURORA_POSTGRESQL'
75 | unless @postgres
76 | # MySql require username between 1 to 16
77 | @username = options[:username][0..15]
78 | end
79 |
80 | @instance_type = options[:instance_type] || _default_instance_type
81 | @instance_subnet = Aws::RailsProvisioner::Utils.subnet_type(
82 | options[:instance_subnet] || 'isolated')
83 |
84 | @backup = BackUp.new(options[:backup]) if options[:backup]
85 | @db_name = options.fetch(:db_name)
86 | @cluster_identifier = options[:cluster_identifier]
87 | @removal_policy = Aws::RailsProvisioner::Utils.removal_policy(
88 | options[:removal_policy] || 'retain')
89 |
90 | @instance_identifier = options[:instance_identifier]
91 | @instances = options[:instances] || 2
92 |
93 | @kms_key = options[:kms_key_arn]
94 |
95 | @port = options[:port]
96 | @preferred_maintenance_window = options[:preferred_maintenance_window]
97 |
98 | pg_opts = options[:parameter_group] || {}
99 | pg_opts[:profile] = options[:profile] if options[:profile]
100 | pg_opts[:stub_client] = options[:stub_client] # test only
101 | @parameter_group = ParameterGroup.new(@engine, pg_opts)
102 | @db_port = @port || _default_db_port
103 | end
104 |
105 | # @return [Boolean]
106 | attr_reader :postgres
107 |
108 | # @return [String]
109 | attr_reader :username
110 |
111 | # @return [String]
112 | attr_reader :engine
113 |
114 | # @return [String]
115 | attr_reader :engine_version
116 |
117 | # @return [String]
118 | attr_reader :instance_type
119 |
120 | # @return [String]
121 | attr_reader :instance_subnet
122 |
123 | # @return [String]
124 | attr_reader :instance_identifier
125 |
126 | # @return [Integer]
127 | attr_reader :instances
128 |
129 | # @return [String]
130 | attr_reader :db_name
131 |
132 | # @return [String]
133 | attr_reader :cluster_identifier
134 |
135 | # @return [String]
136 | attr_reader :removal_policy
137 |
138 | # @return [String]
139 | attr_reader :kms_key
140 |
141 | # @return [Integer]
142 | attr_reader :port
143 |
144 | # @return [String]
145 | attr_reader :preferred_maintenance_window
146 |
147 | # @return [ParameterGroup]
148 | attr_reader :parameter_group
149 |
150 | # @return [BackUp]
151 | attr_reader :backup
152 |
153 | # @return [Integer]
154 | attr_reader :db_port
155 |
156 | class BackUp
157 |
158 | # @param [Hash] options
159 | #
160 | # @option options [required, Integer] :retention_days
161 | # days to retain the backup
162 | #
163 | # @option options [String] :preferred_window A daily
164 | # time range in 24-hours UTC format in which backups
165 | # preferably execute
166 | #
167 | def initialize(options = {})
168 | @retention_days = options[:retention_days]
169 | @preferred_window = options[:preferred_window]
170 | end
171 |
172 | # @return [Integer]
173 | attr_reader :retention_days
174 |
175 | # @return [String]
176 | attr_reader :preferred_window
177 |
178 | end
179 |
180 | class ParameterGroup
181 |
182 | # @param [Hash] options
183 | #
184 | # @option options [String] :family
185 | #
186 | # @option options [String] :description
187 | #
188 | # @option options [Hash] :parameters
189 | #
190 | def initialize(engine, options = {})
191 | # client
192 | @profile = options[:profile]
193 |
194 | @engine = engine
195 | @family = options[:family] || _default_family
196 | @description = options[:description] || 'created by AWS RailsProvisioner'
197 | @cfn = !!options[:parameters]
198 | unless @cfn
199 | suffix = @engine.downcase.gsub(/_/, '-')
200 | @name = "aws-rails-provisioner-default-#{suffix}"
201 | _create_default_pg(options[:stub_client])
202 | else
203 | @parameters = Aws::RailsProvisioner::Utils.to_pairs(options[:parameters])
204 | end
205 | end
206 |
207 | # @return [Boolean]
208 | attr_reader :cfn
209 |
210 | # @return [String]
211 | attr_reader :name
212 |
213 | # @return [String]
214 | attr_reader :family
215 |
216 | # @return [String]
217 | attr_reader :description
218 |
219 | # @return [Array]
220 | attr_reader :parameters
221 |
222 | private
223 |
224 | def _default_family
225 | case @engine
226 | when 'AURORA_POSTGRESQL' then 'aurora-postgresql11'
227 | when 'AURORA_MYSQL' then 'aurora-mysql5.7'
228 | when 'AURORA' then 'aurora5.6'
229 | else
230 | msg = 'Failed to locate a default family type for :engine'\
231 | ' provided, please provide :family for :parameter_group'
232 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
233 | end
234 | end
235 |
236 | # CDK creation requires parameters input
237 | def _create_default_pg(stub_client)
238 | if stub_client
239 | rds = Aws::RDS::Client.new(stub_responses: true)
240 | else
241 | rds = @profile ? Aws::RDS::Client.new(profile: @profile) :
242 | Aws::RDS::Client.new
243 | end
244 |
245 | begin
246 | rds.create_db_cluster_parameter_group(
247 | db_parameter_group_family: @family,
248 | description: @description,
249 | db_cluster_parameter_group_name: @name
250 | )
251 | rescue Aws::RDS::Errors::DBParameterGroupAlreadyExists
252 | # Cluster Parameter Group already exists
253 | # do nothing
254 | end
255 | end
256 |
257 | end
258 |
259 | private
260 |
261 | def _engine_type(engine)
262 | type = engine.dup
263 | case type.downcase
264 | when 'aurora-postgresql' then 'AURORA_POSTGRESQL'
265 | when 'aurora-mysql' then 'AURORA_MYSQL'
266 | when 'aurora' then 'AURORA'
267 | else
268 | msg = "DB engine: #{engine.inspect} not supported"
269 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
270 | end
271 | end
272 |
273 | def _default_instance_type
274 | case @engine
275 | when 'AURORA_POSTGRESQL' then 'r4.large'
276 | when 'AURORA_MYSQL' then 'r5.large'
277 | when 'AURORA' then 'r5.large'
278 | else
279 | msg = 'Failed to locate a default instance type for :engine'\
280 | ' provided, please provide :instance_type'
281 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
282 | end
283 | end
284 |
285 | def _default_db_port
286 | case @engine
287 | when 'AURORA_POSTGRESQL' then 3306
288 | when 'AURORA_MYSQL' then 3306
289 | when 'AURORA' then 3306
290 | else
291 | msg = 'Failed to locate a default db port for :engine'\
292 | ' provided, please provide :port'
293 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
294 | end
295 |
296 | end
297 |
298 | end
299 | end
300 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/errors.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | module Errors
3 |
4 | class ValidationError < RuntimeError; end
5 |
6 | class InvalidCommandOption < RuntimeError
7 |
8 | def initialize(type, option)
9 | msg = "invalid option: #{option}, #{option} is valid for `#{type}` command."
10 | super(msg)
11 | end
12 |
13 | end
14 |
15 | class InvalidYAMLFile < RuntimeError
16 |
17 | def initialize
18 | msg = "Invalid `aws-rails-provisioner.yml` file provided."
19 | super(msg)
20 | end
21 |
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/fargate.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class Fargate
3 |
4 | # Configuration value under :fargate
5 | #
6 | # @param [Hash] options
7 | #
8 | # @option options [Integer] :desired_count number of
9 | # desired copies of running tasks, default to 2
10 | #
11 | # @option options [Boolean] :public when `true` (default)
12 | # Application Load Balancer will be internet-facing
13 | #
14 | # @option options [String] :domain_name domain name for the service,
15 | # e.g. api.example.com
16 | #
17 | # @option options [String] :domain_zone route53 hosted zone for the domain,
18 | # e.g. "example.com.".
19 | #
20 | # @option options [String] :service_name name for the Fargate service
21 | #
22 | # @option options [Integer] :memory default to 512 (MB)
23 | #
24 | # @option options [Integer] :cpu default to 256 (units)
25 | #
26 | # @option options [Hash] :envs environment variable pairs
27 | # for the container used by this Fargate task
28 | #
29 | # @option options [String] :container_name defaults to `FargateTaskContainer`
30 | #
31 | # @option options [Integer] :container_port defaults to 80
32 | #
33 | # @option options [String] :certificate certificate arn. Certificate Manager
34 | # certificate to associate with the load balancer. Setting this option
35 | # will set the load balancer port to 443.
36 | #
37 | def initialize(options = {})
38 | # code gen only
39 | @has_db = !!options[:has_db]
40 |
41 | @service_name = options[:service_name]
42 | @desired_count = options[:desired_count] || 2
43 | @public = !!options[:public]
44 | @domain_name = options[:domain_name]
45 | @domain_zone = options[:domain_zone]
46 | @certificate = options[:certificate]
47 |
48 | @memory = options[:memory] || 512
49 | @cpu = options[:cpu] || 256
50 | @envs = Aws::RailsProvisioner::Utils.to_pairs(options[:envs]) if options[:envs]
51 | @container_port = options[:container_port] || 80
52 | @container_name = options[:container_name] || 'FargateTaskContainer'
53 | end
54 |
55 | # @api private
56 | # @return [Boolean]
57 | attr_reader :has_db
58 |
59 | # @return [Integer]
60 | attr_reader :desired_count
61 |
62 | # @return [String]
63 | attr_reader :service_name
64 |
65 | # @return [Boolean]
66 | attr_reader :public
67 |
68 | # @return [String]
69 | attr_reader :domain_name
70 |
71 | # @return [String]
72 | attr_reader :domain_zone
73 |
74 | # @return [String]
75 | attr_reader :certificate
76 |
77 | # @return [Array]
78 | attr_reader :envs
79 |
80 | # @return [Integer]
81 | attr_reader :memory
82 |
83 | # @return [Integer]
84 | attr_reader :cpu
85 |
86 | # @return [Integer]
87 | attr_reader :container_port
88 |
89 | # @return [String]
90 | attr_reader :container_name
91 |
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/migration.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class Migration < Aws::RailsProvisioner::CodeBuild
3 |
4 | # An AWS CodeBuild Project that runs DB migration
5 | # for the Ruby on Rails App inside private subnet of
6 | # the VPC
7 | #
8 | # configuration for :migration
9 | #
10 | # @param [Hash] options
11 | #
12 | # @option options [String] :project_name name for
13 | # the CodeBuild project, default to 'SERVICE_NAMEDBMigration'
14 | #
15 | # @option options [String] :description description for this
16 | # CodeBuild project, default to 'running DB Migration for
17 | # the rails app inside private subnet'
18 | #
19 | # @option options [String] :buildspec buildspec.yml file path, default
20 | # to `buildspec-db.yml` under root directory, using template under
21 | # `buildspecs/`
22 | #
23 | # @option options [String] :build_image default to codebuild `standard_4_0`
24 | # full list of supported images see:
25 | # https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-codebuild.LinuxBuildImage.html
26 | #
27 | # @option options [Integer] :timeout number of minutes after which
28 | # CodeBuild stops the build if it’s not complete
29 | #
30 | def initialize(options = {})
31 | unless options[:description]
32 | options[:description] = 'running DB Migration for'\
33 | ' the rails app inside private subnet'
34 | end
35 | unless options[:buildspec]
36 | options[:buildspec] = 'buildspec-db.yml'
37 | end
38 | unless options[:build_image]
39 | options[:build_image] = 'standard_4_0'
40 | end
41 | # TODO envs support?
42 | super(options)
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/parser.rb:
--------------------------------------------------------------------------------
1 | require 'yaml'
2 |
3 | module Aws::RailsProvisioner
4 | module Parser
5 |
6 | def parse(file_path)
7 | config = YAML.load(File.read(file_path))
8 | symbolize_keys(config)
9 | end
10 |
11 | private
12 |
13 | def symbolize_keys(hash)
14 | hash.inject({}) do |h, (k,v)|
15 | v = symbolize_keys(v) if v.respond_to?(:keys)
16 | h[k.to_sym] = v
17 | h
18 | end
19 | end
20 |
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/scaling.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class Scaling
3 |
4 | # Configuration for Fargate service scaling
5 | # @param [Hash] options
6 | #
7 | # @option options [required, Integer] :max_capacity maximum capacity to scale to
8 | #
9 | # @option options [Integer] :min_capacity minimum capacity to scale to
10 | # default to 1
11 | #
12 | # @option options [Hash] :on_cpu scale out or in to achieve a
13 | # target CPU utilization
14 | # @example: at `aws-rails-provisioner.yml`
15 | # scaling:
16 | # on_cpu:
17 | # target_util_percent: 80
18 | # scale_in_cool_down: 300
19 | # @see {Aws::RailsProvisioner::Scaling::BaseScaling}
20 | #
21 | # @option options [Hash] :on_memory scale out or in to achieve a
22 | # target memory utilization
23 | # @example: at `aws-rails-provisioner.yml`
24 | # scaling:
25 | # on_memory:
26 | # target_util_percent: 80
27 | # scale_out_cool_down: 200
28 | # @see {Aws::RailsProvisioner::Scaling::BaseScaling}
29 | #
30 | # @option options [Hash] :on_metric scale out or in based on a
31 | # metric value
32 | # @example: at `aws-rails-provisioner.yml`
33 | # on_metric:
34 | # adjustment_type: percentchangeincapacity
35 | # min_adjustment_magnitude: 10
36 | # cooldown: 300
37 | # metric:
38 | # name: foo
39 | # @see {Aws::RailsProvisioner::Scaling::MetricScaling}
40 | #
41 | # @option options [Hash] :on_custom_metric scale out or in to track
42 | # a custom metric
43 | # @example: at `aws-rails-provisioner.yml`
44 | # scaling:
45 | # on_custom_metric:
46 | # target_value: 100
47 | # scale_in_cooldown: 300
48 | # scale_out_cooldown: 500
49 | # metric:
50 | # name: foo
51 | # @see {Aws::RailsProvisioner::Scaling::MetricScaling}
52 | #
53 | # @option options [Hash] :on_request scale out or in to achieve a
54 | # target ALB request count per target
55 | # @example: at `aws-rails-provisioner.yml`
56 | # scaling:
57 | # on_request:
58 | # requests_per_target: 100000
59 | # disable_scale_in: true
60 | # @see {Aws::RailsProvisioner::Scaling::BaseScaling}
61 | #
62 | # @option options [Hash] :on_schedule scale out or in based on time
63 | # @example: at `aws-rails-provisioner.yml`
64 | # scaling:
65 | # on_schedule:
66 | # schedule: 'at(yyyy-mm-ddThh:mm:ss)'
67 | # max_capacity: 10
68 | # min_capacity: 5
69 | # @see {Aws::RailsProvisioner::Scaling::ScheduleScaling}
70 | #
71 | def initialize(options = {})
72 | @max_capacity = options.fetch(:max_capacity)
73 | @min_capacity = options[:min_capacity]
74 |
75 | @on_cpu = _scaling_props(:cpu, options[:cpu])
76 | @on_memory = _scaling_props(:memory, options[:memory])
77 | @on_metric = _scaling_props(:metric, options[:metric])
78 | @on_request = _scaling_props(:request, options[:request])
79 | @on_schedule = _scaling_props(:schedule, options[:schedule])
80 | @to_track_custome_metric = _scaling_props(:custom, options[:custom_metric])
81 | end
82 |
83 | # @return [Integer]
84 | attr_reader :max_capacity
85 |
86 | # @return [Integer]
87 | attr_reader :min_capacity
88 |
89 | # @return [BaseScaling]
90 | attr_reader :on_cpu
91 |
92 | # @return [BaseScaling]
93 | attr_reader :on_memory
94 |
95 | # @return [BaseScaling]
96 | attr_reader :on_request
97 |
98 | # @return [MetricScaling]
99 | attr_reader :on_metric
100 |
101 | # @return [MetricScaling]
102 | attr_reader :to_track_custom_metric
103 |
104 | # @return [ScheduleScaling]
105 | attr_reader :on_schedule
106 |
107 | private
108 |
109 | def _scaling_props(type, opts)
110 | return if opts.nil?
111 | case type
112 | when :cpu, :memory, :request
113 | BaseScaling.new(type, opts)
114 | when :metric, :custom
115 | MetricScaling.new(type, opts)
116 | when :schedule
117 | ScheduleScaling.new(type, opts)
118 | else
119 | raise Aws::RailsProvisioner::Errors::ValidationError.new(
120 | 'Unsupported Scaling type.')
121 | end
122 | end
123 |
124 | class MetricScaling
125 |
126 | # Configuration for metric scaling policy
127 | # @param [Hash] options
128 | #
129 | # @option options [String] :adjustment_type how the adjustment numbers
130 | # inside 'intervals' are interpreted, available for `:on_metric`, supporting
131 | # types: `change_in_capacity`, `percent_change_in_capacity` or `exact_capacity`
132 | #
133 | # @option options [Integer] :min_adjustment_magnitude available for
134 | # `:on_metric`, when :adjustment_type set to `percentchangeincapacity`
135 | # minimum absolute number to adjust capacity with as result of percentage scaling.
136 | #
137 | # @option options [Integer] :cooldown grace period after scaling activity
138 | # in seconds, available for `:on_metric`
139 | #
140 | # @option options [Array] :scaling_steps intervals for scaling, array of
141 | # Aws::RailsProvisioner::Scaling::MetricScaling::ScalingInterval, available for
142 | # `:on_metric`
143 | # @example: at `aws-rails-provisioner.yml`
144 | # on_metric:
145 | # scaling_steps:
146 | # -
147 | # change: 10
148 | # lower: 30
149 | # upper: 60
150 | # -
151 | # change: 20
152 | # lower: 0
153 | # upper: 20
154 | # @see {Aws::RailsProvisioner::Scaling::MetricScaling::ScalingInterval}
155 | #
156 | # @option options [Hash] :metric
157 | # @example: at `aws-rails-provisioner.yml`
158 | # on_metric:
159 | # metric:
160 | # name: foo
161 | # namespace: bar
162 | # dimensions:
163 | # key:value
164 | # on_custom_metric:
165 | # metric:
166 | # name: baz
167 | # @see {Aws::RailsProvisioner::Scaling::MetricScaling::Metric}
168 | #
169 | # @option options [Integer] :target_value the target value to achieve for the metric
170 | # available for :custom_metric
171 | #
172 | # @option options [Boolean] :disable_scale_in whether scale in by the
173 | # target tracking policy is disabled, available for :custom_metric
174 | #
175 | # @option options [Integer] :scale_in_cooldown period (in seconds) after a scale in activity
176 | # completes before another scale in activity can start, available for :custom_metric
177 | #
178 | # @option options [Integer] :scale_out_cooldown period (in seconds) after a scale out activity
179 | # completes before another scale out activity can start, available for :custom_metric
180 | #
181 | def initialize(type, options)
182 | @metric = Metric.new(type, options)
183 | if type == :custom
184 | @target_value = options[:target_value]
185 | @disable_scale_in = !!options[:disable_scale_in]
186 | @scale_in_cooldown = options[:scale_in_cooldown]
187 | @scale_out_cooldown = options[:scale_out_cooldown]
188 | else # :metric
189 | @scaling_steps = _scaling_steps(options[:scaling_steps] || [])
190 | @cooldown_sec = options[:cooldown]
191 | @adjustment_type = Aws::RailsProvisioner::Utils.adjustment_type(
192 | options[:adjustment_type]) if options[:adjustment_type]
193 | if @adjustment_type == 'PercentChangeInCapacity'
194 | @min_adjustment_magnitude = options[:min_adjustment_magnitude]
195 | end
196 | end
197 | end
198 |
199 | # @return [Metric]
200 | attr_reader :metric
201 |
202 | # @return [Integer]
203 | attr_reader :target_value
204 |
205 | # @return [Boolean]
206 | attr_reader :disable_scale_in
207 |
208 | # @return [Integer]
209 | attr_reader :scale_in_cooldown
210 |
211 | # @return [Integer]
212 | attr_reader :scale_out_cooldown
213 |
214 | # @return [Array]
215 | attr_reader :scaling_steps
216 |
217 | # @return [String]
218 | attr_reader :adjustment_type
219 |
220 | # @return [Integer]
221 | attr_reader :cooldown_sec
222 |
223 | # @return [Integer]
224 | attr_reader :min_adjustment_magnitude
225 |
226 | class ScalingInterval
227 |
228 | # Configuration for each scaling interval in
229 | # scaling steps
230 | # @param [Hash] options
231 | #
232 | # @option options [Integer] :change the capacity adjustment
233 | # to apply in this interval, interpreted differently based on :adjustment_type
234 | # * `changeincapacity` - add the adjustment to the current capacity. The number
235 | # can be positive or negative
236 | # * `percentchangeincapacity` - add or remove the given percentage of the
237 | # current capacity to itself. The number can be in the range [-100..100]
238 | # * `exactcapacity` - set the capacity to this number. The number must be positive
239 | #
240 | # @option options [Integer] :lower lower bound of the interval, scaling
241 | # adjustment will be applied if the metric is higher than this value
242 | #
243 | # @option options [Integer] :upper upper bound of the interval, scaling
244 | # adjustment will be applied if the metric is lower than this value
245 | #
246 | def initialize(options = {})
247 | @change = options[:change]
248 | @lower = options[:lower]
249 | @upper = options[:upper]
250 | end
251 |
252 | # @return [Integer]
253 | attr_reader :change
254 |
255 | # @return [Integer]
256 | attr_reader :lower
257 |
258 | # @return [Integer]
259 | attr_reader :upper
260 |
261 | end
262 |
263 | class Metric
264 |
265 | # Metric to scale on
266 | # https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html
267 | # @param [Hash] options
268 | #
269 | # @option options [String] :name
270 | #
271 | # @option options [String] :namespace
272 | #
273 | # @option options [Integer] :period
274 | #
275 | # @option options [String] :statistic
276 | #
277 | # @option options [String] :color
278 | #
279 | # @option options [String] :label
280 | #
281 | # @option options [Hash] :dimensions
282 | #
283 | # @option options [String] :unit available unit:
284 | # `Seconds`, `Microseconds`, `Milliseconds`
285 | # `Bytes`, `Kilobytes`, `Megabytes`, `Gigabytes`, `Terabytes`
286 | # `Bits`, `Kilobits`, `Megabits`, `Gigabits`, `Terabits`,
287 | # `Percent`, `Count`, `None`,
288 | # `BytesPerSecond`, `KilobytesPerSecond`, `MegabytesPerSecond`,
289 | # `GigabytesPerSecond`, `TerabytesPerSecond`, `BitsPerSecond`,
290 | # `KilobitsPerSecond`, `MegabitsPerSecond`, `GigabitsPerSecond`,
291 | # `TerabitsPerSecond`, `CountPerSecond`
292 | #
293 | def initialize(type, options = {})
294 | @name = options.fetch(:name)
295 | @namespace = options.fetch(:namespace)
296 | @color = options[:color]
297 | @dimensions = Aws::RailsProvisioner::Utils.to_pairs(
298 | options[:dimensions]) if options[:dimensions]
299 | @label = options[:label]
300 | @period_sec = options[:period]
301 | @statistic = options[:statistics]
302 | @unit = options[:unit]
303 | end
304 |
305 | # @return [String]
306 | attr_reader :name
307 |
308 | # @return [String]
309 | attr_reader :namespace
310 |
311 | # @return [String]
312 | attr_reader :color
313 |
314 | # @return [String]
315 | attr_reader :dimensions
316 |
317 | # @return [String]
318 | attr_reader :label
319 |
320 | # @return [Integer]
321 | attr_reader :period_sec
322 |
323 | # @return [String]
324 | attr_reader :statistic
325 |
326 | # @return [String]
327 | attr_reader :unit
328 |
329 | end
330 |
331 | def scaling_steps?
332 | @scaling_steps && !@scaling_steps.empty?
333 | end
334 |
335 | private
336 |
337 | def _scaling_steps(steps)
338 | steps.map {|step| ScalingInterval.new(step)}
339 | end
340 |
341 | end
342 |
343 | class BaseScaling
344 |
345 | # Configuration for scaling policy
346 | # @param [Hash] options
347 | #
348 | # @option options [Boolean] :disable_scale_in whether scale in
349 | # by the target tracking policy is disabled, default as `false`
350 | #
351 | # @option options [Integer] :scale_in_cooldown period in seconds
352 | # after a scale in activity completes before another scale in activity
353 | # can start
354 | #
355 | # @option options [Integer] :scale_out_cooldown period in seconds
356 | # after a scale in activity completes before another scale in activity
357 | # can start
358 | #
359 | # @option options [Integer] :target_util_percent available for
360 | # * :on_cpu , target average CPU utilization across the task
361 | # * :on_memory , target average memory utilization across the task
362 | #
363 | # @option options [Integer] :requests_per_target available for
364 | # :on_request, ALB requests per target
365 | #
366 | def initialize(type, options = {})
367 | @disable_scale_in = !!options[:disable_scale_in]
368 | @scale_in_cooldown = options[:scale_in_cooldown]
369 | @scale_out_cooldown = options[:scale_out_cooldown]
370 | var_name = _type_2_var(type)
371 | instance_variable_set("@#{var_name}", options[var_name.to_sym])
372 | end
373 |
374 | # @return [Integer]
375 | attr_reader :target_util_percent
376 |
377 | # @return [Integer]
378 | attr_reader :requests_per_target
379 |
380 | # @return [Boolean]
381 | attr_reader :disable_scale_in
382 |
383 | # @return [Integer]
384 | attr_reader :scale_in_cooldown
385 |
386 | # @return [Integer]
387 | attr_reader :scale_out_cooldown
388 |
389 | private
390 |
391 | def _type_2_var(type)
392 | case type
393 | when :cpu, :memory then 'target_util_percent'
394 | when :request then 'requests_per_target'
395 | end
396 | end
397 |
398 | end
399 |
400 | class ScheduleScaling
401 |
402 | # Configurations for scaling policy based on time
403 | # @param [Hash] options
404 | #
405 | # @option options [required, String] :schedule when to perform this action
406 | # support formats:
407 | # * 'at(yyyy-mm-ddThh:mm:ss)'
408 | # * 'rate(value unit)'
409 | # * 'cron(fields)'
410 | #
411 | # @option options [Integer] :max_capacity the new maximum capacity
412 | #
413 | # @option options [Integer] :min_capacity the new minimum capacity
414 | #
415 | # @option options [Integer] :start_time when this scheduled action becomes active
416 | # milliseconds since Epoch time
417 | #
418 | # @option options [Integer] :end_time when this scheduled action expires
419 | # milliseconds since Epoch time
420 | #
421 | def initialize(options = {})
422 | @schedule = options.fetch(:schedule)
423 | @max_capacity = options[:max_capacity]
424 | @min_capacity = options[:min_capacity]
425 | @start_time = options[:start_time]
426 | @end_time = options[:end_time]
427 | end
428 |
429 | # @return [String]
430 | attr_reader :schedule
431 |
432 | # @return [Integer]
433 | attr_reader :max_capacity
434 |
435 | # @return [Integer]
436 | attr_reader :min_capacity
437 |
438 | # @return [Integer]
439 | attr_reader :start_time
440 |
441 | # @return [Integer]
442 | attr_reader :end_time
443 |
444 | end
445 |
446 | end
447 |
448 | end
449 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/service.rb:
--------------------------------------------------------------------------------
1 | require 'set'
2 |
3 | module Aws::RailsProvisioner
4 | # @api private
5 | class Service
6 |
7 | def initialize(name, options = {})
8 | @name = name.to_s
9 | @file_path = options[:file_path] || 'aws-rails-provisioner.yml'
10 | @stack_prefix = _camel_case(@name)
11 | @path_prefix = _path_prefix(@name)
12 | @const_prefix = _const_prefix(@stack_prefix)
13 | @enable_cicd = !!options[:enable_cicd]
14 | @profile = options[:profile]
15 | @packages = Set.new
16 |
17 | # fargate stack
18 | @source_path = options.fetch(:source_path)
19 | @fargate = options[:fargate] || {}
20 |
21 | @db_cluster = options[:db_cluster]
22 | @scaling = options[:scaling]
23 |
24 | # pipeline stack
25 | @cicd = options[:cicd] || {}
26 | end
27 |
28 | # @return [String]
29 | attr_reader :name
30 |
31 | # @return [String]
32 | attr_reader :stack_prefix
33 |
34 | # @return [String]
35 | attr_reader :path_prefix
36 |
37 | # @return [String]
38 | attr_reader :const_prefix
39 |
40 | # @return [Boolean]
41 | attr_reader :enable_cicd
42 |
43 | # @return [Set]
44 | attr_reader :packages
45 |
46 | def fargate_stack
47 | stack = Aws::RailsProvisioner::Views::FargateStack.new(
48 | file_path: @file_path,
49 | service_name: @name,
50 | path_prefix: @path_prefix,
51 | stack_prefix: @stack_prefix,
52 | profile: @profile,
53 | source_path: @source_path,
54 | db_cluster: @db_cluster,
55 | fargate: @fargate,
56 | scaling: @scaling,
57 | )
58 | @packages.merge(stack.packages)
59 | stack.render
60 | end
61 |
62 | def pipeline_stack
63 | if @db_cluster.nil? || @db_cluster.empty?
64 | @cicd[:skip_migration] = true
65 | end
66 | @cicd[:source_path] = @source_path
67 | @cicd[:stack_prefix] = @stack_prefix
68 | stack = Aws::RailsProvisioner::Views::PipelineStack.new(@cicd)
69 | @packages.merge(stack.packages)
70 | stack.render
71 | end
72 |
73 | private
74 |
75 | def _camel_case(str)
76 | str.split('_').collect(&:capitalize).join
77 | end
78 |
79 | def _path_prefix(str)
80 | str.downcase.gsub('_', '-')
81 | end
82 |
83 | def _const_prefix(str)
84 | dup = str.dup
85 | dup[0] = dup[0].downcase
86 | dup
87 | end
88 |
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/services.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | # @api private
3 | class ServiceEnumerator
4 |
5 | include Enumerable
6 |
7 | def initialize(options = {})
8 | @configs = options || {}
9 | end
10 |
11 | # @param [String] name
12 | def [](name)
13 | if services.key?(name)
14 | services[name]
15 | else
16 | msg = "unknown service #{name} under :services"
17 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
18 | end
19 | end
20 | alias service []
21 |
22 | def each(&block)
23 | services.values.each(&block)
24 | end
25 |
26 | private
27 |
28 | def services
29 | @service ||= begin
30 | @configs.inject({}) do |hash, (name, config)|
31 | hash[name] = build_service(name, config)
32 | hash
33 | end
34 | end
35 | end
36 |
37 | def build_service(name, config)
38 | Aws::RailsProvisioner::Service.new(name, config)
39 | end
40 |
41 | end
42 |
43 | Services = ServiceEnumerator.new
44 | end
45 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/subnet_selection.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class SubnetSelection
3 |
4 | def initialize(options = {})
5 | @name = options[:name]
6 | @type = Aws::RailsProvisioner::Util.subnet_type(options[:type]) if options[:type]
7 | if @name && @type
8 | msg = "At most one of :type and :name can be supplied."
9 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
10 | end
11 | end
12 |
13 | # @return [String]
14 | attr_reader :name
15 |
16 | # @return [String]
17 | attr_reader :type
18 |
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/utils.rb:
--------------------------------------------------------------------------------
1 | require 'yaml'
2 |
3 | module Aws::RailsProvisioner
4 | # @api private
5 | module Utils
6 |
7 | def self.ip_address_type(str)
8 | types = %w(ipv4, dualstack)
9 | if types.include?(str.downcase)
10 | str.upcase
11 | else
12 | msg = "Unsupported ip address type, please choose from #{types}"
13 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
14 | end
15 | end
16 |
17 | def self.subnet_type(str)
18 | types = %w(isolated private public)
19 | if types.include?(str.downcase)
20 | str.upcase
21 | else
22 | msg = "Unsupported subnet type, please choose from #{types}"
23 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
24 | end
25 | end
26 |
27 | def self.removal_policy(str)
28 | types = %w(retain destroy)
29 | if types.include?(str.downcase)
30 | str.upcase
31 | else
32 | msg = "Unsupported removal policy, please choose from #{types}"
33 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
34 | end
35 | end
36 |
37 | def self.protocol(str)
38 | types = %w(https http tcp tls)
39 | if types.include?(str.downcase)
40 | str.upcase
41 | else
42 | msg = "Unsupported protocol type, please choose from #{types}"
43 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
44 | end
45 | end
46 |
47 | def self.adjustment_type(str)
48 | types = %w(change_in_capacity percent_change_in_capacity exact_capacity)
49 | if types.include?(str.downcase)
50 | str.upcase
51 | else
52 | msg = "Unsupported adjustment type, please choose from #{types}"
53 | raise Aws::RailsProvisioner::Errors::ValidationError, msg
54 | end
55 | end
56 |
57 | def self.parse(file_path)
58 | config = YAML.load(File.read(file_path))
59 | symbolize_keys(config)
60 | rescue
61 | raise Aws::RailsProvisioner::Errors::InvalidYAMLFile.new
62 | end
63 |
64 | def self.to_pairs(hash)
65 | hash.inject([]) do |arr, (key, value)|
66 | arr << "'#{key}': '#{value}',"
67 | arr
68 | end
69 | end
70 |
71 | def self.to_pkgs(services)
72 | services.inject([]) do |pkgs, svc|
73 | pkgs << "@aws-cdk/aws-#{svc}"
74 | pkgs
75 | end
76 | end
77 |
78 | private
79 |
80 | def self.symbolize_keys(hash)
81 | hash.inject({}) do |h, (k,v)|
82 | v = symbolize_keys(v) if v.respond_to?(:keys)
83 | h[k.to_sym] = v
84 | h
85 | end
86 | end
87 |
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/version.rb:
--------------------------------------------------------------------------------
1 | module Aws
2 | module RailsProvisioner
3 | VERSION = '0.0.1.rc2'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/view.rb:
--------------------------------------------------------------------------------
1 | require 'mustache'
2 |
3 | module Aws::RailsProvisioner
4 | # @api private
5 | class View < Mustache
6 |
7 | TEMPLATE_DIR = File.expand_path('../../../templates', __FILE__)
8 |
9 | def self.inherited(subclass)
10 | parts = subclass.name.split('::')
11 | parts.shift #=> remove AWS
12 | parts.shift #=> remove RailsProvisioner
13 | parts.shift #=> remove Views
14 | path = parts.map { |part| underscore(part) }.join('/')
15 | subclass.template_path = TEMPLATE_DIR
16 | subclass.template_file = "#{TEMPLATE_DIR}/#{path}.mustache"
17 | subclass.raise_on_context_miss = true
18 | end
19 |
20 | private
21 |
22 | def underscore(str)
23 | string = str.dup
24 | string.scan(/[a-z0-9]+|\d+|[A-Z0-9]+[a-z]*/).join('_'.freeze)
25 | string.downcase!
26 | end
27 |
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/views/fargate_stack.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | module Views
3 |
4 | class FargateStack < View
5 |
6 | # Fargate Stack Generation
7 | #
8 | # @param [Hash] options
9 | #
10 | # @option options [String] :source_path relative path from `aws-rails-provisioner.yml`
11 | # to the directory containing Rails Application source
12 | #
13 | # @option options [Hash] :db_cluster
14 | # @see {Aws::RailsProvisioner::DBCluster}
15 | #
16 | # @option options [Hash] :fargate configurations for
17 | # fargate service.
18 | # @see {Aws::RailsProvisioner::Fargate}
19 | #
20 | # @option options [Hash] :scaling configurations for
21 | # scaling setting for Fargate service
22 | # @see {Aws::RailsProvisioner::Scaling}
23 | #
24 | def initialize(options = {})
25 | # code gen only
26 | @service_name = options[:service_name]
27 | @path_prefix = options[:path_prefix]
28 | @stack_prefix = options[:stack_prefix]
29 | @profile = options[:profile]
30 |
31 | dir = File.dirname(File.expand_path(options[:file_path]))
32 | @source_path = File.join(dir, options[:source_path])
33 | @rds_config = options[:db_cluster] || {}
34 | @fargate_config = options[:fargate] || {}
35 | @scaling_config = options[:scaling] || {}
36 | end
37 |
38 | # @return [String]
39 | attr_reader :stack_prefix
40 |
41 | # @return [String]
42 | attr_reader :source_path
43 |
44 | def services
45 | base = [
46 | { abbr: 'ec2', value: 'ec2' },
47 | { abbr: 'ecs', value: 'ecs' },
48 | { abbr: 'ecs_patterns', value: 'ecs-patterns' },
49 | { abbr: 'ecr_assets', value: 'ecr-assets' },
50 | { abbr: 'rds', value: 'rds' }
51 | ]
52 | if @fargate_config && @fargate_config[:certificate]
53 | base << { abbr: 'certificatemanager', value: 'certificatemanager' }
54 | end
55 | if @scaling_config &&
56 | (@scaling_config[:on_metric] || @scaling_config[:on_custom_metric])
57 | base << { abbr: 'cloudwatch', value: 'cloudwatch' }
58 | end
59 | if @rds_config && !@rds_config.empty?
60 | base << { abbr: 'secretsmanager', value: 'secretsmanager' }
61 | if @rds_config[:kms_key_arn]
62 | base << { abbr: 'kms', value: 'kms'}
63 | end
64 | end
65 | base
66 | end
67 |
68 | def packages
69 | keys = services.map {|svc| svc[:value] }
70 | Aws::RailsProvisioner::Utils.to_pkgs(keys)
71 | end
72 |
73 | def db_cluster
74 | if @rds_config && !@rds_config.empty?
75 | unless @rds_config[:username]
76 | @rds_config[:username] = "#{stack_prefix}DBAdminUser"
77 | end
78 | @rds_config[:profile] = @profile if @profile
79 | Aws::RailsProvisioner::DBCluster.new(@rds_config)
80 | end
81 | end
82 |
83 | def fargate
84 | if @rds_config && !@rds_config.empty?
85 | @fargate_config[:has_db] = true
86 | end
87 | @fargate_config[:service_name] = @stack_prefix
88 | Aws::RailsProvisioner::Fargate.new(@fargate_config)
89 | end
90 |
91 | def scaling
92 | if @scaling_config && !@scaling_config.empty?
93 | Aws::RailsProvisioner::Scaling.new(@scaling_config)
94 | end
95 | end
96 |
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/views/init_stack.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | module Views
3 | class InitStack < View
4 |
5 | # For InitStack Generation
6 | #
7 | # @param [Hash] options
8 | #
9 | # @option options [Hash] :vpc
10 | # @see {Aws::RailsProvisioner::Vpc}
11 | #
12 | def initialize(options = {})
13 | @vpc_config = options[:vpc]
14 | @stack_prefix = options[:stack_prefix]
15 | end
16 |
17 | # @api private
18 | # @return [String]
19 | attr_reader :stack_prefix
20 |
21 | def services
22 | ['ec2', 'ecs']
23 | end
24 |
25 | def packages
26 | Aws::RailsProvisioner::Utils.to_pkgs(services)
27 | end
28 |
29 | def vpc
30 | Aws::RailsProvisioner::Vpc.new(@vpc_config)
31 | end
32 |
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/views/pipeline_stack.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | module Views
3 | class PipelineStack < View
4 |
5 | # Pipeline (CICD) Generation under :cicd
6 | #
7 | # @param [Hash] options
8 | #
9 | # @option options [String] :pipeline_name Name for the
10 | # AWS CodePipeline generated
11 | #
12 | # @option options [String] :source_repo CodeCommit Repo
13 | # name for holding your rails app source, default to
14 | # folder name where your rails app lives
15 | #
16 | # @option options [String] :source_description Description for
17 | # the CodeCommit Repo, default to 'created by aws-rails-provisioner'
18 | #
19 | # @option options [Hash] :build configurations for codebuild project
20 | # for building images
21 | # @see {Aws::RailsProvisioner::Build}
22 | #
23 | # @option options [Hash] :migration configuration for db migration
24 | # codebuild, available if :db_cluster is configured
25 | # @see {Aws::RailsProvisioner::Migration}
26 | #
27 | def initialize(options = {})
28 | @stack_prefix = options[:stack_prefix]
29 |
30 | @pipeline_name = options[:pipeline_name] || "#{@stack_prefix}Pipeline"
31 | @source_repo = options[:source_repo] || _extract_repo_name(options[:source_path])
32 | @source_description = options[:source_description] || "created by aws-rails-provisioner with AWS CDK for #{@stack_prefix}"
33 |
34 | @build_config = options[:build] || {}
35 | unless @build_config[:project_name]
36 | @build_config[:project_name] = "#{@stack_prefix}ImageBuild"
37 | end
38 |
39 | @skip_migration = options[:skip_migration] || false
40 | unless @skip_migration
41 | @migration_config = options[:migration] || {}
42 | unless @migration_config[:project_name]
43 | @migration_config[:project_name] = "#{@stack_prefix}DBMigration"
44 | end
45 | end
46 | end
47 |
48 | def services
49 | [
50 | { abbr: 'iam', value: 'iam'},
51 | { abbr: 'ec2', value: 'ec2'},
52 | { abbr: 'ecr', value: 'ecr' },
53 | { abbr: 'ecs', value: 'ecs' },
54 | { abbr: 'rds', value: 'rds' },
55 | { abbr: 'codebuild', value: 'codebuild'},
56 | { abbr: 'codecommit', value: 'codecommit'},
57 | { abbr: 'codepipeline', value: 'codepipeline' },
58 | { abbr: 'pipelineactions', value: 'codepipeline-actions'}
59 | ]
60 | end
61 |
62 | def packages
63 | keys = services.map {|svc| svc[:value] }
64 | Aws::RailsProvisioner::Utils.to_pkgs(keys)
65 | end
66 |
67 | # @return [String]
68 | attr_reader :stack_prefix
69 |
70 | # @return [String]
71 | attr_reader :pipeline_name
72 |
73 | # @return [String]
74 | attr_reader :source_repo
75 |
76 | # @return [String]
77 | attr_reader :source_description
78 |
79 | # @return [Aws::RailsProvisioner::Build]
80 | attr_reader :build
81 |
82 | # @return [Aws::RailsProvisioner::Migration]
83 | attr_reader :migration
84 |
85 | # @return [Boolean]
86 | attr_reader :skip_migration
87 |
88 | def build
89 | Aws::RailsProvisioner::Build.new(@build_config)
90 | end
91 |
92 | def migration
93 | if @migration_config
94 | Aws::RailsProvisioner::Migration.new(@migration_config)
95 | end
96 | end
97 |
98 | private
99 |
100 | def _extract_repo_name(path)
101 | path.split('/')[-1] || 'AwsRailsProvisionerRailsAppSource'
102 | end
103 |
104 | end
105 | end
106 | end
107 |
108 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/views/project.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | module Views
3 | # @api private
4 | class Project < View
5 |
6 | def initialize(options = {})
7 | @services = options[:services]
8 | @stack_prefix = options[:stack_prefix]
9 | @path_prefix = options[:path_prefix]
10 | end
11 |
12 | # @return [String]
13 | attr_reader :stack_prefix
14 |
15 | # @return [String]
16 | attr_reader :path_prefix
17 |
18 | def stacks
19 | stacks = []
20 | @services.each do |svc|
21 | stacks << {
22 | name: svc.name,
23 | const_prefix: svc.const_prefix,
24 | path_prefix: svc.path_prefix,
25 | stack_prefix: svc.stack_prefix,
26 | enable_cicd: svc.enable_cicd
27 | }
28 | end
29 | end
30 |
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/aws-rails-provisioner/vpc.rb:
--------------------------------------------------------------------------------
1 | module Aws::RailsProvisioner
2 | class Vpc
3 |
4 | SUBNETS_DEFAULTS = {
5 | application: {
6 | cidr_mask: 24,
7 | type: 'private'
8 | },
9 | ingress: {
10 | cidr_mask: 24,
11 | type: 'public'
12 | },
13 | database: {
14 | cidr_mask: 28,
15 | type: 'isolated'
16 | }
17 | }
18 |
19 | # Configuration value under :vpc
20 | # @param [Hash] options
21 | #
22 | # @option options [Integer] :max_azs maximum number
23 | # of AZs to use in this region, default to 3
24 | #
25 | # @option options [String] :cidr CIDR range to use for
26 | # the VPC, default to '10.0.0.0/21'
27 | #
28 | # @option options [Hash] :subnets subnets configuration
29 | # to build for each AZ, default to following example:
30 | #
31 | # @example: at `aws-rails-provisioner.yml`
32 | # subnets:
33 | # application:
34 | # cidr_mask: 24
35 | # type: private
36 | # ingress:
37 | # cidr_mask: 24
38 | # type: public
39 | # database:
40 | # cidr_mask: 28
41 | # type: isolate
42 | #
43 | # @option options [Boolean] :enable_dns whether the DNS
44 | # resolution is supported for the VPC, default to `true`
45 | #
46 | # @option options [Integer] :nat_gateways number of NAT Gateways
47 | # to create, default to :maxAz value
48 | #
49 | # @option options [Hash] :nat_gateway_subnets choose the subnets
50 | # that will have NAT Gateway attached, default to public:
51 | #
52 | # @example: at `aws-rails-provisioner.yml`
53 | # nat_gateway_subnets:
54 | # type: public
55 | #
56 | # Note: Either subnet `:type` or `:name` can be provided
57 | # @see {Aws::RailsProvisioner::SubnetSelection}
58 | #
59 | # @see AWS CDK VpcNetworkProps
60 | def initialize(options = {})
61 | @max_azs = options[:max_azs] || 3
62 | @cidr = options[:cidr] || '10.0.0.0/21'
63 | subnets_config = options[:subnets] || SUBNETS_DEFAULTS
64 | @subnets = subnets_config.map do |name, config|
65 | Subnet.new(
66 | cidr_mask: config[:cidr_mask],
67 | subnet_name: name,
68 | type: config[:type]
69 | )
70 | end
71 | @enable_dns = options[:enable_dns].nil? ? true : !!options[:enable_dns]
72 | @nat_gateways = options[:nat_gateways] || @max_azs
73 | @nat_gateway_subnets = Aws::RailsProvisioner::SubnetSelection.new(options[:nat_gateway_subnets]) if options[:nat_gateway_subnets]
74 | end
75 |
76 | # @return [Integer]
77 | attr_reader :max_azs
78 |
79 | # @return [Integer]
80 | attr_reader :nat_gateways
81 |
82 | # @return [Aws::RailsProvisioner::SubnetSelection | nil]
83 | attr_reader :nat_gateway_subnets
84 |
85 | # @return [String]
86 | attr_reader :cidr
87 |
88 | # @return [Boolean]
89 | attr_reader :enable_dns
90 |
91 | # @return [Array|nil]
92 | attr_reader :subnets
93 |
94 | class Subnet
95 |
96 | def initialize(options)
97 | @subnet_name = options.fetch(:subnet_name)
98 | @cidr_mask = options.fetch(:cidr_mask)
99 | @type = Aws::RailsProvisioner::Utils.subnet_type(options.fetch(:type))
100 | end
101 |
102 | attr_reader :subnet_name
103 |
104 | attr_reader :cidr_mask
105 |
106 | attr_reader :type
107 |
108 | end
109 |
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/spec/cdk_code_builder_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative 'spec_helper'
2 |
3 | module Aws::RailsProvisioner
4 | describe CDKCodeBuilder do
5 |
6 | it 'supports providing :cdk_dir' do
7 | builder = CDKCodeBuilder.new
8 | expect(builder.cdk_dir).to eq('cdk-sample')
9 |
10 | builder = CDKCodeBuilder.new(cdk_dir: 'foo')
11 | expect(builder.cdk_dir).to eq('foo')
12 | end
13 |
14 | it 'caculates required cdk :packages' do
15 | builder = CDKCodeBuilder.new(SpecHelper.single_service)
16 | builder.source_files.each {|_|}
17 | expect(builder.packages).to contain_exactly(
18 | "@aws-cdk/aws-ec2", "@aws-cdk/aws-ecs",
19 | "@aws-cdk/aws-ecr", "@aws-cdk/aws-ecr-assets",
20 | "@aws-cdk/aws-ecs-patterns", "@aws-cdk/aws-rds",
21 | "@aws-cdk/aws-secretsmanager", "@aws-cdk/aws-codebuild",
22 | "@aws-cdk/aws-codecommit", "@aws-cdk/aws-codepipeline",
23 | "@aws-cdk/aws-codepipeline-actions", "@aws-cdk/aws-iam"
24 | )
25 | end
26 |
27 | it 'supports single fargate service' do
28 | allow(File).to receive(:expand_path).with('aws-rails-provisioner.yml').and_return('/absolute/path/to/dir/aws-rails-provisioner.yml')
29 | builder = CDKCodeBuilder.new(SpecHelper.single_service)
30 | expected = SpecHelper.cdk_single_service
31 |
32 | expect(builder.services.to_a.size).to eq(1)
33 | generate_code_as_expected(builder, expected)
34 | end
35 |
36 | it 'supports rails without db' do
37 | allow(File).to receive(:expand_path).with('aws-rails-provisioner.yml').and_return('/absolute/path/to/dir/aws-rails-provisioner.yml')
38 | builder = CDKCodeBuilder.new(SpecHelper.no_db)
39 | expected = SpecHelper.cdk_no_db
40 | generate_code_as_expected(builder, expected)
41 | end
42 |
43 | it 'supports multiple fargate services' do
44 | allow(File).to receive(:expand_path).with('aws-rails-provisioner.yml').and_return('/absolute/path/to/dir/aws-rails-provisioner.yml')
45 | builder = CDKCodeBuilder.new(SpecHelper.multi_service)
46 | expected = SpecHelper.cdk_multi_service
47 |
48 | expect(builder.services.to_a.size).to eq(2)
49 | generate_code_as_expected(builder, expected)
50 | end
51 |
52 | private
53 |
54 | def generate_code_as_expected(builder, expected)
55 | expect(builder.project).to eq(expected[:app])
56 | expect(builder.init_stack).to eq(expected[:init])
57 | builder.services.each do |svc|
58 | expect(svc.fargate_stack).to eq(
59 | expected[:services][svc.path_prefix][:fargate])
60 | expect(svc.pipeline_stack).to eq(
61 | expected[:services][svc.path_prefix][:pipeline])
62 | end
63 | end
64 |
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/multi_service/cdk-sample-init-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import ec2 = require('@aws-cdk/aws-ec2');
3 | import ecs = require('@aws-cdk/aws-ecs');
4 |
5 | export class CdkSampleInitStack extends cdk.Stack {
6 | public readonly vpc: ec2.IVpc;
7 | public readonly cluster: ecs.ICluster;
8 |
9 | constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
10 | super(scope, id, props);
11 |
12 | // Setting up VPC with subnets
13 | const vpc = new ec2.Vpc(this, 'Vpc', {
14 | maxAzs: 2,
15 | cidr: '10.0.0.0/21',
16 | enableDnsSupport: true,
17 | natGateways: 2,
18 | subnetConfiguration: [
19 | {
20 | cidrMask: 24,
21 | name: 'application',
22 | subnetType: ec2.SubnetType.PRIVATE
23 | },
24 | {
25 | cidrMask: 24,
26 | name: 'ingress',
27 | subnetType: ec2.SubnetType.PUBLIC
28 | },
29 | {
30 | cidrMask: 28,
31 | name: 'database',
32 | subnetType: ec2.SubnetType.ISOLATED
33 | },
34 | ]
35 | });
36 | this.vpc = vpc;
37 |
38 | this.cluster = new ecs.Cluster(this, 'FargateCluster', {
39 | vpc: vpc
40 | });
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/multi_service/cdk-sample.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import cdk = require('@aws-cdk/core');
4 | import { CdkSampleInitStack } from '../lib/cdk-sample-init-stack';
5 | import { RailsFooFargateStack } from '../lib/rails-foo-fargate-stack';
6 | import { RailsFooPipelineStack } from '../lib/rails-foo-pipeline-stack';
7 | import { RailsNoDbFargateStack } from '../lib/rails-no-db-fargate-stack';
8 | import { RailsNoDbPipelineStack } from '../lib/rails-no-db-pipeline-stack';
9 |
10 | const app = new cdk.App();
11 | const initStack = new CdkSampleInitStack(app, 'CdkSampleInitStack');
12 |
13 | // for service :rails_foo
14 | const railsFooFargateStack = new RailsFooFargateStack(app, 'RailsFooFargateStack', {
15 | vpc: initStack.vpc,
16 | cluster: initStack.cluster
17 | });
18 |
19 | new RailsFooPipelineStack(app, 'RailsFooPipelineStack', {
20 | vpc: initStack.vpc,
21 | dbUrl: railsFooFargateStack.dbUrl,
22 | db: railsFooFargateStack.db,
23 | repoName: railsFooFargateStack.repoName,
24 | service: railsFooFargateStack.service
25 | });
26 |
27 | // for service :rails_no_db
28 | const railsNoDbFargateStack = new RailsNoDbFargateStack(app, 'RailsNoDbFargateStack', {
29 | vpc: initStack.vpc,
30 | cluster: initStack.cluster
31 | });
32 |
33 | new RailsNoDbPipelineStack(app, 'RailsNoDbPipelineStack', {
34 | vpc: initStack.vpc,
35 | dbUrl: railsNoDbFargateStack.dbUrl,
36 | db: railsNoDbFargateStack.db,
37 | repoName: railsNoDbFargateStack.repoName,
38 | service: railsNoDbFargateStack.service
39 | });
40 |
41 | app.synth();
42 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/multi_service/rails-foo-fargate-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import ec2 = require('@aws-cdk/aws-ec2');
3 | import ecs = require('@aws-cdk/aws-ecs');
4 | import ecs_patterns = require('@aws-cdk/aws-ecs-patterns');
5 | import ecr_assets = require('@aws-cdk/aws-ecr-assets');
6 | import rds = require('@aws-cdk/aws-rds');
7 | import secretsmanager = require('@aws-cdk/aws-secretsmanager');
8 |
9 | interface RailsFooFargateStackProps {
10 | vpc: ec2.IVpc,
11 | cluster: ecs.ICluster,
12 | }
13 |
14 | export class RailsFooFargateStack extends cdk.Stack {
15 | public readonly service: ecs.FargateService;
16 | public readonly repoName: string;
17 | public readonly dbUrl: string;
18 | public readonly db: rds.DatabaseCluster;
19 |
20 | constructor(scope: cdk.App, id: string, props: RailsFooFargateStackProps) {
21 | super(scope, id);
22 |
23 | // import resources
24 | const cluster = props.cluster;
25 | const vpc = props.vpc;
26 |
27 | // Create secret from SecretsManager
28 | const username = 'RailsFooDBAdminU';
29 | const secret = new secretsmanager.Secret(this, 'Secret', {
30 | generateSecretString: {
31 | excludePunctuation: true
32 | }
33 | });
34 | const password = secret.secretValue;
35 |
36 | // Import DB cluster ParameterGroup
37 | const parameterGroup = rds.ParameterGroup.fromParameterGroupName(
38 | this, 'DBClusterPG', 'aws-rails-provisioner-default-aurora-mysql');
39 | // Create DB Cluster
40 | const db = new rds.DatabaseCluster(this, 'DBCluster', {
41 | engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
42 | masterUser: {
43 | username: username,
44 | password: password
45 | },
46 | instanceProps: {
47 | instanceType: new ec2.InstanceType('r5.large'),
48 | vpc: vpc,
49 | vpcSubnets: {
50 | subnetType: ec2.SubnetType.ISOLATED
51 | }
52 | },
53 | defaultDatabaseName: 'app_development',
54 | removalPolicy: cdk.RemovalPolicy.RETAIN,
55 | instances: 2,
56 | parameterGroup: parameterGroup
57 | });
58 | const dbUrl = "mysql2://" + username + ":" + password + "@" + db.clusterEndpoint.socketAddress + "/app_development";
59 | this.dbUrl = dbUrl;
60 |
61 | const asset = new ecr_assets.DockerImageAsset(this, 'ImageAssetBuild', {
62 | directory: '/absolute/path/to/dir/path/to/rails_foo'
63 | });
64 |
65 | // compute repo name from asset image
66 | const parts = asset.imageUri.split("@")[0].split("/");
67 | const repoName = parts.slice(1, parts.length).join("/").split(":")[0];
68 | this.repoName = repoName;
69 |
70 | const image = ecs.ContainerImage.fromDockerImageAsset(asset);
71 |
72 | // Fargate service
73 | const lbFargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'LBFargate', {
74 | serviceName: 'RailsFoo',
75 | cluster: cluster,
76 | taskImageOptions: {
77 | image: image,
78 | containerName: 'FargateTaskContainer',
79 | containerPort: 80,
80 | environment: {
81 | 'DATABASE_URL': dbUrl,
82 | 'PORT': '80',
83 | },
84 | enableLogging: true,
85 | },
86 | memoryLimitMiB: 512,
87 | cpu: 256,
88 | desiredCount: 1,
89 | publicLoadBalancer: true,
90 | assignPublicIp: true
91 | });
92 | db.connections.allowDefaultPortFrom(lbFargate.service, 'From Fargate');
93 | this.db = db;
94 |
95 | const scaling = lbFargate.service.autoScaleTaskCount({
96 | maxCapacity: 2,
97 | });
98 |
99 | this.service = lbFargate.service;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/multi_service/rails-foo-pipeline-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import iam = require('@aws-cdk/aws-iam');
3 | import ec2 = require('@aws-cdk/aws-ec2');
4 | import ecr = require('@aws-cdk/aws-ecr');
5 | import ecs = require('@aws-cdk/aws-ecs');
6 | import rds = require('@aws-cdk/aws-rds');
7 | import codebuild = require('@aws-cdk/aws-codebuild');
8 | import codecommit = require('@aws-cdk/aws-codecommit');
9 | import codepipeline = require('@aws-cdk/aws-codepipeline');
10 | import pipelineactions = require('@aws-cdk/aws-codepipeline-actions');
11 |
12 | interface RailsFooPipelineStackProps {
13 | vpc: ec2.IVpc,
14 | dbUrl: string,
15 | repoName: string,
16 | service: ecs.FargateService,
17 | db: rds.DatabaseCluster
18 | }
19 |
20 | export class RailsFooPipelineStack extends cdk.Stack {
21 | constructor(scope: cdk.App, id: string, props: RailsFooPipelineStackProps) {
22 | super(scope, id);
23 |
24 | const pipeline = new codepipeline.Pipeline(this, 'FargatePipeline', {
25 | pipelineName: 'RailsFooPipeline',
26 | });
27 |
28 | const repo = new codecommit.Repository(this, 'CodeCommitRepo', {
29 | repositoryName: 'rails_foo',
30 | description: 'created by aws-rails-provisioner with AWS CDK for RailsFoo'
31 | });
32 |
33 | const sourceOutput = new codepipeline.Artifact();
34 | const sourceStage = pipeline.addStage({
35 | stageName: 'Source',
36 | actions: [
37 | new pipelineactions.CodeCommitSourceAction({
38 | actionName: 'SourceAction',
39 | repository: repo,
40 | output: sourceOutput
41 | })
42 | ]
43 | });
44 |
45 | const ecrRepo = ecr.Repository.fromRepositoryName(this, 'ImageRepo', props.repoName);
46 |
47 | const role = new iam.Role(this, 'ImageBuildRole', {
48 | assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com')
49 | });
50 | const policy = new iam.PolicyStatement();
51 | policy.addAllResources();
52 | policy.addActions(
53 | "ecr:BatchCheckLayerAvailability",
54 | "ecr:CompleteLayerUpload",
55 | "ecr:GetAuthorizationToken",
56 | "ecr:InitiateLayerUpload",
57 | "ecr:PutImage",
58 | "ecr:UploadLayerPart"
59 | );
60 | role.addToPolicy(policy);
61 |
62 | const build = new codebuild.PipelineProject(this, 'ImageBuildToECR', {
63 | projectName: 'RailsFooImageBuild',
64 | description: 'build, tag and push image to ECR',
65 | environmentVariables: {
66 | 'REPO_NAME': {
67 | value: ecrRepo.repositoryName,
68 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
69 | },
70 | 'REPO_PREFIX': {
71 | value: ecrRepo.repositoryUri,
72 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
73 | },
74 | },
75 | environment: {
76 | buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_DOCKER_18_09_0,
77 | privileged: true
78 | },
79 | buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec-ecr.yml'),
80 | role: role
81 | });
82 |
83 | const buildOutput = new codepipeline.Artifact();
84 | const buildStage = pipeline.addStage({
85 | stageName: 'Build',
86 | placement: {
87 | justAfter: sourceStage
88 | },
89 | actions: [
90 | new pipelineactions.CodeBuildAction({
91 | actionName: 'ImageBuildAction',
92 | input: sourceOutput,
93 | outputs: [ buildOutput ],
94 | project: build
95 | })
96 | ]
97 | });
98 |
99 | const migration = new codebuild.PipelineProject(this, 'DBMigration', {
100 | projectName: 'RailsFooDBMigration',
101 | description: 'running DB Migration for the rails app inside private subnet',
102 | environmentVariables: {
103 | 'DATABASE_URL': {
104 | value: props.dbUrl,
105 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
106 | }
107 | },
108 | environment:{
109 | buildImage: codebuild.LinuxBuildImage.STANDARD_4_0
110 | },
111 | buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec-db.yml'),
112 | vpc: props.vpc,
113 | subnetSelection: {
114 | subnetType: ec2.SubnetType.PRIVATE
115 | }
116 | });
117 | migration.connections.allowToDefaultPort(props.db, 'DB Migration CodeBuild');
118 |
119 | const migrationStage = pipeline.addStage({
120 | stageName: 'DBMigration',
121 | placement: {
122 | justAfter: buildStage
123 | },
124 | actions: [
125 | new pipelineactions.CodeBuildAction({
126 | actionName: 'DBMigrationAction',
127 | project: migration,
128 | input: sourceOutput
129 | })
130 | ]
131 | });
132 |
133 | pipeline.addStage({
134 | stageName: 'Deploy',
135 | placement: {
136 | justAfter: migrationStage
137 | },
138 | actions: [
139 | new pipelineactions.EcsDeployAction({
140 | actionName: 'FargateDeployAction',
141 | service: props.service,
142 | input: buildOutput
143 | })
144 | ]
145 | });
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/multi_service/rails-no-db-fargate-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import ec2 = require('@aws-cdk/aws-ec2');
3 | import ecs = require('@aws-cdk/aws-ecs');
4 | import ecs_patterns = require('@aws-cdk/aws-ecs-patterns');
5 | import ecr_assets = require('@aws-cdk/aws-ecr-assets');
6 | import rds = require('@aws-cdk/aws-rds');
7 |
8 | interface RailsNoDbFargateStackProps {
9 | vpc: ec2.IVpc,
10 | cluster: ecs.ICluster,
11 | }
12 |
13 | export class RailsNoDbFargateStack extends cdk.Stack {
14 | public readonly service: ecs.FargateService;
15 | public readonly repoName: string;
16 | public readonly dbUrl: string;
17 | public readonly db: rds.DatabaseCluster;
18 |
19 | constructor(scope: cdk.App, id: string, props: RailsNoDbFargateStackProps) {
20 | super(scope, id);
21 |
22 | // import resources
23 | const cluster = props.cluster;
24 |
25 | const asset = new ecr_assets.DockerImageAsset(this, 'ImageAssetBuild', {
26 | directory: '/absolute/path/to/dir/path/to/no_db'
27 | });
28 |
29 | // compute repo name from asset image
30 | const parts = asset.imageUri.split("@")[0].split("/");
31 | const repoName = parts.slice(1, parts.length).join("/").split(":")[0];
32 | this.repoName = repoName;
33 |
34 | const image = ecs.ContainerImage.fromDockerImageAsset(asset);
35 |
36 | // Fargate service
37 | const lbFargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'LBFargate', {
38 | serviceName: 'RailsNoDb',
39 | cluster: cluster,
40 | taskImageOptions: {
41 | image: image,
42 | containerName: 'FargateTaskContainer',
43 | containerPort: 80,
44 | environment: {
45 | 'PORT': '80',
46 | },
47 | enableLogging: true,
48 | },
49 | memoryLimitMiB: 512,
50 | cpu: 256,
51 | desiredCount: 1,
52 | publicLoadBalancer: true,
53 | assignPublicIp: true
54 | });
55 | this.service = lbFargate.service;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/multi_service/rails-no-db-pipeline-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import iam = require('@aws-cdk/aws-iam');
3 | import ec2 = require('@aws-cdk/aws-ec2');
4 | import ecr = require('@aws-cdk/aws-ecr');
5 | import ecs = require('@aws-cdk/aws-ecs');
6 | import rds = require('@aws-cdk/aws-rds');
7 | import codebuild = require('@aws-cdk/aws-codebuild');
8 | import codecommit = require('@aws-cdk/aws-codecommit');
9 | import codepipeline = require('@aws-cdk/aws-codepipeline');
10 | import pipelineactions = require('@aws-cdk/aws-codepipeline-actions');
11 |
12 | interface RailsNoDbPipelineStackProps {
13 | vpc: ec2.IVpc,
14 | dbUrl: string,
15 | repoName: string,
16 | service: ecs.FargateService,
17 | db: rds.DatabaseCluster
18 | }
19 |
20 | export class RailsNoDbPipelineStack extends cdk.Stack {
21 | constructor(scope: cdk.App, id: string, props: RailsNoDbPipelineStackProps) {
22 | super(scope, id);
23 |
24 | const pipeline = new codepipeline.Pipeline(this, 'FargatePipeline', {
25 | pipelineName: 'RailsNoDbPipeline',
26 | });
27 |
28 | const repo = new codecommit.Repository(this, 'CodeCommitRepo', {
29 | repositoryName: 'no_db',
30 | description: 'created by aws-rails-provisioner with AWS CDK for RailsNoDb'
31 | });
32 |
33 | const sourceOutput = new codepipeline.Artifact();
34 | const sourceStage = pipeline.addStage({
35 | stageName: 'Source',
36 | actions: [
37 | new pipelineactions.CodeCommitSourceAction({
38 | actionName: 'SourceAction',
39 | repository: repo,
40 | output: sourceOutput
41 | })
42 | ]
43 | });
44 |
45 | const ecrRepo = ecr.Repository.fromRepositoryName(this, 'ImageRepo', props.repoName);
46 |
47 | const role = new iam.Role(this, 'ImageBuildRole', {
48 | assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com')
49 | });
50 | const policy = new iam.PolicyStatement();
51 | policy.addAllResources();
52 | policy.addActions(
53 | "ecr:BatchCheckLayerAvailability",
54 | "ecr:CompleteLayerUpload",
55 | "ecr:GetAuthorizationToken",
56 | "ecr:InitiateLayerUpload",
57 | "ecr:PutImage",
58 | "ecr:UploadLayerPart"
59 | );
60 | role.addToPolicy(policy);
61 |
62 | const build = new codebuild.PipelineProject(this, 'ImageBuildToECR', {
63 | projectName: 'RailsNoDbImageBuild',
64 | description: 'build, tag and push image to ECR',
65 | environmentVariables: {
66 | 'REPO_NAME': {
67 | value: ecrRepo.repositoryName,
68 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
69 | },
70 | 'REPO_PREFIX': {
71 | value: ecrRepo.repositoryUri,
72 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
73 | },
74 | },
75 | environment: {
76 | buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_DOCKER_18_09_0,
77 | privileged: true
78 | },
79 | buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec-ecr.yml'),
80 | role: role
81 | });
82 |
83 | const buildOutput = new codepipeline.Artifact();
84 | const buildStage = pipeline.addStage({
85 | stageName: 'Build',
86 | placement: {
87 | justAfter: sourceStage
88 | },
89 | actions: [
90 | new pipelineactions.CodeBuildAction({
91 | actionName: 'ImageBuildAction',
92 | input: sourceOutput,
93 | outputs: [ buildOutput ],
94 | project: build
95 | })
96 | ]
97 | });
98 |
99 | pipeline.addStage({
100 | stageName: 'Deploy',
101 | placement: {
102 | justAfter: buildStage
103 | },
104 | actions: [
105 | new pipelineactions.EcsDeployAction({
106 | actionName: 'FargateDeployAction',
107 | service: props.service,
108 | input: buildOutput
109 | })
110 | ]
111 | });
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/no_db/cdk-sample-init-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import ec2 = require('@aws-cdk/aws-ec2');
3 | import ecs = require('@aws-cdk/aws-ecs');
4 |
5 | export class CdkSampleInitStack extends cdk.Stack {
6 | public readonly vpc: ec2.IVpc;
7 | public readonly cluster: ecs.ICluster;
8 |
9 | constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
10 | super(scope, id, props);
11 |
12 | // Setting up VPC with subnets
13 | const vpc = new ec2.Vpc(this, 'Vpc', {
14 | maxAzs: 2,
15 | cidr: '10.0.0.0/21',
16 | enableDnsSupport: true,
17 | natGateways: 2,
18 | subnetConfiguration: [
19 | {
20 | cidrMask: 24,
21 | name: 'application',
22 | subnetType: ec2.SubnetType.PRIVATE
23 | },
24 | {
25 | cidrMask: 24,
26 | name: 'ingress',
27 | subnetType: ec2.SubnetType.PUBLIC
28 | },
29 | {
30 | cidrMask: 28,
31 | name: 'database',
32 | subnetType: ec2.SubnetType.ISOLATED
33 | },
34 | ]
35 | });
36 | this.vpc = vpc;
37 |
38 | this.cluster = new ecs.Cluster(this, 'FargateCluster', {
39 | vpc: vpc
40 | });
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/no_db/cdk-sample.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import cdk = require('@aws-cdk/core');
4 | import { CdkSampleInitStack } from '../lib/cdk-sample-init-stack';
5 | import { RailsNoDbFargateStack } from '../lib/rails-no-db-fargate-stack';
6 | import { RailsNoDbPipelineStack } from '../lib/rails-no-db-pipeline-stack';
7 |
8 | const app = new cdk.App();
9 | const initStack = new CdkSampleInitStack(app, 'CdkSampleInitStack');
10 |
11 | // for service :rails_no_db
12 | const railsNoDbFargateStack = new RailsNoDbFargateStack(app, 'RailsNoDbFargateStack', {
13 | vpc: initStack.vpc,
14 | cluster: initStack.cluster
15 | });
16 |
17 | new RailsNoDbPipelineStack(app, 'RailsNoDbPipelineStack', {
18 | vpc: initStack.vpc,
19 | dbUrl: railsNoDbFargateStack.dbUrl,
20 | db: railsNoDbFargateStack.db,
21 | repoName: railsNoDbFargateStack.repoName,
22 | service: railsNoDbFargateStack.service
23 | });
24 |
25 | app.synth();
26 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/no_db/rails-no-db-fargate-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import ec2 = require('@aws-cdk/aws-ec2');
3 | import ecs = require('@aws-cdk/aws-ecs');
4 | import ecs_patterns = require('@aws-cdk/aws-ecs-patterns');
5 | import ecr_assets = require('@aws-cdk/aws-ecr-assets');
6 | import rds = require('@aws-cdk/aws-rds');
7 |
8 | interface RailsNoDbFargateStackProps {
9 | vpc: ec2.IVpc,
10 | cluster: ecs.ICluster,
11 | }
12 |
13 | export class RailsNoDbFargateStack extends cdk.Stack {
14 | public readonly service: ecs.FargateService;
15 | public readonly repoName: string;
16 | public readonly dbUrl: string;
17 | public readonly db: rds.DatabaseCluster;
18 |
19 | constructor(scope: cdk.App, id: string, props: RailsNoDbFargateStackProps) {
20 | super(scope, id);
21 |
22 | // import resources
23 | const cluster = props.cluster;
24 |
25 | const asset = new ecr_assets.DockerImageAsset(this, 'ImageAssetBuild', {
26 | directory: '/absolute/path/to/dir/path/to/rails'
27 | });
28 |
29 | // compute repo name from asset image
30 | const parts = asset.imageUri.split("@")[0].split("/");
31 | const repoName = parts.slice(1, parts.length).join("/").split(":")[0];
32 | this.repoName = repoName;
33 |
34 | const image = ecs.ContainerImage.fromDockerImageAsset(asset);
35 |
36 | // Fargate service
37 | const lbFargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'LBFargate', {
38 | serviceName: 'RailsNoDb',
39 | cluster: cluster,
40 | taskImageOptions: {
41 | image: image,
42 | containerName: 'FargateTaskContainer',
43 | containerPort: 80,
44 | environment: {
45 | 'RAILS_LOG_TO_STDOUT': 'true',
46 | },
47 | enableLogging: true,
48 | },
49 | memoryLimitMiB: 512,
50 | cpu: 256,
51 | desiredCount: 1,
52 | });
53 | this.service = lbFargate.service;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/no_db/rails-no-db-pipeline-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import iam = require('@aws-cdk/aws-iam');
3 | import ec2 = require('@aws-cdk/aws-ec2');
4 | import ecr = require('@aws-cdk/aws-ecr');
5 | import ecs = require('@aws-cdk/aws-ecs');
6 | import rds = require('@aws-cdk/aws-rds');
7 | import codebuild = require('@aws-cdk/aws-codebuild');
8 | import codecommit = require('@aws-cdk/aws-codecommit');
9 | import codepipeline = require('@aws-cdk/aws-codepipeline');
10 | import pipelineactions = require('@aws-cdk/aws-codepipeline-actions');
11 |
12 | interface RailsNoDbPipelineStackProps {
13 | vpc: ec2.IVpc,
14 | dbUrl: string,
15 | repoName: string,
16 | service: ecs.FargateService,
17 | db: rds.DatabaseCluster
18 | }
19 |
20 | export class RailsNoDbPipelineStack extends cdk.Stack {
21 | constructor(scope: cdk.App, id: string, props: RailsNoDbPipelineStackProps) {
22 | super(scope, id);
23 |
24 | const pipeline = new codepipeline.Pipeline(this, 'FargatePipeline', {
25 | pipelineName: 'RailsNoDbPipeline',
26 | });
27 |
28 | const repo = new codecommit.Repository(this, 'CodeCommitRepo', {
29 | repositoryName: 'rails',
30 | description: 'created by aws-rails-provisioner with AWS CDK for RailsNoDb'
31 | });
32 |
33 | const sourceOutput = new codepipeline.Artifact();
34 | const sourceStage = pipeline.addStage({
35 | stageName: 'Source',
36 | actions: [
37 | new pipelineactions.CodeCommitSourceAction({
38 | actionName: 'SourceAction',
39 | repository: repo,
40 | output: sourceOutput
41 | })
42 | ]
43 | });
44 |
45 | const ecrRepo = ecr.Repository.fromRepositoryName(this, 'ImageRepo', props.repoName);
46 |
47 | const role = new iam.Role(this, 'ImageBuildRole', {
48 | assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com')
49 | });
50 | const policy = new iam.PolicyStatement();
51 | policy.addAllResources();
52 | policy.addActions(
53 | "ecr:BatchCheckLayerAvailability",
54 | "ecr:CompleteLayerUpload",
55 | "ecr:GetAuthorizationToken",
56 | "ecr:InitiateLayerUpload",
57 | "ecr:PutImage",
58 | "ecr:UploadLayerPart"
59 | );
60 | role.addToPolicy(policy);
61 |
62 | const build = new codebuild.PipelineProject(this, 'ImageBuildToECR', {
63 | projectName: 'RailsNoDbImageBuild',
64 | description: 'build, tag and push image to ECR',
65 | environmentVariables: {
66 | 'REPO_NAME': {
67 | value: ecrRepo.repositoryName,
68 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
69 | },
70 | 'REPO_PREFIX': {
71 | value: ecrRepo.repositoryUri,
72 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
73 | },
74 | },
75 | environment: {
76 | buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_DOCKER_18_09_0,
77 | privileged: true
78 | },
79 | buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec-ecr.yml'),
80 | role: role
81 | });
82 |
83 | const buildOutput = new codepipeline.Artifact();
84 | const buildStage = pipeline.addStage({
85 | stageName: 'Build',
86 | placement: {
87 | justAfter: sourceStage
88 | },
89 | actions: [
90 | new pipelineactions.CodeBuildAction({
91 | actionName: 'ImageBuildAction',
92 | input: sourceOutput,
93 | outputs: [ buildOutput ],
94 | project: build
95 | })
96 | ]
97 | });
98 |
99 | pipeline.addStage({
100 | stageName: 'Deploy',
101 | placement: {
102 | justAfter: buildStage
103 | },
104 | actions: [
105 | new pipelineactions.EcsDeployAction({
106 | actionName: 'FargateDeployAction',
107 | service: props.service,
108 | input: buildOutput
109 | })
110 | ]
111 | });
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/single_service/cdk-sample-init-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import ec2 = require('@aws-cdk/aws-ec2');
3 | import ecs = require('@aws-cdk/aws-ecs');
4 |
5 | export class CdkSampleInitStack extends cdk.Stack {
6 | public readonly vpc: ec2.IVpc;
7 | public readonly cluster: ecs.ICluster;
8 |
9 | constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
10 | super(scope, id, props);
11 |
12 | // Setting up VPC with subnets
13 | const vpc = new ec2.Vpc(this, 'Vpc', {
14 | maxAzs: 2,
15 | cidr: '10.0.0.0/21',
16 | enableDnsSupport: true,
17 | natGateways: 2,
18 | subnetConfiguration: [
19 | {
20 | cidrMask: 24,
21 | name: 'application',
22 | subnetType: ec2.SubnetType.PRIVATE
23 | },
24 | {
25 | cidrMask: 24,
26 | name: 'ingress',
27 | subnetType: ec2.SubnetType.PUBLIC
28 | },
29 | {
30 | cidrMask: 28,
31 | name: 'database',
32 | subnetType: ec2.SubnetType.ISOLATED
33 | },
34 | ]
35 | });
36 | this.vpc = vpc;
37 |
38 | this.cluster = new ecs.Cluster(this, 'FargateCluster', {
39 | vpc: vpc
40 | });
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/single_service/cdk-sample.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import cdk = require('@aws-cdk/core');
4 | import { CdkSampleInitStack } from '../lib/cdk-sample-init-stack';
5 | import { RailsFooFargateStack } from '../lib/rails-foo-fargate-stack';
6 | import { RailsFooPipelineStack } from '../lib/rails-foo-pipeline-stack';
7 |
8 | const app = new cdk.App();
9 | const initStack = new CdkSampleInitStack(app, 'CdkSampleInitStack');
10 |
11 | // for service :rails_foo
12 | const railsFooFargateStack = new RailsFooFargateStack(app, 'RailsFooFargateStack', {
13 | vpc: initStack.vpc,
14 | cluster: initStack.cluster
15 | });
16 |
17 | new RailsFooPipelineStack(app, 'RailsFooPipelineStack', {
18 | vpc: initStack.vpc,
19 | dbUrl: railsFooFargateStack.dbUrl,
20 | db: railsFooFargateStack.db,
21 | repoName: railsFooFargateStack.repoName,
22 | service: railsFooFargateStack.service
23 | });
24 |
25 | app.synth();
26 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/single_service/rails-foo-fargate-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import ec2 = require('@aws-cdk/aws-ec2');
3 | import ecs = require('@aws-cdk/aws-ecs');
4 | import ecs_patterns = require('@aws-cdk/aws-ecs-patterns');
5 | import ecr_assets = require('@aws-cdk/aws-ecr-assets');
6 | import rds = require('@aws-cdk/aws-rds');
7 | import secretsmanager = require('@aws-cdk/aws-secretsmanager');
8 |
9 | interface RailsFooFargateStackProps {
10 | vpc: ec2.IVpc,
11 | cluster: ecs.ICluster,
12 | }
13 |
14 | export class RailsFooFargateStack extends cdk.Stack {
15 | public readonly service: ecs.FargateService;
16 | public readonly repoName: string;
17 | public readonly dbUrl: string;
18 | public readonly db: rds.DatabaseCluster;
19 |
20 | constructor(scope: cdk.App, id: string, props: RailsFooFargateStackProps) {
21 | super(scope, id);
22 |
23 | // import resources
24 | const cluster = props.cluster;
25 | const vpc = props.vpc;
26 |
27 | // Create secret from SecretsManager
28 | const username = 'RailsFooDBAdminUser';
29 | const secret = new secretsmanager.Secret(this, 'Secret', {
30 | generateSecretString: {
31 | excludePunctuation: true
32 | }
33 | });
34 | const password = secret.secretValue;
35 |
36 | // Import DB cluster ParameterGroup
37 | const parameterGroup = rds.ParameterGroup.fromParameterGroupName(
38 | this, 'DBClusterPG', 'aws-rails-provisioner-default-aurora-postgresql');
39 | // Create DB Cluster
40 | const db = new rds.DatabaseCluster(this, 'DBCluster', {
41 | engine: rds.DatabaseClusterEngine.AURORA_POSTGRESQL,
42 | masterUser: {
43 | username: username,
44 | password: password
45 | },
46 | instanceProps: {
47 | instanceType: new ec2.InstanceType('r4.large'),
48 | vpc: vpc,
49 | vpcSubnets: {
50 | subnetType: ec2.SubnetType.ISOLATED
51 | }
52 | },
53 | defaultDatabaseName: 'app_development',
54 | removalPolicy: cdk.RemovalPolicy.RETAIN,
55 | instances: 2,
56 | parameterGroup: parameterGroup
57 | });
58 | const dbUrl = "postgres://" + username + ":" + password + "@" + db.clusterEndpoint.socketAddress + "/app_development";
59 | this.dbUrl = dbUrl;
60 |
61 | const asset = new ecr_assets.DockerImageAsset(this, 'ImageAssetBuild', {
62 | directory: '/absolute/path/to/dir/path/to/rails_foo'
63 | });
64 |
65 | // compute repo name from asset image
66 | const parts = asset.imageUri.split("@")[0].split("/");
67 | const repoName = parts.slice(1, parts.length).join("/").split(":")[0];
68 | this.repoName = repoName;
69 |
70 | const image = ecs.ContainerImage.fromDockerImageAsset(asset);
71 |
72 | // Fargate service
73 | const lbFargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'LBFargate', {
74 | serviceName: 'RailsFoo',
75 | cluster: cluster,
76 | taskImageOptions: {
77 | image: image,
78 | containerName: 'FargateTaskContainer',
79 | containerPort: 80,
80 | environment: {
81 | 'DATABASE_URL': dbUrl,
82 | },
83 | enableLogging: true,
84 | },
85 | memoryLimitMiB: 512,
86 | cpu: 256,
87 | desiredCount: 5,
88 | publicLoadBalancer: true,
89 | assignPublicIp: true
90 | });
91 | db.connections.allowDefaultPortFrom(lbFargate.service, 'From Fargate');
92 | this.db = db;
93 | this.service = lbFargate.service;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/spec/fixtures/cdk/single_service/rails-foo-pipeline-stack.ts:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | import iam = require('@aws-cdk/aws-iam');
3 | import ec2 = require('@aws-cdk/aws-ec2');
4 | import ecr = require('@aws-cdk/aws-ecr');
5 | import ecs = require('@aws-cdk/aws-ecs');
6 | import rds = require('@aws-cdk/aws-rds');
7 | import codebuild = require('@aws-cdk/aws-codebuild');
8 | import codecommit = require('@aws-cdk/aws-codecommit');
9 | import codepipeline = require('@aws-cdk/aws-codepipeline');
10 | import pipelineactions = require('@aws-cdk/aws-codepipeline-actions');
11 |
12 | interface RailsFooPipelineStackProps {
13 | vpc: ec2.IVpc,
14 | dbUrl: string,
15 | repoName: string,
16 | service: ecs.FargateService,
17 | db: rds.DatabaseCluster
18 | }
19 |
20 | export class RailsFooPipelineStack extends cdk.Stack {
21 | constructor(scope: cdk.App, id: string, props: RailsFooPipelineStackProps) {
22 | super(scope, id);
23 |
24 | const pipeline = new codepipeline.Pipeline(this, 'FargatePipeline', {
25 | pipelineName: 'RailsFoo',
26 | });
27 |
28 | const repo = new codecommit.Repository(this, 'CodeCommitRepo', {
29 | repositoryName: 'rails_foo',
30 | description: 'created by aws-rails-provisioner with AWS CDK for RailsFoo'
31 | });
32 |
33 | const sourceOutput = new codepipeline.Artifact();
34 | const sourceStage = pipeline.addStage({
35 | stageName: 'Source',
36 | actions: [
37 | new pipelineactions.CodeCommitSourceAction({
38 | actionName: 'SourceAction',
39 | repository: repo,
40 | output: sourceOutput
41 | })
42 | ]
43 | });
44 |
45 | const ecrRepo = ecr.Repository.fromRepositoryName(this, 'ImageRepo', props.repoName);
46 |
47 | const role = new iam.Role(this, 'ImageBuildRole', {
48 | assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com')
49 | });
50 | const policy = new iam.PolicyStatement();
51 | policy.addAllResources();
52 | policy.addActions(
53 | "ecr:BatchCheckLayerAvailability",
54 | "ecr:CompleteLayerUpload",
55 | "ecr:GetAuthorizationToken",
56 | "ecr:InitiateLayerUpload",
57 | "ecr:PutImage",
58 | "ecr:UploadLayerPart"
59 | );
60 | role.addToPolicy(policy);
61 |
62 | const build = new codebuild.PipelineProject(this, 'ImageBuildToECR', {
63 | projectName: 'RailsFooImageBuild',
64 | description: 'build, tag and push image to ECR',
65 | environmentVariables: {
66 | 'REPO_NAME': {
67 | value: ecrRepo.repositoryName,
68 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
69 | },
70 | 'REPO_PREFIX': {
71 | value: ecrRepo.repositoryUri,
72 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
73 | },
74 | },
75 | environment: {
76 | buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_DOCKER_18_09_0,
77 | privileged: true
78 | },
79 | buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec-ecr.yml'),
80 | role: role
81 | });
82 |
83 | const buildOutput = new codepipeline.Artifact();
84 | const buildStage = pipeline.addStage({
85 | stageName: 'Build',
86 | placement: {
87 | justAfter: sourceStage
88 | },
89 | actions: [
90 | new pipelineactions.CodeBuildAction({
91 | actionName: 'ImageBuildAction',
92 | input: sourceOutput,
93 | outputs: [ buildOutput ],
94 | project: build
95 | })
96 | ]
97 | });
98 |
99 | const migration = new codebuild.PipelineProject(this, 'DBMigration', {
100 | projectName: 'RailsFooDBMigration',
101 | description: 'running DB Migration for the rails app inside private subnet',
102 | environmentVariables: {
103 | 'DATABASE_URL': {
104 | value: props.dbUrl,
105 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
106 | }
107 | },
108 | environment:{
109 | buildImage: codebuild.LinuxBuildImage.STANDARD_4_0
110 | },
111 | buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec-db.yml'),
112 | vpc: props.vpc,
113 | subnetSelection: {
114 | subnetType: ec2.SubnetType.PRIVATE
115 | }
116 | });
117 | migration.connections.allowToDefaultPort(props.db, 'DB Migration CodeBuild');
118 |
119 | const migrationStage = pipeline.addStage({
120 | stageName: 'DBMigration',
121 | placement: {
122 | justAfter: buildStage
123 | },
124 | actions: [
125 | new pipelineactions.CodeBuildAction({
126 | actionName: 'DBMigrationAction',
127 | project: migration,
128 | input: sourceOutput
129 | })
130 | ]
131 | });
132 |
133 | pipeline.addStage({
134 | stageName: 'Deploy',
135 | placement: {
136 | justAfter: migrationStage
137 | },
138 | actions: [
139 | new pipelineactions.EcsDeployAction({
140 | actionName: 'FargateDeployAction',
141 | service: props.service,
142 | input: buildOutput
143 | })
144 | ]
145 | });
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/spec/fixtures/yml/multi_service.yml:
--------------------------------------------------------------------------------
1 | version: '0'
2 |
3 | vpc:
4 | max_azs: 2
5 | enable_dns: true
6 | services:
7 | rails_foo:
8 | source_path: /path/to/rails_foo
9 | fargate:
10 | desired_count: 1
11 | public: true
12 | envs:
13 | PORT: 80
14 | db_cluster:
15 | stub_client: true
16 | engine: aurora-mysql
17 | db_name: app_development
18 | scaling:
19 | max_capacity: 2
20 | on_cpu:
21 | target_util_percent: 80
22 | scale_in_cool_down: 300
23 | rails_no_db:
24 | source_path: /path/to/no_db
25 | fargate:
26 | desired_count: 1
27 | public: true
28 | envs:
29 | PORT: 80
30 |
--------------------------------------------------------------------------------
/spec/fixtures/yml/no_db.yml:
--------------------------------------------------------------------------------
1 | version: '0'
2 | vpc:
3 | max_azs: 2
4 | services:
5 | rails_no_db:
6 | source_path: /path/to/rails
7 | fargate:
8 | desired_count: 1
9 | envs:
10 | RAILS_LOG_TO_STDOUT: true
11 |
--------------------------------------------------------------------------------
/spec/fixtures/yml/single_service.yml:
--------------------------------------------------------------------------------
1 | version: '0'
2 |
3 | vpc:
4 | max_azs: 2
5 | enable_dns: true
6 | services:
7 | rails_foo:
8 | source_path: /path/to/rails_foo
9 | fargate:
10 | desired_count: 5
11 | public: true
12 | db_cluster:
13 | stub_client: true
14 | engine: aurora-postgresql
15 | db_name: app_development
16 | cicd:
17 | pipeline_name: RailsFoo
18 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rspec'
2 |
3 | $:.unshift(File.expand_path('../../lib', __FILE__))
4 | require 'aws-rails-provisioner'
5 |
6 | module SpecHelper
7 | class << self
8 |
9 | YML_DIR = File.join([
10 | File.dirname(__FILE__),
11 | 'fixtures',
12 | 'yml'
13 | ])
14 |
15 | CDK_DIR = File.join([
16 | File.dirname(__FILE__),
17 | 'fixtures',
18 | 'cdk'
19 | ])
20 |
21 | def single_service
22 | config = yml_fixtures('single_service')
23 | config[:services][:rails_foo][:enable_cicd] = true
24 | config
25 | end
26 |
27 | def cdk_single_service
28 | cdk_fixtures('single_service')
29 | end
30 |
31 | def multi_service
32 | config = yml_fixtures('multi_service')
33 | config[:services][:rails_foo][:enable_cicd] = true
34 | config[:services][:rails_no_db][:enable_cicd] = true
35 | config
36 | end
37 |
38 | def cdk_multi_service
39 | cdk_fixtures('multi_service')
40 | end
41 |
42 | def no_db
43 | config = yml_fixtures('no_db')
44 | config[:services][:rails_no_db][:enable_cicd] = true
45 | config
46 | end
47 |
48 | def cdk_no_db
49 | cdk_fixtures('no_db')
50 | end
51 |
52 | private
53 |
54 | def yml_fixtures(suite)
55 | Aws::RailsProvisioner::Utils.parse("#{YML_DIR}/#{suite}.yml")
56 | end
57 |
58 | def cdk_fixtures(suite)
59 | Dir.glob("#{CDK_DIR}/#{suite}/*").inject({}) do |h, path|
60 | file = path.split("#{suite}/")[-1]
61 | if file == "cdk-sample.ts"
62 | h[:app] = File.read(path)
63 | elsif file == "cdk-sample-init-stack.ts"
64 | h[:init] = File.read(path)
65 | else
66 | h[:services] ||= {}
67 | if file.include?("fargate-stack.ts")
68 | svc = file.split("-fargate-stack.ts")[0]
69 | h[:services][svc] ||= {}
70 | h[:services][svc][:fargate] = File.read(path)
71 | else
72 | svc = file.split("-pipeline-stack.ts")[0]
73 | h[:services][svc] ||= {}
74 | h[:services][svc][:pipeline] = File.read(path)
75 | end
76 | end
77 |
78 | h
79 | end
80 | end
81 |
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/templates/fargate_stack.mustache:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | {{#services}}
3 | import {{abbr}} = require('@aws-cdk/aws-{{value}}');
4 | {{/services}}
5 |
6 | interface {{stack_prefix}}FargateStackProps {
7 | vpc: ec2.IVpc,
8 | cluster: ecs.ICluster,
9 | }
10 |
11 | export class {{stack_prefix}}FargateStack extends cdk.Stack {
12 | public readonly service: ecs.FargateService;
13 | public readonly repoName: string;
14 | public readonly dbUrl: string;
15 | public readonly db: rds.DatabaseCluster;
16 |
17 | constructor(scope: cdk.App, id: string, props: {{stack_prefix}}FargateStackProps) {
18 | super(scope, id);
19 |
20 | // import resources
21 | const cluster = props.cluster;
22 | {{#db_cluster}}
23 | const vpc = props.vpc;
24 |
25 | // Create secret from SecretsManager
26 | const username = '{{username}}';
27 | const secret = new secretsmanager.Secret(this, 'Secret', {
28 | generateSecretString: {
29 | excludePunctuation: true
30 | }
31 | });
32 | const password = secret.secretValue;
33 |
34 | {{#parameter_group}}
35 | {{#cfn}}
36 | // Create DB Cluster ParameterGroup
37 | const parameterGroup = new rds.ParameterGroup(this, 'DBClusterPG', {
38 | description: '{{description}}',
39 | family: '{{family}}',
40 | parameters: {
41 | {{#parameters}}
42 | {{{.}}}
43 | {{/parameters}}
44 | }
45 | });
46 | {{/cfn}}
47 | {{#name}}
48 | // Import DB cluster ParameterGroup
49 | const parameterGroup = rds.ParameterGroup.fromParameterGroupName(
50 | this, 'DBClusterPG', '{{.}}');
51 | {{/name}}
52 | {{/parameter_group}}
53 | // Create DB Cluster
54 | const db = new rds.DatabaseCluster(this, 'DBCluster', {
55 | engine: rds.DatabaseClusterEngine.{{engine}},
56 | {{#engine_version}}
57 | engineVersion: '{{.}}',
58 | {{/engine_version}}
59 | masterUser: {
60 | username: username,
61 | password: password
62 | },
63 | instanceProps: {
64 | instanceType: new ec2.InstanceType('{{instance_type}}'),
65 | vpc: vpc,
66 | vpcSubnets: {
67 | subnetType: ec2.SubnetType.{{instance_subnet}}
68 | }
69 | },
70 | {{#kms_key}}
71 | kmsKey: kms.Key.fromKeyArn(this, 'DBKMSKey', '{{.}}'),
72 | {{/kms_key}}
73 | {{#backup}}
74 | backup: {
75 | {{#retention_days}}
76 | retentionDays: {{.}},
77 | {{/retention_days}}
78 | {{#preferred_window}}
79 | preferred_window: '{{.}}',
80 | {{/preferred_window}}
81 | },
82 | {{/backup}}
83 | defaultDatabaseName: '{{db_name}}',
84 | {{#cluster_identifier}}
85 | clusterIdentifier: '{{.}}',
86 | {{/cluster_identifier}}
87 | {{#removal_policy}}
88 | removalPolicy: cdk.RemovalPolicy.{{.}},
89 | {{/removal_policy}}
90 | {{#instance_identifier}}
91 | instanceIdentifierBase: '{{.}}',
92 | {{/instance_identifier}}
93 | instances: {{instances}},
94 | parameterGroup: parameterGroup
95 | });
96 | const dbUrl = {{#postgres}}"postgres://"{{/postgres}}{{^postgres}}"mysql2://"{{/postgres}} + username + ":" + password + "@" + db.clusterEndpoint.socketAddress + "/{{db_name}}";
97 | this.dbUrl = dbUrl;
98 | {{/db_cluster}}
99 |
100 | const asset = new ecr_assets.DockerImageAsset(this, 'ImageAssetBuild', {
101 | directory: '{{source_path}}'
102 | });
103 |
104 | // compute repo name from asset image
105 | const parts = asset.imageUri.split("@")[0].split("/");
106 | const repoName = parts.slice(1, parts.length).join("/").split(":")[0];
107 | this.repoName = repoName;
108 |
109 | const image = ecs.ContainerImage.fromDockerImageAsset(asset);
110 |
111 | {{#fargate}}
112 | // Fargate service
113 | const lbFargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'LBFargate', {
114 | {{#service_name}}
115 | serviceName: '{{.}}',
116 | {{/service_name}}
117 | cluster: cluster,
118 | taskImageOptions: {
119 | image: image,
120 | containerName: '{{container_name}}',
121 | containerPort: {{container_port}},
122 | environment: {
123 | {{#has_db}}
124 | 'DATABASE_URL': dbUrl,
125 | {{/has_db}}
126 | {{#envs}}
127 | {{{.}}}
128 | {{/envs}}
129 | },
130 | enableLogging: true,
131 | },
132 | {{#certificate}}
133 | certificate: certificatemanager.Certificate.fromCertificateArn(this, 'Certificate', '{{.}}'),
134 | {{/certificate}}
135 | {{#memory}}memoryLimitMiB: {{.}},{{/memory}}
136 | {{#cpu}}cpu: {{.}},{{/cpu}}
137 | desiredCount: {{desired_count}},
138 | {{#domain_name}}
139 | domainName: '{{.}}',
140 | {{/domain_name}}
141 | {{#domain_zone}}
142 | domainZone: '{{.}}',
143 | {{/domain_zone}}
144 | {{#public}}
145 | publicLoadBalancer: true,
146 | assignPublicIp: true
147 | {{/public}}
148 | });
149 | {{#has_db}}
150 | db.connections.allowDefaultPortFrom(lbFargate.service, 'From Fargate');
151 | this.db = db;
152 | {{/has_db}}
153 | {{/fargate}}
154 | {{#scaling}}
155 |
156 | const scaling = lbFargate.service.autoScaleTaskCount({
157 | maxCapacity: {{max_capacity}},
158 | {{#min_capacity}}
159 | minCapacity: {{.}}
160 | {{/min_capacity}}
161 | });
162 | {{#on_cpu}}
163 | scaling.scaleOnCpuUtilization('FargateScalingOnCpu', {
164 | targetUtilizationPercent: {{target_util_percent}},
165 | {{#disable_scale_in}}
166 | disableScaleIn: true,
167 | {{/disable_scale_in}}
168 | {{#scale_in_cooldown}}
169 | scaleInCooldownSec: {{.}},
170 | {{/scale_in_cooldown}}
171 | {{#scale_out_cooldown}}
172 | scaleOutCooldownSec: {{.}},
173 | {{/scale_out_cooldown}}
174 | });
175 | {{/on_cpu}}
176 | {{#on_memory}}
177 | scaling.scaleOnMemoryUtilization('FargateScalingOnMemory', {
178 | targetUtilizationPercent: {{target_util_percent}},
179 | {{#disable_scale_in}}
180 | disableScaleIn: true,
181 | {{/disable_scale_in}}
182 | {{#scale_in_cooldown}}
183 | scaleInCooldownSec: {{.}},
184 | {{/scale_in_cooldown}}
185 | {{#scale_out_cooldown}}
186 | scaleOutCooldownSec: {{.}},
187 | {{/scale_out_cooldown}}
188 | });
189 | {{/on_memory}}
190 | {{#on_metric}}
191 | {{#metric}}
192 | const metric = new cloudwatch.Metric('Metric', {
193 | metricName: {{name}},
194 | metricNamespace: {{namespace}},
195 | {{#color}}
196 | color: '{{.}}',
197 | {{/color}}
198 | dimensions: {
199 | {{#dimensions}}
200 | {{{.}}}
201 | {{/dimensions}}
202 | }
203 | {{#label}}
204 | label: '{{.}}',
205 | {{/label}}
206 | {{#period_sec}}
207 | periodSec: {{.}},
208 | {{/period_sec}}
209 | {{#statistic}}
210 | statistic: {{.}},
211 | {{/statistic}}
212 | {{#unit}}
213 | unit: cloudwatch.Unit.{{.}},
214 | {{/unit}}
215 | });
216 | {{/metric}}
217 | scaling.scaleOnMetric('FargateScalingOnMetric', {
218 | metric: metric,
219 | {{#scaling_steps?}}
220 | scalingSteps: [
221 | {{#scaling_steps}}
222 | {
223 | {{#change}}change: {{.}},{{/change}}
224 | {{#lower}}lower: {{.}},{{/lower}}
225 | {{#upper}}upper: {{.}}{{/upper}}
226 | },
227 | {{/scaling_steps}}
228 | ],
229 | {{/scaling_steps?}}
230 | {{#adjustment_type}}
231 | adjustmentType: applicationautoscaling.{{.}},
232 | {{/adjustment_type}}
233 | {{#cooldown_sec}}
234 | cooldownSec: {{.}},
235 | {{/cooldown_sec}}
236 | {{#min_adjustment_magnitude}}
237 | minAdjustmentMagnitude: {{.}},
238 | {{/min_adjustment_magnitude}}
239 | });
240 | {{/on_metric}}
241 | {{#on_request}}
242 | scaling.scaleOnRequestCount('FargateScalingOnRequest', {
243 | requestsPerTarget: {{requests_per_target}},
244 | targetGroup: target,
245 | {{#disable_scale_in}}
246 | disableScaleIn: true,
247 | {{/disable_scale_in}}
248 | {{#scale_in_cooldown}}
249 | scaleInCooldownSec: {{.}},
250 | {{/scale_in_cooldown}}
251 | {{#scale_out_cooldown}}
252 | scaleOutCooldownSec: {{.}},
253 | {{/scale_out_cooldown}}
254 | });
255 | {{/on_request}}
256 | {{#on_schedule}}
257 | scaling.scaleOnSchedule('FargateScalingOnSchedule', {
258 | schedule: {{{schedule}}},
259 | {{#max_capacity}}
260 | maxCapacity: {{.}},
261 | {{/max_capacity}}
262 | {{#min_capacity}}
263 | minCapacity: {{.}},
264 | {{/min_capacity}}
265 | {{#start_time}}
266 | startTime: new Date({{.}}),
267 | {{/start_time}}
268 | {{#end_time}}
269 | endTime: new Date({{.}}),
270 | {{/end_time}}
271 | });
272 | {{/on_schedule}}
273 | {{#to_track_custom_metric}}
274 | {{#metric}}
275 | const custom_metric = new cloudwatch.Metric('CustomMetric', {
276 | metricName: {{name}},
277 | metricNamespace: {{namespace}},
278 | {{#color}}
279 | color: '{{.}}',
280 | {{/color}}
281 | dimensions: {
282 | {{#dimensions}}
283 | {{{.}}}
284 | {{/dimensions}}
285 | },
286 | {{#label}}
287 | label: '{{.}}',
288 | {{/label}}
289 | {{#period_sec}}
290 | periodSec: {{.}},
291 | {{/period_sec}}
292 | {{#statistic}}
293 | statistic: '{{.}}',
294 | {{/statistic}}
295 | {{#unit}}
296 | unit: cloudwatch.Unit.{{.}},
297 | {{/unit}}
298 | });
299 | {{/metric}}
300 | scaling.scaleToTrackCustomMetric('FargateScalingOnCustomMetric', {
301 | metric: custom_metric,
302 | {{#disable_scale_in}}
303 | disableScaleIn: true,
304 | {{/disable_scale_in}}
305 | {{#scale_in_cooldown}}
306 | scaleInCooldownSec: {{.}},
307 | {{/scale_in_cooldown}}
308 | {{#scale_out_cooldown}}
309 | scaleOutCooldownSec: {{.}},
310 | {{/scale_out_cooldown}}
311 | });
312 | {{/to_track_custom_metric}}
313 |
314 | {{/scaling}}
315 | this.service = lbFargate.service;
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/templates/init_stack.mustache:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | {{#services}}
3 | import {{.}} = require('@aws-cdk/aws-{{.}}');
4 | {{/services}}
5 |
6 | export class {{stack_prefix}}InitStack extends cdk.Stack {
7 | public readonly vpc: ec2.IVpc;
8 | public readonly cluster: ecs.ICluster;
9 |
10 | constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
11 | super(scope, id, props);
12 |
13 | {{#vpc}}
14 | // Setting up VPC with subnets
15 | const vpc = new ec2.Vpc(this, 'Vpc', {
16 | maxAzs: {{max_azs}},
17 | cidr: '{{cidr}}',
18 | {{#enable_dns}}
19 | enableDnsSupport: true,
20 | {{/enable_dns}}
21 | natGateways: {{nat_gateways}},
22 | {{#nat_gateway_subnets}}
23 | natGatewaySubnets: {
24 | {{#name}}
25 | subnetName: '{{.}}'
26 | {{/name}}
27 | {{#type}}
28 | subnetType: ec2.SubnetType.{{.}}
29 | {{/type}}
30 | },
31 | {{/nat_gateway_subnets}}
32 | subnetConfiguration: [
33 | {{#subnets}}
34 | {
35 | cidrMask: {{cidr_mask}},
36 | name: '{{subnet_name}}',
37 | subnetType: ec2.SubnetType.{{type}}
38 | },
39 | {{/subnets}}
40 | ]
41 | });
42 | this.vpc = vpc;
43 | {{/vpc}}
44 |
45 | this.cluster = new ecs.Cluster(this, 'FargateCluster', {
46 | vpc: vpc
47 | });
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/templates/pipeline_stack.mustache:
--------------------------------------------------------------------------------
1 | import cdk = require('@aws-cdk/core');
2 | {{#services}}
3 | import {{abbr}} = require('@aws-cdk/aws-{{value}}');
4 | {{/services}}
5 |
6 | interface {{stack_prefix}}PipelineStackProps {
7 | vpc: ec2.IVpc,
8 | dbUrl: string,
9 | repoName: string,
10 | service: ecs.FargateService,
11 | db: rds.DatabaseCluster
12 | }
13 |
14 | export class {{stack_prefix}}PipelineStack extends cdk.Stack {
15 | constructor(scope: cdk.App, id: string, props: {{stack_prefix}}PipelineStackProps) {
16 | super(scope, id);
17 |
18 | const pipeline = new codepipeline.Pipeline(this, 'FargatePipeline', {
19 | pipelineName: '{{pipeline_name}}',
20 | });
21 |
22 | const repo = new codecommit.Repository(this, 'CodeCommitRepo', {
23 | repositoryName: '{{source_repo}}',
24 | description: '{{source_description}}'
25 | });
26 |
27 | const sourceOutput = new codepipeline.Artifact();
28 | const sourceStage = pipeline.addStage({
29 | stageName: 'Source',
30 | actions: [
31 | new pipelineactions.CodeCommitSourceAction({
32 | actionName: 'SourceAction',
33 | repository: repo,
34 | output: sourceOutput
35 | })
36 | ]
37 | });
38 |
39 | const ecrRepo = ecr.Repository.fromRepositoryName(this, 'ImageRepo', props.repoName);
40 |
41 | const role = new iam.Role(this, 'ImageBuildRole', {
42 | assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com')
43 | });
44 | const policy = new iam.PolicyStatement();
45 | policy.addAllResources();
46 | policy.addActions(
47 | "ecr:BatchCheckLayerAvailability",
48 | "ecr:CompleteLayerUpload",
49 | "ecr:GetAuthorizationToken",
50 | "ecr:InitiateLayerUpload",
51 | "ecr:PutImage",
52 | "ecr:UploadLayerPart"
53 | );
54 | role.addToPolicy(policy);
55 |
56 | {{#build}}
57 | const build = new codebuild.PipelineProject(this, 'ImageBuildToECR', {
58 | projectName: '{{project_name}}',
59 | description: '{{description}}',
60 | environmentVariables: {
61 | 'REPO_NAME': {
62 | value: ecrRepo.repositoryName,
63 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
64 | },
65 | 'REPO_PREFIX': {
66 | value: ecrRepo.repositoryUri,
67 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
68 | },
69 | },
70 | environment: {
71 | buildImage: codebuild.LinuxBuildImage.{{image}},
72 | privileged: true
73 | },
74 | buildSpec: codebuild.BuildSpec.fromSourceFilename('{{buildspec}}'),
75 | {{#timeout}}
76 | timeout: {{.}},
77 | {{/timeout}}
78 | role: role
79 | });
80 | {{/build}}
81 |
82 | const buildOutput = new codepipeline.Artifact();
83 | const buildStage = pipeline.addStage({
84 | stageName: 'Build',
85 | placement: {
86 | justAfter: sourceStage
87 | },
88 | actions: [
89 | new pipelineactions.CodeBuildAction({
90 | actionName: 'ImageBuildAction',
91 | input: sourceOutput,
92 | outputs: [ buildOutput ],
93 | project: build
94 | })
95 | ]
96 | });
97 | {{#migration}}
98 |
99 | const migration = new codebuild.PipelineProject(this, 'DBMigration', {
100 | projectName: '{{project_name}}',
101 | description: '{{description}}',
102 | environmentVariables: {
103 | 'DATABASE_URL': {
104 | value: props.dbUrl,
105 | type: codebuild.BuildEnvironmentVariableType.PLAINTEXT
106 | }
107 | },
108 | environment:{
109 | buildImage: codebuild.LinuxBuildImage.{{image}}
110 | },
111 | buildSpec: codebuild.BuildSpec.fromSourceFilename('{{buildspec}}'),
112 | {{#timeout}}
113 | timeout: {{.}},
114 | {{/timeout}}
115 | vpc: props.vpc,
116 | subnetSelection: {
117 | subnetType: ec2.SubnetType.PRIVATE
118 | }
119 | });
120 | migration.connections.allowToDefaultPort(props.db, 'DB Migration CodeBuild');
121 |
122 | const migrationStage = pipeline.addStage({
123 | stageName: 'DBMigration',
124 | placement: {
125 | justAfter: buildStage
126 | },
127 | actions: [
128 | new pipelineactions.CodeBuildAction({
129 | actionName: 'DBMigrationAction',
130 | project: migration,
131 | input: sourceOutput
132 | })
133 | ]
134 | });
135 | {{/migration}}
136 |
137 | pipeline.addStage({
138 | stageName: 'Deploy',
139 | placement: {
140 | {{#migration}}
141 | justAfter: migrationStage
142 | {{/migration}}
143 | {{#skip_migration}}
144 | justAfter: buildStage
145 | {{/skip_migration}}
146 | },
147 | actions: [
148 | new pipelineactions.EcsDeployAction({
149 | actionName: 'FargateDeployAction',
150 | service: props.service,
151 | input: buildOutput
152 | })
153 | ]
154 | });
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/templates/project.mustache:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import cdk = require('@aws-cdk/core');
4 | import { {{stack_prefix}}InitStack } from '../lib/{{path_prefix}}-init-stack';
5 | {{#stacks}}
6 | import { {{stack_prefix}}FargateStack } from '../lib/{{path_prefix}}-fargate-stack';
7 | {{#enable_cicd}}
8 | import { {{stack_prefix}}PipelineStack } from '../lib/{{path_prefix}}-pipeline-stack';
9 | {{/enable_cicd}}
10 | {{/stacks}}
11 |
12 | const app = new cdk.App();
13 | const initStack = new {{stack_prefix}}InitStack(app, '{{stack_prefix}}InitStack');
14 | {{#stacks}}
15 |
16 | // for service :{{name}}
17 | {{#enable_cicd}}const {{const_prefix}}FargateStack = {{/enable_cicd}}new {{stack_prefix}}FargateStack(app, '{{stack_prefix}}FargateStack', {
18 | vpc: initStack.vpc,
19 | cluster: initStack.cluster
20 | });
21 |
22 | {{#enable_cicd}}
23 | new {{stack_prefix}}PipelineStack(app, '{{stack_prefix}}PipelineStack', {
24 | vpc: initStack.vpc,
25 | dbUrl: {{const_prefix}}FargateStack.dbUrl,
26 | db: {{const_prefix}}FargateStack.db,
27 | repoName: {{const_prefix}}FargateStack.repoName,
28 | service: {{const_prefix}}FargateStack.service
29 | });
30 | {{/enable_cicd}}
31 | {{/stacks}}
32 |
33 | app.synth();
34 |
--------------------------------------------------------------------------------