diff --git a/index.js b/index.js index 406fae2..4b4815b 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ const {deployStack, deployParts, deployFull} = require('./lib/deployStack') const {deployTemplate} = require('./lib/deployTemplate') const deleteStack = require('./lib/deleteStack') const stackExists = require('./lib/stackExists') +const stackResourceExists = require('./lib/stackResourceExists') const resourceExists = require('./lib/resourceExists') module.exports.generateTemplate = generateTemplate; @@ -12,6 +13,7 @@ module.exports.deployFull = deployFull; module.exports.deployTemplate = deployTemplate; module.exports.deleteStack = deleteStack; module.exports.stackExists = stackExists; +module.exports.stackResourceExists = stackResourceExists; module.exports.resourceExists = resourceExists; diff --git a/lib/deleteStack.js b/lib/deleteStack.js index a1c9d57..d7d20b6 100644 --- a/lib/deleteStack.js +++ b/lib/deleteStack.js @@ -1,4 +1,4 @@ -require('dotenv').config(); +//require('dotenv').config(); var AWS = require('aws-sdk'); var cloudformation = new AWS.CloudFormation({apiVersion: '2010-05-15'}); diff --git a/lib/deployCertificate.js b/lib/deployCertificate.js new file mode 100644 index 0000000..8414121 --- /dev/null +++ b/lib/deployCertificate.js @@ -0,0 +1,68 @@ +require('dotenv').config(); +var AWS = require('aws-sdk'); +var acm = new AWS.ACM({apiVersion: '2015-12-08'}); +var dns = require('@torus-tools/domains') +var {AcmCertificate} = require('./resourceExists') + +function createCertificate(domain){ + return new Promise((resolve, reject) => { + AcmCertificate(domain).then(data=>{ + if(data) resolve(data) + else { + var params = { + DomainName: domain, /* required */ + DomainValidationOptions: [ + { + DomainName: domain, + ValidationDomain: domain, + }, + ], + SubjectAlternativeNames: [ + `*.${domain}`, + ], + ValidationMethod: 'DNS' + }; + acm.requestCertificate(params).promise() + .then(data=>resolve(data.CertificateArn)) + .catch(err=>reject(err)) + } + }).catch(err=>reject(err)) + }) +} + + +function getCname(certificateArn){ + return new Promise((resolve, reject) => { + var params = {CertificateArn: certificateArn}; + acm.describeCertificate(params).promise() + .then(data=> { + if(data.Certificate.DomainValidationOptions[0].ResourceRecord){ + let cName = data.Certificate.DomainValidationOptions[0].ResourceRecord.Name; + let cValue = data.Certificate.DomainValidationOptions[0].ResourceRecord.Value; + resolve({"cName": cName, "cValue": cValue}); + } + else reject('error no CNAME available for the given certificate') + }) + }) +} + +function validateCertificate(config, cName, cValue) { + return new Promise((resolve, reject) => { + let records = [ + { + data: cValue, + name: cName, + type: 'CNAME' + } + ] + dns[config.providers.dns].upsertRecords(domain, records) + .then(resolve('All Done!')) + .catch(err=>reject(err)) + }) +} + +module.exports = { + createCertificate, + getCname, + validateCertificate, +} \ No newline at end of file diff --git a/lib/deployStack.js b/lib/deployStack.js index 1b9a690..fcb33e4 100644 --- a/lib/deployStack.js +++ b/lib/deployStack.js @@ -11,11 +11,11 @@ const AWS = require('aws-sdk'); var cloudformation = new AWS.CloudFormation({apiVersion: '2010-05-15'}); const supported_providers = { - domain:['aws', 'godaddy'], + registrar:['aws', 'godaddy'], + bucket: ['aws'], dns:['aws', 'godaddy'], - storage: ['aws'], cdn: ['aws'], - certificate: ['aws'] + ssl: ['aws'] } async function deployStack(domain, stack, config, content, overwrite, local, cli){ @@ -75,8 +75,6 @@ async function deployStack(domain, stack, config, content, overwrite, local, cli function deployParts(domain, stack, config, partialTemplate, partialStack, fullTemplate, template, content, cli){ return new Promise((resolve, reject) => { let size = 0; - //console.log('PARTIAL STACK ', partialStack) - //console.log(template) for(let key in partialStack) { size += 1; //should add an or at the end if the template does exist but the bucket policy isnt public @@ -103,134 +101,115 @@ function deployParts(domain, stack, config, partialTemplate, partialStack, fullT //before deploy full must obtain nameservers for route53 function deployFull(domain, stack, config, fullTemplate, partialTemplate, content, cli){ return new Promise((resolve, reject) => { - const url = `http://${domain}.s3-website-${process.env.AWS_REGION}.amazonaws.com`; - let arb = [] - let arr = [] - let ns = '' - //Upload Files - if(content) { - arb.push('content') - listFiles().then(data => { - uploadContent(domain, data, false, false, null, cli).then(()=>{ - arr.push(1) - if(arb.includes('ns')) cli? cli.action.start('Updating domain nameservers') : console.log('Updating domain nameservers...') - else if(arb.includes('mns')) console.log('\n\x1b[33mPlease update the nameservers for this domain to the following:\x1b[0m\n'+ns) - if(arr.length >= 3) resolve(url) - }).catch(err => reject(err)) - }).catch(err => reject(err)) - } - else { - arr.push(1) - if(arr.length >= 3) resolve(url) - } + let fullstack = false + let done = false //Transfer Nameservers - if(stack.dns && config.providers.dns !== config.providers.domain){ + transferNs(domain, stack, config, cli).then(data=>{ + if(content) { + listFiles().then(data => { + uploadContent(domain, data, false, false, null, cli).then(()=>{ + done? resolve('All Done!'): done=true + if(fullstack) cli? cli.action.start('Deploying additional resources'): console.log('Deploying additional resources') + }).catch(err => reject(err)) + }).catch(err => reject(err)) + } + if(JSON.stringify(fullTemplate) !== JSON.stringify(partialTemplate)){ + fullstack = true + deployTemplate(domain, fullTemplate).then(()=>{ + if(cli) cli.action.stop() + CloudFrontDist(domain).then(data => { + cli? cli.action.start('creating records'): console.log('creating records...') + createRecords(domain, stack, config, data.domain, cli) + .then(data=>{ + if(cli) cli.action.stop() + done? resolve('All Done!'): done=true + }) + }) + }) + } + else done? resolve('All Done!'): done=true + }) + }) +} + +function transferNs(domain, stack, config, cli){ + return new Promise((resolve, reject)=>{ + let ns = '' + if(stack.dns && config.providers.dns !== config.providers.registrar){ domains[config.providers.dns].getNameservers(domain).then(nameservers => { - if(supported_providers.domain.includes(config.providers.domain)) { - arb.push('ns') - //cli? cli.action.start('Updating domain nameservers') : console.log('Updating domain nameservers...') - domains[config.providers.domain].updateNameservers(domain, nameservers).then(data=>{ - arr.push(2) - if(cli) cli.action.stop() - if(arb.includes('stack')) cli? cli.action.start('Deploying additional reosurces'): console.log('Deploying additional resources...') - if(arr.length >= 3) resolve(url) + if(supported_providers.registrar.includes(config.providers.registrar)) { + //update nameservers automatrically for godaddy & AWS + cli? cli.action.start('Updating domain nameservers') : console.log('Updating domain nameservers...') + domains[config.providers.registrar].updateNameservers(domain, nameservers).then(data=>{ + resolve(data) }).catch(err => reject(err)) } else { - arb.push('mns') - //manually update unsupported providers + //manually update nameservers for unsupported providers for(let n of nameservers) ns += n+'\n' - //console.log('Please update the nameservers for this domain to the following:\n', ns) - arr.push(2) - if(cli) cli.action.stop() - if(arb.includes('stack')) cli? cli.action.start('Deploying additional reosurces'): console.log('Deploying additional resources...') - if(arr.length >= 3) resolve(url) + console.log('\n\x1b[33mPlease update the nameservers for this domain to the following:\x1b[0m\n'+ns) + cli.prompt('Have you finished updating the nameservers?').then(res=>{ + if(res==='y' || res==='yes' || res==='Y' || res==='YES') resolve('done') + else reject('You must update your nameservers when the DNS is different to the registrar') + }) } }).catch(err => reject(err)) } - else { - arr.push(2) - if(arr.length >= 3) resolve(url) - } - //create and verify certificate then - //Deploy the Full Stack with CDN - if(JSON.stringify(fullTemplate) !== JSON.stringify(partialTemplate)){ - arb.push('stack') - //cli? cli.action.start('Deploying additional reosurces') : console.log('Deploying additional resources...') - deployTemplate(domain, fullTemplate).then(()=>{ - //if(cli) cli.action.stop() - CloudFrontDist(domain).then(data => { - //must get the SSL certificate created for the stack - let records = [ - { - data: data.domain, - name: 'www', - ttl: 3600, - type: 'CNAME' - } - ] - if(config.providers['dns'] !== 'aws') { - if(supported_providers.dns.includes(config.providers.dns)){ - cli? cli.action.start('creating DNS records'): console.log('creating DNS records...') - let recordReroute = false - let redirectUrl = stack.https?'https://www.'+domain:'http://www.'+domain - domains[config.providers.dns].upsertRecords(domain, records).then(()=>{ - if(recordReroute) { - arr.push(3) - if(cli) cli.action.stop() - if(arr.length >= 3) resolve(url) - } - else recordReroute = true - }).catch(err => reject(err)) - domains[config.providers.dns].createRedirect(domain, redirectUrl).then(()=>{ - if(recordReroute) { - arr.push(3) - if(cli) cli.action.stop() - if(arr.length >= 3) resolve(url) - } - else recordReroute = true - }).catch(err => reject(err)) - } - else { - console.log('Please create a DNS record with the following properties:\n', records[0], '\n', 'Then create a 301 redirect from the root to www.') - arr.push(3) - if(arr.length >= 3) resolve(url) - } - } - else { - arr.push(3) - if(arr.length >= 3) resolve(url) - } - }) - }) - } - else { - arr.push(3) - if(arr.length >= 3) resolve(url) - } }) } -/* function transferDns(domain, domainProvider, dnsProvider){ - if(domainProvider === dnsProvider) resolve(null) - else { - //get the nameservers - if(providers.domain.includes(provider)) updateNameservers(nameservers).then(()=>resolve({auotmatic:true, nameservers:nameservers})) - else resolve({auotmatic:false, nameservers:nameservers}) - } -} */ +function createRecords(domain, stack, config, url, cli){ + return new Promise((resolve, reject)=> { + let records = [ + { + data: url, + name: 'www', + ttl: 3600, + type: 'CNAME' + } + ] + if(config.providers['dns'] !== 'aws') { + if(supported_providers.dns.includes(config.providers.dns)){ + cli? cli.action.start('creating DNS records'): console.log('creating DNS records...') + let recordReroute = false + let redirectUrl = stack.https?'https://www.'+domain:'http://www.'+domain + domains[config.providers.dns].upsertRecords(domain, records).then(()=>{ + recordReroute? resolve('All Done'): recordReroute = true + }).catch(err => reject(err)) + domains[config.providers.dns].createRedirect(domain, redirectUrl).then(()=>{ + recordReroute? resolve('All Done'): recordReroute = true + }).catch(err => reject(err)) + } + else { + console.log('Please create a DNS record with the following properties:\n', records[0], '\n', 'Then create a 301 redirect from the root to www.') + resolve('All Done') + } + } + }) +} -/* function publishUpdatedContent(uploads, files, force){ - let filesArr = uploads.keys() - if(files) filesArr = files - if(force) for(let key of filesArr) upload(key) - else for(let key of filesArr) if(uploads[key].last_mod > uploads[key].last_upload) upload(key) +//FOR S3 +/* ResourceRecordSet: { + AliasTarget: { + DNSName: `s3-website-${process.env.AWS_REGION}.amazonaws.com`, + EvaluateTargetHealth: false, + HostedZoneId: 'Z3AQBSTGFYJSTF' // a code depending on your region and resource for more info refer to https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_website_region_endpoints + }, + Name: wname, + Type: "A" } */ -/* -function upload(filePath){ - file = fs.readFileSync(filePath) - uploadFile(file, filePath) +//FOR CLOUDFRONT +/* ResourceRecordSet: { + Name: domain, + Type: 'A', + ResourceRecords: [], + AliasTarget: + { + HostedZoneId: obj.hostedZoneId, + DNSName: data.Distribution.DomainName, + EvaluateTargetHealth: false + } } */ diff --git a/lib/generateTemplate.js b/lib/generateTemplate.js index 685218b..915ced4 100644 --- a/lib/generateTemplate.js +++ b/lib/generateTemplate.js @@ -28,8 +28,8 @@ module.exports = function genTemplate(domain, stack, config, template, records, } //console.log('STACK ', stack, template) let defaults = templateDefaults(domain, stack, config) - if(!config || !config.index) config.index = 'index.html' - if(!config || !config.error) config.error = 'error.html' + //if(!config || !config.options || !config.options.index) config.index = 'index.html' + //if(!config || !config.options || !config.options.error) config.error = 'error.html' let new_temp = template? template : initialTemplate if(stack.bucket) new_temp.Resources['BucketPolicy'] = defaults['BucketPolicy'] if(records && stack.dns && config.providers.dns === 'aws') new_temp.Resources['RecordSet'] = defaults['RecordSet'] @@ -52,8 +52,8 @@ module.exports = function genTemplate(domain, stack, config, template, records, } //console.log('STACK2 ', stack, new_temp) } - //custom stuff for the CDN depending on the https option - if(stack.https && config.providers.https === 'aws'){ + //custom stuff for the CDN depending on the ssl option + if(stack.ssl && config.providers.ssl === 'aws'){ new_temp.Resources.CloudFrontDist.Properties.DistributionConfig["ViewerCertificate"] = { "AcmCertificateArn" : { "Ref": "AcmCertificate"}, "MinimumProtocolVersion" : "TLSv1.2_2018", diff --git a/lib/resourceExists.js b/lib/resourceExists.js index 82fcd4f..d7d74f9 100644 --- a/lib/resourceExists.js +++ b/lib/resourceExists.js @@ -1,5 +1,5 @@ //load files from the .env file -require('dotenv').config(); +//require('dotenv').config(); // Load the AWS SDK for Node.js var AWS = require('aws-sdk'); var route53 = new AWS.Route53({apiVersion: '2013-04-01'}); diff --git a/lib/stackResourceExists.js b/lib/stackResourceExists.js new file mode 100644 index 0000000..7219803 --- /dev/null +++ b/lib/stackResourceExists.js @@ -0,0 +1,15 @@ +require('dotenv').config(); +var AWS = require('aws-sdk'); +var cloudformation = new AWS.CloudFormation({apiVersion: '2010-05-15'}); + +module.exports = function getStackResources(domain){ + return new Promise((resolve, reject)=> { + let stackName = domain.split('.').join('') + 'Stack' + var params = {StackName: stackName}; + cloudformation.describeStackResources(params).promise().then(data=>{ + let resources = {} + for(let obj of data.StackResources) resources[obj.LogicalResourceId]=obj.PhysicalResourceId + resolve(resources) + }).catch(err=> reject(err)) + }) +} \ No newline at end of file diff --git a/lib/templateDefaults.js b/lib/templateDefaults.js index ddcaaa7..9d8e3f5 100644 --- a/lib/templateDefaults.js +++ b/lib/templateDefaults.js @@ -20,7 +20,7 @@ const stackResources = { www: "WwwBucket", cdn: "CloudFrontDist", dns: "HostedZone", - https: "AcmCertificate" + ssl: "AcmCertificate" } const importables = { @@ -44,8 +44,8 @@ function templateDefaults(domain, stack, config) { "AccessControl": "PublicRead", "BucketName": domain, "WebsiteConfiguration": { - "ErrorDocument" : config.error, - "IndexDocument" : config.index + "ErrorDocument" : config.options.error, + "IndexDocument" : config.options.index }, "PublicAccessBlockConfiguration": { "BlockPublicAcls" : false, diff --git a/node_modules/@torus-tools/content/README.md b/node_modules/@torus-tools/content/README.md index a80322b..0aa870a 100644 --- a/node_modules/@torus-tools/content/README.md +++ b/node_modules/@torus-tools/content/README.md @@ -7,7 +7,6 @@ A promise-based javascript SDK that facilitates operations related to content st - upload - delete - ## Currently Supporting - AWS diff --git a/node_modules/@torus-tools/content/lib/storage/upload.js b/node_modules/@torus-tools/content/lib/storage/upload.js index b7cdc48..0464621 100644 --- a/node_modules/@torus-tools/content/lib/storage/upload.js +++ b/node_modules/@torus-tools/content/lib/storage/upload.js @@ -13,9 +13,11 @@ function scanFiles(dir){ } function scanDir(currentDirPath, callback) { - var gitignore = fs.readFileSync('.torusignore', 'utf8') + var gitignore = fs.existsSync('.gitignore')? fs.readFileSync('.gitignore', 'utf8'): null + var torusignore = fs.existsSync('.torusignore')? fs.readFileSync('.torusignore', 'utf8'): null var ignorePaths = {} for(let path of gitignore.split('\n')) if(path.trim().length > 1) ignorePaths[path.trim()] = true + for(let path of torusignore.split('\n')) if(path.trim().length > 1) ignorePaths[path.trim()] = true fs.readdirSync(currentDirPath).forEach((name)=>{ var filePath = path.join(currentDirPath, name); var stat = fs.statSync(filePath); diff --git a/node_modules/@torus-tools/content/package.json b/node_modules/@torus-tools/content/package.json index 4166dce..0a39226 100644 --- a/node_modules/@torus-tools/content/package.json +++ b/node_modules/@torus-tools/content/package.json @@ -1,28 +1,27 @@ { - "_from": "@torus-tools/content", - "_id": "@torus-tools/content@0.0.2", + "_from": "@torus-tools/content@0.0.21", + "_id": "@torus-tools/content@0.0.21", "_inBundle": false, - "_integrity": "sha512-xwcjA/byjmbnJ8bWI0goC5tIbSg5HKd+iZeRaYmTGon9bohqqop72FK2L2qFp1AOM0/Qo6Xa0Pbzkrqi4a+1vw==", + "_integrity": "sha512-ljuPYk6VXvOR0smA8UIRj4KpUi8gRF33oDfSpNxFZ43/JaWsh5boMYSrN+LCOOEJk5r392xkzIeoPDnpYg42Vw==", "_location": "/@torus-tools/content", "_phantomChildren": {}, "_requested": { - "type": "tag", + "type": "version", "registry": true, - "raw": "@torus-tools/content", + "raw": "@torus-tools/content@0.0.21", "name": "@torus-tools/content", "escapedName": "@torus-tools%2fcontent", "scope": "@torus-tools", - "rawSpec": "", + "rawSpec": "0.0.21", "saveSpec": null, - "fetchSpec": "latest" + "fetchSpec": "0.0.21" }, "_requiredBy": [ - "#USER", "/" ], - "_resolved": "https://registry.npmjs.org/@torus-tools/content/-/content-0.0.2.tgz", - "_shasum": "12a39e3afe24b722c66d3558de8c6f021eb8ef1b", - "_spec": "@torus-tools/content", + "_resolved": "https://registry.npmjs.org/@torus-tools/content/-/content-0.0.21.tgz", + "_shasum": "9790f82422541a7c0bd0332d90c1d6c85787eba5", + "_spec": "@torus-tools/content@0.0.21", "_where": "/home/fargo/Desktop/torus-tools/stack", "author": { "name": "gabriel kardonski @gkpty" @@ -57,5 +56,5 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "version": "0.0.2" + "version": "0.0.21" } diff --git a/package-lock.json b/package-lock.json index 9cfa0a0..f554941 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "@torus-tools/stack", - "version": "0.0.11", + "version": "0.0.121", "lockfileVersion": 1, "requires": true, "dependencies": { "@torus-tools/content": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@torus-tools/content/-/content-0.0.2.tgz", - "integrity": "sha512-xwcjA/byjmbnJ8bWI0goC5tIbSg5HKd+iZeRaYmTGon9bohqqop72FK2L2qFp1AOM0/Qo6Xa0Pbzkrqi4a+1vw==", + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@torus-tools/content/-/content-0.0.21.tgz", + "integrity": "sha512-ljuPYk6VXvOR0smA8UIRj4KpUi8gRF33oDfSpNxFZ43/JaWsh5boMYSrN+LCOOEJk5r392xkzIeoPDnpYg42Vw==", "requires": { "aws-sdk": "^2.732.0" } diff --git a/package.json b/package.json index ffff5ac..c508696 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@torus-tools/stack", - "version": "0.0.11", + "version": "0.0.122", "description": "A promise-based SDK for generating, deploying and managing stacks in AWS with cloudformation.", "main": "index.js", "scripts": { @@ -24,7 +24,7 @@ }, "homepage": "https://github.com/torus-tools/stack#readme", "dependencies": { - "@torus-tools/content": "0.0.2", + "@torus-tools/content": "0.0.21", "@torus-tools/domains": "0.0.14", "aws-sdk": "^2.732.0", "dotenv": "^8.0.0" diff --git a/test/certificateTest.js b/test/certificateTest.js new file mode 100644 index 0000000..a088538 --- /dev/null +++ b/test/certificateTest.js @@ -0,0 +1,29 @@ +const Cert = require('../lib/deployCertificate') +const Domains = require('@torus-tools/domains') +const domain = 'gkpty.com' +const certArn = '' +const hzid = '' +cfdomain = '' + +/* Cert.createCertificate(domain) +.then(data=>console.log(data)) +.catch(err=>console.log(err)) */ + +/* +Name: domain, +Type: 'A', +ResourceRecords: [], +AliasTarget: +{ + HostedZoneId: obj.hostedZoneId, + DNSName: data.Distribution.DomainName, + EvaluateTargetHealth: false +} */ + +Cert.getCname(certArn) +.then(data=>{ + console.log(data) +}) +.catch(err=>console.log(err)) + +Domains.aws.upsertRecords(domain, ) \ No newline at end of file