Compare commits
31 commits
1.0.2.dev3
...
master
Author | SHA1 | Date | |
---|---|---|---|
7c4cd23e56 | |||
3fd4a72d3d | |||
0fb8218dbf | |||
3c92a8b2e6 | |||
be63bfde3e | |||
a3d97a0d04 | |||
c1c25a016f | |||
0abb2370e4 | |||
5af484bcce | |||
722ba5f716 | |||
|
d88670503b | ||
bc27522fb5 | |||
441bc497d1 | |||
ddb8e4160d | |||
71c544bad8 | |||
1a7955b738 | |||
9c9d7dc14b | |||
f94f3a7c43 | |||
026e5f260a | |||
35b52fdbe8 | |||
d44570e9bb | |||
5e204617d0 | |||
7d50eec689 | |||
b509685c62 | |||
30daf13244 | |||
fd431b229d | |||
58e4e9f065 | |||
124300c271 | |||
382c571d42 | |||
cb30395d4d | |||
dcbeb0e934 |
11 changed files with 164 additions and 78 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -12,6 +12,9 @@ dist/
|
||||||
build/
|
build/
|
||||||
/pytestdebug.log
|
/pytestdebug.log
|
||||||
.pytestdebug.log
|
.pytestdebug.log
|
||||||
|
/pytest_cache
|
||||||
|
.lsp
|
||||||
|
|
||||||
|
|
||||||
# virtualenv
|
# virtualenv
|
||||||
.virtualenv/
|
.virtualenv/
|
||||||
|
@ -20,6 +23,7 @@ venv/
|
||||||
|
|
||||||
# Intellij
|
# Intellij
|
||||||
.idea
|
.idea
|
||||||
|
.idea/
|
||||||
|
|
||||||
# VSCode
|
# VSCode
|
||||||
.vscode/
|
.vscode/
|
||||||
|
@ -32,3 +36,4 @@ tmp.txt
|
||||||
/.tox/
|
/.tox/
|
||||||
env/
|
env/
|
||||||
Icon
|
Icon
|
||||||
|
.clj-kondo
|
|
@ -4,7 +4,7 @@ before_script:
|
||||||
- python --version
|
- python --version
|
||||||
- pip install setuptools wheel twine
|
- pip install setuptools wheel twine
|
||||||
- pip install .
|
- pip install .
|
||||||
- pip install -r dev_requirements.txt
|
- pip install -r requirements_dev.txt
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- lint
|
- lint
|
||||||
|
@ -16,21 +16,21 @@ flake8:
|
||||||
stage: lint
|
stage: lint
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
script:
|
script:
|
||||||
- flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics python_terraform/*.py
|
- flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics dda_python_terraform/*.py
|
||||||
- flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics python_terraform/*.py
|
- flake8 --count --exit-zero --max-complexity=13 --max-line-length=127 --statistics --ignore F401 dda_python_terraform/*.py
|
||||||
|
|
||||||
mypy:
|
mypy:
|
||||||
stage: lint
|
stage: lint
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
script:
|
script:
|
||||||
- python -m mypy python_terraform/terraform.py
|
- python -m mypy dda_python_terraform/terraform.py
|
||||||
- python -m mypy python_terraform/tfstate.py
|
- python -m mypy dda_python_terraform/tfstate.py
|
||||||
|
|
||||||
pylint:
|
pylint:
|
||||||
stage: lint
|
stage: lint
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
script:
|
script:
|
||||||
- pylint -d C0301 python_terraform/*.py
|
- pylint -d C0112,C0115,C0301,R0913,R0903,R0902,R0914,R1705,R1732,W0622 dda_python_terraform/*.py
|
||||||
|
|
||||||
|
|
||||||
test-0.13.7:
|
test-0.13.7:
|
||||||
|
@ -81,7 +81,7 @@ test-1.1.3:
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_TAG != null'
|
- if: '$CI_COMMIT_TAG =~ /^release-.*$/'
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- dist/*
|
- dist/*
|
||||||
|
@ -91,7 +91,7 @@ build:
|
||||||
pypi:
|
pypi:
|
||||||
stage: upload
|
stage: upload
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_TAG != null'
|
- if: '$CI_COMMIT_TAG =~ /^release-.*$/'
|
||||||
script:
|
script:
|
||||||
- twine upload dist/*
|
- twine upload dist/*
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ gitlab:
|
||||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||||
stage: upload
|
stage: upload
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_TAG != null'
|
- if: '$CI_COMMIT_TAG =~ /^release-.*$/'
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- release/*
|
- release/*
|
||||||
|
|
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
|
## Introduction
|
||||||
|
|
||||||
python-terraform is a python module provide a wrapper of `terraform` command line tool.
|
dda-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/
|
`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
|
## Installation
|
||||||
pip install python-terraform
|
pip install python-terraform
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
#### For any terraform command
|
#### For any terraform command
|
||||||
|
|
||||||
from python_terraform import *
|
from dda_python_terraform import *
|
||||||
t = Terraform()
|
t = Terraform()
|
||||||
return_code, stdout, stderr = t.<cmd_name>(*arguments, **options)
|
return_code, stdout, stderr = t.<cmd_name>(*arguments, **options)
|
||||||
|
|
||||||
|
@ -23,13 +23,13 @@ python-terraform is a python module provide a wrapper of `terraform` command lin
|
||||||
to be able to call the method, you could call cmd_name by adding `_cmd` after command name, for example,
|
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
|
`import` here could be called by
|
||||||
|
|
||||||
from python_terraform import *
|
from dda_python_terraform import *
|
||||||
t = Terraform()
|
t = Terraform()
|
||||||
return_code, stdout, stderr = t.import_cmd(*arguments, **options)
|
return_code, stdout, stderr = t.import_cmd(*arguments, **options)
|
||||||
|
|
||||||
or just call cmd method directly
|
or just call cmd method directly
|
||||||
|
|
||||||
from python_terraform import *
|
from dda_python_terraform import *
|
||||||
t = Terraform()
|
t = Terraform()
|
||||||
return_code, stdout, stderr = t.cmd(<cmd_name>, *arguments, **options)
|
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`.
|
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 python_terraform import Terraform
|
from dda_python_terraform import Terraform
|
||||||
t = Terraform()
|
t = Terraform()
|
||||||
return_code, stdout, stderr = t.<cmd_name>(capture_output=False)
|
return_code, stdout, stderr = t.<cmd_name>(capture_output=False)
|
||||||
|
|
||||||
|
@ -96,19 +96,19 @@ In shell:
|
||||||
|
|
||||||
In python-terraform:
|
In python-terraform:
|
||||||
|
|
||||||
from python_terraform import *
|
from dda_python_terraform import *
|
||||||
tf = Terraform(working_dir='/home/test')
|
tf = Terraform(working_dir='/home/test')
|
||||||
tf.apply(no_color=IsFlagged, refresh=False, var={'a':'b', 'c':'d'})
|
tf.apply(no_color=IsFlagged, refresh=False, var={'a':'b', 'c':'d'})
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
from python_terraform import *
|
from dda_python_terraform import *
|
||||||
tf = Terraform()
|
tf = Terraform()
|
||||||
tf.apply('/home/test', no_color=IsFlagged, refresh=False, var={'a':'b', 'c':'d'})
|
tf.apply('/home/test', no_color=IsFlagged, refresh=False, var={'a':'b', 'c':'d'})
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
from python_terraform import *
|
from dda_python_terraform import *
|
||||||
tf = Terraform(working_dir='/home/test', variables={'a':'b', 'c':'d'})
|
tf = Terraform(working_dir='/home/test', variables={'a':'b', 'c':'d'})
|
||||||
tf.apply(no_color=IsFlagged, refresh=False)
|
tf.apply(no_color=IsFlagged, refresh=False)
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ In shell:
|
||||||
|
|
||||||
In python-terraform:
|
In python-terraform:
|
||||||
|
|
||||||
from python_terraform import *
|
from dda_python_terraform import *
|
||||||
tf = terraform(working_dir='/home/test')
|
tf = terraform(working_dir='/home/test')
|
||||||
tf.fmt(diff=True)
|
tf.fmt(diff=True)
|
||||||
|
|
||||||
|
@ -140,3 +140,12 @@ 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.
|
a exhaustive method implementation which I don't prefer to.
|
||||||
Therefore I end-up with using `IsFlagged` or `IsNotFlagged` as value of option
|
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`
|
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,3 +1,4 @@
|
||||||
|
"""Module providing wrapper for terraform."""
|
||||||
from .terraform import (
|
from .terraform import (
|
||||||
IsFlagged,
|
IsFlagged,
|
||||||
IsNotFlagged,
|
IsNotFlagged,
|
|
@ -1,3 +1,4 @@
|
||||||
|
"""Module providing wrapper for terraform."""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -5,8 +6,9 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
|
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
from python_terraform.tfstate import Tfstate
|
from dda_python_terraform.tfstate import Tfstate
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -29,8 +31,9 @@ CommandOutput = Tuple[Optional[int], Optional[str], Optional[str]]
|
||||||
|
|
||||||
|
|
||||||
class TerraformCommandError(subprocess.CalledProcessError):
|
class TerraformCommandError(subprocess.CalledProcessError):
|
||||||
|
"""Class representing a terraform error"""
|
||||||
def __init__(self, ret_code: int, cmd: str, out: Optional[str], err: Optional[str]):
|
def __init__(self, ret_code: int, cmd: str, out: Optional[str], err: Optional[str]):
|
||||||
super(TerraformCommandError, self).__init__(ret_code, cmd)
|
super().__init__(ret_code, cmd)
|
||||||
self.out = out
|
self.out = out
|
||||||
self.err = err
|
self.err = err
|
||||||
logger.error("Error with command %s. Reason: %s", self.cmd, self.err)
|
logger.error("Error with command %s. Reason: %s", self.cmd, self.err)
|
||||||
|
@ -52,7 +55,7 @@ class Terraform:
|
||||||
var_file: Optional[str] = None,
|
var_file: Optional[str] = None,
|
||||||
terraform_bin_path: Optional[str] = None,
|
terraform_bin_path: Optional[str] = None,
|
||||||
is_env_vars_included: bool = True,
|
is_env_vars_included: bool = True,
|
||||||
terraform_version: Optional[float] = 0.13
|
terraform_semantic_version: Optional[str] = "0.13.0"
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:param working_dir: the folder of the working folder, if not given,
|
:param working_dir: the folder of the working folder, if not given,
|
||||||
|
@ -69,17 +72,18 @@ class Terraform:
|
||||||
:param terraform_bin_path: binary path of terraform
|
:param terraform_bin_path: binary path of terraform
|
||||||
:type is_env_vars_included: bool
|
:type is_env_vars_included: bool
|
||||||
:param is_env_vars_included: included env variables when calling terraform cmd
|
:param is_env_vars_included: included env variables when calling terraform cmd
|
||||||
|
:param terrform_semantic_version encodes major.minor.patch version of terraform. Defaults to 0.13.0
|
||||||
"""
|
"""
|
||||||
self.is_env_vars_included = is_env_vars_included
|
self.is_env_vars_included = is_env_vars_included
|
||||||
self.working_dir = working_dir
|
self.working_dir = working_dir
|
||||||
self.state = state
|
self.state = state
|
||||||
self.targets = [] if targets is None else targets
|
self.targets = [] if targets is None else targets
|
||||||
self.variables = dict() if variables is None else variables
|
self.variables = {} if variables is None else variables
|
||||||
self.parallelism = parallelism
|
self.parallelism = parallelism
|
||||||
self.terraform_bin_path = (
|
self.terraform_bin_path = (
|
||||||
terraform_bin_path if terraform_bin_path else "terraform"
|
terraform_bin_path if terraform_bin_path else "terraform"
|
||||||
)
|
)
|
||||||
self.terraform_version = terraform_version
|
self.terraform_semantic_version = terraform_semantic_version
|
||||||
self.var_file = var_file
|
self.var_file = var_file
|
||||||
self.temp_var_files = VariableFiles()
|
self.temp_var_files = VariableFiles()
|
||||||
|
|
||||||
|
@ -156,7 +160,7 @@ class Terraform:
|
||||||
global_opts = self._generate_default_general_options(dir_or_plan)
|
global_opts = self._generate_default_general_options(dir_or_plan)
|
||||||
default = kwargs.copy()
|
default = kwargs.copy()
|
||||||
# force is no longer a flag in version >= 1.0
|
# force is no longer a flag in version >= 1.0
|
||||||
if self.terraform_version < 1.0:
|
if version.parse(self.terraform_semantic_version) < version.parse("1.0.0"):
|
||||||
default["force"] = force
|
default["force"] = force
|
||||||
default["auto-approve"] = True
|
default["auto-approve"] = True
|
||||||
options = self._generate_default_options(default)
|
options = self._generate_default_options(default)
|
||||||
|
@ -311,15 +315,15 @@ class Terraform:
|
||||||
if self.is_env_vars_included:
|
if self.is_env_vars_included:
|
||||||
environ_vars = os.environ.copy()
|
environ_vars = os.environ.copy()
|
||||||
|
|
||||||
p = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
cmds, stdout=stdout, stderr=stderr, cwd=working_folder, env=environ_vars
|
cmds, stdout=stdout, stderr=stderr, cwd=working_folder, env=environ_vars
|
||||||
)
|
)
|
||||||
|
|
||||||
if not synchronous:
|
if not synchronous:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
out, err = p.communicate()
|
out, err = proc.communicate()
|
||||||
ret_code = p.returncode
|
ret_code = proc.returncode
|
||||||
logger.info("output: %s", out)
|
logger.info("output: %s", out)
|
||||||
|
|
||||||
if ret_code == 0:
|
if ret_code == 0:
|
||||||
|
@ -437,15 +441,43 @@ class Terraform:
|
||||||
global_opts = self._generate_default_general_options(False)
|
global_opts = self._generate_default_general_options(False)
|
||||||
return self.cmd(global_opts, "workspace", "show", **kwargs)
|
return self.cmd(global_opts, "workspace", "show", **kwargs)
|
||||||
|
|
||||||
|
def list_workspace(self) -> List[str]:
|
||||||
|
"""List of workspaces
|
||||||
|
|
||||||
|
:return: workspaces
|
||||||
|
:example:
|
||||||
|
>>> tf = Terraform()
|
||||||
|
>>> tf.list_workspace()
|
||||||
|
['default', 'test']
|
||||||
|
"""
|
||||||
|
global_opts = self._generate_default_general_options(False)
|
||||||
|
return list(
|
||||||
|
filter(
|
||||||
|
lambda workspace: len(workspace) > 0,
|
||||||
|
map(
|
||||||
|
lambda workspace: workspace.strip('*').strip(),
|
||||||
|
(self.cmd(global_opts, "workspace", "list")[1] or '').split()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _generate_default_args(self, dir_or_plan: Optional[str]) -> Sequence[str]:
|
def _generate_default_args(self, dir_or_plan: Optional[str]) -> Sequence[str]:
|
||||||
if (self.terraform_version < 1.0 and dir_or_plan):
|
if (version.parse(self.terraform_semantic_version) < version.parse("1.0.0") and dir_or_plan):
|
||||||
return [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:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _generate_default_general_options(self, dir_or_plan: Optional[str]) -> Dict[str, Any]:
|
def _generate_default_general_options(self, dir_or_plan: Optional[str]) -> Dict[str, Any]:
|
||||||
if (self.terraform_version >= 1.0 and dir_or_plan):
|
if (version.parse(self.terraform_semantic_version) >= version.parse("1.0.0") and dir_or_plan):
|
||||||
return {"chdir": 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}
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -464,8 +496,6 @@ class Terraform:
|
||||||
}
|
}
|
||||||
|
|
||||||
def _generate_cmd_options(self, **kwargs) -> List[str]:
|
def _generate_cmd_options(self, **kwargs) -> List[str]:
|
||||||
"""
|
|
||||||
"""
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
for option, value in kwargs.items():
|
for option, value in kwargs.items():
|
||||||
|
@ -479,13 +509,12 @@ class Terraform:
|
||||||
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
if "backend-config" in option:
|
if "backend-config" in option:
|
||||||
for bk, bv in value.items():
|
for backend_key, backend_value in value.items():
|
||||||
result += [f"-backend-config={bk}={bv}"]
|
result += [f"-backend-config={backend_key}={backend_value}"]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# since map type sent in string won't work, create temp var file for
|
# since map type sent in string won't work, create temp var file for
|
||||||
# variables, and clean it up later
|
# variables, and clean it up later
|
||||||
elif option == "var":
|
if option == "var":
|
||||||
# We do not create empty var-files if there is no var passed.
|
# 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
|
# An empty var-file would result in an error: An argument or block definition is required here
|
||||||
if value:
|
if value:
|
||||||
|
@ -523,13 +552,13 @@ class Terraform:
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class VariableFiles:
|
class VariableFiles:
|
||||||
|
"""Class representing a terraform var files"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.files = []
|
self.files = []
|
||||||
|
|
||||||
def create(self, variables: Dict[str, str]) -> str:
|
def create(self, variables: Dict[str, str]) -> str:
|
||||||
|
"""create var file in temp"""
|
||||||
with tempfile.NamedTemporaryFile(
|
with tempfile.NamedTemporaryFile(
|
||||||
"w+t", suffix=".tfvars.json", delete=False
|
"w+t", suffix=".tfvars.json", delete=False
|
||||||
) as temp:
|
) as temp:
|
||||||
|
@ -542,7 +571,8 @@ class VariableFiles:
|
||||||
return file_name
|
return file_name
|
||||||
|
|
||||||
def clean_up(self):
|
def clean_up(self):
|
||||||
for f in self.files:
|
"""cleanup the var file"""
|
||||||
os.unlink(f.name)
|
for fle in self.files:
|
||||||
|
os.unlink(fle.name)
|
||||||
|
|
||||||
self.files = []
|
self.files = []
|
|
@ -1,3 +1,4 @@
|
||||||
|
"""Helper Module providing wrapper for terraform state."""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -7,6 +8,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Tfstate:
|
class Tfstate:
|
||||||
|
"""Class representing a terraform state"""
|
||||||
def __init__(self, data: Optional[Dict[str, str]] = None):
|
def __init__(self, data: Optional[Dict[str, str]] = None):
|
||||||
self.tfstate_file: Optional[str] = None
|
self.tfstate_file: Optional[str] = None
|
||||||
self.native_data = data
|
self.native_data = data
|
||||||
|
@ -21,8 +23,8 @@ class Tfstate:
|
||||||
"""
|
"""
|
||||||
logger.debug("read data from %s", file_path)
|
logger.debug("read data from %s", file_path)
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
with open(file_path) as f:
|
with open(file_path, encoding="utf-8") as fle:
|
||||||
json_data = json.load(f)
|
json_data = json.load(fle)
|
||||||
|
|
||||||
tf_state = Tfstate(json_data)
|
tf_state = Tfstate(json_data)
|
||||||
tf_state.tfstate_file = file_path
|
tf_state.tfstate_file = file_path
|
12
doc/releasing.md
Normal file
12
doc/releasing.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
## Release
|
||||||
|
|
||||||
|
```
|
||||||
|
adjust version number in setup.py to release version number.
|
||||||
|
git commit -am "release"
|
||||||
|
git tag -am "release" release-[release version no]
|
||||||
|
git push --follow-tags
|
||||||
|
increase version no in setup.py
|
||||||
|
git commit -am "version bump"
|
||||||
|
git push
|
||||||
|
pip3 install --upgrade --user dda-python-terraform
|
||||||
|
```
|
|
@ -0,0 +1 @@
|
||||||
|
packaging
|
10
setup.py
10
setup.py
|
@ -21,14 +21,14 @@ except IOError:
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=module_name,
|
name=module_name,
|
||||||
version="1.0.2-dev",
|
version="2.1.2-dev",
|
||||||
url="https://github.com/DomainDrivenArchitecture/python-terraform",
|
url="https://repo.prod.meissa.de/meissa/dda-python-terraform",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
author="Freddy Tan",
|
author="Freddy Tan, meissa team",
|
||||||
author_email="beelit94@gmail.com",
|
author_email="buero@meissa.de",
|
||||||
description=short_description,
|
description=short_description,
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
packages=["python_terraform"],
|
packages=["dda_python_terraform"],
|
||||||
package_data={},
|
package_data={},
|
||||||
platforms="any",
|
platforms="any",
|
||||||
install_requires=dependencies,
|
install_requires=dependencies,
|
||||||
|
|
|
@ -6,19 +6,19 @@ import shutil
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.logging import LogCaptureFixture, caplog
|
from _pytest.logging import LogCaptureFixture, caplog
|
||||||
|
|
||||||
from python_terraform import IsFlagged, IsNotFlagged, Terraform, TerraformCommandError
|
from dda_python_terraform import IsFlagged, IsNotFlagged, Terraform, TerraformCommandError
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
|
|
||||||
current_path = os.path.dirname(os.path.realpath(__file__))
|
current_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
version = 1.0 if (os.environ.get("TFVER") and os.environ.get(
|
semantic_version = os.environ.get("TFVER")
|
||||||
"TFVER").startswith("1")) else 0.13
|
|
||||||
|
|
||||||
FILE_PATH_WITH_SPACE_AND_SPACIAL_CHARS = "test 'test.out!"
|
FILE_PATH_WITH_SPACE_AND_SPACIAL_CHARS = "test 'test.out!"
|
||||||
STRING_CASES = [
|
STRING_CASES = [
|
||||||
|
@ -60,7 +60,7 @@ CMD_CASES_0_x = [
|
||||||
var={"test_var": "test"},
|
var={"test_var": "test"},
|
||||||
raise_on_error=False,
|
raise_on_error=False,
|
||||||
),
|
),
|
||||||
# Expected output varies by terraform version
|
# Expected output varies by terraform semantic_version
|
||||||
"Plan: 0 to add, 0 to change, 0 to destroy.",
|
"Plan: 0 to add, 0 to change, 0 to destroy.",
|
||||||
0,
|
0,
|
||||||
False,
|
False,
|
||||||
|
@ -131,7 +131,7 @@ CMD_CASES_1_x = [
|
||||||
var={"test_var": "test"},
|
var={"test_var": "test"},
|
||||||
raise_on_error=False,
|
raise_on_error=False,
|
||||||
),
|
),
|
||||||
# Expected output varies by terraform version
|
# Expected output varies by terraform semantic_version
|
||||||
"Changes to Outputs:",
|
"Changes to Outputs:",
|
||||||
0,
|
0,
|
||||||
False,
|
False,
|
||||||
|
@ -236,7 +236,7 @@ def workspace_setup_teardown():
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def wrapper(workspace_name, create=True, delete=True, *args, **kwargs):
|
def wrapper(workspace_name, create=True, delete=True, *args, **kwargs):
|
||||||
tf = Terraform(working_dir=current_path, terraform_version=version)
|
tf = Terraform(working_dir=current_path, terraform_semantic_version=semantic_version)
|
||||||
tf.init()
|
tf.init()
|
||||||
if create:
|
if create:
|
||||||
tf.create_workspace(workspace_name, *args, **kwargs)
|
tf.create_workspace(workspace_name, *args, **kwargs)
|
||||||
|
@ -271,14 +271,14 @@ class TestTerraform:
|
||||||
|
|
||||||
@pytest.mark.parametrize(["method", "expected"], STRING_CASES)
|
@pytest.mark.parametrize(["method", "expected"], STRING_CASES)
|
||||||
def test_generate_cmd_string(self, method: Callable[..., str], expected: str):
|
def test_generate_cmd_string(self, method: Callable[..., str], expected: str):
|
||||||
tf = Terraform(working_dir=current_path, terraform_version=version)
|
tf = Terraform(working_dir=current_path, terraform_semantic_version=semantic_version)
|
||||||
result = method(tf)
|
result = method(tf)
|
||||||
|
|
||||||
strs = expected.split()
|
strs = expected.split()
|
||||||
for s in strs:
|
for s in strs:
|
||||||
assert s in result
|
assert s in result
|
||||||
|
|
||||||
@pytest.mark.parametrize(*(CMD_CASES_1_x if version >= 1.0 else CMD_CASES_0_x))
|
@pytest.mark.parametrize(*(CMD_CASES_1_x if version.parse(semantic_version) >= version.parse("1.0.0") else CMD_CASES_0_x))
|
||||||
def test_cmd(
|
def test_cmd(
|
||||||
self,
|
self,
|
||||||
method: Callable[..., str],
|
method: Callable[..., str],
|
||||||
|
@ -290,7 +290,7 @@ class TestTerraform:
|
||||||
folder: str,
|
folder: str,
|
||||||
):
|
):
|
||||||
with caplog.at_level(logging.INFO):
|
with caplog.at_level(logging.INFO):
|
||||||
tf = Terraform(working_dir=current_path, terraform_version=version)
|
tf = Terraform(working_dir=current_path, terraform_semantic_version=semantic_version)
|
||||||
tf.init(folder)
|
tf.init(folder)
|
||||||
try:
|
try:
|
||||||
ret, out, _ = method(tf)
|
ret, out, _ = method(tf)
|
||||||
|
@ -305,10 +305,10 @@ class TestTerraform:
|
||||||
assert expected_logs in caplog.text
|
assert expected_logs in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(*(APPLY_CASES_1_x if version >= 1.0 else APPLY_CASES_0_x))
|
@pytest.mark.parametrize(*(APPLY_CASES_1_x if version.parse(semantic_version) >= version.parse("1.0.0") else APPLY_CASES_0_x))
|
||||||
def test_apply(self, folder, variables, var_files, expected_output, options):
|
def test_apply(self, folder, variables, var_files, expected_output, options):
|
||||||
tf = Terraform(
|
tf = Terraform(
|
||||||
working_dir=current_path, variables=variables, var_file=var_files, terraform_version=version
|
working_dir=current_path, variables=variables, var_file=var_files, terraform_semantic_version=semantic_version
|
||||||
)
|
)
|
||||||
tf.init(folder)
|
tf.init(folder)
|
||||||
ret, out, err = tf.apply(folder, **options)
|
ret, out, err = tf.apply(folder, **options)
|
||||||
|
@ -316,9 +316,29 @@ class TestTerraform:
|
||||||
assert expected_output in out.replace("\n", "").replace(" ", "")
|
assert expected_output in out.replace("\n", "").replace(" ", "")
|
||||||
assert err == ""
|
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):
|
def test_apply_with_var_file(self, caplog: LogCaptureFixture):
|
||||||
with caplog.at_level(logging.INFO):
|
with caplog.at_level(logging.INFO):
|
||||||
tf = Terraform(working_dir=current_path, terraform_version=version)
|
tf = Terraform(working_dir=current_path, terraform_semantic_version=semantic_version)
|
||||||
folder = "var_to_output"
|
folder = "var_to_output"
|
||||||
tf.init(folder)
|
tf.init(folder)
|
||||||
tf.apply(
|
tf.apply(
|
||||||
|
@ -338,7 +358,7 @@ class TestTerraform:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_options(self, cmd, args, options, fmt_test_file):
|
def test_options(self, cmd, args, options, fmt_test_file):
|
||||||
tf = Terraform(working_dir=current_path, terraform_version=version)
|
tf = Terraform(working_dir=current_path, terraform_semantic_version=semantic_version)
|
||||||
ret, out, err = getattr(tf, cmd)(*args, **options)
|
ret, out, err = getattr(tf, cmd)(*args, **options)
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
assert out == ""
|
assert out == ""
|
||||||
|
@ -346,26 +366,26 @@ class TestTerraform:
|
||||||
def test_state_data(self):
|
def test_state_data(self):
|
||||||
cwd = os.path.join(current_path, "test_tfstate_file")
|
cwd = os.path.join(current_path, "test_tfstate_file")
|
||||||
tf = Terraform(working_dir=cwd, state="tfstate.test",
|
tf = Terraform(working_dir=cwd, state="tfstate.test",
|
||||||
terraform_version=version)
|
terraform_semantic_version=semantic_version)
|
||||||
tf.read_state_file()
|
tf.read_state_file()
|
||||||
assert tf.tfstate.modules[0]["path"] == ["root"]
|
assert tf.tfstate.modules[0]["path"] == ["root"]
|
||||||
|
|
||||||
def test_state_default(self):
|
def test_state_default(self):
|
||||||
cwd = os.path.join(current_path, "test_tfstate_file2")
|
cwd = os.path.join(current_path, "test_tfstate_file2")
|
||||||
tf = Terraform(working_dir=cwd, terraform_version=version)
|
tf = Terraform(working_dir=cwd, terraform_semantic_version=semantic_version)
|
||||||
tf.read_state_file()
|
tf.read_state_file()
|
||||||
assert tf.tfstate.modules[0]["path"] == ["default"]
|
assert tf.tfstate.modules[0]["path"] == ["default"]
|
||||||
|
|
||||||
def test_state_default_backend(self):
|
def test_state_default_backend(self):
|
||||||
cwd = os.path.join(current_path, "test_tfstate_file3")
|
cwd = os.path.join(current_path, "test_tfstate_file3")
|
||||||
tf = Terraform(working_dir=cwd, terraform_version=version)
|
tf = Terraform(working_dir=cwd, terraform_semantic_version=semantic_version)
|
||||||
tf.read_state_file()
|
tf.read_state_file()
|
||||||
assert tf.tfstate.modules[0]["path"] == ["default_backend"]
|
assert tf.tfstate.modules[0]["path"] == ["default_backend"]
|
||||||
|
|
||||||
def test_pre_load_state_data(self):
|
def test_pre_load_state_data(self):
|
||||||
cwd = os.path.join(current_path, "test_tfstate_file")
|
cwd = os.path.join(current_path, "test_tfstate_file")
|
||||||
tf = Terraform(working_dir=cwd, state="tfstate.test",
|
tf = Terraform(working_dir=cwd, state="tfstate.test",
|
||||||
terraform_version=version)
|
terraform_semantic_version=semantic_version)
|
||||||
assert tf.tfstate.modules[0]["path"] == ["root"]
|
assert tf.tfstate.modules[0]["path"] == ["root"]
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -373,7 +393,7 @@ class TestTerraform:
|
||||||
)
|
)
|
||||||
def test_override_default(self, folder, variables):
|
def test_override_default(self, folder, variables):
|
||||||
tf = Terraform(working_dir=current_path,
|
tf = Terraform(working_dir=current_path,
|
||||||
variables=variables, terraform_version=version)
|
variables=variables, terraform_semantic_version=semantic_version)
|
||||||
tf.init(folder)
|
tf.init(folder)
|
||||||
ret, out, err = tf.apply(
|
ret, out, err = tf.apply(
|
||||||
folder, var={"test_var": "test2"}, no_color=IsNotFlagged,
|
folder, var={"test_var": "test2"}, no_color=IsNotFlagged,
|
||||||
|
@ -389,7 +409,7 @@ class TestTerraform:
|
||||||
required_output = "test_output"
|
required_output = "test_output"
|
||||||
with caplog.at_level(logging.INFO):
|
with caplog.at_level(logging.INFO):
|
||||||
tf = Terraform(
|
tf = Terraform(
|
||||||
working_dir=current_path, variables={"test_var": expected_value}, terraform_version=version
|
working_dir=current_path, variables={"test_var": expected_value}, terraform_semantic_version=semantic_version
|
||||||
)
|
)
|
||||||
tf.init("var_to_output")
|
tf.init("var_to_output")
|
||||||
tf.apply("var_to_output")
|
tf.apply("var_to_output")
|
||||||
|
@ -403,7 +423,7 @@ class TestTerraform:
|
||||||
|
|
||||||
def test_destroy(self):
|
def test_destroy(self):
|
||||||
tf = Terraform(working_dir=current_path, variables={
|
tf = Terraform(working_dir=current_path, variables={
|
||||||
"test_var": "test"}, terraform_version=version)
|
"test_var": "test"}, terraform_semantic_version=semantic_version)
|
||||||
tf.init("var_to_output")
|
tf.init("var_to_output")
|
||||||
ret, out, err = tf.destroy("var_to_output")
|
ret, out, err = tf.destroy("var_to_output")
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
@ -414,7 +434,7 @@ class TestTerraform:
|
||||||
)
|
)
|
||||||
def test_plan(self, plan, variables, expected_ret):
|
def test_plan(self, plan, variables, expected_ret):
|
||||||
tf = Terraform(working_dir=current_path,
|
tf = Terraform(working_dir=current_path,
|
||||||
variables=variables, terraform_version=version)
|
variables=variables, terraform_semantic_version=semantic_version)
|
||||||
tf.init(plan)
|
tf.init(plan)
|
||||||
with pytest.raises(TerraformCommandError) as e:
|
with pytest.raises(TerraformCommandError) as e:
|
||||||
tf.plan(plan)
|
tf.plan(plan)
|
||||||
|
@ -424,7 +444,7 @@ class TestTerraform:
|
||||||
|
|
||||||
def test_fmt(self, fmt_test_file):
|
def test_fmt(self, fmt_test_file):
|
||||||
tf = Terraform(working_dir=current_path, variables={
|
tf = Terraform(working_dir=current_path, variables={
|
||||||
"test_var": "test"}, terraform_version=version)
|
"test_var": "test"}, terraform_semantic_version=semantic_version)
|
||||||
ret, out, err = tf.fmt(diff=True)
|
ret, out, err = tf.fmt(diff=True)
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
|
||||||
|
@ -529,3 +549,9 @@ class TestTerraform:
|
||||||
in caplog.messages
|
in caplog.messages
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def test_list_workspace(self):
|
||||||
|
tf = Terraform(working_dir=current_path)
|
||||||
|
workspaces = tf.list_workspace()
|
||||||
|
assert len(workspaces) > 0
|
||||||
|
assert 'default' in workspaces
|
||||||
|
|
Loading…
Reference in a new issue