From 29582331cb0923efc831437cae0a0b0c8a6df19c Mon Sep 17 00:00:00 2001 From: thevickypedia Date: Thu, 28 Sep 2023 10:09:50 -0500 Subject: [PATCH] Release `v1.5` Update docstrings --- .pre-commit-config.yaml | 2 +- doc_gen/index.rst | 2 +- docs/README.md | 92 +++++++++++++++++++++++++++++ docs/_sources/index.rst.txt | 2 +- docs/index.html | 113 +++++++++++++++++++++++------------ docs/searchindex.js | 2 +- gen_docs.sh | 10 ---- pre_commit.sh | 37 ++++++++++++ vpn/__init__.py | 2 +- vpn/main.py | 115 +++++++++++++++++++++++------------- vpn/models/image_factory.py | 2 +- vpn/models/logger.py | 6 +- vpn/models/route53.py | 20 +++---- vpn/models/server.py | 9 +-- 14 files changed, 300 insertions(+), 114 deletions(-) create mode 100644 docs/README.md delete mode 100644 gen_docs.sh create mode 100644 pre_commit.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index beb70cc..5246c56 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - id: docs name: docs - entry: /bin/bash gen_docs.sh + entry: /bin/bash pre_commit.sh language: system pass_filenames: false always_run: true diff --git a/doc_gen/index.rst b/doc_gen/index.rst index a71f421..0e6d30e 100644 --- a/doc_gen/index.rst +++ b/doc_gen/index.rst @@ -35,7 +35,7 @@ Configuration ==== -.. autoclass:: vpn.models.config.EnvConfig(pydantic.BaseSettings) +.. autoclass:: vpn.models.config.EnvConfig(pydantic_settings.BaseSettings) :members: :exclude-members: _abc_impl, model_config, model_fields diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2aeab1e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,92 @@ +![Python](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue) + +###### Platform Supported +![Generic badge](https://img.shields.io/badge/Platform-MacOS|Windows-1f425f.svg) + +###### Repo Stats +[![GitHub](https://img.shields.io/github/license/thevickypedia/vpn-server)][LICENSE] +[![GitHub repo size](https://img.shields.io/github/repo-size/thevickypedia/vpn-server)][API_REPO] +[![GitHub code size](https://img.shields.io/github/languages/code-size/thevickypedia/vpn-server)][API_REPO] + +###### Deployments +[![pages-build-deployment](https://github.com/thevickypedia/vpn-server/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/thevickypedia/vpn-server/actions/workflows/pages/pages-build-deployment) +[![pypi](https://github.com/thevickypedia/vpn-server/actions/workflows/python-publish.yml/badge.svg)](https://github.com/thevickypedia/vpn-server/actions/workflows/python-publish.yml) + +[![Pypi-format](https://img.shields.io/pypi/format/vpn-server)](https://pypi.org/project/vpn-server/#files) +[![Pypi-status](https://img.shields.io/pypi/status/vpn-server)](https://pypi.org/project/vpn-server) +[![sourcerank](https://img.shields.io/librariesio/sourcerank/pypi/vpn-server)](https://libraries.io/pypi/vpn-server) + +# VPN Server +Establish a scalable, on-demand VPN Server powered by OpenVPN on AWS EC2. + +### Install +```shell +python -m pip install vpn-server +``` + +### Usage +```python +import vpn + +# Instantiates the object +vpn_server = vpn.VPNServer() + +# Create a VPN Server +vpn_server.create_vpn_server() + +# Test an existing VPN Server +# vpn_server.test_vpn() + +# Deletes the VPN Server +vpn_server.delete_vpn_server() +``` + +> :bulb:   Please refer to the [wiki page](https://github.com/thevickypedia/vpn-server/wiki) for more usage instructions and payload requirements. + +## Coding Standards +Docstring format: [`Google`](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
+Styling conventions: [`PEP 8`](https://www.python.org/dev/peps/pep-0008/)
+Clean code with pre-commit hooks: [`flake8`](https://flake8.pycqa.org/en/latest/) and +[`isort`](https://pycqa.github.io/isort/) + +### [Release Notes](https://github.com/thevickypedia/vpn-server/blob/main/release_notes.rst) +**Requirement** +```shell +python -m pip install gitverse +``` + +**Usage** +```shell +gitverse-release reverse -f release_notes.rst -t 'Release Notes' +``` + +### Linting +`PreCommit` will ensure linting, and the doc creation are run on every commit. + +**Requirement** +```shell +pip install sphinx==5.1.1 pre-commit recommonmark +``` + +**Usage** +```shell +pre-commit run --all-files +``` + +## Project Links +[Wiki](https://github.com/thevickypedia/vpn-server/wiki) + +[Repository](https://github.com/thevickypedia/vpn-server) + +[Runbook](https://thevickypedia.github.io/vpn-server/) + +[Package](https://pypi.org/project/vpn-server/) + +## License & copyright + +© Vignesh Rao + +Licensed under the [MIT License][LICENSE] + +[LICENSE]: https://github.com/thevickypedia/vpn-server/blob/main/LICENSE +[API_REPO]: https://api.github.com/repos/thevickypedia/vpn-server diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index a71f421..0e6d30e 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -35,7 +35,7 @@ Configuration ==== -.. autoclass:: vpn.models.config.EnvConfig(pydantic.BaseSettings) +.. autoclass:: vpn.models.config.EnvConfig(pydantic_settings.BaseSettings) :members: :exclude-members: _abc_impl, model_config, model_fields diff --git a/docs/index.html b/docs/index.html index d0ccbd5..46126ae 100644 --- a/docs/index.html +++ b/docs/index.html @@ -65,17 +65,65 @@

Welcome to VPN Server’s documentation!

VPN Server

-class vpn.main.VPNServer(**kwargs)
+class vpn.main.VPNServer(**kwargs: Unpack)

Initiates VPNServer object to spin up an EC2 instance with a pre-configured AMI which serves as a VPN server.

>>> VPNServer
 
-

Assigns a name to the PEM file, initiates the logger, client and resource for EC2 using boto3 module.

+

Unpacks the kwargs and loads it as Envconfig, the subclass for BaseSettings validated using pydantic.

Parameters:
-

logger – Bring your own logger.

+

kwargs – Dictionary of keyword arguments for the following key-value pairs.

+
    +
  • vpn_username - Username to access VPN client.

  • +
  • vpn_password - Password to access VPN client.

  • +
  • vpn_port - Port number for OpenVPN web interface access.

  • +
  • aws_profile_name - Name of the AWS profile to initiate a session.

  • +
  • 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 AWS profile default.

  • +
  • image_id - AMI ID using which the instance should be created.

  • +
  • instance_type - Instance type to use in AWS to host the OpenVPN Access Server.

  • +
  • key_pair - Name of the key pair for the ec2 and the file stored for SSH connection.

  • +
  • security_group - Name of the security group to be created in AWS.

  • +
  • vpn_info - Name of the JSON file to dump the instance and connection information.

  • +
  • domain - Domain name for the hosted zone.

  • +
  • hosted_zone - Name of the hosted zone in route53.

  • +
  • subdomain - Name of the subdomain to be created in the hosted zone.

  • +
  • logger - Bring your own logger.

  • +
+
+

See also

+

Access point to the VPN server will be the combination of subdomain and hosted_zone.

+
+
Examples:
    +
  • +
    If you have a hosted zone in AWS, named example.com and want the entrypoint to be
    +
    vpn.example.com then,
    +
    +
    +
      +
    • hosted_zone should be example.com

    • +
    • subdomain should be vpn

    • +
    +
    +
  • +
+
+
Notes:
    +
  • Please note that the hosted zone should also be a registered domain in AWS.

  • +
  • You cannot simply create a hosted zone named google.com and expect it to work.

  • +
  • +
    The alias record will fail as duplicate at DNS name resolution level unless a domain is
    +
    registered with the same name.
    +
    +
  • +
+
+
+
_authorize_security_group(security_group_id: str) bool
@@ -98,7 +146,7 @@

Welcome to VPN Server’s documentation!
Returns:
-

Flag to indicate the calling function whether the security group was authorized.

+

Flag to indicate the calling function, whether the security group was authorized successfully.

Return type:

bool

@@ -137,7 +185,7 @@

Welcome to VPN Server’s documentation!KeyPair of type RSA stored as a PEM file to use with OpenSSH.

Returns:
-

Boolean flag to indicate the calling function if a KeyPair was created.

+

Flag to indicate the calling function, if a KeyPair was created successfully.

Return type:

bool

@@ -169,7 +217,7 @@

Welcome to VPN Server’s documentation!KeyPair created to access the ec2 instance.

Returns:
-

Boolean flag to indicate the calling function if the KeyPair was deleted successfully.

+

Flag to indicate the calling function, if the KeyPair was deleted successfully.

Return type:

bool

@@ -186,7 +234,7 @@

Welcome to VPN Server’s documentation!

security_group_id – Takes the SecurityGroup ID as an argument.

Returns:
-

Boolean flag to indicate the calling function whether the SecurityGroup was deleted.

+

Flag to indicate the calling function, whether the SecurityGroup was deleted successfully.

Return type:

bool

@@ -196,7 +244,7 @@

Welcome to VPN Server’s documentation!
-_disassociate_security_group(security_group_id: str, instance: object = None, instance_id: str = None) bool
+_disassociate_security_group(security_group_id: str, instance: ServiceResource = None, instance_id: str = None) bool

Disassociates an SG from the ec2 instance by assigning it to the default security group.

Parameters:
@@ -207,7 +255,7 @@

Welcome to VPN Server’s documentation!Returns: -

Boolean flag to indicate the calling function whether the disassociation was successful.

+

Flag to indicate the calling function, whether the instance was disassociated from the SG successfully.

Return type:

bool

@@ -235,14 +283,14 @@

Welcome to VPN Server’s documentation!
Parameters:
-

start – Boolean flag to indicate if its startup or shutdown.

+

start – Flag to indicate if its startup or shutdown.

-_terminate_ec2_instance(instance_id: str = None, instance: object = None) ServiceResource
+_terminate_ec2_instance(instance_id: str = None, instance: ServiceResource = None) ServiceResource

Terminates the requested instance.

Parameters:
@@ -252,7 +300,7 @@

Welcome to VPN Server’s documentation!Returns: -

Boolean flag to indicate the calling function whether the instance was terminated.

+

Flag to indicate the calling function, whether the instance was terminated successfully.

Return type:

bool

@@ -282,7 +330,7 @@

Welcome to VPN Server’s documentation!
-_tester(data: Dict[str, Union[str, int]]) bool
+_tester(data: Dict[str, Union[str, int]]) None

Tests GET and SSH connections on the existing server.

Parameters:
@@ -292,21 +340,18 @@

Welcome to VPN Server’s documentation!

See also

    -
  • Called when a startup request is made but info file and pem file are present already.

  • -
  • Called when a manual test request is made.

  • -
  • Testing SSH connection will also run updates on the VM.

  • +
  • GET request against the public IP of the ec2 instance.

  • +
  • GET request against the public DNS of the ec2 instance.

  • +
  • SSH connection with the OpenVPN Access Server.

  • +
  • Test openvpnas service availability on the server.

-
Returns:
-

    -
  • True if the existing connection is reachable and ssh to the origin succeeds.

  • -
  • False if the connection fails or unable to ssh to the origin.

  • +
    Raises:
    +
      +
    • AssertionError

    • +
    • When any of the tests fail.

    -

    -
    -
    Return type:
    -

    bool

@@ -411,7 +456,7 @@

Configuration
-class vpn.models.config.EnvConfig(pydantic.BaseSettings)
+class vpn.models.config.EnvConfig(pydantic_settings.BaseSettings)

Env configuration.

>>> EnvConfig
 
@@ -483,7 +528,7 @@

Configuration
>>> ImageFactory
 

-

Instantiates the ImageFactory object.

+

Creates ec2 and ssm client using an existing boto3 session.

Parameters:
diff --git a/docs/searchindex.js b/docs/searchindex.js index f4677b5..6efd41a 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["README", "index"], "filenames": ["README.md", "index.rst"], "titles": ["Platform Supported", "Welcome to VPN Server\u2019s documentation!"], "terms": {"establish": 0, "scalabl": 0, "demand": 0, "power": 0, "openvpn": [0, 1], "aw": [0, 1], "ec2": [0, 1], "python": 0, "m": 0, "pip": 0, "import": 0, "instanti": [0, 1], "object": [0, 1], "vpn_server": 0, "vpnserver": [0, 1], "creat": [0, 1], "create_vpn_serv": [0, 1], "test": [0, 1], "an": [0, 1], "exist": [0, 1], "test_vpn": [0, 1], "delet": [0, 1], "delete_vpn_serv": [0, 1], "bulb": 0, "pleas": 0, "refer": [0, 1], "wiki": 0, "page": [0, 1], "more": [0, 1], "instruct": 0, "payload": 0, "requir": [0, 1], "docstr": 0, "format": [0, 1], "googl": 0, "style": 0, "convent": 0, "pep": 0, "8": 0, "clean": 0, "pre": [0, 1], "commit": 0, "hook": 0, "flake8": 0, "isort": 0, "gitvers": 0, "revers": 0, "f": 0, "release_not": 0, "rst": 0, "t": [0, 1], "precommit": 0, "ensur": 0, "doc": [0, 1], "creation": 0, "ar": [0, 1], "run": [0, 1], "everi": 0, "sphinx": 0, "5": 0, "1": 0, "recommonmark": 0, "all": [0, 1], "file": [0, 1], "repositori": 0, "runbook": 0, "packag": 0, "vignesh": 0, "rao": 0, "under": 0, "mit": 0, "platform": 1, "support": 1, "repo": 1, "stat": 1, "deploy": 1, "instal": 1, "usag": 1, "code": 1, "standard": 1, "project": 1, "link": 1, "licens": 1, "copyright": 1, "class": 1, "main": 1, "kwarg": 1, "initi": 1, "spin": 1, "up": 1, "instanc": 1, "ami": 1, "which": 1, "serv": 1, "assign": 1, "name": 1, "pem": 1, "client": 1, "resourc": 1, "us": 1, "boto3": 1, "modul": 1, "paramet": 1, "bring": 1, "your": 1, "own": 1, "_authorize_security_group": 1, "security_group_id": 1, "str": 1, "bool": 1, "author": 1, "secur": 1, "group": 1, "certain": 1, "ingress": 1, "list": 1, "take": 1, "securitygroup": 1, "id": 1, "argument": 1, "firewal": 1, "port": 1, "open": 1, "tcp": 1, "22": 1, "access": 1, "443": 1, "web": 1, "interfac": 1, "connect": 1, "943": 1, "can": 1, "dynam": 1, "945": 1, "cluster": 1, "control": 1, "channel": 1, "udp": 1, "1194": 1, "return": 1, "flag": 1, "call": 1, "function": 1, "whether": 1, "wa": 1, "type": 1, "_configure_vpn": 1, "public_dn": 1, "none": 1, "traffic": 1, "from": 1, "localhost": 1, "tunnel": 1, "public": 1, "dn": 1, "_create_ec2_inst": 1, "option": 1, "tupl": 1, "success": 1, "union": 1, "_create_key_pair": 1, "keypair": 1, "rsa": 1, "store": 1, "openssh": 1, "boolean": 1, "_create_security_group": 1, "get": 1, "vpc": 1, "re": 1, "sg": 1, "case": 1, "same": 1, "alreadi": 1, "_delete_key_pair": 1, "successfulli": 1, "_delete_security_group": 1, "_disassociate_security_group": 1, "instance_id": 1, "disassoci": 1, "default": 1, "i": 1, "unavail": 1, "_get_vpc_id": 1, "fetch": 1, "_init": 1, "start": 1, "int": 1, "its": 1, "startup": 1, "shutdown": 1, "_terminate_ec2_inst": 1, "serviceresourc": 1, "termin": 1, "request": 1, "_test_get": 1, "server_hostnam": 1, "timeout": 1, "3": 1, "respons": 1, "multipl": 1, "hostnam": 1, "ip": 1, "address": 1, "alia": 1, "record": 1, "entrypoint": 1, "_tester": 1, "data": 1, "dict": 1, "inform": 1, "dictionari": 1, "when": 1, "made": 1, "info": 1, "present": 1, "manual": 1, "also": 1, "updat": 1, "vm": 1, "true": 1, "reachabl": 1, "origin": 1, "succe": 1, "fals": 1, "fail": 1, "unabl": 1, "method": 1, "_instance_info": 1, "check": 1, "befor": 1, "new": 1, "If": 1, "tear": 1, "down": 1, "notifi": 1, "user": 1, "detail": 1, "add": 1, "kei": 1, "valu": 1, "pair": 1, "retri": 1, "anoth": 1, "sent": 1, "regardless": 1, "public_ip": 1, "disabl": 1, "remov": 1, "acquir": 1, "ha": 1, "A": 1, "doesn": 1, "ani": 1, "long": 1, "json": 1, "dump": 1, "neither": 1, "nor": 1, "modifi": 1, "hand": 1, "http": 1, "amazonaw": 1, "com": 1, "v1": 1, "api": 1, "latest": 1, "servic": 1, "wait_until_termin": 1, "html": 1, "model": 1, "config": 1, "configurationset": 1, "pydant": 1, "basemodel": 1, "set": 1, "interact": 1, "pars": 1, "valid": 1, "input": 1, "keyword": 1, "rais": 1, "validationerror": 1, "pydantic_cor": 1, "cannot": 1, "form": 1, "__init__": 1, "__pydantic_self__": 1, "instead": 1, "common": 1, "self": 1, "first": 1, "arg": 1, "allow": 1, "field": 1, "amibas": 1, "imag": 1, "model_post_init": 1, "__context": 1, "thi": 1, "meant": 1, "behav": 1, "like": 1, "initialis": 1, "privat": 1, "attribut": 1, "It": 1, "context": 1, "sinc": 1, "what": 1, "core": 1, "pass": 1, "The": 1, "envconfig": 1, "baseset": 1, "env": 1, "dev": 1, "2": 1, "migrat": 1, "nullabl": 1, "extra": 1, "classmethod": 1, "validate_instance_typ": 1, "v": 1, "make": 1, "sure": 1, "nano": 1, "validate_vpn_password": 1, "vpn_password": 1, "per": 1, "regex": 1, "wrapper": 1, "awsresourceerror": 1, "status_cod": 1, "error_msg": 1, "custom": 1, "error": 1, "notimplementedwarn": 1, "implement": 1, "warn": 1, "image_factori": 1, "session": 1, "handl": 1, "retriev": 1, "sourc": 1, "get_ami_id_nam": 1, "get_ami_id_product_cod": 1, "product": 1, "get_ami_id_ssm": 1, "get_image_id": 1, "tri": 1, "sequenti": 1, "execut": 1, "sequenc": 1, "fastest": 1, "ssm": 1, "point": 1, "singl": 1, "possibli": 1, "contain": 1, "lookup": 1, "specif": 1, "mani": 1, "so": 1, "grab": 1, "most": 1, "recent": 1, "one": 1, "deprecation_warn": 1, "image_id": 1, "deprecation_tim": 1, "deprec": 1, "chosen": 1, "nearing": 1, "deprecationtim": 1, "load": 1, "streamhandl": 1, "debug": 1, "mode": 1, "change_record_set": 1, "destin": 1, "zone_id": 1, "action": 1, "chang": 1, "within": 1, "host": 1, "zone": 1, "perform": 1, "exampl": 1, "upsert": 1, "changeset": 1, "get_zone_id": 1, "init": 1, "regist": 1, "miss": 1, "usernam": 1, "rsakei": 1, "gener": 1, "add_formatt": 1, "formatt": 1, "were": 1, "dure": 1, "remove_formatt": 1, "log": 1, "room": 1, "restart_servic": 1, "restart": 1, "run_interactive_ssh": 1, "displai": 1, "30": 1, "command": 1, "screen": 1, "complet": 1, "test_servic": 1, "statu": 1, "remot": 1, "available_instance_typ": 1, "avail": 1, "loop": 1, "through": 1, "describ": 1, "yield": 1, "available_region": 1, "region": 1, "index": 1, "search": 1}, "objects": {"vpn": [[1, 0, 0, "-", "main"]], "vpn.main": [[1, 1, 1, "", "VPNServer"]], "vpn.main.VPNServer": [[1, 2, 1, "", "_authorize_security_group"], [1, 2, 1, "", "_configure_vpn"], [1, 2, 1, "", "_create_ec2_instance"], [1, 2, 1, "", "_create_key_pair"], [1, 2, 1, "", "_create_security_group"], [1, 2, 1, "", "_delete_key_pair"], [1, 2, 1, "", "_delete_security_group"], [1, 2, 1, "", "_disassociate_security_group"], [1, 2, 1, "", "_get_vpc_id"], [1, 2, 1, "", "_init"], [1, 2, 1, "", "_terminate_ec2_instance"], [1, 2, 1, "", "_test_get"], [1, 2, 1, "", "_tester"], [1, 2, 1, "", "create_vpn_server"], [1, 2, 1, "", "delete_vpn_server"], [1, 2, 1, "", "test_vpn"]], "vpn.models.config": [[1, 1, 1, "", "AMIBase"], [1, 1, 1, "", "ConfigurationSettings"], [1, 1, 1, "", "EnvConfig"], [1, 1, 1, "", "Settings"]], "vpn.models.config.AMIBase": [[1, 2, 1, "", "model_post_init"]], "vpn.models.config.EnvConfig": [[1, 1, 1, "", "Config"], [1, 2, 1, "", "validate_instance_type"], [1, 2, 1, "", "validate_vpn_password"]], "vpn.models": [[1, 0, 0, "-", "exceptions"], [1, 0, 0, "-", "image_factory"], [1, 0, 0, "-", "logger"], [1, 0, 0, "-", "route53"], [1, 0, 0, "-", "server"], [1, 0, 0, "-", "util"]], "vpn.models.exceptions": [[1, 3, 1, "", "AWSResourceError"], [1, 3, 1, "", "NotImplementedWarning"]], "vpn.models.image_factory": [[1, 1, 1, "", "ImageFactory"], [1, 4, 1, "", "deprecation_warning"]], "vpn.models.image_factory.ImageFactory": [[1, 2, 1, "", "get_ami_id_name"], [1, 2, 1, "", "get_ami_id_product_code"], [1, 2, 1, "", "get_ami_id_ssm"], [1, 2, 1, "", "get_image_id"]], "vpn.models.route53": [[1, 4, 1, "", "change_record_set"], [1, 4, 1, "", "get_zone_id"]], "vpn.models.server": [[1, 1, 1, "", "Server"]], "vpn.models.server.Server": [[1, 2, 1, "", "add_formatter"], [1, 2, 1, "", "remove_formatter"], [1, 2, 1, "", "restart_service"], [1, 2, 1, "", "run_interactive_ssh"], [1, 2, 1, "", "test_service"]], "vpn.models.util": [[1, 4, 1, "", "available_instance_types"], [1, 4, 1, "", "available_regions"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:exception", "4": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "exception", "Python exception"], "4": ["py", "function", "Python function"]}, "titleterms": {"platform": 0, "support": 0, "repo": 0, "stat": 0, "deploy": 0, "vpn": [0, 1], "server": [0, 1], "instal": 0, "usag": 0, "code": 0, "standard": 0, "releas": 0, "note": 0, "lint": 0, "project": 0, "link": 0, "licens": 0, "copyright": 0, "welcom": 1, "": 1, "document": 1, "read": 1, "me": 1, "configur": 1, "except": 1, "imagefactori": 1, "logger": 1, "route53": 1, "ssh": 1, "util": 1, "indic": 1, "tabl": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "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}}) \ No newline at end of file +Search.setIndex({"docnames": ["README", "index"], "filenames": ["README.md", "index.rst"], "titles": ["Platform Supported", "Welcome to VPN Server\u2019s documentation!"], "terms": {"establish": 0, "scalabl": 0, "demand": 0, "power": 0, "openvpn": [0, 1], "aw": [0, 1], "ec2": [0, 1], "python": 0, "m": 0, "pip": 0, "import": 0, "instanti": [0, 1], "object": [0, 1], "vpn_server": 0, "vpnserver": [0, 1], "creat": [0, 1], "create_vpn_serv": [0, 1], "test": [0, 1], "an": [0, 1], "exist": [0, 1], "test_vpn": [0, 1], "delet": [0, 1], "delete_vpn_serv": [0, 1], "bulb": 0, "pleas": [0, 1], "refer": [0, 1], "wiki": 0, "page": [0, 1], "more": [0, 1], "instruct": 0, "payload": 0, "requir": [0, 1], "docstr": 0, "format": [0, 1], "googl": [0, 1], "style": 0, "convent": 0, "pep": 0, "8": 0, "clean": 0, "pre": [0, 1], "commit": 0, "hook": 0, "flake8": 0, "isort": 0, "gitvers": 0, "revers": 0, "f": 0, "release_not": 0, "rst": 0, "t": [0, 1], "precommit": 0, "ensur": 0, "doc": [0, 1], "creation": 0, "ar": [0, 1], "run": [0, 1], "everi": 0, "sphinx": 0, "5": 0, "1": 0, "recommonmark": 0, "all": [0, 1], "file": [0, 1], "repositori": 0, "runbook": 0, "packag": 0, "vignesh": 0, "rao": 0, "under": 0, "mit": 0, "platform": 1, "support": 1, "repo": 1, "stat": 1, "deploy": 1, "instal": 1, "usag": 1, "code": 1, "standard": 1, "project": 1, "link": 1, "licens": 1, "copyright": 1, "class": 1, "main": 1, "kwarg": 1, "unpack": 1, "initi": 1, "spin": 1, "up": 1, "instanc": 1, "ami": 1, "which": 1, "serv": 1, "load": 1, "envconfig": 1, "subclass": 1, "baseset": 1, "valid": 1, "us": 1, "pydant": 1, "paramet": 1, "dictionari": 1, "keyword": 1, "argument": 1, "follow": 1, "kei": 1, "valu": 1, "pair": 1, "vpn_usernam": 1, "usernam": 1, "access": 1, "client": 1, "vpn_password": 1, "password": 1, "vpn_port": 1, "port": 1, "number": 1, "web": 1, "interfac": 1, "aws_profile_nam": 1, "name": 1, "profil": 1, "session": 1, "aws_access_kei": 1, "token": 1, "account": 1, "aws_secret_kei": 1, "secret": 1, "id": 1, "aws_region_nam": 1, "region": 1, "where": 1, "should": 1, "live": 1, "default": 1, "image_id": 1, "instance_typ": 1, "type": 1, "host": 1, "key_pair": 1, "store": 1, "connect": 1, "security_group": 1, "secur": 1, "group": 1, "vpn_info": 1, "json": 1, "dump": 1, "inform": 1, "domain": 1, "zone": 1, "hosted_zon": 1, "subdomain": 1, "bring": 1, "your": 1, "own": 1, "point": 1, "combin": 1, "exampl": 1, "If": 1, "you": 1, "have": 1, "com": 1, "want": 1, "entrypoint": 1, "note": 1, "also": 1, "regist": 1, "cannot": 1, "simpli": 1, "expect": 1, "work": 1, "The": 1, "alia": 1, "record": 1, "fail": 1, "duplic": 1, "dn": 1, "resolut": 1, "level": 1, "unless": 1, "i": 1, "same": 1, "_authorize_security_group": 1, "security_group_id": 1, "str": 1, "bool": 1, "author": 1, "certain": 1, "ingress": 1, "list": 1, "take": 1, "securitygroup": 1, "firewal": 1, "open": 1, "tcp": 1, "22": 1, "443": 1, "943": 1, "can": 1, "dynam": 1, "945": 1, "cluster": 1, "control": 1, "channel": 1, "udp": 1, "1194": 1, "return": 1, "flag": 1, "call": 1, "function": 1, "whether": 1, "wa": 1, "successfulli": 1, "_configure_vpn": 1, "public_dn": 1, "none": 1, "traffic": 1, "from": 1, "localhost": 1, "tunnel": 1, "public": 1, "_create_ec2_inst": 1, "option": 1, "tupl": 1, "success": 1, "union": 1, "_create_key_pair": 1, "keypair": 1, "rsa": 1, "pem": 1, "openssh": 1, "_create_security_group": 1, "get": 1, "vpc": 1, "re": 1, "sg": 1, "case": 1, "alreadi": 1, "_delete_key_pair": 1, "_delete_security_group": 1, "_disassociate_security_group": 1, "serviceresourc": 1, "instance_id": 1, "disassoci": 1, "assign": 1, "unavail": 1, "_get_vpc_id": 1, "fetch": 1, "_init": 1, "start": 1, "int": 1, "its": 1, "startup": 1, "shutdown": 1, "_terminate_ec2_inst": 1, "termin": 1, "request": 1, "_test_get": 1, "server_hostnam": 1, "timeout": 1, "3": 1, "respons": 1, "multipl": 1, "hostnam": 1, "ip": 1, "address": 1, "_tester": 1, "data": 1, "dict": 1, "against": 1, "openvpna": 1, "servic": 1, "avail": 1, "rais": 1, "assertionerror": 1, "when": 1, "ani": 1, "method": 1, "_instance_info": 1, "check": 1, "info": 1, "present": 1, "befor": 1, "new": 1, "origin": 1, "tear": 1, "down": 1, "notifi": 1, "user": 1, "detail": 1, "add": 1, "retri": 1, "true": 1, "anoth": 1, "sent": 1, "regardless": 1, "public_ip": 1, "disabl": 1, "remov": 1, "resourc": 1, "acquir": 1, "ha": 1, "A": 1, "doesn": 1, "long": 1, "neither": 1, "nor": 1, "modifi": 1, "hand": 1, "http": 1, "boto3": 1, "amazonaw": 1, "v1": 1, "api": 1, "latest": 1, "wait_until_termin": 1, "html": 1, "model": 1, "config": 1, "configurationset": 1, "basemodel": 1, "set": 1, "interact": 1, "pars": 1, "input": 1, "validationerror": 1, "pydantic_cor": 1, "form": 1, "__init__": 1, "__pydantic_self__": 1, "instead": 1, "common": 1, "self": 1, "first": 1, "arg": 1, "allow": 1, "field": 1, "amibas": 1, "imag": 1, "model_post_init": 1, "__context": 1, "thi": 1, "meant": 1, "behav": 1, "like": 1, "initialis": 1, "privat": 1, "attribut": 1, "It": 1, "context": 1, "sinc": 1, "what": 1, "core": 1, "pass": 1, "pydantic_set": 1, "env": 1, "dev": 1, "2": 1, "migrat": 1, "nullabl": 1, "extra": 1, "classmethod": 1, "validate_instance_typ": 1, "v": 1, "make": 1, "sure": 1, "nano": 1, "validate_vpn_password": 1, "per": 1, "regex": 1, "wrapper": 1, "awsresourceerror": 1, "status_cod": 1, "error_msg": 1, "custom": 1, "error": 1, "notimplementedwarn": 1, "implement": 1, "warn": 1, "image_factori": 1, "handl": 1, "retriev": 1, "sourc": 1, "ssm": 1, "get_ami_id_nam": 1, "get_ami_id_product_cod": 1, "product": 1, "get_ami_id_ssm": 1, "get_image_id": 1, "tri": 1, "sequenti": 1, "execut": 1, "sequenc": 1, "fastest": 1, "singl": 1, "possibli": 1, "contain": 1, "lookup": 1, "specif": 1, "mani": 1, "so": 1, "grab": 1, "most": 1, "recent": 1, "one": 1, "unabl": 1, "deprecation_warn": 1, "deprecation_tim": 1, "deprec": 1, "chosen": 1, "nearing": 1, "deprecationtim": 1, "streamhandl": 1, "debug": 1, "mode": 1, "change_record_set": 1, "destin": 1, "zone_id": 1, "action": 1, "chang": 1, "within": 1, "perform": 1, "upsert": 1, "get_zone_id": 1, "init": 1, "fals": 1, "boolean": 1, "miss": 1, "rsakei": 1, "gener": 1, "add_formatt": 1, "formatt": 1, "were": 1, "dure": 1, "remove_formatt": 1, "log": 1, "room": 1, "restart_servic": 1, "restart": 1, "run_interactive_ssh": 1, "displai": 1, "30": 1, "command": 1, "screen": 1, "complet": 1, "test_servic": 1, "statu": 1, "remot": 1, "available_instance_typ": 1, "loop": 1, "through": 1, "describ": 1, "yield": 1, "available_region": 1, "index": 1, "modul": 1, "search": 1}, "objects": {"vpn": [[1, 0, 0, "-", "main"]], "vpn.main": [[1, 1, 1, "", "VPNServer"]], "vpn.main.VPNServer": [[1, 2, 1, "", "_authorize_security_group"], [1, 2, 1, "", "_configure_vpn"], [1, 2, 1, "", "_create_ec2_instance"], [1, 2, 1, "", "_create_key_pair"], [1, 2, 1, "", "_create_security_group"], [1, 2, 1, "", "_delete_key_pair"], [1, 2, 1, "", "_delete_security_group"], [1, 2, 1, "", "_disassociate_security_group"], [1, 2, 1, "", "_get_vpc_id"], [1, 2, 1, "", "_init"], [1, 2, 1, "", "_terminate_ec2_instance"], [1, 2, 1, "", "_test_get"], [1, 2, 1, "", "_tester"], [1, 2, 1, "", "create_vpn_server"], [1, 2, 1, "", "delete_vpn_server"], [1, 2, 1, "", "test_vpn"]], "vpn.models.config": [[1, 1, 1, "", "AMIBase"], [1, 1, 1, "", "ConfigurationSettings"], [1, 1, 1, "", "EnvConfig"], [1, 1, 1, "", "Settings"]], "vpn.models.config.AMIBase": [[1, 2, 1, "", "model_post_init"]], "vpn.models.config.EnvConfig": [[1, 1, 1, "", "Config"], [1, 2, 1, "", "validate_instance_type"], [1, 2, 1, "", "validate_vpn_password"]], "vpn.models": [[1, 0, 0, "-", "exceptions"], [1, 0, 0, "-", "image_factory"], [1, 0, 0, "-", "logger"], [1, 0, 0, "-", "route53"], [1, 0, 0, "-", "server"], [1, 0, 0, "-", "util"]], "vpn.models.exceptions": [[1, 3, 1, "", "AWSResourceError"], [1, 3, 1, "", "NotImplementedWarning"]], "vpn.models.image_factory": [[1, 1, 1, "", "ImageFactory"], [1, 4, 1, "", "deprecation_warning"]], "vpn.models.image_factory.ImageFactory": [[1, 2, 1, "", "get_ami_id_name"], [1, 2, 1, "", "get_ami_id_product_code"], [1, 2, 1, "", "get_ami_id_ssm"], [1, 2, 1, "", "get_image_id"]], "vpn.models.route53": [[1, 4, 1, "", "change_record_set"], [1, 4, 1, "", "get_zone_id"]], "vpn.models.server": [[1, 1, 1, "", "Server"]], "vpn.models.server.Server": [[1, 2, 1, "", "add_formatter"], [1, 2, 1, "", "remove_formatter"], [1, 2, 1, "", "restart_service"], [1, 2, 1, "", "run_interactive_ssh"], [1, 2, 1, "", "test_service"]], "vpn.models.util": [[1, 4, 1, "", "available_instance_types"], [1, 4, 1, "", "available_regions"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:exception", "4": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "exception", "Python exception"], "4": ["py", "function", "Python function"]}, "titleterms": {"platform": 0, "support": 0, "repo": 0, "stat": 0, "deploy": 0, "vpn": [0, 1], "server": [0, 1], "instal": 0, "usag": 0, "code": 0, "standard": 0, "releas": 0, "note": 0, "lint": 0, "project": 0, "link": 0, "licens": 0, "copyright": 0, "welcom": 1, "": 1, "document": 1, "read": 1, "me": 1, "configur": 1, "except": 1, "imagefactori": 1, "logger": 1, "route53": 1, "ssh": 1, "util": 1, "indic": 1, "tabl": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "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}}) \ No newline at end of file diff --git a/gen_docs.sh b/gen_docs.sh deleted file mode 100644 index fd85cc8..0000000 --- a/gen_docs.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -# 'set -e' stops the execution of a script if a command or pipeline has an error. -# This is the opposite of the default shell behaviour, which is to ignore errors in scripts. -set -e -gitverse-release reverse -f release_notes.rst -t 'Release Notes' -rm -rf docs -mkdir docs -mkdir -p doc_gen/_static -cp README.md doc_gen && cd doc_gen && make clean html && mv _build/html/* ../docs && rm README.md -touch ../docs/.nojekyll diff --git a/pre_commit.sh b/pre_commit.sh new file mode 100644 index 0000000..6c91f37 --- /dev/null +++ b/pre_commit.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# 'set -e' stops the execution of a script if a command or pipeline has an error. +# This is the opposite of the default shell behaviour, which is to ignore errors in scripts. +set -e + +clean_docs() { + rm -rf docs && mkdir docs +} + +update_release_notes() { + # Update release notes + gitverse-release reverse -f release_notes.rst -t 'Release Notes' +} + +gen_docs() { + # Generate sphinx docs + mkdir -p doc_gen/_static # Create a _static directory if unavailable + cp README.md doc_gen # Copy readme file to doc_gen + cd doc_gen && make clean html # cd into doc_gen and create the runbook + mv _build/html/* ../docs && mv README.md ../docs # Move the runbook, readme and cleanup + cp static.css ../docs/_static +} + +run_pytest() { + # Run pytest + python -m pytest +} + +gen_docs & +clean_docs & +update_release_notes & + +wait + +# The existence of this file tells GitHub Pages not to run the published files through Jekyll. +# This is important since Jekyll will discard any files that begin with _ +touch docs/.nojekyll diff --git a/vpn/__init__.py b/vpn/__init__.py index 66dbe2d..cf96e58 100644 --- a/vpn/__init__.py +++ b/vpn/__init__.py @@ -4,4 +4,4 @@ from vpn.models import (config, exceptions, image_factory, # noqa: F401 logger, route53, server, util) -version = "1.4b" +version = "1.5" diff --git a/vpn/main.py b/vpn/main.py index aadbed7..09edab4 100644 --- a/vpn/main.py +++ b/vpn/main.py @@ -2,6 +2,7 @@ import os import time import warnings +from logging import Logger from typing import Dict, Tuple, Union import boto3 @@ -11,6 +12,7 @@ from boto3.resources.base import ServiceResource from botocore.exceptions import ClientError, WaiterError from requests.models import Response +from typing_extensions import Unpack from urllib3.exceptions import InsecureRequestWarning from vpn.models.config import EnvConfig, Settings, configuration_dict @@ -28,11 +30,44 @@ class VPNServer: """ - def __init__(self, **kwargs): - """Assigns a name to the PEM file, initiates the logger, client and resource for EC2 using ``boto3`` module. + def __init__(self, **kwargs: Unpack[Union[EnvConfig, Logger]]): + """Unpacks the kwargs and loads it as Envconfig, the subclass for BaseSettings validated using pydantic. Args: - logger: Bring your own logger. + kwargs: Dictionary of keyword arguments for the following key-value pairs. + + - ``vpn_username`` - Username to access VPN client. + - ``vpn_password`` - Password to access VPN client. + - ``vpn_port`` - Port number for OpenVPN web interface access. + - ``aws_profile_name`` - Name of the AWS profile to initiate a session. + - ``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 AWS profile default. + - ``image_id`` - AMI ID using which the instance should be created. + - ``instance_type`` - Instance type to use in AWS to host the OpenVPN Access Server. + - ``key_pair`` - Name of the key pair for the ec2 and the file stored for SSH connection. + - ``security_group`` - Name of the security group to be created in AWS. + - ``vpn_info`` - Name of the JSON file to dump the instance and connection information. + - ``domain`` - Domain name for the hosted zone. + - ``hosted_zone`` - Name of the hosted zone in route53. + - ``subdomain`` - Name of the subdomain to be created in the hosted zone. + - ``logger`` - Bring your own logger. + + See Also: + Access point to the VPN server will be the combination of subdomain and hosted_zone. + + Examples: + - | If you have a hosted zone in AWS, named ``example.com`` and want the entrypoint to be + | ``vpn.example.com`` then, + + - ``hosted_zone`` should be ``example.com`` + - ``subdomain`` should be ``vpn`` + + Notes: + - Please note that the hosted zone should also be a registered domain in AWS. + - You cannot simply create a hosted zone named google.com and expect it to work. + - | The alias record will fail as duplicate at DNS name resolution level unless a domain is + | registered with the same name. """ self.env = EnvConfig(**kwargs) self.settings = Settings() @@ -60,7 +95,7 @@ def _init(self, """Initializer function. Args: - start: Boolean flag to indicate if its startup or shutdown. + start: Flag to indicate if its startup or shutdown. """ if start: # Not required during shutdown, since image_id is only used to create an ec2 instance variable = "created in" # var for logging if entrypoint is present @@ -84,7 +119,7 @@ def _create_key_pair(self) -> bool: Returns: bool: - Boolean flag to indicate the calling function if a ``KeyPair`` was created. + Flag to indicate the calling function, if a ``KeyPair`` was created successfully. """ try: key_pair = self.ec2_resource.create_key_pair( @@ -150,7 +185,7 @@ def _authorize_security_group(self, Returns: bool: - Flag to indicate the calling function whether the security group was authorized. + Flag to indicate the calling function, whether the security group was authorized successfully. """ try: security_group = self.ec2_resource.SecurityGroup(security_group_id) @@ -271,7 +306,7 @@ def _delete_key_pair(self) -> bool: Returns: bool: - Boolean flag to indicate the calling function if the KeyPair was deleted successfully. + Flag to indicate the calling function, if the KeyPair was deleted successfully. """ try: key_pair = self.ec2_resource.KeyPair(self.env.key_pair) @@ -292,7 +327,7 @@ def _delete_key_pair(self) -> bool: def _disassociate_security_group(self, security_group_id: str, - instance: object = None, + instance: ServiceResource or None = None, instance_id: str = None) -> bool: """Disassociates an SG from the ec2 instance by assigning it to the default security group. @@ -303,7 +338,7 @@ def _disassociate_security_group(self, Returns: bool: - Boolean flag to indicate the calling function whether the disassociation was successful. + Flag to indicate the calling function, whether the instance was disassociated from the SG successfully. """ try: if not instance: @@ -329,7 +364,7 @@ def _delete_security_group(self, security_group_id: str) -> bool: Returns: bool: - Boolean flag to indicate the calling function whether the SecurityGroup was deleted. + Flag to indicate the calling function, whether the SecurityGroup was deleted successfully. """ try: security_group = self.ec2_resource.SecurityGroup(security_group_id) @@ -345,7 +380,7 @@ def _delete_security_group(self, security_group_id: str) -> bool: def _terminate_ec2_instance(self, instance_id: str = None, - instance: object = None) -> ServiceResource or None: + instance: ServiceResource or None = None) -> ServiceResource or None: """Terminates the requested instance. Args: @@ -354,7 +389,7 @@ def _terminate_ec2_instance(self, Returns: bool: - Boolean flag to indicate the calling function whether the instance was terminated. + Flag to indicate the calling function, whether the instance was terminated successfully. """ try: if not instance: @@ -362,12 +397,11 @@ def _terminate_ec2_instance(self, if not instance_id: instance_id = instance.id instance.terminate() + self.logger.info('InstanceId %s has been set to terminate.', instance_id) + return instance except ClientError as error: self.logger.warning('API call to terminate the instance has failed.') self.logger.error(error) - return - self.logger.info('InstanceId %s has been set to terminate.', instance_id) - return instance def _test_get(self, server_hostname: str, timeout: Tuple = (3, 3)) -> Response: """Test GET connection with multiple hostnames. @@ -388,21 +422,21 @@ def _test_get(self, server_hostname: str, timeout: Tuple = (3, 3)) -> Response: except requests.RequestException as error: self.logger.error(error) - def _tester(self, data: Dict[str, Union[str, int]]) -> bool: + def _tester(self, data: Dict[str, Union[str, int]]) -> None: """Tests ``GET`` and ``SSH`` connections on the existing server. Args: data: Takes the instance information in a dictionary format as an argument. See Also: - - Called when a startup request is made but info file and pem file are present already. - - Called when a manual test request is made. - - Testing SSH connection will also run updates on the VM. - - Returns: - bool: - - ``True`` if the existing connection is reachable and ``ssh`` to the origin succeeds. - - ``False`` if the connection fails or unable to ``ssh`` to the origin. + - GET request against the public IP of the ec2 instance. + - GET request against the public DNS of the ec2 instance. + - SSH connection with the OpenVPN Access Server. + - Test ``openvpnas`` service availability on the server. + + Raises: + AssertionError: + When any of the tests fail. """ urllib3.disable_warnings(InsecureRequestWarning) # Disable warnings for self-signed certificates self.logger.info(f"Testing GET connection to https://{data.get('public_ip')}:{self.env.vpn_port}") @@ -417,23 +451,19 @@ def _tester(self, data: Dict[str, Union[str, int]]) -> bool: self.logger.info(f"Testing SSH connection to {data.get('public_dns')}") test_ssh = Server(username=self.env.vpn_username, hostname=data.get('public_dns'), logger=self.logger, env=self.env, settings=self.settings) - if test_ssh.test_service(display=False, timeout=5): - self.logger.info(f"Connection to https://{data.get('public_ip')}:{self.env.vpn_port} and " - f"SSH to {data.get('public_dns')} was successful.") - return True - else: - self.logger.error('Unable to establish SSH connection with the VPN server. ' - 'Please check the logs for more information.') - return False + test_ssh.test_service(display=False, timeout=5) + self.logger.info(f"Connection to https://{data.get('public_ip')}:{self.env.vpn_port} and " + f"SSH to {data.get('public_dns')} was successful.") def test_vpn(self) -> None: """Tests the ``GET`` and ``SSH`` connections to an existing VPN server.""" - if os.path.isfile(self.env.vpn_info) and os.path.isfile(self.settings.key_pair_file): + try: with open(self.env.vpn_info) as file: data_exist = json.load(file) - self._tester(data=data_exist) - else: + except FileNotFoundError: self.logger.error(f'Input file: {self.env.vpn_info} is missing. CANNOT proceed.') + return + self._tester(data=data_exist) def create_vpn_server(self) -> None: """Calls the class methods ``_create_ec2_instance`` and ``_instance_info`` to configure the VPN server. @@ -451,7 +481,9 @@ def create_vpn_server(self) -> None: data = json.load(file) self.env.image_id = 'ami-0000000000' # placeholder value since this won't be used in re-configuration self._init(True) - if not self._tester(data): + try: + self._tester(data) + except AssertionError: self._configure_vpn(data['public_dns']) return self._init(True) @@ -528,12 +560,13 @@ def create_vpn_server(self) -> None: json.dump(instance_info, file, indent=2) file.flush() - if not self._tester(data=instance_info): + try: + self._tester(data=instance_info) + except AssertionError: self.logger.error('Failed to configure VPN server. Please check the logs for more information.') - return - - self.logger.info('VPN server has been configured successfully. Details have been stored in %s.', - self.env.vpn_info) + else: + self.logger.info('VPN server has been configured successfully. Details have been stored in %s.', + self.env.vpn_info) def _configure_vpn(self, public_dns: str) -> None: """Configures the ec2 instance to take traffic from localhost and initiates tunneling. diff --git a/vpn/models/image_factory.py b/vpn/models/image_factory.py index 32c444f..efeb45e 100644 --- a/vpn/models/image_factory.py +++ b/vpn/models/image_factory.py @@ -36,7 +36,7 @@ class ImageFactory: def __init__(self, session: boto3.Session, logger: logging.Logger): - """Instantiates the ``ImageFactory`` object. + """Creates ec2 and ssm client using an existing boto3 session. Args: session: boto3 session instantiated in the origin class. diff --git a/vpn/models/logger.py b/vpn/models/logger.py index c922b8c..433dc16 100644 --- a/vpn/models/logger.py +++ b/vpn/models/logger.py @@ -7,10 +7,10 @@ import logging LOGGER = logging.getLogger(__name__) -log_handler = logging.StreamHandler() -log_handler.setFormatter(fmt=logging.Formatter( +HANDLER = logging.StreamHandler() +HANDLER.setFormatter(fmt=logging.Formatter( fmt='%(asctime)s - %(levelname)s - [%(module)s:%(lineno)d] - %(funcName)s - %(message)s', datefmt='%b-%d-%Y %I:%M:%S %p' )) -LOGGER.addHandler(hdlr=log_handler) +LOGGER.addHandler(hdlr=HANDLER) LOGGER.setLevel(level=logging.DEBUG) diff --git a/vpn/models/route53.py b/vpn/models/route53.py index 1056d50..af813e0 100644 --- a/vpn/models/route53.py +++ b/vpn/models/route53.py @@ -1,6 +1,6 @@ import logging from http.client import responses as http_response -from typing import Dict, Union +from typing import Union import boto3 from botocore.exceptions import ClientError @@ -51,7 +51,7 @@ def change_record_set(client: boto3.client, destination: str, logger: logging.Logger, zone_id: str, - action: str) -> Union[Dict, None]: + action: str) -> bool: """Changes a record set within an existing hosted zone. Args: @@ -63,8 +63,8 @@ def change_record_set(client: boto3.client, action: Action to perform. Example: UPSERT or DELETE Returns: - Union[Dict, None]: - ChangeSet response from AWS. + bool: + Flag to indicate the calling function, whether the record was modified successfully. """ logger.info("%s `%s` record::%s -> %s", action, 'A', source, destination) try: @@ -87,9 +87,9 @@ def change_record_set(client: boto3.client, ) except ClientError as error: logger.error(error) - return - if response.get('ResponseMetadata', {}).get('HTTPStatusCode') != 200: - logger.error(response) - return - logger.info(response.get('ChangeInfo', {}).get('Comment')) - logger.debug(response.get('ChangeInfo')) + return False + if response.get('ResponseMetadata', {}).get('HTTPStatusCode') == 200: + logger.info(response.get('ChangeInfo', {}).get('Comment')) + logger.debug(response.get('ChangeInfo')) + return True + logger.error(response) diff --git a/vpn/models/server.py b/vpn/models/server.py index 7e03e60..277493f 100644 --- a/vpn/models/server.py +++ b/vpn/models/server.py @@ -68,16 +68,12 @@ def restart_service(self) -> None: self.ssh_client.exec_command("sudo service openvpnas start") time.sleep(3) - def test_service(self, timeout: int, display: bool) -> bool: + def test_service(self, timeout: int, display: bool) -> None: """Check status of the service running on remote server. Args: timeout: Default interaction session timeout. display: Boolean flag whether to display interaction data on screen. - - Returns: - bool: - Returns a boolean flag if test was successful. """ with SSHClientInteraction(client=self.ssh_client, timeout=timeout, @@ -87,7 +83,6 @@ def test_service(self, timeout: int, display: bool) -> bool: interact.send("systemctl status openvpnas", '\n') interact.expect(r".*Started OpenVPN Access Server\..*", timeout) self.add_formatter() - return True def run_interactive_ssh(self, display: bool = True, @@ -100,7 +95,7 @@ def run_interactive_ssh(self, Returns: bool: - Flag to indicate whether the interactive session has completed successfully. + Flag to indicate the calling function, whether the interactive session has completed successfully. """ self.remove_formatter() with SSHClientInteraction(client=self.ssh_client,