Compare commits
No commits in common. "master" and "release-2.0.1" have entirely different histories.
master
...
release-2.
9 changed files with 43 additions and 88 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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
|
|
@ -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:
|
||||
|
|
39
README.md
39
README.md
|
@ -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
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
"""Module providing wrapper for terraform."""
|
||||
from .terraform import (
|
||||
IsFlagged,
|
||||
IsNotFlagged,
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
8
setup.py
8
setup.py
|
@ -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"],
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue