Merge branch 'release/0.9.1'
This commit is contained in:
commit
9afa4e1d4e
7 changed files with 221 additions and 29 deletions
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.9.0
|
current_version = 0.9.1
|
||||||
commit = True
|
commit = True
|
||||||
tag = False
|
tag = False
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
## [0.9.1]
|
||||||
|
1. [#10] log handler error on Linux environment
|
||||||
|
1. [#11] Fix reading state file for remote state and support backend config for
|
||||||
|
init command
|
||||||
## [0.9.0]
|
## [0.9.0]
|
||||||
### Fixed
|
### Fixed
|
||||||
1. [#12] Output function doesn't accept parameter 'module'
|
1. [#12] Output function doesn't accept parameter 'module'
|
||||||
|
|
|
@ -10,7 +10,15 @@ import tempfile
|
||||||
|
|
||||||
from python_terraform.tfstate import Tfstate
|
from python_terraform.tfstate import Tfstate
|
||||||
|
|
||||||
|
try: # Python 2.7+
|
||||||
|
from logging import NullHandler
|
||||||
|
except ImportError:
|
||||||
|
class NullHandler(logging.Handler):
|
||||||
|
def emit(self, record):
|
||||||
|
pass
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
log.addHandler(NullHandler())
|
||||||
|
|
||||||
|
|
||||||
class IsFlagged:
|
class IsFlagged:
|
||||||
|
@ -76,7 +84,8 @@ class Terraform(object):
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def apply(self, dir_or_plan=None, input=False, no_color=IsFlagged, **kwargs):
|
def apply(self, dir_or_plan=None, input=False, no_color=IsFlagged,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
refer to https://terraform.io/docs/commands/apply.html
|
refer to https://terraform.io/docs/commands/apply.html
|
||||||
no-color is flagged by default
|
no-color is flagged by default
|
||||||
|
@ -122,7 +131,7 @@ class Terraform(object):
|
||||||
|
|
||||||
def plan(self, dir_or_plan=None, detailed_exitcode=IsFlagged, **kwargs):
|
def plan(self, dir_or_plan=None, detailed_exitcode=IsFlagged, **kwargs):
|
||||||
"""
|
"""
|
||||||
refert to https://www.terraform.io/docs/commands/plan.html
|
refer to https://www.terraform.io/docs/commands/plan.html
|
||||||
:param detailed_exitcode: Return a detailed exit code when the command exits.
|
:param detailed_exitcode: Return a detailed exit code when the command exits.
|
||||||
:param dir_or_plan: relative path to plan/folder
|
:param dir_or_plan: relative path to plan/folder
|
||||||
:param kwargs: options
|
:param kwargs: options
|
||||||
|
@ -134,6 +143,32 @@ class Terraform(object):
|
||||||
args = self._generate_default_args(dir_or_plan)
|
args = self._generate_default_args(dir_or_plan)
|
||||||
return self.cmd('plan', *args, **options)
|
return self.cmd('plan', *args, **options)
|
||||||
|
|
||||||
|
def init(self, dir_or_plan=None, backend_config=None,
|
||||||
|
reconfigure=IsFlagged, backend=True, **kwargs):
|
||||||
|
"""
|
||||||
|
refer to https://www.terraform.io/docs/commands/init.html
|
||||||
|
|
||||||
|
By default, this assumes you want to use backend config, and tries to
|
||||||
|
init fresh. The flags -reconfigure and -backend=true are default.
|
||||||
|
|
||||||
|
:param dir_or_plan: relative path to the folder want to init
|
||||||
|
:param backend_config: a dictionary of backend config options. eg.
|
||||||
|
t = Terraform()
|
||||||
|
t.init(backend_config={'access_key': 'myaccesskey',
|
||||||
|
'secret_key': 'mysecretkey', 'bucket': 'mybucketname'})
|
||||||
|
:param reconfigure: whether or not to force reconfiguration of backend
|
||||||
|
:param backend: whether or not to use backend settings for init
|
||||||
|
:param kwargs: options
|
||||||
|
:return: ret_code, stdout, stderr
|
||||||
|
"""
|
||||||
|
options = kwargs
|
||||||
|
options['backend_config'] = backend_config
|
||||||
|
options['reconfigure'] = reconfigure
|
||||||
|
options['backend'] = backend
|
||||||
|
options = self._generate_default_options(options)
|
||||||
|
args = self._generate_default_args(dir_or_plan)
|
||||||
|
return self.cmd('init', *args, **options)
|
||||||
|
|
||||||
def generate_cmd_string(self, cmd, *args, **kwargs):
|
def generate_cmd_string(self, cmd, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
for any generate_cmd_string doesn't written as public method of terraform
|
for any generate_cmd_string doesn't written as public method of terraform
|
||||||
|
@ -161,35 +196,40 @@ class Terraform(object):
|
||||||
cmds = cmd.split()
|
cmds = cmd.split()
|
||||||
cmds = [self.terraform_bin_path] + cmds
|
cmds = [self.terraform_bin_path] + cmds
|
||||||
|
|
||||||
for k, v in kwargs.items():
|
for option, value in kwargs.items():
|
||||||
if '_' in k:
|
if '_' in option:
|
||||||
k = k.replace('_', '-')
|
option = option.replace('_', '-')
|
||||||
|
|
||||||
if type(v) is list:
|
if type(value) is list:
|
||||||
for sub_v in v:
|
for sub_v in value:
|
||||||
cmds += ['-{k}={v}'.format(k=k, v=sub_v)]
|
cmds += ['-{k}={v}'.format(k=option, v=sub_v)]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# right now we assume only variables will be passed as dict
|
if type(value) is dict:
|
||||||
# since map type sent in string won't work, create temp var file for
|
if 'backend-config' in option:
|
||||||
# variables, and clean it up later
|
for bk, bv in value.items():
|
||||||
if type(v) is dict:
|
cmds += ['-backend-config={k}={v}'.format(k=bk, v=bv)]
|
||||||
filename = self.temp_var_files.create(v)
|
continue
|
||||||
cmds += ['-var-file={0}'.format(filename)]
|
|
||||||
continue
|
# since map type sent in string won't work, create temp var file for
|
||||||
|
# variables, and clean it up later
|
||||||
|
else:
|
||||||
|
filename = self.temp_var_files.create(value)
|
||||||
|
cmds += ['-var-file={0}'.format(filename)]
|
||||||
|
continue
|
||||||
|
|
||||||
# simple flag,
|
# simple flag,
|
||||||
if v is IsFlagged:
|
if value is IsFlagged:
|
||||||
cmds += ['-{k}'.format(k=k)]
|
cmds += ['-{k}'.format(k=option)]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if v is None or v is IsNotFlagged:
|
if value is None or value is IsNotFlagged:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if type(v) is bool:
|
if type(value) is bool:
|
||||||
v = 'true' if v else 'false'
|
value = 'true' if value else 'false'
|
||||||
|
|
||||||
cmds += ['-{k}={v}'.format(k=k, v=v)]
|
cmds += ['-{k}={v}'.format(k=option, v=value)]
|
||||||
|
|
||||||
cmds += args
|
cmds += args
|
||||||
return cmds
|
return cmds
|
||||||
|
@ -274,14 +314,20 @@ class Terraform(object):
|
||||||
:return: states file in dict type
|
:return: states file in dict type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not file_path:
|
working_dir = self.working_dir or ''
|
||||||
file_path = self.state
|
|
||||||
|
file_path = file_path or self.state or ''
|
||||||
|
|
||||||
if not file_path:
|
if not file_path:
|
||||||
file_path = 'terraform.tfstate'
|
backend_path = os.path.join(file_path, '.terraform',
|
||||||
|
'terraform.tfstate')
|
||||||
|
|
||||||
if self.working_dir:
|
if os.path.exists(os.path.join(working_dir, backend_path)):
|
||||||
file_path = os.path.join(self.working_dir, file_path)
|
file_path = backend_path
|
||||||
|
else:
|
||||||
|
file_path = os.path.join(file_path, 'terraform.tfstate')
|
||||||
|
|
||||||
|
file_path = os.path.join(working_dir, file_path)
|
||||||
|
|
||||||
self.tfstate = Tfstate.load_file(file_path)
|
self.tfstate = Tfstate.load_file(file_path)
|
||||||
|
|
||||||
|
@ -297,7 +343,8 @@ class VariableFiles(object):
|
||||||
with tempfile.NamedTemporaryFile('w+t', delete=False) as temp:
|
with tempfile.NamedTemporaryFile('w+t', delete=False) as temp:
|
||||||
log.debug('{0} is created'.format(temp.name))
|
log.debug('{0} is created'.format(temp.name))
|
||||||
self.files.append(temp)
|
self.files.append(temp)
|
||||||
log.debug('variables wrote to tempfile: {0}'.format(str(variables)))
|
log.debug(
|
||||||
|
'variables wrote to tempfile: {0}'.format(str(variables)))
|
||||||
temp.write(json.dumps(variables))
|
temp.write(json.dumps(variables))
|
||||||
file_name = temp.name
|
file_name = temp.name
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -20,7 +20,7 @@ except IOError:
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=module_name,
|
name=module_name,
|
||||||
version='0.9.0',
|
version='0.9.1',
|
||||||
url='https://github.com/beelit94/python-terraform',
|
url='https://github.com/beelit94/python-terraform',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
author='Freddy Tan',
|
author='Freddy Tan',
|
||||||
|
|
|
@ -92,9 +92,13 @@ class TestTerraform(object):
|
||||||
""" teardown any state that was previously setup with a setup_method
|
""" teardown any state that was previously setup with a setup_method
|
||||||
call.
|
call.
|
||||||
"""
|
"""
|
||||||
|
exclude = ['test_tfstate_file',
|
||||||
|
'test_tfstate_file2',
|
||||||
|
'test_tfstate_file3']
|
||||||
|
|
||||||
def purge(dir, pattern):
|
def purge(dir, pattern):
|
||||||
for root, dirnames, filenames in os.walk(dir):
|
for root, dirnames, filenames in os.walk(dir):
|
||||||
|
dirnames[:] = [d for d in dirnames if d not in exclude]
|
||||||
for filename in fnmatch.filter(filenames, pattern):
|
for filename in fnmatch.filter(filenames, pattern):
|
||||||
f = os.path.join(root, filename)
|
f = os.path.join(root, filename)
|
||||||
os.remove(f)
|
os.remove(f)
|
||||||
|
@ -103,6 +107,7 @@ class TestTerraform(object):
|
||||||
shutil.rmtree(d)
|
shutil.rmtree(d)
|
||||||
|
|
||||||
purge('.', '*.tfstate')
|
purge('.', '*.tfstate')
|
||||||
|
purge('.', '*.tfstate.backup')
|
||||||
purge('.', '*.terraform')
|
purge('.', '*.terraform')
|
||||||
purge('.', FILE_PATH_WITH_SPACE_AND_SPACIAL_CHARS)
|
purge('.', FILE_PATH_WITH_SPACE_AND_SPACIAL_CHARS)
|
||||||
|
|
||||||
|
@ -166,6 +171,18 @@ class TestTerraform(object):
|
||||||
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):
|
||||||
|
cwd = os.path.join(current_path, 'test_tfstate_file2')
|
||||||
|
tf = Terraform(working_dir=cwd)
|
||||||
|
tf.read_state_file()
|
||||||
|
assert tf.tfstate.modules[0]['path'] == ['default']
|
||||||
|
|
||||||
|
def test_state_default_backend(self):
|
||||||
|
cwd = os.path.join(current_path, 'test_tfstate_file3')
|
||||||
|
tf = Terraform(working_dir=cwd)
|
||||||
|
tf.read_state_file()
|
||||||
|
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')
|
||||||
|
|
62
test/test_tfstate_file2/terraform.tfstate
Normal file
62
test/test_tfstate_file2/terraform.tfstate
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"terraform_version": "0.7.10",
|
||||||
|
"serial": 0,
|
||||||
|
"lineage": "d03ecdf7-8be0-4593-a952-1d8127875119",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"default"
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"resources": {
|
||||||
|
"aws_instance.ubuntu-1404": {
|
||||||
|
"type": "aws_instance",
|
||||||
|
"depends_on": [],
|
||||||
|
"primary": {
|
||||||
|
"id": "i-84d10edb",
|
||||||
|
"attributes": {
|
||||||
|
"ami": "ami-9abea4fb",
|
||||||
|
"associate_public_ip_address": "true",
|
||||||
|
"availability_zone": "us-west-2b",
|
||||||
|
"disable_api_termination": "false",
|
||||||
|
"ebs_block_device.#": "0",
|
||||||
|
"ebs_optimized": "false",
|
||||||
|
"ephemeral_block_device.#": "0",
|
||||||
|
"iam_instance_profile": "",
|
||||||
|
"id": "i-84d10edb",
|
||||||
|
"instance_state": "running",
|
||||||
|
"instance_type": "t2.micro",
|
||||||
|
"key_name": "",
|
||||||
|
"monitoring": "false",
|
||||||
|
"network_interface_id": "eni-46544f07",
|
||||||
|
"private_dns": "ip-172-31-25-244.us-west-2.compute.internal",
|
||||||
|
"private_ip": "172.31.25.244",
|
||||||
|
"public_dns": "ec2-35-162-30-219.us-west-2.compute.amazonaws.com",
|
||||||
|
"public_ip": "35.162.30.219",
|
||||||
|
"root_block_device.#": "1",
|
||||||
|
"root_block_device.0.delete_on_termination": "true",
|
||||||
|
"root_block_device.0.iops": "100",
|
||||||
|
"root_block_device.0.volume_size": "8",
|
||||||
|
"root_block_device.0.volume_type": "gp2",
|
||||||
|
"security_groups.#": "0",
|
||||||
|
"source_dest_check": "true",
|
||||||
|
"subnet_id": "subnet-d2c0f0a6",
|
||||||
|
"tags.%": "0",
|
||||||
|
"tenancy": "default",
|
||||||
|
"vpc_security_group_ids.#": "1",
|
||||||
|
"vpc_security_group_ids.619359045": "sg-9fc7dcfd"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"schema_version": "1"
|
||||||
|
},
|
||||||
|
"tainted": false
|
||||||
|
},
|
||||||
|
"deposed": [],
|
||||||
|
"provider": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"depends_on": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
62
test/test_tfstate_file3/.terraform/terraform.tfstate
Normal file
62
test/test_tfstate_file3/.terraform/terraform.tfstate
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"terraform_version": "0.7.10",
|
||||||
|
"serial": 0,
|
||||||
|
"lineage": "d03ecdf7-8be0-4593-a952-1d8127875119",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"default_backend"
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"resources": {
|
||||||
|
"aws_instance.ubuntu-1404": {
|
||||||
|
"type": "aws_instance",
|
||||||
|
"depends_on": [],
|
||||||
|
"primary": {
|
||||||
|
"id": "i-84d10edb",
|
||||||
|
"attributes": {
|
||||||
|
"ami": "ami-9abea4fb",
|
||||||
|
"associate_public_ip_address": "true",
|
||||||
|
"availability_zone": "us-west-2b",
|
||||||
|
"disable_api_termination": "false",
|
||||||
|
"ebs_block_device.#": "0",
|
||||||
|
"ebs_optimized": "false",
|
||||||
|
"ephemeral_block_device.#": "0",
|
||||||
|
"iam_instance_profile": "",
|
||||||
|
"id": "i-84d10edb",
|
||||||
|
"instance_state": "running",
|
||||||
|
"instance_type": "t2.micro",
|
||||||
|
"key_name": "",
|
||||||
|
"monitoring": "false",
|
||||||
|
"network_interface_id": "eni-46544f07",
|
||||||
|
"private_dns": "ip-172-31-25-244.us-west-2.compute.internal",
|
||||||
|
"private_ip": "172.31.25.244",
|
||||||
|
"public_dns": "ec2-35-162-30-219.us-west-2.compute.amazonaws.com",
|
||||||
|
"public_ip": "35.162.30.219",
|
||||||
|
"root_block_device.#": "1",
|
||||||
|
"root_block_device.0.delete_on_termination": "true",
|
||||||
|
"root_block_device.0.iops": "100",
|
||||||
|
"root_block_device.0.volume_size": "8",
|
||||||
|
"root_block_device.0.volume_type": "gp2",
|
||||||
|
"security_groups.#": "0",
|
||||||
|
"source_dest_check": "true",
|
||||||
|
"subnet_id": "subnet-d2c0f0a6",
|
||||||
|
"tags.%": "0",
|
||||||
|
"tenancy": "default",
|
||||||
|
"vpc_security_group_ids.#": "1",
|
||||||
|
"vpc_security_group_ids.619359045": "sg-9fc7dcfd"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"schema_version": "1"
|
||||||
|
},
|
||||||
|
"tainted": false
|
||||||
|
},
|
||||||
|
"deposed": [],
|
||||||
|
"provider": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"depends_on": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue