diff --git a/.gitignore b/.gitignore index 1269488..2c74b23 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,32 @@ -data +dataDir/* + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +# example.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..854ddb8 --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.32.0" + constraints = "~> 4.16" + hashes = [ + "h1:CmNkbcfctajxal8fnFFWLSC5dDvQwSJrw7HiwpTdyUc=", + "zh:062c30cd8bcf29f8ee34c2b2509e4e8695c2bcac8b7a8145e1c72e83d4e68b13", + "zh:1503fabaace96a7eea4d73ced36a02a75ec587760850e58162e7eff419dcbb31", + "zh:39a1fa36f8cb999f048bf0000d9dab40b8b0c77df35584fb08aa8bd6c5052dee", + "zh:471a755d43b51cd7be3e386cebc151ad8d548c5dea798343620476887e721882", + "zh:61ed56fab811e62b8286e606d003f7eeb7e940ef99bb49c1d283d91c0b748cc7", + "zh:80607dfe5f7770d136d5c451308b9861084ffad08139de8014e48672ec43ea3f", + "zh:863bf0a6576f7a969a89631525250d947fbb207d3d13e7ca4f74d86bd97cdda3", + "zh:9a8f2e77e4f99dbb618eb8ad17218a4698833754b50d46da5727323a2050a400", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9b74ff6e638c2a470b3599d57c2081e0095976da0a54b6590884d571f930b53b", + "zh:da4fc553d50ae833d860ec95120e271c29b4cb636917ab5991327362b7486bb7", + "zh:f4b86e7df4e846a38774e8e648b41c5ebaddcefa913cfa1864568086b7735575", + ] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8d2a0d5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:current-slim + +# To Create nodejsapp directory +WORKDIR /nodejsapp + +# To Install All dependencies +COPY package*.json ./ +RUN npm install + +# To copy all application packages +COPY . . + +# Expose port 1280 and Run the server.js file to start node js application +EXPOSE 1280 +CMD [ "node", "dumbserver.js" ] \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..7cc6e9f --- /dev/null +++ b/README @@ -0,0 +1,19 @@ +I am taking this as a test project for docker and terraform too... + +for the docker imaging look at: https://www.fosstechnix.com/how-to-create-docker-image-for-node-js-application/ +The docker image can be built with +docker build -t dumbserver . + +the image then can be extracted with +docker image save -o dumbserver.image.tar dumbserver + +on the target machine it can be imported with +docker image load -i dumbserer.image.tar + +and running a container with the image id done via: +docker run -p 1280:1280 dumbserver +(Port 1280 is what it listens on) + +At this stage, terraform will create an ssh accessable server (key is in "terraformtest1.ppk") - tutorial used for that: https://medium.com/@hmalgewatta/setting-up-an-aws-ec2-instance-with-ssh-access-using-terraform-c336c812322f + +what is required next is to install docker on the server and deploy the image. diff --git a/ddd b/ddd new file mode 100644 index 0000000..3a178f3 Binary files /dev/null and b/ddd differ diff --git a/dumbserver.js b/dumbserver.js index 2f1c8e8..60453cb 100644 --- a/dumbserver.js +++ b/dumbserver.js @@ -1,225 +1,233 @@ // // Dependencies // -let http = require('http'); -let fs = require('fs'); +let http = require("http"); +let fs = require("fs"); // // Constants // -let DATA_LOCATION = 'data'; // both the url root and the location of data file +let DATA_LOCATION = "dataDir/data"; // both the url root and the location of data file let DATA_LOCATION_LEN = DATA_LOCATION.length; -let DATA_FILE = __dirname + '/' + DATA_LOCATION; -let PORT = 1280; +let DATA_FILE = __dirname + "/" + DATA_LOCATION; +let PORT = 1280; // // Load store file // let store = {}; // deafult empty object -let json=''; +let json = ""; try { - let json = fs.readFileSync(DATA_FILE); - store = JSON.parse(json); - console.log('Read store file'); -} catch (e) { // lazy error handling - console.log('Could not load store file. Using empty array.'); + let json = fs.readFileSync(DATA_FILE); + store = JSON.parse(json); + console.log("Read store file"); +} catch (e) { + // lazy error handling + console.log("Could not load store file. Using empty array."); } - // // Flush to store file on exit // -let flush = function() -{ - console.log('Flushing data to file [' + DATA_FILE + ']\n'); - fs.writeFileSync(DATA_FILE, JSON.stringify(store)); +let flush = function () { + console.log("Flushing data to file [" + DATA_FILE + "]\n"); + fs.writeFileSync(DATA_FILE, JSON.stringify(store)); }; -let handleInterrupt = function() { flush(); process.exit(); }; - -process.on('SIGINT', handleInterrupt); -process.on('SIGTERM', handleInterrupt); -process.on('SIGHUP', flush); // HUP flushes to file but keeps the server running +let handleInterrupt = function () { + flush(); + process.exit(); +}; +process.on("SIGINT", handleInterrupt); +process.on("SIGTERM", handleInterrupt); +process.on("SIGHUP", flush); // HUP flushes to file but keeps the server running // // If request URI is prefixed with the value of DATA_LOCATION, we treat incoming request as a REST request // Otherwise we serve the file referenced by the URI // -let serveFile = function(loc, req, resp) { - let fullPath = __dirname + '/' + loc; - console.log('Trying to serve file at [' + fullPath + ']'); +let serveFile = function (loc, req, resp) { + let fullPath = __dirname + "/" + loc; + console.log("Trying to serve file at [" + fullPath + "]"); - if (req.method !== 'GET') { // read only! - resp.writeHead(405); - resp.write('Unsupported method\n'); - resp.end(); - } else if (fs.existsSync(fullPath)) { - resp.writeHead(200); - fs.createReadStream(fullPath).pipe(resp); - } else { - resp.writeHead(404); - resp.write('Not Found\n'); - resp.end(); - } + if (req.method !== "GET") { + // read only! + resp.writeHead(405); + resp.write("Unsupported method\n"); + resp.end(); + } else if (fs.existsSync(fullPath)) { + resp.writeHead(200); + fs.createReadStream(fullPath).pipe(resp); + } else { + resp.writeHead(404); + resp.write("Not Found\n"); + resp.end(); + } }; -let serveRestData = function(urlPath, req, resp) { - console.log(req.method + ' request for path [' + urlPath + ']'); +let serveRestData = function (urlPath, req, resp) { + console.log(req.method + " request for path [" + urlPath + "]"); - switch (req.method) { + switch (req.method) { + case "GET": + let exactMatch = !urlPath.endsWith("/"); + let matchFound = false; - case 'GET': - let exactMatch=!urlPath.endsWith('/'); - let matchFound=false; - - if (exactMatch) { // assumes that only a single resource has this ID. - console.log(" - Exact Match."); - if (store.hasOwnProperty(urlPath)) { - resp.writeHead(200, { 'Content-Type' : 'application/json' }); - resp.write(JSON.stringify(store[urlPath]) + '\n'); - matchFound=true; - } - } else { - console.log(" - All Matches."); - let matches=[]; - Object.keys(store).forEach(theKey=>{ - if (theKey.startsWith(urlPath)){ - matches.push(store[theKey]); - matchFound=true; - } - }); - - if (matchFound) { - resp.writeHead(200, { 'Content-Type' : 'application/json' }); - resp.write(JSON.stringify(matches)); - } - } - - if (!matchFound) { - console.log(" - Match NOT found."); - resp.writeHead(404, { 'Content-Type' : 'application/json' }); - resp.write('{"success":false, "error":"Not Found"}'); - } else { - console.log(" - Match found."); - } - resp.end(); - break; + if (exactMatch) { + // assumes that only a single resource has this ID. + console.log(" - Exact Match."); + if (store.hasOwnProperty(urlPath)) { + resp.writeHead(200, { "Content-Type": "application/json" }); + resp.write(JSON.stringify(store[urlPath]) + "\n"); + matchFound = true; + } + } else { + console.log(" - All Matches."); + let matches = []; + Object.keys(store).forEach((theKey) => { + if (theKey.startsWith(urlPath)) { + matches.push(store[theKey]); + matchFound = true; + } + }); - case 'PUT': - json = ''; - req.on('data', function(chunk) { json += chunk; }); - req.on('end', function() { - try { - let data = JSON.parse(json); - store[urlPath] = data; - resp.writeHead(201, { 'Content-Type' : 'application/json' }); - resp.write('{"success":true}'); - } catch (e) { - resp.writeHead(400, { 'Content-Type' : 'application/json' }); - resp.write('{"success":false, "error":"Invalid JSON"}'); - } - resp.end(); - flush(); - }); - break; - - - case 'POST': - json = ''; - req.on('data', function(chunk) { json += chunk; }); - req.on('end', function() { - try { - let data = JSON.parse(json); - if (data.ID === undefined) { - resp.writeHead(400, { 'Content-Type' : 'application/json' }); - resp.write('{"success":false, "error":"The ID Field needs to be given"}'); - } else { - // make sure the ID from the data is used as the final resource Identifier, - // even if the URL has none or says different. - let re=new RegExp("/[^/]*$"); - urlPath=urlPath.replace(re,""); - urlPath=urlPath+"/"+data.ID; - console.log(" - storing with resource key: [" + urlPath + "]"); + if (matchFound) { + resp.writeHead(200, { "Content-Type": "application/json" }); + resp.write(JSON.stringify(matches)); + } + } - store[urlPath] = data; + if (!matchFound) { + console.log(" - Match NOT found."); + resp.writeHead(404, { "Content-Type": "application/json" }); + resp.write('{"success":false, "error":"Not Found"}'); + } else { + console.log(" - Match found."); + } + resp.end(); + break; - resp.writeHead(201, { 'Content-Type' : 'application/json' }); - resp.write(JSON.stringify(store[urlPath])); - } - } catch (e) { - resp.writeHead(400, { 'Content-Type' : 'application/json' }); - resp.write('{"success":false, "error":"Invalid JSON"}'); - } - resp.end(); - flush(); - }); - break; - - case 'PATCH': - json = ''; - req.on('data', function(chunk) { json += chunk; }); - req.on('end', function() { - try { - let data = JSON.parse(json); - Object.keys(data).forEach(theKey=>{ - store[urlPath][theKey] = data[theKey] - }); - - resp.writeHead(201, { 'Content-Type' : 'application/json' }); - resp.write('{"success":true}'); - } catch (e) { - resp.writeHead(400, { 'Content-Type' : 'application/json' }); - resp.write('{"success":false, "error":"Invalid JSON"}'); - } - resp.end(); - flush(); - }); - break; - - - case 'OPTIONS': - resp.end(); - break; + case "PUT": + json = ""; + req.on("data", function (chunk) { + json += chunk; + }); + req.on("end", function () { + try { + let data = JSON.parse(json); + store[urlPath] = data; + resp.writeHead(201, { "Content-Type": "application/json" }); + resp.write('{"success":true}'); + } catch (e) { + resp.writeHead(400, { "Content-Type": "application/json" }); + resp.write('{"success":false, "error":"Invalid JSON"}'); + } + resp.end(); + flush(); + }); + break; - case 'DELETE': - if (store.hasOwnProperty(urlPath)) { - delete store[urlPath]; - resp.writeHead(204, { 'Content-Type' : 'application/json' }); - resp.write('{"success":true}'); - } else { - resp.writeHead(404, { 'Content-Type' : 'application/json' }); - resp.write('{"success":false, "error":"Not Found"}'); - } - resp.end(); - flush(); - break; + case "POST": + json = ""; + req.on("data", function (chunk) { + json += chunk; + }); + req.on("end", function () { + try { + let data = JSON.parse(json); + if (data.ID === undefined) { + resp.writeHead(400, { "Content-Type": "application/json" }); + resp.write('{"success":false, "error":"The ID Field needs to be given"}'); + } else { + // make sure the ID from the data is used as the final resource Identifier, + // even if the URL has none or says different. + let re = new RegExp("/[^/]*$"); + urlPath = urlPath.replace(re, ""); + urlPath = urlPath + "/" + data.ID; + console.log(" - storing with resource key: [" + urlPath + "]"); - default: - resp.writeHead(405, { 'Content-Type' : 'application/json' }); - resp.write('{"success":false, "error":"Unsupported method"}'); - resp.end(); - } + store[urlPath] = data; + resp.writeHead(201, { "Content-Type": "application/json" }); + resp.write(JSON.stringify(store[urlPath])); + } + } catch (e) { + resp.writeHead(400, { "Content-Type": "application/json" }); + resp.write('{"success":false, "error":"Invalid JSON"}'); + } + resp.end(); + flush(); + }); + break; + + case "PATCH": + json = ""; + req.on("data", function (chunk) { + json += chunk; + }); + req.on("end", function () { + try { + let data = JSON.parse(json); + Object.keys(data).forEach((theKey) => { + store[urlPath][theKey] = data[theKey]; + }); + + resp.writeHead(201, { "Content-Type": "application/json" }); + resp.write('{"success":true}'); + } catch (e) { + resp.writeHead(400, { "Content-Type": "application/json" }); + resp.write('{"success":false, "error":"Invalid JSON"}'); + } + resp.end(); + flush(); + }); + break; + + case "OPTIONS": + resp.end(); + break; + + case "DELETE": + if (store.hasOwnProperty(urlPath)) { + delete store[urlPath]; + resp.writeHead(204, { "Content-Type": "application/json" }); + resp.write('{"success":true}'); + } else { + resp.writeHead(404, { "Content-Type": "application/json" }); + resp.write('{"success":false, "error":"Not Found"}'); + } + resp.end(); + flush(); + break; + + default: + resp.writeHead(405, { "Content-Type": "application/json" }); + resp.write('{"success":false, "error":"Unsupported method"}'); + resp.end(); + } }; // // Listen for connections // -http.createServer(function(req, resp) { - // CORS headers - resp.setHeader('Access-Control-Allow-Origin', '*'); - resp.setHeader('Access-Control-Allow-Headers', 'X-Request-With, content-type'); - resp.setHeader('Vary', 'Origin'); - - resp.setHeader('Access-Control-Allow-Methods','GET, PATCH, POST, PUT, DELETE, OPTIONS'); - - console.log('Serving [' + req.url + ']'); - - if (req.url === '/') { // server index file - serveFile('index.html', req, resp); - } else { // rest request - serveRestData(req.url, req, resp); - } +http + .createServer(function (req, resp) { + // CORS headers + resp.setHeader("Access-Control-Allow-Origin", "*"); + resp.setHeader("Access-Control-Allow-Headers", "X-Request-With, content-type"); + resp.setHeader("Vary", "Origin"); -}).listen(PORT); + resp.setHeader("Access-Control-Allow-Methods", "GET, PATCH, POST, PUT, DELETE, OPTIONS"); + + console.log("Serving [" + req.url + "]"); + + if (req.url === "/") { + // server index file + serveFile("index.html", req, resp); + } else { + // rest request + serveRestData(req.url, req, resp); + } + }) + .listen(PORT); diff --git a/gateways.tf b/gateways.tf new file mode 100644 index 0000000..8edf93f --- /dev/null +++ b/gateways.tf @@ -0,0 +1,3 @@ +resource "aws_internet_gateway" "test-env-gw" { + vpc_id = "${aws_vpc.test-env.id}" +} \ No newline at end of file diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..16c9359 --- /dev/null +++ b/main.tf @@ -0,0 +1,27 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.16" + } + } + + required_version = ">= 1.2.0" +} + +provider "aws" { + region = "us-west-2" +} + +resource "aws_instance" "app_server" { + ami = var.ami_id + instance_type = "t2.micro" + key_name = var.ami_key_pair_name + security_groups = ["${aws_security_group.ingress-all-test.id}"] + + tags = { + "Name" = var.ami_name + } + + subnet_id = "${aws_subnet.subnet-uno.id}" +} diff --git a/network.tf b/network.tf new file mode 100644 index 0000000..81796e4 --- /dev/null +++ b/network.tf @@ -0,0 +1,10 @@ +resource "aws_vpc" "test-env" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true +} + +resource "aws_eip" "ip-test-env" { + instance = "${aws_instance.app_server.id}" + vpc = true +} \ No newline at end of file diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..796edcf --- /dev/null +++ b/outputs.tf @@ -0,0 +1,9 @@ +/* output "instance_public_ip" { + description = "Public IP address of the EC2 instance" + value = aws_instance.app_server.public_ip +} */ + +output "elastic_ip" { + description = "ELASTIC IP" + value = aws_eip.ip-test-env.public_ip +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4993b07 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "dumbserver", + "version": "1.0.0", + "description": "", + "main": "dumbserver.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/noelbennett/dumbserver.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/noelbennett/dumbserver/issues" + }, + "homepage": "https://github.com/noelbennett/dumbserver#readme" +} diff --git a/security.tf b/security.tf new file mode 100644 index 0000000..be7629c --- /dev/null +++ b/security.tf @@ -0,0 +1,27 @@ +resource "aws_security_group" "ingress-all-test" { +name = "allow-all-sg" +vpc_id = "${aws_vpc.test-env.id}" +ingress { + cidr_blocks = [ + "0.0.0.0/0" + ] + from_port = 22 + to_port = 22 + protocol = "tcp" + } + ingress { + cidr_blocks = [ + "0.0.0.0/0" + ] + from_port = 1280 + to_port = 1280 + protocol = "tcp" + } +// Terraform removes the default rule + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} \ No newline at end of file diff --git a/subnets.tf b/subnets.tf new file mode 100644 index 0000000..7a8a8dd --- /dev/null +++ b/subnets.tf @@ -0,0 +1,17 @@ +resource "aws_subnet" "subnet-uno" { + cidr_block = "${cidrsubnet(aws_vpc.test-env.cidr_block, 3, 1)}" + vpc_id = "${aws_vpc.test-env.id}" + availability_zone = "us-west-2a" +} + +resource "aws_route_table" "route-table-test-env" { + vpc_id = "${aws_vpc.test-env.id}" +route { + cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.test-env-gw.id}" + } +} +resource "aws_route_table_association" "subnet-association" { + subnet_id = "${aws_subnet.subnet-uno.id}" + route_table_id = "${aws_route_table.route-table-test-env.id}" +} \ No newline at end of file diff --git a/teraformtest1.ppk b/teraformtest1.ppk new file mode 100644 index 0000000..e3632e8 --- /dev/null +++ b/teraformtest1.ppk @@ -0,0 +1,26 @@ +PuTTY-User-Key-File-2: ssh-rsa +Encryption: none +Comment: teraformtest1 +Public-Lines: 6 +AAAAB3NzaC1yc2EAAAADAQABAAABAQChxclCCpzzSqjbocO5s5/Pm0XFWDqxenuA +XEDxxBz2Y8kKeIZeaf6YKMWBYUM+SFBNH8Y5VkHoBPggTWohKe/Yxwwj/abhEZzZ +pSEzhGUm2xii4Qwc/MRyGNe6d99caXJJy+CJQztMkJdzwpY3r384HBmdy+SRmW2S ++Isz0FOMfF+eIZf63ifU3RQKldTKl+vzOlugRj0AROxkF3bvRDwhpCYwuCrtK+hC +rSdRoVcgQVp97lvXbl5GM+bCBg/TNGP05dIs2eZoFPeVgh74TCU53yab7RPVmgEj +oXMGx/pLBtmua6uYsQXt2tLnnPIwaTOI+NLRrycTa89xM2jU3Vv/ +Private-Lines: 14 +AAABAQCOY1xluJm0ur4tsxBnX2cGgJDExofCtyAFNy4ino8vf1zwzmzWpUzl+Nz+ +p/Fb3KJxf8olpXqVEeqj4//J+POXRnu1IgnEiJAaMXIruhiePrJMivDygrkWBC+9 +fM0otddWhRC5QGftWEc2KMu3b8z2QiV58oDYdscvWXyMuo0PVPJhjgHcTZVMbpYW +ISWckZQUMQRV/rAZVAegLDYsQP212KogFPQlzaHJRFIvxf6OZkE7bvvIMSARXEGu +OeKru38n34vk7Tp7f6+f8KtGYCkyjYyhImThfA1ne/SjEM9CRIf+AH2jhKyB+C8D +o4tsf5DVnC6orbdnt4bT0gwjaeihAAAAgQDaY5BCPLD+w3Q8TRZbGw73AsA8tPss +1Qv5p044iFXu2PkL/YtaMrMYwRn9IqasjvM0BJlNJDfEmUu67jsa+nWgaKlVWmYz +Em4WYK8CFkWr5wZLUqztemKYfgKT7vEmPLQun3HS9P7+AyyiX71YBEXucJu7HsIc +I46tTza842XtUwAAAIEAvaIYUqlbHOqxdFyqMAH6daH5KzCneF9S3JmVt6U52GAE +8d1Cj3XRRSKTPPt66dDaewlVqhqPjcnSxET/n0+VnrKal2UBrslwFWF8szpX6+Zo +B3n3Upg1MdlTaFrmPpJGDehT9NjerUOLFvgEvkwdCHedoyLeIXm6zsFJ3/Mk1SUA +AACBAMgeyHq/4Nur+/DY4i5IrlQUX6gpQF2hb1xj8WElaBQa+4IgHWeApDAycIc1 +D1pLXKHClacSpKqv/6gHlz4KkGsnRUe3bpm+fP0XjIjTPp4l4s32TCqY9X3Eu5fL +UaRqrMtlJ19TMD8rgD1GkVVzBOJXsUHUwTcCg0JhY1DNuC4Y +Private-MAC: 1e430a894b1ac27e8e67f633c823792c078574ed diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..475b01e --- /dev/null +++ b/variables.tf @@ -0,0 +1,13 @@ +variable "ami_id" { + type = string + default = "ami-017fecd1353bcc96e" +} +variable "ami_key_pair_name" { + type=string + default = "teraformtest1" +} + +variable "ami_name" { + type = string + default = "dumbApplicationServer" +} \ No newline at end of file