UNPKG

@0x4447/potato

Version:

🥔 Upload a static page to AWS S3 while automatically configuring CloudFront.

493 lines (396 loc) • 9.8 kB
let term = require('terminal-kit').terminal; let upload = require('../helpers/upload'); // // This promises is responsible for updating the content of a selected // S3 bucket and invalidating the CloudFront Distribution Cash // module.exports = function(container) { return new Promise(function(resolve, reject) { get_s3_buckets(container) .then(function(container) { return query_for_bucket_policy(container); }).then(function(container) { return find_out_which_bucket_is_public(container); }).then(function(container) { return pick_a_bucket(container); }).then(function(container) { return upload(container); }).then(function(container) { return list_cloudfront_distributions(container); }).then(function(container) { return look_for_distribution_id(container); }).then(function(container) { return invalidate_cloudfront(container); }).then(function(container) { return resolve(container); }).catch(function(error) { return reject(error); }); }); } // _____ _____ ____ __ __ _____ _____ ______ _____ // | __ \ | __ \ / __ \ | \/ | |_ _| / ____| | ____| / ____| // | |__) | | |__) | | | | | | \ / | | | | (___ | |__ | (___ // | ___/ | _ / | | | | | |\/| | | | \___ \ | __| \___ \ // | | | | \ \ | |__| | | | | | _| |_ ____) | | |____ ____) | // |_| |_| \_\ \____/ |_| |_| |_____| |_____/ |______| |_____/ // // // Read all the buckets that are hosted on this S3 account // function get_s3_buckets(container) { return new Promise(function(resolve, reject) { // // 1. Skip this view if the information was already passed in the // CLI // if(container.bucket) { // // -> Move to the next chain // return resolve(container); } // // 2. List all buckets // container.s3.listBuckets(function(error, data) { // // 1. Check if there was an error // if(error) { return reject(error); } // // 2. Save the bucket names for the next chain // container.raw_buckets = data.Buckets; // // -> Move to the next chain // return resolve(container); }); }); } // // Get the ACL for each bucket so we can later filter out and keep only // buckets that are public. So we shorten the list of the buckets that we // show up. // function query_for_bucket_policy(container) { return new Promise(function(resolve, reject) { // // 1. Skip this view if the information was already passed in the // CLI // if(container.bucket) { // // -> Move to the next chain // return resolve(container); } // // 2. An array that will hold all the promises to get the ACL // for each bucket. // let promises = []; // // 3. Loop over the result and add the name to the array // container.raw_buckets.forEach(function(bucket) { // // 1. Add the promise to the array to be executed later with // Promise.all(). // promises.push( new Promise(function(resolve, reject) { container.s3.getBucketPolicy({ Bucket: bucket.Name }, function(error, bp) { // // 1. Do not check for errors because if a bucket // is in a different region the AWS SDK will // throw the error, and those errors we don't // care about. // if (error) { } // // 2. By default we assume there was no Policy // attached to the bucket // let statement = null; // // 3. Check if the policy is in place // if(bp) { // // 1. Just save the first statement which in // this case will always have just one // object in the array. Which potato adds // to make the bucket accessible for CloudFront // statement = JSON.parse(bp.Policy).Statement[0] } // // -> Return the result // return resolve({ bucket_name: bucket.Name, statement: statement }); }) }) ); }); // // 4. Execute all the quires to AWS S3. // Promise.all(promises) .then(function(data) { // // -> Save the result for the next chain. // container.bucket_policys = data; // // -> Move to the next chain. // return resolve(container); }).catch(function(error) { // // -> Stop the chain and surface the error. // return reject(error); }); }); } // // Loop over the bucket that were enriched with Policy data and find out the // public buckets, and discard the rest. // function find_out_which_bucket_is_public(container) { return new Promise(function(resolve, reject) { // // 1. Skip this view if the information was already passed in the // CLI // if(container.bucket) { // // -> Move to the next chain // return resolve(container); } // // 2. The array that will hold only the public buckets. // let public_buckets = []; // // 3. Loop over each bucket and check for the right Policy. // container.bucket_policys.forEach(function(data) { // // 1. Check if we have a policy. // if(data.statement) { // // 1. Convert the strings that we care about in to boolean // values. // let effect = (data.statement.Effect == 'Allow') ? true : false; let principal = (data.statement.Principal == '*') ? true : false; let action = (data.statement.Action == 's3:GetObject') ? true : false; // // 2. Sum all of our boolean values and see if we got all // 3 metrics that describe a public bucket. // if((effect + principal + action) == 3) { // // 1. Save he bucket for other promises to use. // public_buckets.push(data.bucket_name) } } }); // // 4. Save the public bucket names for the next chain // container.buckets = public_buckets; // // -> Move to the next chain // return resolve(container); }); } // // Ask the user to pick a bucket to be updated // function pick_a_bucket(container) { return new Promise(function(resolve, reject) { // // 1. Skip this view if the information was already passed in the // CLI // if(container.bucket) { // // -> Move to the next chain // return resolve(container); } term.clear(); term("\n"); term.yellow("\tChoose the bucket that you want to update"); term('\n'); // // 2. Draw the menu with one tab to the left to so the UI stay // consistent // let options = { leftPadding: "\t" } // // 3. Draw the drop down menu // term.singleColumnMenu(container.buckets, options, function(error, res) { // // 1. Get the Property name based on the user selection // let bucket = container.buckets[res.selectedIndex]; // // 2. Save the selection for other promises to use. It will // be used in API calls // container.bucket = bucket; // // -> Move to the next chain // return resolve(container); }); }); } //////////////////////////////////////////////////////////////////////////////// // // Upload the file to the S3 bucket so we can deliver something // // .upload(); // //////////////////////////////////////////////////////////////////////////////// // // Get all the CloudFront Distributions so we can find out the ID that // we have to use to invalidate the data, so cloud front will actually // show the changes // function list_cloudfront_distributions(container) { return new Promise(function(resolve, reject) { // // 1. Ask for the distributions // container.cloudfront.listDistributions({}, function(error, data) { // // 1. Check if there was no error // if(error) { return reject(new Error(error.message)); } // // 2. Save the response as is for the next chain // container.distributions = data.DistributionList.Items // // -> Move to the next step once the animation finishes drawing // return resolve(container); }); }); } // // Loop over all the distributions to find out the ID based on the // domain name selected by the user // function look_for_distribution_id(container) { return new Promise(function(resolve, reject) { // // 1. Make a variable that will hold the Distribution ID // let distribution_id = null; // // 2. Loop over the result and look for the domain // for(let key in container.distributions) { // // 1. See if the distribution contains the domain that we // care about // if(container.distributions[key].Aliases.Items[0] == container.bucket) { // // 1. Save the Distribution ID once we found the domain // distribution_id = container.distributions[key].Id // // -> Stop the loop to preserve CPU cycles // break; } } // // 3. Save the distribution ID for the next chain // container.distribution_id = distribution_id // // -> Move to the next step once the animation finishes drawing // return resolve(container); }); } // // Tell CloudFront to invalidate the cash so it can get new data that we // just uploaded // function invalidate_cloudfront(container) { return new Promise(function(resolve, reject) { // // 1. Settings for CloudFront // let params = { DistributionId: container.distribution_id, InvalidationBatch: { CallerReference: new Date().toString(), Paths: { Quantity: 1, Items: ["/*"] } } }; // // 2. Invalidate the cash // container.cloudfront.createInvalidation(params, function(error, data) { // // 1. Check if there was no error // if(error) { return reject(new Error(error.message)); } // // -> Move to the next chain // return resolve(container); }); }); }