{"id":1294,"date":"2020-04-03T11:53:30","date_gmt":"2020-04-03T09:53:30","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=1294"},"modified":"2021-03-24T12:32:49","modified_gmt":"2021-03-24T11:32:49","slug":"how-to-make-a-fully-automated-cd-pipeline-for-an-angular-frontend-application","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/how-to-make-a-fully-automated-cd-pipeline-for-an-angular-frontend-application\/","title":{"rendered":"How to make a fully automated CD pipeline for an Angular frontend application"},"content":{"rendered":"
We have already written about Continuous Delivery\/Continuous Integration<\/strong> several times, most of them mainly focused on the backend part of an application. Of course, web applications also require a front-end<\/strong>, thus, making the deployment process of the front-end agile and fast<\/strong> is crucial for the success of a project.\u00a0<\/span><\/p>\n In this article, we want to bring you the complete project<\/strong> for an AWS infrastructure to host an Angular application<\/strong>, complete with a CDN, a custom function to invalidate the cache and a fully automated CD pipeline<\/strong>.<\/span><\/p>\n This project aims to fulfill the best practices and will provide a robust, highly scalable and fully managed solution for the hosting of front-end applications on AWS. This is also the most cost-optimized<\/strong> solution we have found so far, allowing you to serve your static content in a very reliable, performant and cheap way.<\/span><\/p>\n Our architecture is logically partitioned in 2 parts, the customer-facing hosting, and the developer-facing Continuous Delivery infrastructure.<\/span><\/p>\n For the hosting, we will use the following AWS services:<\/span><\/p>\n The developer-facing infrastructure will be made using:<\/span><\/p>\n <\/p>\n The diagram above shows the complete infrastructure, it also shows the triggers and the action taken during the pipeline execution.<\/span><\/p>\n The customer-facing hosting is based on CloudFront, which allows us to serve the Angular application in a very efficient and reliable way. Using CloudFront is also required to enable HTTPS<\/strong> for our front-end application.<\/span><\/p>\n The compiled assets of the front-end application will be stored inside an S3 Bucket, used as an origin for the CloudFront distribution. We strongly advise keeping the S3 Bucket private, in this way no external user will be able to directly access the files on S3, limiting costs and possibilities of exploits.<\/span><\/p>\n We included Route53 in the architecture because it\u2019s the most straightforward way to manage DNS records for the domain, anyway, any DNS service would do, as long as you can add a CNAME record for your front-end application.<\/span><\/p>\n The real interesting part of the architecture is the developer-facing<\/strong> one. The core of the solution is CodePipeline, which we use as an orchestrator to react to changes made in the repository and pass the data between all the services used during the pipeline execution.<\/span><\/p>\n We also used CodeCommit as a repository for the application code. At the time of writing, CodePipeline supports CodeCommit and GitHub. In order to support other Git repo configurations, further customization<\/strong> would be needed.\u00a0<\/span><\/p>\n In order to build the angular solution, we leverage CodeBuild to automatically provision a container for the build process.<\/span><\/p>\n To speed up the deploy and avoid waiting for the expiration of each object in the CDN, we included an Invoke step<\/strong> at the end of the pipeline to run a Lambda Function which creates an invalidation request for the CloudFront Distribution.<\/span><\/p>\n Our pipeline will be composed of 4 steps, let\u2019s break down the pipeline mechanism.<\/span><\/p>\n This step is fully managed by AWS CodePipeline. The step is configured to automatically start the pipeline when a push is detected in a specific branch, download the source code, make an archive and send it as an artifact for the next step.<\/span><\/p>\n The build step needs the source code artifact from the previous step as an input.<\/span><\/p>\n CodeBuild will then provision a container for the build, download the source code and execute the commands contained in the buildspec.yml file.<\/span><\/p>\n The yml file must be stored in the root of the repository.<\/span><\/p>\n Here a sample buildspec.yml<\/strong> file for a standard Angular application:<\/span><\/p>\n The output of the build process is an archive of the files and folders contained in the dist folder.<\/span><\/p>\n The deploy step is also completely managed, CodePipeline will unpack and copy to S3 all the files and folders of the build output archive.<\/span><\/p>\n At the time of writing, this step is not able to delete files from S3, usually, this is not a problem given the structure of a standard Angular application. However, keep in mind that if you want to delete a file from the website you have to add a step to empty the bucket before deploying or deleting it after the deploy with another step.<\/span><\/p>\n The last step of the pipeline invokes a Lambda function to create an invalidation for the CloudFront distribution, allowing our users to obtain the updated version of the applications in few minutes after the deploy, instead of wait for objects expirations in each CDN node, which may occur at different times.\u00a0<\/span><\/p>\n The lambda function can use the AWS SDK to create the invalidation. This function also needs to notify CodePipeline whenever it encounters errors or finishes with success using a specific CodePipeline API. Here an example lambda to completely invalidate a CloudFront distribution and then notify to CodePipeline the result:<\/span><\/p>\n The following is a list of operations to follow in order to build the solution. Please note that this is not a copy and paste tutorial, it gives you the right order for each operation and an insight into what to do.<\/span><\/p>\n Before starting, make sure to have your source code in a CodeCommit repository and to have full access to the account to create and configure each service of the solution.<\/span><\/p>\n Congratulations! You completed the deploy of the solution! You should now have a fully functional Angular hosting, complete with a continuous delivery pipeline.<\/span><\/p>\n Still curious about Continuous Delivery\/Continuous Integration?\u00a0<\/strong><\/p>\n Read also:<\/p>\nThe services<\/span><\/h2>\n
\n
\n
The architecture<\/span><\/h2>\n
The pipeline steps<\/span><\/h2>\n
Source<\/span><\/h3>\n
Build<\/span><\/h3>\n
version: '0.2'\r\nphases:\r\n install:\r\n runtime-versions:\r\n nodejs: 12\r\n pre_build:\r\n commands:\r\n - echo \"Prebuild, installing npm dependencies\"\r\n - npm install\r\n build:\r\n commands:\r\n - echo \"Starting the build step\"\r\n - npm run build\r\n - echo \"Finished\"\r\nartifacts:\r\n name: \"BuildOutput\"\r\n files:\r\n - '**\/*'\r\n base-directory: 'dist'\r\n<\/pre>\n
Deploy<\/span><\/h3>\n
CloudFront Invalidation<\/span><\/h3>\n
import boto3\r\nimport os\r\n \r\nfrom botocore.exceptions import ClientError\r\n \r\ncloudfront_client = boto3.client('cloudfront')\r\ncodepipeline_client = boto3.client('codepipeline')\r\n \r\n \r\ndef lambda_handler(event, context):\r\n try:\r\n cdn_id = os.environ[\"CDN\"]\r\n cloudfront_client.create_invalidation(\r\n DistributionId=cdn_id,\r\n InvalidationBatch={\r\n 'Paths': {\r\n 'Quantity': 1,\r\n 'Items': [\r\n '\/*'\r\n ],\r\n },\r\n 'CallerReference': event['CodePipeline.job']['id']\r\n }\r\n )\r\n \r\n codepipeline_client.put_job_success_result(jobId=event['CodePipeline.job']['id'])\r\n except ClientError as e:\r\n print(\"Boto3 exception\", e)\r\n codepipeline_client.put_job_failure_result(\r\n jobId=event['CodePipeline.job']['id'],\r\n failureDetails={\r\n 'type': 'JobFailed',\r\n 'message': e.response\r\n })\r\n except Exception as e:\r\n print(\"Error\", e)\r\n codepipeline_client.put_job_failure_result(\r\n jobId=event['CodePipeline.job']['id'],\r\n failureDetails={\r\n 'type': 'JobFailed',\r\n 'message': e.args\r\n })\r\n<\/pre>\n
How to build the solution<\/span><\/h2>\n
\n
\n
\n
\n
\n
\n
\n
\n
\n