diff --git a/CHANGELOG b/CHANGELOG index 3a419c0..a4e89cd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,15 @@ Change Log ========== +0.2.6 (01/04/2022) +------------------ +- Download configuration files from git during run time +- Take all env vars as optional arguments during class initialization + +0.2.5 (01/04/2022) +------------------ +- Move configuration files into a dedicated directory + 0.2.4 (01/03/2022) ------------------ - Fix `requirements.txt` path in `setup.py` diff --git a/doc_generator/conf.py b/doc_generator/conf.py index 02dd466..3527d79 100644 --- a/doc_generator/conf.py +++ b/doc_generator/conf.py @@ -14,6 +14,7 @@ import sys sys.path.insert(0, os.path.abspath(f'..{os.path.sep}expose')) +sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- diff --git a/docs/index.html b/docs/index.html index 7a6ac2d..1a171af 100644 --- a/docs/index.html +++ b/docs/index.html @@ -60,7 +60,7 @@

Welcome to Expose’s documentation!

Expose - Main Module

-class tunnel.Tunnel(aws_access_key: Optional[str] = None, aws_secret_key: Optional[str] = None, aws_region_name: str = 'us-west-2')
+class tunnel.Tunnel(aws_access_key: Optional[str] = None, aws_secret_key: Optional[str] = None, aws_region_name: str = 'us-west-2', image_id: Optional[str] = None, port: Optional[int] = None, domain_name: Optional[str] = None, subdomain: Optional[str] = None)

Initiates Tunnel object to spin up an EC2 instance with a pre-configured AMI which acts as a tunnel.

@@ -143,17 +147,14 @@

Welcome to Expose’s documentation!
-_create_ec2_instance(image_id: Optional[str] = None) str
+_create_ec2_instance() str

Creates an EC2 instance of type t2.nano with the pre-configured AMI id.

-
Parameters
-

image_id – Takes image ID as an argument. Defaults to ami_id in environment variable. Exits if null.

-
-
Returns
-

Instance ID.

+
Returns
+

Instance ID.

-
Return type
-

str or None

+
Return type
+

str or None

@@ -225,27 +226,19 @@

Welcome to Expose’s documentation!
-start(port: Optional[int] = None) None
+start() None

Calls the class methods _create_ec2_instance and _instance_info to configure the ec2 instance.

-
-
Parameters
-

port – Port number where the application/API is running in localhost.

-
-
-_configure_vm(public_dns: str, public_ip: str, port: int, domain_name: Optional[str] = None, subdomain: Optional[str] = None)
+_configure_vm(public_dns: str, public_ip: str)

Configures the ec2 instance to take traffic from localhost.

Parameters
  • public_dns – Public DNS name of the EC2 that was created.

  • public_ip – Public IP of the EC2 that was created.

  • -
  • port – Port number on which the app/api is running.

  • -
  • domain_name – Name of the hosted zone in which an A record has to be added. [example.com]

  • -
  • subdomain – Subdomain using which the localhost has to be accessed. [tunnel or tunnel.example.com]

