Compare commits

...

31 commits

Author SHA1 Message Date
7c4cd23e56 [Skip-CI] Fix mastodon add website link 2024-08-06 15:07:51 +02:00
bom
3fd4a72d3d Adjust logic for plan files (version > 1.0.0)
We will no longer try to chdir into files and instead
add them as subcommands.
Includes regression test

Resolves #3 (https://gitlab.com/domaindrivenarchitecture/dda-python-terraform/-/issues/3)
2023-11-24 22:08:38 +01:00
0fb8218dbf [Skip-CI] Add Development and mirrors section 2023-07-28 14:30:52 +02:00
3c92a8b2e6 Change wording 2023-02-15 14:38:56 +01:00
be63bfde3e update doku 2023-01-27 13:20:33 +01:00
a3d97a0d04 fix most pylints 2023-01-27 11:19:03 +01:00
c1c25a016f fix flake8 2023-01-27 10:47:01 +01:00
0abb2370e4 version bump 2023-01-27 10:46:53 +01:00
5af484bcce release 2023-01-27 10:35:48 +01:00
722ba5f716 update the setup.py url to point to the new repository
See merge request domaindrivenarchitecture/dda-python-terraform!3
2023-01-23 16:57:54 +00:00
Marc Seiler
d88670503b update the setup.py url to point to the new repository 2023-01-23 16:57:54 +00:00
bc27522fb5 adjust readme 2022-08-19 15:50:09 +02:00
441bc497d1 version bump 2022-08-19 14:12:20 +02:00
ddb8e4160d release 2022-08-19 14:11:41 +02:00
71c544bad8 Merge branch 'use-semantic-version--string-instead-of-float' into 'master'
[breaking change] use semantic version string

See merge request domaindrivenarchitecture/python-terraform!2
2022-08-19 10:16:40 +00:00
1a7955b738 [breaking change] use semantic version string 2022-08-19 10:16:40 +00:00
9c9d7dc14b add list workspace 2022-08-05 18:09:17 +02:00
f94f3a7c43 adjust gitignore 2022-08-05 18:08:56 +02:00
026e5f260a adjust dev_requirements name 2022-08-05 17:48:19 +02:00
bom
35b52fdbe8 version bump 2022-01-28 09:26:05 +01:00
bom
d44570e9bb release 2022-01-28 09:24:38 +01:00
bom
5e204617d0 fixed releasing doc 2022-01-28 09:23:58 +01:00
bom
7d50eec689 use dda_python_terraform module 2022-01-28 09:17:28 +01:00
bom
b509685c62 rename package 2022-01-27 17:12:33 +01:00
bom
30daf13244 renamed python_terraform to dda_python_terraform 2022-01-27 17:03:30 +01:00
jem
fd431b229d version bump 2022-01-21 13:39:49 +01:00
jem
58e4e9f065 release 2022-01-21 13:37:07 +01:00
jem
124300c271 fix some pylints 2022-01-21 13:36:42 +01:00
jem
382c571d42 release 2022-01-21 13:31:00 +01:00
jem
cb30395d4d release 2022-01-21 13:26:46 +01:00
jem
dcbeb0e934 use protected tags for releasing 2022-01-21 13:23:50 +01:00
11 changed files with 164 additions and 78 deletions

5
.gitignore vendored
View file

@ -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

View file

@ -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/*

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 ## 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

View file

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

View file

@ -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 = []

View file

@ -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
View 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
```

View file

@ -0,0 +1 @@
packaging

View file

@ -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,

View file

@ -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