Compare commits

..

No commits in common. "master" and "release-2.0.1" have entirely different histories.

9 changed files with 43 additions and 88 deletions

4
.gitignore vendored
View file

@ -12,9 +12,6 @@ dist/
build/
/pytestdebug.log
.pytestdebug.log
/pytest_cache
.lsp
# virtualenv
.virtualenv/
@ -36,4 +33,3 @@ tmp.txt
/.tox/
env/
Icon
.clj-kondo

View file

@ -16,21 +16,21 @@ flake8:
stage: lint
allow_failure: true
script:
- flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics dda_python_terraform/*.py
- flake8 --count --exit-zero --max-complexity=13 --max-line-length=127 --statistics --ignore F401 dda_python_terraform/*.py
- flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics python_terraform/*.py
- flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics python_terraform/*.py
mypy:
stage: lint
allow_failure: true
script:
- python -m mypy dda_python_terraform/terraform.py
- python -m mypy dda_python_terraform/tfstate.py
- python -m mypy python_terraform/terraform.py
- python -m mypy python_terraform/tfstate.py
pylint:
stage: lint
allow_failure: true
script:
- pylint -d C0112,C0115,C0301,R0913,R0903,R0902,R0914,R1705,R1732,W0622 dda_python_terraform/*.py
- pylint -d C0301 python_terraform/*.py
test-0.13.7:

View file

@ -1,21 +1,21 @@
# dda-python-terraform
[![pipeline status](https://gitlab.com/domaindrivenarchitecture/dda-python-terraform/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/dda-python-terraform/-/commits/main)
[<img src="https://domaindrivenarchitecture.org/img/delta-chat.svg" width=20 alt="DeltaChat"> chat over e-mail](mailto:buero@meissa-gmbh.de?subject=community-chat) | [<img src="https://meissa.de/images/parts/contact/mastodon36_hue9b2464f10b18e134322af482b9c915e_5501_filter_14705073121015236177.png" width=20 alt="M"> meissa@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@meissa) | [Blog](https://domaindrivenarchitecture.org) | [Website](https://meissa.de)
## Introduction
dda-python-terraform is a python module provide a wrapper of `terraform` command line tool.
python-terraform is a python module provide a wrapper of `terraform` command line tool.
`terraform` is a tool made by Hashicorp, please refer to https://terraform.io/
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
### Status
[![Build Status](https://travis-ci.org/aubustou/python-terraform.svg?branch=develop)](https://travis-ci.org/aubustou/python-terraform)
## Installation
pip install python-terraform
## Usage
#### For any terraform command
from dda_python_terraform import *
from python_terraform import *
t = Terraform()
return_code, stdout, stderr = t.<cmd_name>(*arguments, **options)
@ -23,13 +23,13 @@ dda-python-terraform is a python module provide a wrapper of `terraform` command
to be able to call the method, you could call cmd_name by adding `_cmd` after command name, for example,
`import` here could be called by
from dda_python_terraform import *
from python_terraform import *
t = Terraform()
return_code, stdout, stderr = t.import_cmd(*arguments, **options)
or just call cmd method directly
from dda_python_terraform import *
from python_terraform import *
t = Terraform()
return_code, stdout, stderr = t.cmd(<cmd_name>, *arguments, **options)
@ -82,7 +82,7 @@ simply pass the string to arguments of the method, for example,
By default, stdout and stderr are captured and returned. This causes the application to appear to hang. To print terraform output in real time, provide the `capture_output` option with any value other than `None`. This will cause the output of terraform to be printed to the terminal in real time. The value of `stdout` and `stderr` below will be `None`.
from dda_python_terraform import Terraform
from python_terraform import Terraform
t = Terraform()
return_code, stdout, stderr = t.<cmd_name>(capture_output=False)
@ -96,19 +96,19 @@ In shell:
In python-terraform:
from dda_python_terraform import *
from python_terraform import *
tf = Terraform(working_dir='/home/test')
tf.apply(no_color=IsFlagged, refresh=False, var={'a':'b', 'c':'d'})
or
from dda_python_terraform import *
from python_terraform import *
tf = Terraform()
tf.apply('/home/test', no_color=IsFlagged, refresh=False, var={'a':'b', 'c':'d'})
or
from dda_python_terraform import *
from python_terraform import *
tf = Terraform(working_dir='/home/test', variables={'a':'b', 'c':'d'})
tf.apply(no_color=IsFlagged, refresh=False)
@ -120,7 +120,7 @@ In shell:
In python-terraform:
from dda_python_terraform import *
from python_terraform import *
tf = terraform(working_dir='/home/test')
tf.fmt(diff=True)
@ -140,12 +140,3 @@ This make api caller don't have a general rule to follow but to do
a exhaustive method implementation which I don't prefer to.
Therefore I end-up with using `IsFlagged` or `IsNotFlagged` as value of option
like `-no-color` and `True/False` value reserved for option like `refresh=true`
## Development & mirrors
Development happens at: https://repo.prod.meissa.de/meissa/dda-python-terraform
Mirrors are:
* https://gitlab.com/domaindrivenarchitecture/dda-python-terraform (CI issues and PR)
* https://github.com/DomainDrivenArchitecture/dda-python-terraform
For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos

View file

@ -1,4 +1,3 @@
"""Module providing wrapper for terraform."""
from .terraform import (
IsFlagged,
IsNotFlagged,

View file

@ -1,4 +1,3 @@
"""Module providing wrapper for terraform."""
import json
import logging
import os
@ -31,9 +30,8 @@ CommandOutput = Tuple[Optional[int], Optional[str], Optional[str]]
class TerraformCommandError(subprocess.CalledProcessError):
"""Class representing a terraform error"""
def __init__(self, ret_code: int, cmd: str, out: Optional[str], err: Optional[str]):
super().__init__(ret_code, cmd)
super(TerraformCommandError, self).__init__(ret_code, cmd)
self.out = out
self.err = err
logger.error("Error with command %s. Reason: %s", self.cmd, self.err)
@ -78,7 +76,7 @@ class Terraform:
self.working_dir = working_dir
self.state = state
self.targets = [] if targets is None else targets
self.variables = {} if variables is None else variables
self.variables = dict() if variables is None else variables
self.parallelism = parallelism
self.terraform_bin_path = (
terraform_bin_path if terraform_bin_path else "terraform"
@ -315,15 +313,15 @@ class Terraform:
if self.is_env_vars_included:
environ_vars = os.environ.copy()
proc = subprocess.Popen(
p = subprocess.Popen(
cmds, stdout=stdout, stderr=stderr, cwd=working_folder, env=environ_vars
)
if not synchronous:
return None, None, None
out, err = proc.communicate()
ret_code = proc.returncode
out, err = p.communicate()
ret_code = p.returncode
logger.info("output: %s", out)
if ret_code == 0:
@ -455,7 +453,7 @@ class Terraform:
filter(
lambda workspace: len(workspace) > 0,
map(
lambda workspace: workspace.strip('*').strip(),
lambda workspace: workspace.strip('*').strip(),
(self.cmd(global_opts, "workspace", "list")[1] or '').split()
)
)
@ -464,20 +462,12 @@ class Terraform:
def _generate_default_args(self, dir_or_plan: Optional[str]) -> Sequence[str]:
if (version.parse(self.terraform_semantic_version) < version.parse("1.0.0") and dir_or_plan):
return [dir_or_plan]
elif (version.parse(self.terraform_semantic_version) >= version.parse("1.0.0") and dir_or_plan and os.path.isfile(f'{self.working_dir}/{dir_or_plan}')):
plan = dir_or_plan.split('/')[-1]
return [plan]
else:
return []
def _generate_default_general_options(self, dir_or_plan: Optional[str]) -> Dict[str, Any]:
if (version.parse(self.terraform_semantic_version) >= version.parse("1.0.0") and dir_or_plan):
if os.path.isdir(self.working_dir + '/' + dir_or_plan):
return {"chdir": dir_or_plan}
else:
plan_path = dir_or_plan.split('/')
dir_to_plan_path = "/".join(plan_path[:-1])
return {"chdir": dir_to_plan_path}
return {"chdir": dir_or_plan}
else:
return {}
@ -496,6 +486,8 @@ class Terraform:
}
def _generate_cmd_options(self, **kwargs) -> List[str]:
"""
"""
result = []
for option, value in kwargs.items():
@ -509,12 +501,13 @@ class Terraform:
if isinstance(value, dict):
if "backend-config" in option:
for backend_key, backend_value in value.items():
result += [f"-backend-config={backend_key}={backend_value}"]
for bk, bv in value.items():
result += [f"-backend-config={bk}={bv}"]
continue
# since map type sent in string won't work, create temp var file for
# variables, and clean it up later
if option == "var":
elif option == "var":
# We do not create empty var-files if there is no var passed.
# An empty var-file would result in an error: An argument or block definition is required here
if value:
@ -552,13 +545,12 @@ class Terraform:
return wrapper
class VariableFiles:
"""Class representing a terraform var files"""
def __init__(self):
self.files = []
def create(self, variables: Dict[str, str]) -> str:
"""create var file in temp"""
with tempfile.NamedTemporaryFile(
"w+t", suffix=".tfvars.json", delete=False
) as temp:
@ -571,8 +563,7 @@ class VariableFiles:
return file_name
def clean_up(self):
"""cleanup the var file"""
for fle in self.files:
os.unlink(fle.name)
for f in self.files:
os.unlink(f.name)
self.files = []

View file

@ -1,4 +1,3 @@
"""Helper Module providing wrapper for terraform state."""
import json
import logging
import os
@ -8,7 +7,6 @@ logger = logging.getLogger(__name__)
class Tfstate:
"""Class representing a terraform state"""
def __init__(self, data: Optional[Dict[str, str]] = None):
self.tfstate_file: Optional[str] = None
self.native_data = data
@ -23,8 +21,8 @@ class Tfstate:
"""
logger.debug("read data from %s", file_path)
if os.path.exists(file_path):
with open(file_path, encoding="utf-8") as fle:
json_data = json.load(fle)
with open(file_path) as f:
json_data = json.load(f)
tf_state = Tfstate(json_data)
tf_state.tfstate_file = file_path

View file

@ -1,7 +1,7 @@
## Release
```
adjust version number in setup.py to release version number.
adjust version no in setup.py to release version no.
git commit -am "release"
git tag -am "release" release-[release version no]
git push --follow-tags

View file

@ -21,11 +21,11 @@ except IOError:
setup(
name=module_name,
version="2.1.2-dev",
url="https://repo.prod.meissa.de/meissa/dda-python-terraform",
version="2.0.1",
url="https://github.com/DomainDrivenArchitecture/python-terraform",
license="MIT",
author="Freddy Tan, meissa team",
author_email="buero@meissa.de",
author="Freddy Tan",
author_email="beelit94@gmail.com",
description=short_description,
long_description=long_description,
packages=["dda_python_terraform"],

View file

@ -316,26 +316,6 @@ class TestTerraform:
assert expected_output in out.replace("\n", "").replace(" ", "")
assert err == ""
def test_apply_plan(self):
# test is only applicable to version > 1.0.0
if version.parse(semantic_version) < version.parse("1.0.0"):
return
tf = Terraform(
working_dir=current_path, terraform_semantic_version=semantic_version
)
out_folder = 'var_to_output'
out_file_name = 'test.out'
out_file_path = f'{out_folder}/{out_file_name}'
tf.init(out_folder)
ret, _, err = tf.plan(out_folder, detailed_exitcode=IsNotFlagged, out=out_file_name)
assert ret == 0
assert err == ""
ret, _, err = tf.apply(out_file_path, skip_plan=True)
assert ret == 0
assert err == ""
def test_apply_with_var_file(self, caplog: LogCaptureFixture):
with caplog.at_level(logging.INFO):
tf = Terraform(working_dir=current_path, terraform_semantic_version=semantic_version)