diff --git a/docs/searchindex.js b/docs/searchindex.js index 2a846aa..c691113 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["README","index"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["README.md","index.rst"],objects:{"":[[1,0,0,"-","tunnel"]],"helpers.auxiliary":[[1,1,1,"","get_public_ip"],[1,1,1,"","sleeper"],[1,1,1,"","time_converter"]],"helpers.nginx_server":[[1,1,1,"","prefix"],[1,1,1,"","run_interactive_ssh"]],"helpers.route_53":[[1,1,1,"","_get_zone_id"],[1,1,1,"","change_record_set"]],"tunnel.Tunnel":[[1,3,1,"","_authorize_security_group"],[1,3,1,"","_configure_vm"],[1,3,1,"","_create_ec2_instance"],[1,3,1,"","_create_key_pair"],[1,3,1,"","_create_security_group"],[1,3,1,"","_delete_key_pair"],[1,3,1,"","_delete_security_group"],[1,3,1,"","_get_vpc_id"],[1,3,1,"","_instance_info"],[1,3,1,"","_terminate_ec2_instance"],[1,3,1,"","start"],[1,3,1,"","stop"]],helpers:[[1,0,0,"-","auxiliary"],[1,0,0,"-","nginx_server"],[1,0,0,"-","route_53"]],tunnel:[[1,2,1,"","Tunnel"]]},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method"},terms:{"2":1,"2021":0,"2048":0,"3650":0,"boolean":1,"class":1,"default":1,"float":1,"function":1,"int":1,"long":0,"new":0,"null":1,"public":[0,1],"return":1,"throw":0,"var":[0,1],A:[0,1],But:0,If:[0,1],No:0,The:[0,1],There:1,These:0,To:0,_authorize_security_group:1,_configure_vm:1,_create_ec2_inst:1,_create_key_pair:1,_create_security_group:1,_delete_key_pair:1,_delete_security_group:1,_get_vpc_id:1,_get_zone_id:1,_instance_info:1,_terminate_ec2_inst:1,access:[0,1],accordingli:[0,1],account:[0,1],act:1,action:1,activ:0,ad:[0,1],add:1,address:1,all:[0,1],also:0,ami:[0,1],ami_id:[0,1],an:[0,1],ani:0,api:[0,1],app:[0,1],applic:1,appropri:1,apt:0,ar:[0,1],arg:0,argument:1,assign:1,authent:1,author:1,avoid:0,aw:[0,1],await:1,aws_access_kei:1,aws_region_nam:1,aws_secret_kei:1,befor:1,block:0,bool:1,boto3:1,browser:0,bulb:0,ca:0,cach:0,call:1,can:[0,1],cert:0,certain:1,chain:0,change_record_set:1,changeset:1,check:1,client:1,color:1,com:[0,1],command:1,commit:0,config:1,configur:0,connect:1,consol:1,convert:1,copyright:1,could:0,creat:1,creation:0,credenti:1,custom:0,dai:[0,1],debug:1,delai:1,delet:1,describe_instance_statu:1,destin:1,dict:1,dictionari:1,disabl:1,dn:[0,1],dns_name:1,doc:0,domain:[0,1],domain_nam:1,download:0,dpkg:0,durat:1,dure:[0,1],e:0,ec2:1,either:[0,1],endpoint:0,ensur:0,env:[0,1],environ:1,even:0,everi:0,exampl:[0,1],exist:1,exit:1,file:[0,1],filenam:1,flag:1,format:1,from:1,frontend:0,gener:0,get:[0,1],get_public_ip:1,github:0,give:0,group:1,ha:[0,1],handshak:0,hasn:0,helper:1,host:[0,1],hostnam:1,hour:1,http:0,id:[0,1],idl:1,imag:1,image_id:1,index:1,ingress:1,initi:1,instal:0,instanc:[0,1],instance_id:1,interact:1,internet:0,io:[0,1],ip:1,ipinfo:1,jsontest:1,just:1,kei:[0,1],keyout:0,keypair:1,level:1,lib:0,liberti:0,licens:1,list:1,live:1,local:0,localhost:1,lock:0,log:1,logger:1,mai:1,make:1,mani:0,messi:0,method:1,might:0,minut:1,mit:0,modifi:1,mywebsit:0,name:[0,1],nano:1,newkei:0,nginx:0,nginx_serv:1,node:0,none:1,number:[0,1],object:1,occur:0,openssh:1,openssl:0,option:[0,1],os:0,out:0,output:1,page:1,paramet:1,paramiko:1,particular:1,pass:[0,1],pem:[0,1],pem_fil:1,perform:1,pip:0,port:[0,1],post:1,pre:[0,1],precommit:0,prefix:1,previous:1,print:1,privat:0,problem:0,provid:0,public_dn:1,public_ip:1,py:0,python:0,rao:0,re:0,recommonmark:0,record:[0,1],record_typ:1,region:1,regist:[0,1],releas:0,remain:1,remot:1,replic:1,repositori:0,req:0,request:1,requir:1,rerun:0,resourc:[0,1],respons:1,route53:0,route_53:1,rsa:[0,1],run:[0,1],run_interactive_ssh:1,runbook:1,save:0,script:[0,1],search:1,second:[0,1],secret:1,secur:[0,1],security_group_id:1,securitygroup:1,self:0,server:1,servic:0,set:1,setup:1,sever:0,should:[0,1],sign:0,simpli:0,sivanandha:0,sleep:1,sleep_tim:1,sleeper:1,some:0,sourc:1,specif:0,sphinx:0,spin:1,ssh:[0,1],ssl:0,start:[0,1],startup:0,statement:1,statu:1,still:0,stop:[0,1],store:[0,1],str:1,sub:0,subdomain:[0,1],success:1,t2:1,t:0,take:[0,1],termin:1,thevickypedia:0,thi:[0,1],time:1,time_convert:1,token:1,traffic:1,troubleshoot:0,tunnel:[0,1],tupl:1,twice:1,two:0,type:1,ubuntu:[0,1],under:[0,1],unfortun:0,up:1,upgrad:0,upsert:1,us:1,usernam:1,valu:1,variabl:1,vignesh:0,vpc:1,wa:1,warn:0,web:0,well:1,were:1,west:1,when:0,where:1,whether:1,which:[0,1],within:0,without:0,work:0,x509:0,yet:0,zone:[0,1]},titles:["Expose localhost using EC2","Welcome to Expose\u2019s documentation!"],titleterms:{auxiliari:1,certif:0,cli:0,command:0,configur:1,copyright:0,document:1,ec2:0,environ:0,expos:[0,1],indic:1,licens:0,lint:0,localhost:0,main:1,me:1,modul:1,read:1,requir:0,route53:1,runbook:0,s:1,setup:0,tabl:1,us:0,usag:0,variabl:0,welcom:1}}) \ No newline at end of file +Search.setIndex({docnames:["README","index"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["README.md","index.rst"],objects:{"":[[1,0,0,"-","tunnel"]],"helpers.auxiliary":[[1,1,1,"","get_public_ip"],[1,1,1,"","sleeper"],[1,1,1,"","time_converter"]],"helpers.nginx_server":[[1,1,1,"","prefix"],[1,1,1,"","run_interactive_ssh"]],"helpers.route_53":[[1,1,1,"","_get_zone_id"],[1,1,1,"","change_record_set"]],"tunnel.Tunnel":[[1,3,1,"","_authorize_security_group"],[1,3,1,"","_configure_vm"],[1,3,1,"","_create_ec2_instance"],[1,3,1,"","_create_key_pair"],[1,3,1,"","_create_security_group"],[1,3,1,"","_delete_key_pair"],[1,3,1,"","_delete_security_group"],[1,3,1,"","_get_vpc_id"],[1,3,1,"","_instance_info"],[1,3,1,"","_terminate_ec2_instance"],[1,3,1,"","start"],[1,3,1,"","stop"]],helpers:[[1,0,0,"-","auxiliary"],[1,0,0,"-","nginx_server"],[1,0,0,"-","route_53"]],tunnel:[[1,2,1,"","Tunnel"]]},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method"},terms:{"2":1,"2021":0,"2048":0,"3650":0,"boolean":1,"class":1,"default":1,"float":1,"function":1,"int":1,"long":0,"new":0,"null":1,"public":[0,1],"return":1,"throw":0,"var":[0,1],A:[0,1],But:0,If:[0,1],No:0,The:[0,1],There:1,These:0,To:0,_authorize_security_group:1,_configure_vm:1,_create_ec2_inst:1,_create_key_pair:1,_create_security_group:1,_delete_key_pair:1,_delete_security_group:1,_get_vpc_id:1,_get_zone_id:1,_instance_info:1,_terminate_ec2_inst:1,access:[0,1],accordingli:[0,1],account:[0,1],act:1,action:1,activ:0,ad:[0,1],add:1,address:1,all:[0,1],also:0,ami:[0,1],ami_id:[0,1],an:[0,1],ani:0,api:[0,1],app:0,applic:1,appropri:1,apt:0,ar:[0,1],arg:0,argument:1,assign:1,authent:1,author:1,avoid:0,aw:[0,1],await:1,aws_access_kei:1,aws_region_nam:1,aws_secret_kei:1,befor:1,block:0,bool:1,boto3:1,browser:0,bulb:0,ca:0,cach:0,call:1,can:[0,1],cert:0,certain:1,chain:0,change_record_set:1,changeset:1,check:1,client:1,color:1,com:[0,1],command:1,commit:0,config:1,configur:0,connect:1,consol:1,convert:1,copyright:1,could:0,creat:1,creation:0,credenti:1,custom:0,dai:[0,1],debug:1,delai:1,delet:1,describe_instance_statu:1,destin:1,dict:1,dictionari:1,disabl:1,dn:[0,1],dns_name:1,doc:0,domain:[0,1],domain_nam:1,download:0,dpkg:0,durat:1,dure:[0,1],e:0,ec2:1,either:[0,1],endpoint:0,ensur:0,env:[0,1],environ:1,even:0,everi:0,exampl:[0,1],exist:1,file:[0,1],filenam:1,flag:1,format:1,from:1,frontend:0,gener:0,get:[0,1],get_public_ip:1,github:0,give:0,group:1,ha:[0,1],handshak:0,hasn:0,helper:1,host:[0,1],hostnam:1,hour:1,http:0,id:[0,1],idl:1,imag:1,image_id:1,index:1,ingress:1,initi:1,instal:0,instanc:[0,1],instance_id:1,interact:1,internet:0,io:[0,1],ip:1,ipinfo:1,jsontest:1,just:1,kei:[0,1],keyout:0,keypair:1,level:1,lib:0,liberti:0,licens:1,list:1,live:1,local:0,localhost:1,lock:0,log:1,logger:1,mai:1,make:1,mani:0,messi:0,method:1,might:0,minut:1,mit:0,modifi:1,mywebsit:0,name:[0,1],nano:1,newkei:0,nginx:0,nginx_serv:1,node:0,none:1,number:[0,1],object:1,occur:0,openssh:1,openssl:0,option:[0,1],os:0,out:0,output:1,page:1,paramet:1,paramiko:1,particular:1,pass:[0,1],pem:[0,1],pem_fil:1,perform:1,pip:0,port:[0,1],post:1,pre:[0,1],precommit:0,prefix:1,previous:1,print:1,privat:0,problem:0,provid:0,public_dn:1,public_ip:1,py:0,python:0,rao:0,re:0,recommonmark:0,record:[0,1],record_typ:1,region:1,regist:[0,1],releas:0,remain:1,remot:1,replic:1,repositori:0,req:0,request:1,requir:1,rerun:0,resourc:[0,1],respons:1,route53:0,route_53:1,rsa:[0,1],run:[0,1],run_interactive_ssh:1,runbook:1,save:0,script:[0,1],search:1,second:[0,1],secret:1,secur:[0,1],security_group_id:1,securitygroup:1,self:0,server:1,servic:0,set:1,setup:1,sever:0,should:[0,1],sign:0,simpli:0,sivanandha:0,sleep:1,sleep_tim:1,sleeper:1,some:0,sourc:1,specif:0,sphinx:0,spin:1,ssh:[0,1],ssl:0,start:[0,1],startup:0,statement:1,statu:1,still:0,stop:[0,1],store:[0,1],str:1,sub:0,subdomain:[0,1],success:1,t2:1,t:0,take:[0,1],termin:1,thevickypedia:0,thi:[0,1],time:1,time_convert:1,token:1,traffic:1,troubleshoot:0,tunnel:[0,1],tupl:1,twice:1,two:0,type:1,ubuntu:[0,1],under:[0,1],unfortun:0,up:1,upgrad:0,upsert:1,us:1,usernam:1,valu:1,variabl:1,vignesh:0,vpc:1,wa:1,warn:0,web:0,well:1,were:1,west:1,when:0,where:1,whether:1,which:[0,1],within:0,without:0,work:0,x509:0,yet:0,zone:[0,1]},titles:["Expose localhost using EC2","Welcome to Expose\u2019s documentation!"],titleterms:{auxiliari:1,certif:0,cli:0,command:0,configur:1,copyright:0,document:1,ec2:0,environ:0,expos:[0,1],indic:1,licens:0,lint:0,localhost:0,main:1,me:1,modul:1,read:1,requir:0,route53:1,runbook:0,s:1,setup:0,tabl:1,us:0,usag:0,variabl:0,welcom:1}}) \ No newline at end of file diff --git a/expose/helpers/nginx_server.py b/expose/helpers/nginx_server.py index 3aa0849..aa3471d 100644 --- a/expose/helpers/nginx_server.py +++ b/expose/helpers/nginx_server.py @@ -1,8 +1,9 @@ from datetime import datetime from inspect import currentframe, getframeinfo, getouterframes, stack -import paramiko -from helpers.auxiliary import sleeper +from paramiko import AutoAddPolicy, RSAKey, SSHClient + +from expose.helpers.auxiliary import sleeper DATETIME_FORMAT = '%b-%d-%Y %I:%M:%S %p' @@ -35,9 +36,9 @@ def run_interactive_ssh(hostname: str, pem_file: str, commands: dict, username: bool: Returns a boolean flag if all commands were successful. """ - pem_key = paramiko.RSAKey.from_private_key_file(filename=pem_file) - ssh_client = paramiko.SSHClient() - ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + pem_key = RSAKey.from_private_key_file(filename=pem_file) + ssh_client = SSHClient() + ssh_client.set_missing_host_key_policy(AutoAddPolicy()) ssh_client.connect(hostname=hostname, username=username, pkey=pem_key) for command in commands: diff --git a/expose/tunnel.py b/expose/tunnel.py index ecde578..aca02ac 100644 --- a/expose/tunnel.py +++ b/expose/tunnel.py @@ -1,19 +1,19 @@ import json import logging -from os import environ, getpid, path, system -from time import perf_counter, sleep +from os import environ, path, system +from time import perf_counter +import requests from boto3 import client, resource from botocore.exceptions import ClientError -from click import argument, command, pass_context, secho from dotenv import load_dotenv -from helpers.auxiliary import get_public_ip, sleeper, time_converter -from helpers.nginx_server import DATETIME_FORMAT, prefix, run_interactive_ssh -from helpers.route_53 import change_record_set -from psutil import Process from urllib3 import disable_warnings from urllib3.exceptions import InsecureRequestWarning +from expose.helpers.auxiliary import get_public_ip, sleeper, time_converter +from expose.helpers.nginx_server import DATETIME_FORMAT, run_interactive_ssh +from expose.helpers.route_53 import change_record_set + disable_warnings(InsecureRequestWarning) # Disable warnings for self-signed certificates if path.isfile('.env'): @@ -21,6 +21,7 @@ HOME_DIR = path.expanduser('~') SEP = path.sep +CONFIGURATION_FILES = ['server.conf', 'nginx-ssl.conf', 'nginx-non-ssl.conf', 'options-ssl-nginx.conf'] class Tunnel: @@ -30,14 +31,25 @@ class Tunnel: """ - def __init__(self, aws_access_key: str = environ.get('ACCESS_KEY'), aws_secret_key: str = environ.get('SECRET_KEY'), - aws_region_name: str = environ.get('REGION_NAME', 'us-west-2')): + def __init__(self, + aws_access_key: str = environ.get('ACCESS_KEY'), + aws_secret_key: str = environ.get('SECRET_KEY'), + aws_region_name: str = environ.get('REGION_NAME', 'us-west-2'), + image_id: str = environ.get('AMI_ID'), + port: int = environ.get('PORT'), + domain_name: str = environ.get('DOMAIN'), + subdomain: str = environ.get('SUBDOMAIN') + ): """Assigns a name to the PEM file, initiates the logger, client and resource for EC2 using ``boto3`` module. Args: aws_access_key: Access token for AWS account. aws_secret_key: Secret ID for AWS account. aws_region_name: Region where the instance should live. Defaults to ``us-west-2`` + image_id: Takes image ID as an argument. Defaults to ``ami_id`` in environment variable. + port: Port number where the application/API is running in localhost. + domain_name: Name of the hosted zone in which an ``A`` record has to be added. [``example.com``] + subdomain: Subdomain using which the localhost has to be accessed. [``tunnel`` or ``tunnel.example.com``] See Also: - If no values (for aws authentication) are passed during object initialization, script checks for env vars. @@ -67,6 +79,12 @@ def __init__(self, aws_access_key: str = environ.get('ACCESS_KEY'), aws_secret_k self.ec2_resource = resource(service_name='ec2', region_name=aws_region_name, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) + # Others + self.image_id = image_id + self.port = port + self.domain_name = domain_name + self.subdomain = subdomain + def __del__(self): """Destructor to print the run time at the end.""" self.logger.info(f'Total runtime: {time_converter(perf_counter())}') @@ -207,17 +225,14 @@ def _create_security_group(self) -> str or None: else: self.logger.error('Failed to created the SecurityGroup') - def _create_ec2_instance(self, image_id: str = environ.get('AMI_ID')) -> str or None: + def _create_ec2_instance(self) -> str or None: """Creates an EC2 instance of type ``t2.nano`` with the pre-configured AMI id. - Args: - image_id: Takes image ID as an argument. Defaults to ``ami_id`` in environment variable. Exits if `null`. - Returns: str or None: Instance ID. """ - if not image_id: + if not self.image_id: self.logger.error('AMI is mandatory to spin up an EC2 instance. Received `null`') return @@ -233,7 +248,7 @@ def _create_ec2_instance(self, image_id: str = environ.get('AMI_ID')) -> str or InstanceType="t2.nano", MaxCount=1, MinCount=1, - ImageId=image_id, + ImageId=self.image_id, KeyName=self.key_name, SecurityGroupIds=[security_group_id] ) @@ -337,9 +352,9 @@ def _instance_info(self, instance_id: str) -> tuple or None: A tuple object of Public DNS Name and Public IP Address. """ self.logger.info('Waiting for the instance to go live.') - sleeper(sleep_time=15) + sleeper(sleep_time=25) while True: - sleep(3) + sleeper(sleep_time=5) try: response = self.ec2_client.describe_instance_status( InstanceIds=[instance_id] @@ -355,21 +370,15 @@ def _instance_info(self, instance_id: str) -> tuple or None: instance_info = self.ec2_resource.Instance(instance_id) return instance_info.public_dns_name, instance_info.public_ip_address - def start(self, port: int = environ.get('PORT')) -> None: - """Calls the class methods ``_create_ec2_instance`` and ``_instance_info`` to configure the ec2 instance. - - Args: - port: Port number where the application/API is running in localhost. - """ + def start(self) -> None: + """Calls the class methods ``_create_ec2_instance`` and ``_instance_info`` to configure the ec2 instance.""" if path.isfile(self.server_file) and path.isfile(f'{self.key_name}.pem'): self.logger.warning('Received request to start VM, but looks like a session is up and running already.') self.logger.warning('Initiating re-configuration.') sleeper(sleep_time=10) - system('git checkout -- nginx-ssl.conf server.conf') - system('rm nginx.conf') with open(self.server_file, 'r') as file: data = json.load(file) - self._configure_vm(public_dns=data.get('public_dns'), public_ip=data.get('public_ip'), port=port) + self._configure_vm(public_dns=data.get('public_dns'), public_ip=data.get('public_ip')) return if instance_basic := self._create_ec2_instance(): @@ -394,7 +403,7 @@ def start(self, port: int = environ.get('PORT')) -> None: 'public_ip': public_ip, 'security_group_id': security_group_id, 'ssh_endpoint': f'ssh -i {self.key_name}.pem ubuntu@{public_dns}', - 'start_tunneling': f"ssh -i {self.key_name}.pem -R 8080:localhost:{port} ubuntu@{public_dns}" + 'start_tunneling': f"ssh -i {self.key_name}.pem -R 8080:localhost:{self.port} ubuntu@{public_dns}" } self.logger.info(f'Restricting wide open permissions to {self.key_name}.pem') @@ -406,45 +415,39 @@ def start(self, port: int = environ.get('PORT')) -> None: self.logger.info('Waiting for SSH origin to be active.') sleeper(sleep_time=15) - self._configure_vm(public_dns=public_dns, public_ip=public_ip, port=port) + self._configure_vm(public_dns=public_dns, public_ip=public_ip) - def _configure_vm(self, public_dns: str, public_ip: str, port: int, - domain_name: str = environ.get('DOMAIN'), subdomain: str = environ.get('SUBDOMAIN')): + def _configure_vm(self, public_dns: str, public_ip: str): """Configures the ec2 instance to take traffic from localhost. Args: public_dns: Public DNS name of the EC2 that was created. public_ip: Public IP of the EC2 that was created. - port: Port number on which the app/api is running. - domain_name: Name of the hosted zone in which an ``A`` record has to be added. [``example.com``] - subdomain: Subdomain using which the localhost has to be accessed. [``tunnel`` or ``tunnel.example.com``] """ self.logger.info('Gathering pieces for configuration.') custom_servers = f"{public_dns} {public_ip}" endpoint = None - if domain_name and subdomain: - if subdomain.endswith(domain_name): - endpoint = subdomain - custom_servers += f' {subdomain}' + if self.domain_name and self.subdomain: + if self.subdomain.endswith(self.domain_name): + endpoint = self.subdomain + custom_servers += f' {self.subdomain}' else: - endpoint = f'{subdomain}.{domain_name}' - custom_servers += f' {subdomain}.{domain_name}' - - with open('server.conf', 'r+') as file: - server_conf = file.read().replace('SERVER_NAME_HERE', custom_servers) - file.seek(0) - file.truncate() - file.write(server_conf) + endpoint = f'{self.subdomain}.{self.domain_name}' + custom_servers += f' {self.subdomain}.{self.domain_name}' - with open('nginx-ssl.conf', 'r+') as file: - ssl_conf = file.read().replace('SERVER_NAME_HERE', custom_servers) - file.seek(0) - file.truncate() - file.write(ssl_conf) + for conf_file in CONFIGURATION_FILES: + self.logger.info(f'Downloading the configuration file: {conf_file}.') + conf_content = requests.get( + url=f'https://raw.githubusercontent.com/thevickypedia/expose/main/configuration/{conf_file}' + ).text + with open(conf_file, 'w') as file: + file.write(conf_content.replace('SERVER_NAME_HERE', custom_servers)) copy_files = "server.conf nginx.conf" - if path.isfile(f"{HOME_DIR}{SEP}.ssh{SEP}") and path.isfile(f"{HOME_DIR}{SEP}.ssh{SEP}key.pem"): + if path.isdir(f"{HOME_DIR}{SEP}.ssh") and \ + path.isfile(f"{HOME_DIR}{SEP}.ssh{SEP}key.pem") and \ + path.isfile(f"{HOME_DIR}{SEP}.ssh{SEP}cert.pem"): secured = True copy_files += f" {HOME_DIR}{SEP}.ssh{SEP}cert.pem {HOME_DIR}{SEP}.ssh{SEP}key.pem options-ssl-nginx.conf" system("cp -p nginx-ssl.conf nginx.conf") @@ -479,20 +482,21 @@ def _configure_vm(self, public_dns: str, public_ip: str, port: int, return self.logger.info('Nginx server was configured successfully.') - system('git checkout -- nginx-ssl.conf server.conf') - system('rm nginx.conf') + system(f"rm {' '.join(CONFIGURATION_FILES)} nginx.conf") if endpoint: - change_record_set(dns_name=domain_name, source=subdomain, destination=public_ip, record_type='A') + change_record_set(dns_name=self.domain_name, source=self.subdomain, destination=public_ip, record_type='A') if secured: - self.logger.info(f'https://{endpoint} → http://localhost:{port}') + self.logger.info(f'https://{endpoint} → http://localhost:{self.port}') else: - self.logger.info(f'http://{endpoint} → http://localhost:{port}') + self.logger.info(f'http://{endpoint} → http://localhost:{self.port}') else: - self.logger.info(f'http://{public_dns} → http://localhost:{port}') + self.logger.info(f'http://{public_dns} → http://localhost:{self.port}') self.logger.info('Initiating tunnel') - system(f"ssh -o StrictHostKeyChecking=no -i {self.key_name}.pem -R 8080:localhost:{port} ubuntu@{public_dns}") + system( + f"ssh -o StrictHostKeyChecking=no -i {self.key_name}.pem -R 8080:localhost:{self.port} ubuntu@{public_dns}" + ) def stop(self) -> None: """Disables tunnelling by terminating the ``EC2`` instance, ``KeyPair``, and the ``SecurityGroup`` created. @@ -508,6 +512,9 @@ def stop(self) -> None: data = json.load(file) if self._delete_key_pair() and self._terminate_ec2_instance(instance_id=data.get('instance_id')): + if (domain_name := self.domain_name) and (subdomain := self.subdomain): + change_record_set(dns_name=domain_name, source=subdomain, destination=data.get('public_ip'), + record_type='A', action='DELETE') self.logger.info('Waiting for dependent objects to delete SecurityGroup.') sleeper(sleep_time=90) while True: @@ -516,51 +523,3 @@ def stop(self) -> None: else: sleeper(sleep_time=20) system(f'rm {self.server_file}') - if (domain_name := environ.get('DOMAIN')) and (subdomain := environ.get('SUBDOMAIN')): - change_record_set(dns_name=domain_name, source=subdomain, destination=data.get('public_ip'), - record_type='A', action='DELETE') - - -@command() -@pass_context -@argument('initiator', required=False) -@argument('port', required=False) -def main(*args, initiator: str, port: int): - """Handles the CLI initiation. - - Args: - *args: Context object of click. This will be ignored - initiator: Command to start or stop the tunneling. Options are: ``START`` or ``STOP`` - port: Port number which should be tunnelled via nginx server running on EC2 instance. - """ - run_env = Process(getpid()).parent().name() - if not run_env.endswith('sh'): - print(f"\033[31m{prefix(level='ERROR')}This is a CLI tool.\n\n" - f"Please use a terminal to run the script and pass the arg `start` or `stop`\033[00m") - exit(1) - - if not initiator: - secho(message='Please pass the arg `START` [OR] `STOP`', fg='bright_red') - exit(1) - if initiator.upper() == 'START': - port = port or environ.get('PORT') - if not port: - secho(message=f'{prefix(level="ERROR")}Port number should be passed as `python expose.py start 2021`' - f'or stored as an env var `export PORT=2021`.', - fg='bright_red') - else: - secho(message=f'{prefix(level="INFO")}Initiating localhost tunneling on {port}.', fg='bright_green') - Tunnel().start(port=port) - elif initiator.upper() == 'STOP': - secho(message=f'{prefix(level="INFO")}Shutting down localhost tunneling.', fg='bright_yellow') - Tunnel().stop() - else: - secho(message='The allowed options are:\n\t' - '1. python expose.py start\n\t' - '2. python expose.py start [PORT_NUMBER]\n\t' - '3. python expose.py stop', - fg='bright_red') - - -if __name__ == '__main__': - main() diff --git a/version.py b/version.py index a751975..8278c45 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -version_info = (0, 2, 4) +version_info = (0, 2, 6)