├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── code ├── Controllers │ ├── MyToDo.cs │ ├── Parameters.cs │ ├── ToDo.cs │ ├── ToDoContext.cs │ ├── ToDoController.cs │ └── ValuesController.cs ├── Dockerfile ├── Migrations │ ├── 20190830151214_Initial.Designer.cs │ ├── 20190830151214_Initial.cs │ └── ToDoContextModelSnapshot.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json ├── obj │ └── Debug │ │ └── netcoreapp2.2 │ │ ├── todo-app.AssemblyInfo.cs │ │ ├── todo-app.AssemblyInfoInputs.cache │ │ └── todo-app.csproj.CoreCompileInputs.cache └── todo-app.csproj └── config ├── buildspec.yml ├── net-core-task-infrastucture.yaml └── net-core-task-services.yaml /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, or recently closed, 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' 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](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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Developing a Microsoft .NET Core Web API application with Aurora Database using CloudFormation. 3 | 4 | **As part of this blog we will do the following.** 5 | 6 | 1. Use/convert existing Microsoft .NET Core WebAPI's and integrate AWS SDK Package like SSM into it. 7 | 8 | 2. Utilize Elastic Container Service (ECS), Elastic Container Registry (ECR), Amazon Systems Manager (SSM) [maintains the Aurora DB Credentials] 9 | 10 | 3. Cloudformation to spin up the AWS Infrastructure and Services required for this application to run. 11 | 12 | 4. CodeCommit, CodeBuild tools for development/build CI/CD pipeline setup. 13 | 14 | 4. Amazon Aurora (Serverless) Database for better database freedom. 15 | 16 | ### Design Considerations 17 | 18 | 1. ECS in public subnet. RDS in private subnet. Other combinations of private subnet also exist 19 | 20 | 2. Store database credentials in SSM. Used in second stack to spin up the TODOS - table before ECS service spins up 21 | 22 | 3. TODOS table can be created as part of Dotnet application also. Other possible design options also exist. 23 | 24 | 4. LoadBalancer is created without HTTPS/TLS. Real world this should not be the case 25 | 26 | ### Steps 27 | 28 | 1. Execute the below command to spin up the infrastructure cloudformation stack (parent stack) 29 | 30 | ``` 31 | aws cloudformation create-stack --stack-name net-core-stack --template-body file://config/net-core-task-infrastucture.yaml --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=AppStackName,ParameterValue=net-core-stack 32 | 33 | ``` 34 | 35 | 2. Check in the .NET Core WebAPI code (sample code provided) to AWS CodeCommit. This will automatically trigger CodeBuild which compiles the code pushes the docker image to Amazon ECR. 36 | 37 | 3. Execute the below command to create the ECS Service stack. Service has the task definition to read the image from ECR. This command exposes the HealthCheckURL and the WebAPIURL (that interacts with TODO database) as Outputs in the CloudFormation. 38 | 39 | **Note:** The below command takes StackName, KeyName (EC2 key pair YOUR account that is already created). The "StackName" parameter is provided as a parameter in the above stack. It uses the parent (first CloudFormation) ex: net-core-stack by default in the yaml. You can also provide that as a parameter if you have a different name for your first stack 40 | 41 | ``` 42 | 43 | aws cloudformation create-stack --stack-name net-core-service-stack --template-body file://config/net-core-task-services.yaml --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=AppStackName,ParameterValue=net-core-service-stack ParameterKey=KeyName,ParameterValue=my-east1-keypair ParameterKey=StackName,ParameterValue=net-core-stack 44 | 45 | ``` 46 | 47 | 4. The WebAPI is exposed to the outside world using Public LoadBalancer. 48 | 49 | ### Test 50 | 51 | 1. From the output of the second stack use the "WebApiUrl" to test the api. 52 | 53 | 2. You can use tools like Postman, ARC Rest Client or Browser extensions like RestMan 54 | 55 | 3. Select "content-type" as "application/json" 56 | 57 | 4. POST as rawdata/json - sample below 58 | 59 | ``` 60 | { 61 | "Task": "new TODO Application", 62 | "Status": "Completed" 63 | } 64 | ``` 65 | 5. Use the same url and fire a GET call to see the previously posted todo item as response. 66 | 67 | 68 | ### Clean Up 69 | 70 | **Make sure to check the following are deleted before the delete stacks are performed** 71 | 72 | - Contents of the S3 files are deleted 73 | - S3 bucket will in the format -region-accountId. ex: net-core-stack-us-east-1-1234567890 74 | 75 | - ECR (Container Registry) repository is deleted 76 | - Container repository will be in the format -todo-repository. ex: net-core-stack-todo-repository 77 | 78 | - EC2 RDS Table Creator instance is terminated 79 | - The name will be in the format: --rds-table-creator-instance. ex: net-core-service-stack-rds-table-creator-instance 80 | 81 | **Once above steps are completed. Execute the below commands:** 82 | - $ aws cloudformation delete-stack --stack-name 83 | - Eg: $ aws cloudformation delete-stack --stack-name net-core-service-stack 84 | - $ aws cloudformation delete-stack --stack-name 85 | - Eg: $ aws cloudformation delete-stack --stack-name net-core-stack 86 | 87 | 88 | ### Troubleshooting 89 | 90 | 1) Make sure to review the AWS service limits. Ex: 5 VPCs per region 91 | 92 | 2) After the service stack completes if the WebAPIURL displays blank 93 | - In AWS Console, navigate to EC2 section and make sure the -tablecreatorinstance is running 2/2 complete 94 | - This EC2 creates the "ToDos" table in the "todo" database in Aurora 95 | - If issue persist, Navigate to RDS in AWS Console 96 | - Validate if the "ToDos" table is created in the Aurora database 97 | - select your database typically -todo 98 | - click "Modify". Scroll down and select "Data API", Apply immediately in next screen - This lets you to query the DB using AWS RDS Console query editor 99 | - select "Query Editor" - Provide the connection string. Typically this is available in the first/infrastructure stack 100 | - in the Query window - execute below SQLs to validate 101 | ``` 102 | 103 | use todo; 104 | select * from ToDos; 105 | 106 | ``` 107 | If this doesn't return records you can manually create the table with below query 108 | 109 | ``` 110 | use todo; 111 | drop table IF EXISTS ToDos; 112 | create table ToDos( 113 | id MEDIUMINT not null auto_increment, 114 | CreatedTime TIMESTAMP DEFAULT now(), 115 | Status VARCHAR(50), 116 | Task VARCHAR(50), 117 | primary key(id) 118 | ); 119 | select * from ToDos; 120 | 121 | ''' 122 | 123 | ##### Note: Inactivity in Aurora Serverless - RDS Table could put the RDS in suspended state to reduce the cost. You might receive a communication error after no activity while trying to invoke the database DDL/DML statements. You can notice this by connecting to the SQL in Query Editor with below output. 124 | - Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. 125 | Retrying the select queries will warm up the RDS database for subsequent connection to be served. 126 | 127 | ## License 128 | 129 | This library is licensed under the MIT-0 License. See the LICENSE file. 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /code/Controllers/MyToDo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace todo_app.Controllers 4 | { 5 | public class MyToDo{ 6 | public string ToDo { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /code/Controllers/Parameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace todo_app 4 | { 5 | public class Parameters 6 | { 7 | public string AuroraConnectionString {get;set;} 8 | } 9 | } -------------------------------------------------------------------------------- /code/Controllers/ToDo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace todo_app.Controllers 4 | { 5 | public class ToDo{ 6 | public int Id { get; set; } 7 | public DateTime CreatedTime {get;set;} 8 | public string Status {get;set;} 9 | public string Task {get;set;} 10 | } 11 | } -------------------------------------------------------------------------------- /code/Controllers/ToDoContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace todo_app.Controllers 6 | { 7 | public class ToDoContext : DbContext 8 | { 9 | string TODO_CONNECTION_STRING = ""; 10 | 11 | public ToDoContext(IOptions options) 12 | { 13 | Console.WriteLine("DB Connection String ToDoContext - " + options.Value.AuroraConnectionString); 14 | TODO_CONNECTION_STRING = options.Value.AuroraConnectionString; 15 | } 16 | 17 | 18 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 19 | { 20 | optionsBuilder.UseMySQL(TODO_CONNECTION_STRING); 21 | base.OnConfiguring(optionsBuilder); 22 | } 23 | public DbSet ToDos { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /code/Controllers/ToDoController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Options; 8 | using Newtonsoft.Json; 9 | 10 | namespace todo_app.Controllers 11 | { 12 | [Route("api/[controller]")] 13 | [ApiController] 14 | public class ToDoController : ControllerBase 15 | { 16 | private IOptions _options; 17 | public ToDoController(IOptions options) 18 | { 19 | Console.WriteLine("ToDo Controller!"); 20 | _options = options; 21 | } 22 | 23 | [HttpGet] 24 | public ActionResult> Get() 25 | { 26 | StringBuilder response = new StringBuilder(); 27 | using (var db = new ToDoContext(_options)) 28 | { 29 | foreach(ToDo tdo in db.ToDos) 30 | { 31 | response.Append(tdo.Id + " " + tdo.CreatedTime + " " + tdo.Status + " " + tdo.Task + " "); 32 | } 33 | } 34 | return new string[] { "value from db - ", response.ToString()}; 35 | } 36 | 37 | [HttpGet("{id}")] 38 | public ActionResult Get(int id) 39 | { 40 | return "value"; 41 | } 42 | 43 | // POST api/values 44 | [HttpPost] 45 | public void Post(ToDo todoVal) 46 | { 47 | Console.WriteLine("Entering ToDo Put - " + JsonConvert.SerializeObject(todoVal, Formatting.Indented)); 48 | 49 | if (todoVal != null && !string.IsNullOrEmpty(todoVal.Status) && 50 | !string.IsNullOrEmpty(todoVal.Task)){ 51 | Console.WriteLine("Saving DBContext!"); 52 | using (var db = new ToDoContext(_options)) 53 | { 54 | todoVal.CreatedTime = DateTime.Now; 55 | var todo = db.ToDos.Add(todoVal).Entity; 56 | Console.WriteLine(todo?.Task); 57 | db.SaveChanges(); 58 | } 59 | } 60 | 61 | Console.WriteLine("ToDo Saved Succesfully!"); 62 | } 63 | 64 | // PUT api/values/5 65 | [HttpPut("{id}")] 66 | public void Put(int id, [FromBody] string value) 67 | { 68 | 69 | } 70 | 71 | // DELETE api/values/5 72 | [HttpDelete("{id}")] 73 | public void Delete(int id) 74 | { 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /code/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | namespace todo_app.Controllers 10 | { 11 | [Route("api/[controller]")] 12 | [ApiController] 13 | public class ValuesController : ControllerBase 14 | { 15 | // GET api/values 16 | [HttpGet] 17 | public ActionResult> Get() 18 | { 19 | Console.WriteLine("Values Controller!"); 20 | return new string[] { "value1", "value2" }; 21 | } 22 | 23 | // GET api/values/5 24 | [HttpGet("{id}")] 25 | public ActionResult Get(int id) 26 | { 27 | return "value"; 28 | } 29 | 30 | // POST api/values 31 | [HttpPost] 32 | public void Post([FromBody] string value) 33 | { 34 | } 35 | 36 | // PUT api/values/5 37 | [HttpPut("{id}")] 38 | public void Put(int id, [FromBody] string value) 39 | { 40 | } 41 | 42 | // DELETE api/values/5 43 | [HttpDelete("{id}")] 44 | public void Delete(int id) 45 | { 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build-env 2 | WORKDIR /app 3 | 4 | # Copy csproj and restore as distinct layers 5 | COPY *.csproj ./ 6 | RUN dotnet restore 7 | 8 | # Copy everything else and build 9 | COPY . ./ 10 | RUN dotnet publish -c Release -o out 11 | 12 | # Build runtime image 13 | FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 14 | WORKDIR /app 15 | COPY --from=build-env /app/out . 16 | ENTRYPOINT ["dotnet", "todo-app.dll"] 17 | -------------------------------------------------------------------------------- /code/Migrations/20190830151214_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using todo_app.Controllers; 8 | 9 | namespace todo_app.Migrations 10 | { 11 | [DbContext(typeof(ToDoContext))] 12 | [Migration("20190830151214_Initial")] 13 | partial class Initial 14 | { 15 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); 20 | 21 | modelBuilder.Entity("todo_app.Controllers.ToDo", b => 22 | { 23 | b.Property("Id") 24 | .ValueGeneratedOnAdd(); 25 | 26 | b.Property("CreatedTime"); 27 | 28 | b.Property("Status"); 29 | 30 | b.Property("Task"); 31 | 32 | b.HasKey("Id"); 33 | 34 | b.ToTable("ToDos"); 35 | }); 36 | #pragma warning restore 612, 618 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /code/Migrations/20190830151214_Initial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace todo_app.Migrations 5 | { 6 | public partial class Initial : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "ToDos", 12 | columns: table => new 13 | { 14 | Id = table.Column(nullable: false) 15 | .Annotation("MySQL:AutoIncrement", true), 16 | CreatedTime = table.Column(nullable: false), 17 | Status = table.Column(nullable: true), 18 | Task = table.Column(nullable: true) 19 | }, 20 | constraints: table => 21 | { 22 | table.PrimaryKey("PK_ToDos", x => x.Id); 23 | }); 24 | } 25 | 26 | protected override void Down(MigrationBuilder migrationBuilder) 27 | { 28 | migrationBuilder.DropTable( 29 | name: "ToDos"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /code/Migrations/ToDoContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using todo_app.Controllers; 7 | 8 | namespace todo_app.Migrations 9 | { 10 | [DbContext(typeof(ToDoContext))] 11 | partial class ToDoContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | #pragma warning disable 612, 618 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); 18 | 19 | modelBuilder.Entity("todo_app.Controllers.ToDo", b => 20 | { 21 | b.Property("Id") 22 | .ValueGeneratedOnAdd(); 23 | 24 | b.Property("CreatedTime"); 25 | 26 | b.Property("Status"); 27 | 28 | b.Property("Task"); 29 | 30 | b.HasKey("Id"); 31 | 32 | b.ToTable("ToDos"); 33 | }); 34 | #pragma warning restore 612, 618 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /code/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace todo_app 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .ConfigureAppConfiguration(builder => 23 | { 24 | builder.AddSystemsManager("/Database"); 25 | }) 26 | .UseStartup(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:47913", 8 | "sslPort": 44329 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "todo_app": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /code/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | 14 | using todo_app.Controllers; 15 | 16 | namespace todo_app 17 | { 18 | public class Startup 19 | { 20 | public Startup(IConfiguration configuration) 21 | { 22 | Configuration = configuration; 23 | } 24 | 25 | public IConfiguration Configuration { get; } 26 | 27 | // This method gets called by the runtime. Use this method to add services to the container. 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | services.Configure(Configuration.GetSection("Config")); 31 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 32 | } 33 | 34 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 35 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 36 | { 37 | if (env.IsDevelopment()) 38 | { 39 | app.UseDeveloperExceptionPage(); 40 | } 41 | else 42 | { 43 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 44 | app.UseHsts(); 45 | } 46 | 47 | //Accept All HTTP Request Methods from all origins 48 | app.UseCors(builder =>builder.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod()); 49 | app.UseHttpsRedirection(); 50 | app.UseMvc(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /code/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /code/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /code/obj/Debug/netcoreapp2.2/todo-app.AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | using System; 12 | using System.Reflection; 13 | 14 | [assembly: System.Reflection.AssemblyCompanyAttribute("todo-app")] 15 | [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] 16 | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] 17 | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] 18 | [assembly: System.Reflection.AssemblyProductAttribute("todo-app")] 19 | [assembly: System.Reflection.AssemblyTitleAttribute("todo-app")] 20 | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] 21 | 22 | // Generated by the MSBuild WriteCodeFragment class. 23 | 24 | -------------------------------------------------------------------------------- /code/obj/Debug/netcoreapp2.2/todo-app.AssemblyInfoInputs.cache: -------------------------------------------------------------------------------- 1 | a40e8753ef23e0421220beb4b2358aad574796b1 2 | -------------------------------------------------------------------------------- /code/obj/Debug/netcoreapp2.2/todo-app.csproj.CoreCompileInputs.cache: -------------------------------------------------------------------------------- 1 | ec05edbcc82a7ff75da5fff09a34d5662d13acc9 2 | -------------------------------------------------------------------------------- /code/todo-app.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | todo_app 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /config/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | dotnet: 2.2 7 | pre_build: 8 | commands: 9 | - aws --version 10 | # Cloudformation will set this application environment variables for CodeBuild 11 | # REPOSITORY_URI=.dkr.ecr..amazonaws.com/todo-repository 12 | # AWS_DEFAULT_REGION=region ex: us-east-1 13 | - echo 'region - ' - $AWS_DEFAULT_REGION 14 | - echo 'repository - ' $REPOSITORY_URI 15 | - cd code/ 16 | - echo Logging in to Amazon ECR 17 | - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email) 18 | build: 19 | commands: 20 | - echo Build started on `date` 21 | - echo Building the Docker image... 22 | - docker build -t $REPOSITORY_URI:latest . 23 | - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:latest 24 | post_build: 25 | commands: 26 | - echo Build completed on `date` 27 | - echo Push the latest Docker Image... 28 | - docker push $REPOSITORY_URI:latest -------------------------------------------------------------------------------- /config/net-core-task-infrastucture.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: Create AWS infrastructure to deploy .NET WebApi on AWS Fargate that talks to Aurora. 4 | Mappings: 5 | SubnetConfig: 6 | VPC: 7 | CIDR: '10.0.0.0/16' 8 | PublicOne: 9 | CIDR: '10.0.0.0/24' 10 | PublicTwo: 11 | CIDR: '10.0.1.0/24' 12 | PrivateOne: 13 | CIDR: '10.0.2.0/24' 14 | PrivateTwo: 15 | CIDR: '10.0.3.0/24' 16 | Parameters: 17 | ECSDeployEnv: 18 | Type: String 19 | Default: Dev 20 | AllowedValues: 21 | - Dev 22 | - Test 23 | - Prod 24 | DBUser: 25 | Type: String 26 | Default: master 27 | DBPassword: 28 | Type: String 29 | Default: netc0re123 30 | DBName: 31 | Type: String 32 | Default: todo 33 | AppStackName: 34 | Description: An Application name that will be prefixed to resource names 35 | Type: String 36 | Default: net-core-stack 37 | Resources: 38 | ApiVPC: 39 | Type: AWS::EC2::VPC 40 | Properties: 41 | EnableDnsSupport: true 42 | EnableDnsHostnames: true 43 | CidrBlock: 44 | !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] 45 | Tags: 46 | - Key: Name 47 | Value: !Ref AppStackName 48 | - Key: Project 49 | Value: .Net Core on AWS 50 | PublicSubnet1: 51 | Type: AWS::EC2::Subnet 52 | Properties: 53 | VpcId: 54 | Ref: ApiVPC 55 | CidrBlock: 56 | !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] 57 | AvailabilityZone: 58 | Fn::Select: 59 | - 0 60 | - Fn::GetAZs: {Ref: 'AWS::Region'} 61 | MapPublicIpOnLaunch: true 62 | Tags: 63 | - Key: Name 64 | Value: !Sub ${AppStackName} Public Subnet (AZ1) 65 | - Key: Project 66 | Value: .Net Core on AWS 67 | PublicSubnet2: 68 | Type: AWS::EC2::Subnet 69 | Properties: 70 | VpcId: 71 | Ref: ApiVPC 72 | CidrBlock: 73 | !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] 74 | AvailabilityZone: 75 | Fn::Select: 76 | - 1 77 | - Fn::GetAZs: {Ref: 'AWS::Region'} 78 | MapPublicIpOnLaunch: true 79 | Tags: 80 | - Key: Name 81 | Value: !Sub ${AppStackName} Public Subnet (AZ2) 82 | - Key: Project 83 | Value: .Net Core on AWS 84 | PrivateSubnet1: 85 | Type: AWS::EC2::Subnet 86 | Properties: 87 | VpcId: 88 | Ref: ApiVPC 89 | CidrBlock: 90 | !FindInMap ['SubnetConfig', 'PrivateOne', 'CIDR'] 91 | AvailabilityZone: 92 | Fn::Select: 93 | - 0 94 | - Fn::GetAZs: {Ref: 'AWS::Region'} 95 | Tags: 96 | - Key: Name 97 | Value: !Sub ${AppStackName} Private Subnet (AZ1) 98 | - Key: Project 99 | Value: .Net Core on AWS 100 | PrivateSubnet2: 101 | Type: AWS::EC2::Subnet 102 | Properties: 103 | VpcId: 104 | Ref: ApiVPC 105 | CidrBlock: 106 | !FindInMap ['SubnetConfig', 'PrivateTwo', 'CIDR'] 107 | AvailabilityZone: 108 | Fn::Select: 109 | - 1 110 | - Fn::GetAZs: {Ref: 'AWS::Region'} 111 | Tags: 112 | - Key: Name 113 | Value: !Sub ${AppStackName} Private Subnet (AZ2) 114 | - Key: Project 115 | Value: .Net Core on AWS 116 | IGW: 117 | DependsOn: ApiVPC 118 | Type: AWS::EC2::InternetGateway 119 | Properties: 120 | Tags: 121 | - Key: Name 122 | Value: !Sub ${AppStackName} - Internet Gateway 123 | - Key: Project 124 | Value: .Net Core on AWS 125 | VPCGatewayAttachment: 126 | DependsOn: IGW 127 | Type: AWS::EC2::VPCGatewayAttachment 128 | Properties: 129 | VpcId: 130 | Ref: ApiVPC 131 | InternetGatewayId: 132 | Ref: IGW 133 | NAT1EIP: 134 | DependsOn: VPCGatewayAttachment 135 | Type: AWS::EC2::EIP 136 | Properties: 137 | Domain: vpc 138 | NAT2EIP: 139 | DependsOn: VPCGatewayAttachment 140 | Type: AWS::EC2::EIP 141 | Properties: 142 | Domain: vpc 143 | NATGateway1: 144 | DependsOn: VPCGatewayAttachment 145 | Type: AWS::EC2::NatGateway 146 | Properties: 147 | AllocationId: 148 | Fn::GetAtt: 149 | - NAT1EIP 150 | - AllocationId 151 | SubnetId: 152 | Ref: PublicSubnet1 153 | NATGateway2: 154 | DependsOn: VPCGatewayAttachment 155 | Type: AWS::EC2::NatGateway 156 | Properties: 157 | AllocationId: 158 | Fn::GetAtt: 159 | - NAT2EIP 160 | - AllocationId 161 | SubnetId: 162 | Ref: PublicSubnet2 163 | PrivateSubnet1RouteTable: 164 | Type: AWS::EC2::RouteTable 165 | Properties: 166 | VpcId: 167 | Ref: ApiVPC 168 | Tags: 169 | - Key: Name 170 | Value: !Sub ${AppStackName} Private Route 171 | - Key: Project 172 | Value: .Net Core on AWS 173 | PrivateSubnet1Route: 174 | Type: AWS::EC2::Route 175 | Properties: 176 | RouteTableId: !Ref 'PrivateSubnet1RouteTable' 177 | DestinationCidrBlock: 0.0.0.0/0 178 | NatGatewayId: 179 | Ref: NATGateway1 180 | PrivateSubnet1RouteTableAssociation: 181 | Type: AWS::EC2::SubnetRouteTableAssociation 182 | Properties: 183 | SubnetId: 184 | Ref: PrivateSubnet1 185 | RouteTableId: 186 | Ref: PrivateSubnet1RouteTable 187 | PrivateSubnet2RouteTable: 188 | Type: AWS::EC2::RouteTable 189 | Properties: 190 | VpcId: 191 | Ref: ApiVPC 192 | Tags: 193 | - Key: Name 194 | Value: !Sub ${AppStackName} Private Route 195 | - Key: Project 196 | Value: .Net Core on AWS 197 | PrivateSubnet2Route: 198 | Type: AWS::EC2::Route 199 | Properties: 200 | RouteTableId: 201 | Ref: PrivateSubnet2RouteTable 202 | DestinationCidrBlock: 0.0.0.0/0 203 | NatGatewayId: 204 | Ref: NATGateway2 205 | PrivateSubnet2RouteTableAssociation: 206 | Type: AWS::EC2::SubnetRouteTableAssociation 207 | Properties: 208 | SubnetId: 209 | Ref: PrivateSubnet2 210 | RouteTableId: 211 | Ref: PrivateSubnet2RouteTable 212 | PublicSubnetRouteTable: 213 | Type: AWS::EC2::RouteTable 214 | Properties: 215 | VpcId: 216 | Ref: ApiVPC 217 | Tags: 218 | - Key: Name 219 | Value: !Sub ${AppStackName} Public Route 220 | - Key: Project 221 | Value: .Net Core on AWS 222 | PublicSubnetRoute: 223 | Type: AWS::EC2::Route 224 | DependsOn: VPCGatewayAttachment 225 | Properties: 226 | RouteTableId: 227 | Ref: PublicSubnetRouteTable 228 | DestinationCidrBlock: 0.0.0.0/0 229 | GatewayId: 230 | Ref: IGW 231 | PublicSubnet1RouteTableAssociation: 232 | Type: AWS::EC2::SubnetRouteTableAssociation 233 | Properties: 234 | SubnetId: 235 | Ref: PublicSubnet1 236 | RouteTableId: 237 | Ref: PublicSubnetRouteTable 238 | PublicSubnet2RouteTableAssociation: 239 | Type: AWS::EC2::SubnetRouteTableAssociation 240 | Properties: 241 | SubnetId: 242 | Ref: PublicSubnet2 243 | RouteTableId: 244 | Ref: PublicSubnetRouteTable 245 | #ECS Cluster 246 | ECSCluster: 247 | DependsOn: ApiVPC 248 | Type: AWS::ECS::Cluster 249 | Properties: 250 | ClusterName: !Sub ${AppStackName}-ecs-cluster 251 | Tags: 252 | - Key: Name 253 | Value: !Sub ${AppStackName} ECS Cluster 254 | - Key: Project 255 | Value: .Net Core on AWS 256 | #ECS Task Definition 257 | ECSTaskDefinition: 258 | DependsOn: ApiVPC 259 | Type: AWS::ECS::TaskDefinition 260 | Properties: 261 | Family: !Sub ${AppStackName} 262 | NetworkMode: awsvpc 263 | ExecutionRoleArn: !Ref ECSTaskExecutionRole 264 | TaskRoleArn: !Ref 'ECSRole' 265 | Cpu: 256 266 | Memory: 512 267 | RequiresCompatibilities: 268 | - FARGATE 269 | ContainerDefinitions: 270 | - 271 | Name: web 272 | LogConfiguration: 273 | LogDriver: awslogs 274 | Options: 275 | awslogs-group: !Ref 'ECSLogGroup' 276 | awslogs-region: !Ref AWS::Region 277 | awslogs-stream-prefix: !Sub ${AppStackName}-logs 278 | Image: 279 | Fn::Join: 280 | - '' 281 | - - Ref: AWS::AccountId 282 | - '.dkr.ecr.' 283 | - Ref: AWS::Region 284 | - '.amazonaws.com/' 285 | - !Sub ${AppStackName}-todo-repository:latest 286 | Cpu: 256 287 | Memory: 256 288 | PortMappings: 289 | - ContainerPort: 80 290 | Protocol: tcp 291 | Command: 292 | - "dotnet" 293 | - "todo-app.dll" 294 | ECSLogGroup: 295 | Type: AWS::Logs::LogGroup 296 | Properties: 297 | LogGroupName: !Sub ${AppStackName}-awslogs 298 | RetentionInDays: 30 299 | ECSSecurityGroup: 300 | Type: AWS::EC2::SecurityGroup 301 | Properties: 302 | GroupName: !Sub ${AppStackName}-ecs-app-sg 303 | GroupDescription: Access to the Fargate containers 304 | VpcId: !Ref 'ApiVPC' 305 | SecurityGroupIngress: 306 | # Allow access to ALB from anywhere on the internet 307 | - CidrIp: 0.0.0.0/0 308 | IpProtocol: tcp 309 | FromPort: 80 310 | ToPort: 80 311 | EcsSecurityGroupIngressTCP: 312 | Type: AWS::EC2::SecurityGroupIngress 313 | Properties: 314 | Description: Ingress TCP All Port from ELB SG 315 | GroupId: !Ref 'ECSSecurityGroup' 316 | IpProtocol: tcp 317 | FromPort: 1 318 | ToPort: 65535 319 | SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG' 320 | 321 | DBSecurityGroup: 322 | Type: AWS::EC2::SecurityGroup 323 | Properties: 324 | GroupName: !Sub ${AppStackName}-ecs-db-sg 325 | GroupDescription: Access to the RDS 326 | VpcId: !Ref 'ApiVPC' 327 | 328 | DBSecurityGroupIngressTCP: 329 | Type: AWS::EC2::SecurityGroupIngress 330 | DependsOn: DBSecurityGroup 331 | Properties: 332 | Description: Ingress 3306 333 | GroupId: !Ref 'DBSecurityGroup' 334 | IpProtocol: tcp 335 | FromPort: 3306 336 | ToPort: 3306 337 | SourceSecurityGroupId: !Ref 'ECSSecurityGroup' 338 | 339 | #ALB 340 | PublicLoadBalancerSG: 341 | Type: AWS::EC2::SecurityGroup 342 | Properties: 343 | GroupName: !Sub ${AppStackName}-elb-sg 344 | GroupDescription: Access to the public facing load balancer 345 | VpcId: !Ref 'ApiVPC' 346 | SecurityGroupIngress: 347 | - CidrIp: 0.0.0.0/0 348 | IpProtocol: tcp 349 | FromPort: 80 350 | ToPort: 80 351 | PublicLoadBalancer: 352 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 353 | DependsOn: VPCGatewayAttachment 354 | Properties: 355 | Name: !Sub ${AppStackName}-elb 356 | Scheme: internet-facing 357 | LoadBalancerAttributes: 358 | - Key: idle_timeout.timeout_seconds 359 | Value: '60' 360 | Subnets: 361 | - !Ref PublicSubnet1 362 | - !Ref PublicSubnet2 363 | SecurityGroups: [!Ref 'PublicLoadBalancerSG'] 364 | 365 | PublicLoadBalancerListener: 366 | Type: AWS::ElasticLoadBalancingV2::Listener 367 | DependsOn: 368 | - PublicLoadBalancer 369 | Properties: 370 | DefaultActions: 371 | - TargetGroupArn: !Ref 'TargetGroup' 372 | Type: 'forward' 373 | LoadBalancerArn: !Ref 'PublicLoadBalancer' 374 | Port: 80 375 | Protocol: HTTP 376 | 377 | TargetGroup: 378 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 379 | Properties: 380 | HealthCheckIntervalSeconds: 6 381 | HealthCheckPath: /api/values 382 | HealthCheckProtocol: HTTP 383 | HealthCheckTimeoutSeconds: 5 384 | HealthyThresholdCount: 2 385 | TargetType: ip 386 | Name: !Sub ${AppStackName}-tgt-grp 387 | Port: 80 388 | Protocol: HTTP 389 | UnhealthyThresholdCount: 2 390 | VpcId: !Ref 'ApiVPC' 391 | 392 | LoadBalancerRule: 393 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 394 | Properties: 395 | Actions: 396 | - TargetGroupArn: !Ref 'TargetGroup' 397 | Type: 'forward' 398 | Conditions: 399 | - Field: path-pattern 400 | Values: ['*'] 401 | ListenerArn: !Ref PublicLoadBalancerListener 402 | Priority: 1 403 | 404 | 405 | ECSRole: 406 | Type: AWS::IAM::Role 407 | Properties: 408 | RoleName: !Sub ${AppStackName}-ecs-role 409 | ManagedPolicyArns: 410 | - arn:aws:iam::aws:policy/AmazonRDSFullAccess 411 | - arn:aws:iam::aws:policy/AmazonSSMFullAccess 412 | - arn:aws:iam::aws:policy/AmazonEC2ContainerServiceFullAccess 413 | - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role 414 | AssumeRolePolicyDocument: 415 | Statement: 416 | - Effect: Allow 417 | Principal: 418 | Service: [ecs.amazonaws.com] 419 | Action: ['sts:AssumeRole'] 420 | - Effect: Allow 421 | Principal: 422 | Service: [ecs-tasks.amazonaws.com] 423 | Action: ['sts:AssumeRole'] 424 | Path: / 425 | Policies: 426 | - PolicyName: ecs-service 427 | PolicyDocument: 428 | Statement: 429 | - Effect: Allow 430 | Action: 431 | - 'ec2:AttachNetworkInterface' 432 | - 'ec2:CreateNetworkInterface' 433 | - 'ec2:CreateNetworkInterfacePermission' 434 | - 'ec2:DeleteNetworkInterface' 435 | - 'ec2:DeleteNetworkInterfacePermission' 436 | - 'ec2:Describe*' 437 | - 'ec2:DetachNetworkInterface' 438 | - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' 439 | - 'elasticloadbalancing:DeregisterTargets' 440 | - 'elasticloadbalancing:Describe*' 441 | - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' 442 | - 'elasticloadbalancing:RegisterTargets' 443 | Resource: '*' 444 | # ECS Task Role 445 | ECSTaskExecutionRole: 446 | Type: AWS::IAM::Role 447 | Properties: 448 | RoleName: !Sub ${AppStackName}-taskexec-role 449 | AssumeRolePolicyDocument: 450 | Statement: 451 | - Effect: Allow 452 | Principal: 453 | Service: [ecs-tasks.amazonaws.com] 454 | Action: ['sts:AssumeRole'] 455 | Path: / 456 | Policies: 457 | - PolicyName: AmazonECSTaskExecutionRolePolicy 458 | PolicyDocument: 459 | Statement: 460 | - Effect: Allow 461 | Action: 462 | - 'ecr:GetAuthorizationToken' 463 | - 'ecr:BatchCheckLayerAvailability' 464 | - 'ecr:GetDownloadUrlForLayer' 465 | - 'ecr:BatchGetImage' 466 | - 'logs:CreateLogStream' 467 | - 'logs:PutLogEvents' 468 | Resource: '*' 469 | #Aurora SQL 470 | RDSCluster: 471 | Properties: 472 | DBClusterParameterGroupName: 473 | Ref: RDSDBClusterParameterGroup 474 | Engine: aurora 475 | EngineMode: serverless 476 | MasterUserPassword: 477 | Ref: DBPassword 478 | MasterUsername: 479 | Ref: DBUser 480 | DBClusterIdentifier: !Sub ${AppStackName} 481 | DatabaseName: 482 | Ref: DBName 483 | DBSubnetGroupName: 484 | Ref: DBSubnetGroup 485 | Tags: 486 | - Key: Name 487 | Value: !Sub ${AppStackName} Aurora Serverless Cluster 488 | - Key: Project 489 | Value: .Net Core on AWS 490 | VpcSecurityGroupIds: 491 | - Ref: DBSecurityGroup 492 | Type: "AWS::RDS::DBCluster" 493 | DependsOn: DBSubnetGroup 494 | RDSDBClusterParameterGroup: 495 | Properties: 496 | Description: "CloudFormation Sample Aurora Cluster Parameter Group" 497 | Family: aurora5.6 498 | Parameters: 499 | time_zone: US/Eastern 500 | Type: "AWS::RDS::DBClusterParameterGroup" 501 | DBSubnetGroup: 502 | Type: AWS::RDS::DBSubnetGroup 503 | Properties: 504 | DBSubnetGroupDescription: RDS Subnet group for Aurora Serverless 505 | DBSubnetGroupName: !Sub ${AppStackName}-aurora-subnet-group 506 | SubnetIds: 507 | - !Ref PrivateSubnet1 508 | - !Ref PrivateSubnet2 509 | Tags: 510 | - Key: Name 511 | Value: !Sub ${AppStackName} RDS Subnet Group 512 | - Key: Project 513 | Value: .Net Core on AWS 514 | #Code Commit 515 | CodeCommitRepository: 516 | Type: AWS::CodeCommit::Repository 517 | Properties: 518 | RepositoryName: !Sub ${AppStackName}-todo-repo 519 | RepositoryDescription: Respository to maintain code related to the Todo Api. 520 | #Code Build 521 | CodeBuildProject: 522 | Type: AWS::CodeBuild::Project 523 | Properties: 524 | Name: !Sub ${AppStackName}-todo-app-build 525 | Description: Todo Api application codebuild project. 526 | ServiceRole: !GetAtt CodeBuildRole.Arn 527 | Artifacts: 528 | Type: no_artifacts 529 | Environment: 530 | Type: LINUX_CONTAINER 531 | ComputeType: BUILD_GENERAL1_SMALL 532 | Image: aws/codebuild/amazonlinux2-x86_64-standard:1.0 533 | PrivilegedMode: true 534 | EnvironmentVariables: 535 | - Name: REPOSITORY_URI 536 | Type: PLAINTEXT 537 | Value: 538 | Fn::Join: 539 | - '' 540 | - - Ref: AWS::AccountId 541 | - '.dkr.ecr.' 542 | - Ref: AWS::Region 543 | - '.amazonaws.com/' 544 | - !Sub ${AppStackName}-todo-repository 545 | - Name: AWS_DEFAULT_REGION 546 | Type: PLAINTEXT 547 | Value: 548 | Ref: AWS::Region 549 | Source: 550 | BuildSpec: config/buildspec.yml 551 | Location: 552 | Fn::Join: 553 | - '' 554 | - - 'https://git-codecommit.' 555 | - Ref: AWS::Region 556 | - '.amazonaws.com/v1/repos/' 557 | - !Sub ${AppStackName}-todo-repo 558 | Type: CODECOMMIT 559 | SourceVersion: refs/heads/master 560 | TimeoutInMinutes: 10 561 | CodeBuildRole: 562 | Type: AWS::IAM::Role 563 | Properties: 564 | ManagedPolicyArns: 565 | - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess 566 | - arn:aws:iam::aws:policy/AWSCodeCommitFullAccess 567 | AssumeRolePolicyDocument: 568 | Statement: 569 | - Action: ['sts:AssumeRole'] 570 | Effect: Allow 571 | Principal: 572 | Service: [codebuild.amazonaws.com] 573 | Version: '2012-10-17' 574 | Path: / 575 | Policies: 576 | - PolicyName: CodeBuildAccess 577 | PolicyDocument: 578 | Version: '2012-10-17' 579 | Statement: 580 | - Action: 581 | - 'logs:*' 582 | - 'ec2:CreateNetworkInterface' 583 | - 'ec2:DescribeNetworkInterfaces' 584 | - 'ec2:DeleteNetworkInterface' 585 | - 'ec2:DescribeSubnets' 586 | - 'ec2:DescribeSecurityGroups' 587 | - 'ec2:DescribeDhcpOptions' 588 | - 'ec2:DescribeVpcs' 589 | - 'ec2:CreateNetworkInterfacePermission' 590 | Effect: Allow 591 | Resource: '*' 592 | #CodeDeploy 593 | CodeDeployApplication: 594 | Type: AWS::CodeDeploy::Application 595 | Properties: 596 | ComputePlatform: ECS 597 | #ECR 598 | Repository: 599 | Type: AWS::ECR::Repository 600 | Properties: 601 | RepositoryName: !Sub ${AppStackName}-todo-repository 602 | RepositoryPolicyText: 603 | Version: "2012-10-17" 604 | Statement: 605 | - Effect: Allow 606 | Principal: 607 | Service: codebuild.amazonaws.com 608 | Action: 'sts:AssumeRole' 609 | # CloudWatchEvents Code build Rold 610 | CloudWatchEventsCodeBuildRole: 611 | Type: AWS::IAM::Role 612 | Properties: 613 | RoleName: !Sub ${AppStackName}-cloud-watch-events-codebuild-role 614 | AssumeRolePolicyDocument: 615 | Version: 2012-10-17 616 | Statement: 617 | - 618 | Effect: Allow 619 | Principal: 620 | Service: 621 | - events.amazonaws.com 622 | Action: sts:AssumeRole 623 | Policies: 624 | - PolicyName: aws-events-code-build 625 | PolicyDocument: 626 | Version: 2012-10-17 627 | Statement: 628 | - Effect: Allow 629 | Action: 630 | - 'codebuild:StartBuild' 631 | Resource: !GetAtt CodeBuildProject.Arn 632 | # CloudWatch Event Rule for codecommit build trigger 633 | CloudWatchEventCodeBuildEventRule: 634 | Type: AWS::Events::Rule 635 | Properties: 636 | Description: "This event rule triggers the build on code commit event" 637 | EventPattern: 638 | source: 639 | - "aws.codecommit" 640 | detail-type: 641 | - "CodeCommit Repository State Change" 642 | detail: 643 | event: 644 | - "referenceCreated" 645 | - "referenceUpdated" 646 | referenceType: 647 | - "branch" 648 | referenceName: 649 | - "master" 650 | State: "ENABLED" 651 | Targets: 652 | - 653 | Arn: {'Fn::GetAtt': [CodeBuildProject, Arn]} 654 | Id: cloudwatch-codebuild-eventrules 655 | RoleArn: !GetAtt CloudWatchEventsCodeBuildRole.Arn 656 | #S3 bucket for putting the published api folder. 657 | CodeS3Bucket: 658 | Type: AWS::S3::Bucket 659 | Properties: 660 | BucketName: 661 | !Sub ${AppStackName}-${AWS::Region}-${AWS::AccountId} 662 | #Amazon Systems Manager 663 | SystemsManagerBasicParameter: 664 | Type: "AWS::SSM::Parameter" 665 | Properties: 666 | Name: "/Database/Config/AuroraConnectionString" 667 | Type: "String" 668 | Value: !Join [ "", 669 | [ 670 | "server=", 671 | !Join [ "", [ !GetAtt RDSCluster.Endpoint.Address, "" ] ], 672 | ";port=", 673 | !GetAtt RDSCluster.Endpoint.Port, 674 | ";database=", !Join [ "", [ Ref: DBName, "" ] ], 675 | ";uid=", !Join [ "", [ Ref: DBUser, "" ] ], 676 | ";password=", !Join [ "", [ Ref: DBPassword, "" ] ], 677 | ";SSLMode=None" 678 | ] 679 | ] 680 | Description: "SSM Parameter for maintaining the Aurora DB connection string." 681 | # AllowedPattern: "^[a-zA-Z]{1,10}$" 682 | Tags: 683 | "Environment": "DEV" 684 | SystemsManagerBasicDBNameParameter: 685 | Type: "AWS::SSM::Parameter" 686 | Properties: 687 | Name: "/Database/Config/DBName" 688 | Type: "String" 689 | Value: !Join [ "", [ Ref: DBName, "" ] ] 690 | Description: "SSM Parameter for maintaining the Aurora DBName." 691 | Tags: 692 | "Environment": "DEV" 693 | SystemsManagerBasicDBUserParameter: 694 | Type: "AWS::SSM::Parameter" 695 | Properties: 696 | Name: "/Database/Config/DBUser" 697 | Type: "String" 698 | Value: !Join [ "", [ Ref: DBUser, "" ] ] 699 | Description: "SSM Parameter for maintaining the Aurora DBUser." 700 | Tags: 701 | "Environment": "DEV" 702 | SystemsManagerBasicDBPasswordParameter: 703 | Type: "AWS::SSM::Parameter" 704 | Properties: 705 | Name: "/Database/Config/DBPassword" 706 | Type: "String" 707 | Value: !Join [ "", [ Ref: DBPassword, "" ] ] 708 | Description: "SSM Parameter for maintaining the Aurora DBPassword." 709 | Tags: 710 | "Environment": "DEV" 711 | SystemsManagerBasicDBHostParameter: 712 | Type: "AWS::SSM::Parameter" 713 | Properties: 714 | Name: "/Database/Config/DBHost" 715 | Type: "String" 716 | Value: !Join [ "", [ !GetAtt RDSCluster.Endpoint.Address, "" ] ] 717 | Description: "SSM Parameter for maintaining the Aurora DB Host." 718 | Tags: 719 | "Environment": "DEV" 720 | Outputs: 721 | CodeCommitRepositoryName: 722 | Description: The name of the CodeCommit Repository 723 | Value: !Ref 'CodeCommitRepository' 724 | CodeBuildName: 725 | Description: The name of the CodeBuild. 726 | Value: !Ref 'CodeBuildProject' 727 | ClusterName: 728 | Description: The name of the ECS cluster 729 | Value: !Ref 'ECSCluster' 730 | Export: 731 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ] 732 | CodeS3BucketName: 733 | Description: The name of the S3 Bucket which has code for mysql table creation scripts 734 | Value: !Ref 'CodeS3Bucket' 735 | Export: 736 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CodeS3Bucket' ] ] 737 | ExternalUrl: 738 | Description: The url of the external load balancer 739 | Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']] 740 | Export: 741 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ExternalUrl' ] ] 742 | VPCId: 743 | Description: The ID of the VPC that this stack is deployed in 744 | Value: !Ref 'ApiVPC' 745 | Export: 746 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ApiVPC' ] ] 747 | PublicSubnet1: 748 | Description: Public subnet one 749 | Value: !Ref 'PublicSubnet1' 750 | Export: 751 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnet1' ] ] 752 | PublicSubnet2: 753 | Description: Public subnet two 754 | Value: !Ref 'PublicSubnet2' 755 | Export: 756 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnet2' ] ] 757 | PrivateSubnet1: 758 | Description: Private subnet one 759 | Value: !Ref 'PrivateSubnet1' 760 | Export: 761 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnet1' ] ] 762 | PrivateSubnet2: 763 | Description: Private subnet two 764 | Value: !Ref 'PrivateSubnet2' 765 | Export: 766 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnet2' ] ] 767 | ECSRole: 768 | Description: The ARN of the ECS role 769 | Value: !GetAtt 'ECSRole.Arn' 770 | Export: 771 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSRole' ] ] 772 | ECSTaskExecutionRole: 773 | Description: The ARN of the ECS role 774 | Value: !GetAtt 'ECSTaskExecutionRole.Arn' 775 | Export: 776 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSTaskExecutionRole' ] ] 777 | ECSTaskDefinition: 778 | Description: TaskDefinition for the ECS Cluster 779 | Value: !Ref 'ECSTaskDefinition' 780 | Export: 781 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSTaskDefinition' ] ] 782 | TargetGroupName: 783 | Description: TargetGroup for the loadbalancer 784 | Value: !Ref TargetGroup 785 | Export: 786 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'TargetGroupName' ] ] 787 | ECSSecurityGroup: 788 | Description: ECSSecurityGroup for the loadbalancer 789 | Value: !Ref 'ECSSecurityGroup' 790 | Export: 791 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSSecurityGroup' ] ] 792 | PublicListener: 793 | Description: The ARN of the public load balancer's Listener 794 | Value: !Ref PublicLoadBalancerListener 795 | Export: 796 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicListener' ] ] -------------------------------------------------------------------------------- /config/net-core-task-services.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a .NET WebApi on AWS Fargate that talks to Aurora, hosted in a private subnet/FARGATE accessible via a public load balancer. 3 | Mappings: 4 | AWSInstanceType2Arch: 5 | t3.micro: 6 | Arch: HVM64 7 | t3.small: 8 | Arch: HVM64 9 | t3.medium: 10 | Arch: HVM64 11 | t3.large: 12 | Arch: HVM64 13 | m3.small: 14 | Arch: HVM64 15 | AWSRegionArch2AMI: 16 | us-east-1: 17 | HVM64: ami-0b69ea66ff7391e80 18 | us-east-2: 19 | HVM64: ami-00c03f7f7f2ec15c3 20 | us-west-1: 21 | HVM64: ami-0245d318c6788de52 22 | us-west-2: 23 | HVM64: ami-04b762b4289fba92b 24 | ap-south-1: 25 | HVM64: ami-0cb0e70f44e1a4bb5 26 | 27 | Parameters: 28 | StackName: 29 | Type: String 30 | Default: net-core-stack 31 | Description: The name of the parent stack that spun up infrastructure already 32 | AppStackName: 33 | Description: Service stack for net-core-task 34 | Type: String 35 | Default: net-core-stack-service 36 | KeyName: 37 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 38 | Type: AWS::EC2::KeyPair::KeyName 39 | Default: my-east1-keypair 40 | InstanceType: 41 | Description: RDS DBTable Instance type 42 | Type: String 43 | Default: t3.large 44 | AllowedValues: 45 | - t3.micro 46 | - t3.small 47 | - t3.medium 48 | - t3.large 49 | - m3.small 50 | 51 | Resources: 52 | 53 | #Inline Lambda function to create the table 54 | CreateTableScriptLambda: 55 | Type: AWS::Lambda::Function 56 | Properties: 57 | FunctionName: !Sub ${AppStackName}-createtablelambda 58 | Handler: index.handler 59 | Runtime: python3.6 60 | Role: !GetAtt LambdaBasicExecutionRole.Arn 61 | Environment: 62 | Variables: 63 | SOURCE_S3_BUCKET: 64 | Fn::ImportValue: 65 | !Join [':', [!Ref 'StackName', 'CodeS3Bucket']] 66 | Code: 67 | ZipFile: | 68 | import os 69 | import json 70 | import boto3 71 | 72 | def handler(event, context): 73 | print("lambda - table creator - entered") 74 | s3 = boto3.resource('s3') 75 | ssm_client = boto3.client('ssm') 76 | ssm_db_name = ssm_client.get_parameter(Name='/Database/Config/DBName', WithDecryption=False)['Parameter']['Value'] 77 | ssm_db_user = ssm_client.get_parameter(Name='/Database/Config/DBUser', WithDecryption=False)['Parameter']['Value'] 78 | ssm_db_password = ssm_client.get_parameter(Name='/Database/Config/DBPassword', WithDecryption=False)['Parameter']['Value'] 79 | ssm_db_host = ssm_client.get_parameter(Name='/Database/Config/DBHost', WithDecryption=False)['Parameter']['Value'] 80 | 81 | script_string = "let mysql = require('mysql');\nlet connection = mysql.createConnection({\n host : '<>',\n user : '<>',\n password : '<>',\n database : '<>'\n});\n \n// connect to the MySQL server\nconnection.connect(function(err) {\n if (err) {\n return console.error('error: ' + err.message);\n }\n \n let createTodos = `CREATE TABLE IF NOT EXISTS ToDos(\n id MEDIUMINT not null auto_increment,\n CreatedTime TIMESTAMP DEFAULT now(),\n Status VARCHAR(50),\n Task VARCHAR(50),\n primary key(id)\n )`;\n \n connection.query(createTodos, function(err, results, fields) {\n if (err) {\n console.log(err.message);\n }else{\n console.log('table created successfully')\n }\n });\n \n connection.end(function(err) {\n if (err) {\n return console.log(err.message);\n }\n });\n});" 82 | 83 | string = script_string.replace("<>", ssm_db_host) 84 | string = string.replace("<>", ssm_db_name) 85 | string = string.replace("<>", ssm_db_user) 86 | string = string.replace("<>", ssm_db_password) 87 | 88 | encoded_string = string.encode("utf-8") 89 | 90 | bucket_name = os.environ["SOURCE_S3_BUCKET"] 91 | file_name = "mysql-create-table.js" 92 | 93 | s3.Bucket(bucket_name).put_object(Key=file_name, Body=encoded_string) 94 | print("MySQL Script for table creation deployed in S3 bucket " + bucket_name + " successfully! ") 95 | return "file-path: " + bucket_name + "/" + file_name 96 | 97 | LambdaBasicExecutionRole: 98 | Type: AWS::IAM::Role 99 | Properties: 100 | RoleName: !Sub ${AppStackName}-create-table-lambda-role 101 | AssumeRolePolicyDocument: 102 | Version: 2012-10-17 103 | Statement: 104 | - 105 | Effect: Allow 106 | Principal: 107 | Service: 108 | - lambda.amazonaws.com 109 | Action: 110 | - sts:AssumeRole 111 | Path: / 112 | Policies: 113 | - PolicyName: !Sub ${AppStackName}-s3-file-create-policy 114 | PolicyDocument: 115 | Version: '2012-10-17' 116 | Statement: 117 | - Effect: Allow 118 | Action: ['cloudwatch:*', 'logs:*'] 119 | Resource: '*' 120 | - Effect: Allow 121 | Action: ['ssm:*'] 122 | Resource: '*' 123 | - Effect: Allow 124 | Action: 125 | - s3:* 126 | Resource: 127 | Fn::Join: 128 | - '' 129 | - - "arn:aws:s3:::" 130 | - Fn::ImportValue: 131 | !Join [':', [!Ref 'StackName', 'CodeS3Bucket']] 132 | - "/*" 133 | 134 | EC2RDSTableHandlerInstance: 135 | Type: AWS::EC2::Instance 136 | DependsOn: CreateTableScriptLambda 137 | Properties: 138 | IamInstanceProfile: 139 | Ref: EC2RDSTableHandlerIamInstanceProfile 140 | ImageId: 141 | Fn::FindInMap: 142 | - AWSRegionArch2AMI 143 | - Ref: AWS::Region 144 | - Fn::FindInMap: 145 | - AWSInstanceType2Arch 146 | - Ref: InstanceType 147 | - Arch 148 | InstanceType: 149 | Ref: InstanceType 150 | KeyName: 151 | Ref: KeyName 152 | UserData: 153 | Fn::Base64: !Sub 154 | - | 155 | #!/bin/bash -x 156 | echo "Retrieving env variables - bucket name - ${S3_BUCKET_NAME} - lambda name - ${LAMBDA_NAME} - region - ${REGION}" >> /home/ec2-user/STEP1 || exit 1 157 | 158 | aws lambda invoke --function-name ${LAMBDA_NAME} {} --region ${REGION} || exit 1 159 | echo "lambda_invoked_successfully_${LAMBDA_NAME}_${REGION}" >> /home/ec2-user/STEP2 || exit 1 160 | 161 | mkdir /home/ec2-user/net-core && cd /home/ec2-user/net-core && aws s3 cp s3://${S3_BUCKET_NAME}/ /home/ec2-user/net-core/ --recursive && curl --silent --location https://rpm.nodesource.com/setup_12.x | bash - && yum -y install nodejs && npm install mysql && node mysql-create-table.js || exit 1 162 | echo "executed node script for tables" >> /home/ec2-user/STEP7 163 | - 164 | S3_BUCKET_NAME: 165 | Fn::ImportValue: 166 | Fn::Sub: "${StackName}:CodeS3Bucket" 167 | LAMBDA_NAME: !Sub ${AppStackName}-createtablelambda 168 | REGION: !Ref AWS::Region 169 | 170 | NetworkInterfaces: 171 | - AssociatePublicIpAddress: "true" 172 | DeviceIndex: "0" 173 | GroupSet: 174 | - Fn::ImportValue: 175 | !Join [':', [!Ref 'StackName', 'ECSSecurityGroup']] 176 | SubnetId: 177 | Fn::ImportValue: 178 | !Join [':', [!Ref 'StackName', 'PublicSubnet1']] 179 | Tags: 180 | - Key: Name 181 | Value: !Sub ${AppStackName}-rds-table-creator-instance 182 | - Key: SourceBucket 183 | Value: 184 | Fn::ImportValue: 185 | !Join [':', [!Ref 'StackName', 'CodeS3Bucket']] 186 | 187 | EC2RDSTableHandlerLogGroup: 188 | Type: AWS::Logs::LogGroup 189 | Properties: 190 | LogGroupName: !Sub ${AppStackName}-ec2-table-handler-awslogs 191 | RetentionInDays: 30 192 | 193 | EC2RDSTableHandlerSecurityGroup: 194 | Type: AWS::EC2::SecurityGroup 195 | Properties: 196 | GroupName: !Sub ${AppStackName}-ec2-table-handler-sg 197 | GroupDescription: Enable HTTP access via SSH 198 | VpcId: 199 | Fn::ImportValue: 200 | !Join [':', [!Ref 'StackName', 'ApiVPC']] 201 | GroupDescription: Enable HTTP access via SSH 202 | SecurityGroupIngress: 203 | - IpProtocol: tcp 204 | FromPort: '22' 205 | ToPort: '22' 206 | CidrIp: '72.21.196.65/32' 207 | 208 | EC2RDSTableHandlerIamInstanceProfile: 209 | Type: AWS::IAM::InstanceProfile 210 | Properties: 211 | InstanceProfileName: !Sub ${AppStackName}-ec2-rds-table-handler-instanceprofile 212 | Path: "/" 213 | Roles: 214 | - Ref: EC2RDSTableHandlerRole 215 | 216 | EC2RDSTableHandlerRole: 217 | Type: AWS::IAM::Role 218 | Properties: 219 | RoleName: !Sub ${AppStackName}-ec2-rds-table-handler-role 220 | ManagedPolicyArns: 221 | - arn:aws:iam::aws:policy/AmazonRDSFullAccess 222 | - arn:aws:iam::aws:policy/AmazonS3FullAccess 223 | - arn:aws:iam::aws:policy/AmazonEC2FullAccess 224 | - arn:aws:iam::aws:policy/AWSLambdaFullAccess 225 | AssumeRolePolicyDocument: 226 | Version: '2012-10-17' 227 | Statement: 228 | - Effect: Allow 229 | Principal: 230 | Service: 231 | - ec2.amazonaws.com 232 | Action: 233 | - sts:AssumeRole 234 | Path: "/" 235 | Policies: 236 | - PolicyName: EC2RDSTableHandlerPolicy 237 | PolicyDocument: 238 | Version: '2012-10-17' 239 | Statement: 240 | - Effect: Allow 241 | Action: ecs:* 242 | Resource: "*" 243 | 244 | ECSService: 245 | Type: AWS::ECS::Service 246 | DependsOn: EC2RDSTableHandlerInstance 247 | Properties: 248 | ServiceName: !Sub ${AppStackName}-todo-app-service 249 | Cluster: 250 | Fn::ImportValue: 251 | !Join [':', [!Ref 'StackName', 'ClusterName']] 252 | LaunchType: FARGATE 253 | DesiredCount: 1 254 | NetworkConfiguration: 255 | AwsvpcConfiguration: 256 | AssignPublicIp: ENABLED 257 | SecurityGroups: 258 | - Fn::ImportValue: 259 | !Join [':', [!Ref 'StackName', 'ECSSecurityGroup']] 260 | Subnets: 261 | - Fn::ImportValue: 262 | !Join [':', [!Ref 'StackName', 'PublicSubnet1']] 263 | - Fn::ImportValue: 264 | !Join [':', [!Ref 'StackName', 'PublicSubnet2']] 265 | 266 | TaskDefinition: 267 | Fn::ImportValue: 268 | !Join [':', [!Ref 'StackName', 'ECSTaskDefinition']] 269 | LoadBalancers: 270 | - ContainerName: web 271 | ContainerPort: 80 272 | TargetGroupArn: 273 | Fn::ImportValue: 274 | !Join [':', [!Ref 'StackName', 'TargetGroupName']] 275 | 276 | 277 | Outputs: 278 | HealthCheckUrl: 279 | Description: Healthcheck URL 280 | Value: 281 | Fn::Join: 282 | - '' 283 | - - Fn::ImportValue: 284 | !Join [':', [!Ref 'StackName', 'ExternalUrl']] 285 | - '/api/values' 286 | WebApiUrl: 287 | Description: WebApi URL 288 | Value: 289 | Fn::Join: 290 | - '' 291 | - - Fn::ImportValue: 292 | !Join [':', [!Ref 'StackName', 'ExternalUrl']] 293 | - '/api/todo' --------------------------------------------------------------------------------