From 2ff59f3630fe74183c682b497dd3d795b8f5834a Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 15:35:14 +0800 Subject: [PATCH 01/33] 1. add test cases, try tdd 2. refactor to more generic method instead of aws method 3. ready for release to pypi --- VERSION | 2 +- python_terraform/__init__.py | 310 ++++++++++++++++++----------------- python_terraform/tfstate.py | 31 ++++ setup.cfg | 2 + setup.py | 4 +- test/__init__.py | 0 test/apply_tf/test.tf | 8 + test/aws_tf/test.tf | 13 ++ test/test.tf | 24 --- test/test_terraform.py | 82 ++++++++- 10 files changed, 290 insertions(+), 186 deletions(-) create mode 100644 python_terraform/tfstate.py create mode 100644 setup.cfg delete mode 100644 test/__init__.py create mode 100644 test/apply_tf/test.tf create mode 100644 test/aws_tf/test.tf delete mode 100644 test/test.tf diff --git a/VERSION b/VERSION index 05b19b1..7deb86f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4 \ No newline at end of file +0.7.1 \ No newline at end of file diff --git a/python_terraform/__init__.py b/python_terraform/__init__.py index 9abb999..d9067ac 100644 --- a/python_terraform/__init__.py +++ b/python_terraform/__init__.py @@ -3,180 +3,186 @@ import os import json import logging +from python_terraform.tfstate import Tfstate + log = logging.getLogger(__name__) class Terraform: - def __init__(self, targets=None, state='terraform.tfstate', variables=None): + """ + Wrapper of terraform command line tool + https://www.terraform.io/ + """ + + def __init__(self, working_dir=None, + targets=None, + state=None, + variables=None, + parallelism=None, + var_file=None): + self.working_dir = working_dir + self.state = state self.targets = [] if targets is None else targets self.variables = dict() if variables is None else variables - - self.state_filename = state - self.state_data = dict() - self.parallelism = 50 - - def apply(self, targets=None, variables=None, **kargs): + self.parallelism = parallelism + self.terraform_bin_path = 'terraform' + self.var_file = var_file + self.input = False + + # store the tfstate data + self.tfstate = dict() + + def apply(self, + working_dir=None, + no_color=True, + **kwargs): """ refer to https://terraform.io/docs/commands/apply.html - :param variables: variables in dict type - :param targets: targets in list + :param working_dir: working folder + :param no_color: Disables output with coloring. :returns return_code, stdout, stderr """ - variables = self.variables if variables is None else variables - targets = self.targets if targets is None else targets - - parameters = [] - parameters += self._generate_targets(targets) - parameters += self._generate_var_string(variables) - parameters += self._gen_param_string(kargs) - - parameters = \ - ['terraform', 'apply', '-state=%s' % self.state_filename] + parameters - - cmd = ' '.join(parameters) - return self._run_cmd(cmd) - - def _gen_param_string(self, kargs): - params = [] - for key, value in kargs.items(): - if value: - params += ['-%s=%s' % (key, value)] - else: - params += ['-%s' % key] - return params - - def _run_cmd(self, cmd): - log.debug('command: ' + cmd) - - p = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - out, err = p.communicate() - ret_code = p.returncode - log.debug('output: ' + out) + if not working_dir: + working_dir = self.working_dir - if ret_code == 0: - log.debug('error: ' + err) - self.read_state_file() - return ret_code, out, err - - def destroy(self, targets=None, variables=None, **kwargs): - variables = self.variables if variables is None else variables - targets = self.targets if targets is None else targets - - parameters = [] - parameters += self._generate_targets(targets) - parameters += self._generate_var_string(variables) - - parameters = \ - ['terraform', 'destroy', '-force', '-state=%s' % self.state_filename] + \ - parameters - cmd = ' '.join(parameters) - return self._run_cmd(cmd) - - def refresh(self, targets=None, variables=None): - variables = self.variables if variables is None else variables - targets = self.targets if targets is None else targets - - parameters = [] - parameters += self._generate_targets(targets) - parameters += self._generate_var_string(variables) - parameters = \ - ['terraform', 'refresh', '-state=%s' % self.state_filename] + \ - parameters - cmd = ' '.join(parameters) - return self._run_cmd(cmd) - - def read_state_file(self): + option_dict = dict() + option_dict['state'] = self.state + option_dict['target'] = self.targets + option_dict['var'] = self.variables + option_dict['var_file'] = self.var_file + option_dict['parallelism'] = self.parallelism + if no_color: + option_dict['no_color'] = '' + option_dict['input'] = self.input + + option_dict.update(kwargs) + + args = [working_dir] if working_dir else [] + + ret, out, err = self.cmd('apply', *args, **option_dict) + + if ret != 0: + raise RuntimeError(err) + + def generate_cmd_string(self, cmd, *args, **kwargs): """ - read .tfstate file - :return: states file in dict type + for any generate_cmd_string doesn't written as public method of terraform + + examples: + 1. call import command, + ref to https://www.terraform.io/docs/commands/import.html + --> generate_cmd_string call: + terraform import -input=true aws_instance.foo i-abcd1234 + --> python call: + tf.generate_cmd_string('import', 'aws_instance.foo', 'i-abcd1234', input=True) + + 2. call apply command, + --> generate_cmd_string call: + terraform apply -var='a=b' -var='c=d' -no-color the_folder + --> python call: + tf.generate_cmd_string('apply', the_folder, no_color='', var={'a':'b', 'c':'d'}) + + :param cmd: command and sub-command of terraform, seperated with space + refer to https://www.terraform.io/docs/commands/index.html + :param args: argument other than options of a command + :param kwargs: same as kwags in method 'cmd' + :return: string of valid terraform command """ - if os.path.exists(self.state_filename): - with open(self.state_filename) as f: - json_data = json.load(f) - self.state_data = json_data - log.debug("state_data=%s" % str(self.state_data)) - return json_data - - return dict() - - def is_any_aws_instance_alive(self): - self.refresh() - if not os.path.exists(self.state_filename): - log.debug("can't find %s " % self.state_data) - return False - - self.read_state_file() - try: - main_module = self._get_main_module() - for resource_key, info in main_module['resources'].items(): - if 'aws_instance' in resource_key: - log.debug("%s is found when read state" % resource_key) - return True - log.debug("no aws_instance found in resource key") - return False - except KeyError as err: - log.debug(str(err)) - return False - except TypeError as err: - log.debug(str(err)) - return False - - def _get_main_module(self): - return self.state_data['modules'][0] - - def get_aws_instances(self): - instances = dict() - - try: - main_module = self._get_main_module() - for resource_key, info in main_module['resources'].items(): - if 'aws_instance' in resource_key: - instances[resource_key] = info - except KeyError: - return instances - except TypeError: - return instances - - return instances - - def get_aws_instance(self, resource_name): + cmds = cmd.split() + cmds = [self.terraform_bin_path] + cmds + + for k, v in kwargs.items(): + if '_' in k: + k = k.replace('_', '-') + + if type(v) is list: + for sub_v in v: + cmds += ['-{k}={v}'.format(k=k, v=sub_v)] + continue + + if type(v) is dict: + for sub_k, sub_v in v.items(): + cmds += ["-{k}='{var_k}={var_v}'".format(k=k, + var_k=sub_k, + var_v=sub_v)] + continue + + # simple flag, + if v == '': + cmds += ['-{k}'.format(k=k)] + continue + + if not v: + continue + + if type(v) is bool: + v = 'true' if v else 'false' + + cmds += ['-{k}={v}'.format(k=k, v=v)] + + cmds += args + cmd = ' '.join(cmds) + return cmd + + def cmd(self, cmd, *args, **kwargs): """ - :param resource_name: - name of terraform resource, make source count is attached - :return: return None if not exist, dict type if exist + run a terraform command, if success, will try to read state file + :param cmd: command and sub-command of terraform, seperated with space + refer to https://www.terraform.io/docs/commands/index.html + :param args: argument other than options of a command + :param kwargs: any option flag with key value other than variables, + if there's a dash in the option name, use under line instead of dash, ex -no-color --> no_color + if it's a simple flag with no value, value should be empty string + if it's a boolean value flag, assign True or false + if it's a flag could be used multiple times, assign list to it's value + if it's a "var" variable flag, assign dictionary to it + if a value is None, will skip this option + :return: ret_code, out, err """ - try: - return self.get_aws_instances()[resource_name] - except KeyError: - return None + cmd_string = self.generate_cmd_string(cmd, *args, **kwargs) + log.debug('command: {c}'.format(c=cmd_string)) - def get_output_value(self, output_name): - """ + p = subprocess.Popen(cmd_string, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) + out, err = p.communicate() + ret_code = p.returncode + log.debug('output: {o}'.format(o=out)) - :param output_name: - :return: + if ret_code == 0: + self.read_state_file() + else: + log.warn('error: {e}'.format(e=err)) + return ret_code, out.decode('utf-8'), err.decode('utf-8') + + def output(self, name): """ - try: - main_module = self._get_main_module() - return main_module['outputs'][output_name] - except KeyError: + https://www.terraform.io/docs/commands/output.html + :param name: name of output + :return: output value + """ + ret, out, err = self.cmd('output', name, json='') + + if ret != 0: return None + out = out.lstrip() - @staticmethod - def _generate_var_string(d): - str_t = [] - for k, v in d.iteritems(): - str_t += ['-var'] + ["%s=%s" % (k, v)] + output_dict = json.loads(out) + return output_dict['value'] - return str_t + def read_state_file(self, file_path=None): + """ + read .tfstate file + :param file_path: relative path to working dir + :return: states file in dict type + """ - @staticmethod - def _generate_targets(targets): - str_t = [] - for t in targets: - str_t += ['-target=%s' % t] - return str_t + if not file_path: + file_path = self.state + if not file_path: + file_path = 'terraform.tfstate' + if self.working_dir: + file_path = os.path.join(self.working_dir, file_path) + self.tfstate = Tfstate.load_file(file_path) diff --git a/python_terraform/tfstate.py b/python_terraform/tfstate.py new file mode 100644 index 0000000..81e26a5 --- /dev/null +++ b/python_terraform/tfstate.py @@ -0,0 +1,31 @@ +import json +import os +import logging + +log = logging.getLogger(__name__) + + +class Tfstate(object): + def __init__(self, data=None): + self.tfstate_file = None + self.native_data = data + if data: + self.__dict__ = data + + @staticmethod + def load_file(file_path): + """ + Read the tfstate file and load its contents, parses then as JSON and put the result into the object + """ + log.debug('read data from {0}'.format(file_path)) + if os.path.exists(file_path): + with open(file_path) as f: + json_data = json.load(f) + + tf_state = Tfstate(json_data) + tf_state.tfstate_file = file_path + return tf_state + + log.warn('{0} is not exist'.format(file_path)) + + return Tfstate() \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5e40900 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[wheel] +universal = 1 diff --git a/setup.py b/setup.py index 3c4bbe0..de4e272 100644 --- a/setup.py +++ b/setup.py @@ -38,8 +38,8 @@ setup( # 'Development Status :: 1 - Planning', # 'Development Status :: 2 - Pre-Alpha', # 'Development Status :: 3 - Alpha', - 'Development Status :: 4 - Beta', - # 'Development Status :: 5 - Production/Stable', + # 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', # 'Development Status :: 6 - Mature', # 'Development Status :: 7 - Inactive', 'Environment :: Console', diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/apply_tf/test.tf b/test/apply_tf/test.tf new file mode 100644 index 0000000..c247514 --- /dev/null +++ b/test/apply_tf/test.tf @@ -0,0 +1,8 @@ +variable "test_var" {} + +provider "archive" { +} + +output "test_output" { + value = "${var.test_var}" +} \ No newline at end of file diff --git a/test/aws_tf/test.tf b/test/aws_tf/test.tf new file mode 100644 index 0000000..1e79483 --- /dev/null +++ b/test/aws_tf/test.tf @@ -0,0 +1,13 @@ +variable "access_key" {} +variable "secret_key" {} + +provider "aws" { + access_key = "${var.access_key}" + secret_key = "${var.secret_key}" + region = "us-west-2" +} + +resource "aws_instance" "ubuntu-1404" { + ami = "ami-9abea4fb" + instance_type = "t2.micro" +} \ No newline at end of file diff --git a/test/test.tf b/test/test.tf deleted file mode 100644 index 6219dce..0000000 --- a/test/test.tf +++ /dev/null @@ -1,24 +0,0 @@ -variable "access_key" {} -variable "secret_key" {} - -provider "aws" { - access_key = "${var.access_key}" - secret_key = "${var.secret_key}" - region = "us-west-2" -} - -resource "aws_instance" "ubuntu-1404" { - ami = "ami-9abea4fb" - instance_type = "t2.micro" - security_groups = ["terraform-salty-splunk"] - tags { - Name = "python-terraform-test" - } -// key_name = "${aws_key_pair.key.key_name}" -// connection { -// type = "ssh" -// user = "ubuntu" -// key_file = "${var.key_path}" -// timeout = "10m" -// } -} \ No newline at end of file diff --git a/test/test_terraform.py b/test/test_terraform.py index 6fbdd32..e0c8bb9 100644 --- a/test/test_terraform.py +++ b/test/test_terraform.py @@ -1,15 +1,83 @@ from python_terraform import Terraform +import pytest +import os +import logging +import re + +logging.basicConfig(level=logging.DEBUG) + +ACCESS_KEY = os.environ['PACKER_access_key'] +SECRET_KEY = os.environ['PACKER_secret_key'] + +STRING_CASES = [ + [ + lambda x: x.generate_cmd_string('apply', 'the_folder', + no_color='', + var={'a': 'b', 'c': 'd'}), + "terraform apply -var='a=b' -var='c=d' -no-color the_folder" + ], + [ + lambda x: x.generate_cmd_string('push', 'path', + var={'a': 'b'}, vcs=True, + token='token', + atlas_address='url'), + "terraform push -var='a=b' -vcs=true -token=token -atlas-address=url path" + ], + ] + +CMD_CASES = [ + ['method', 'expected_output'], + [ + [ + lambda x: x.cmd('plan', 'aws_tf', no_color='', var={'access_key': ACCESS_KEY, 'secret_key': SECRET_KEY}) , + 'Plan: 1 to add, 0 to change, 0 to destroy' + ] + ] +] class TestTerraform: - def test_apply_and_destory(self): + def teardown_method(self, method): + """ teardown any state that was previously setup with a setup_method + call. + """ + + def purge(dir, pattern): + for f in os.listdir(dir): + if re.search(pattern, f): + if os.path.isfile(f): + os.remove(os.path.join(dir, f)) + + purge('.', '.tfstate') + + @pytest.mark.parametrize([ + "method", "expected" + ], STRING_CASES) + def test_generate_cmd_string(self, method, expected): + tf = Terraform() + result = method(tf) + + strs = expected.split() + for s in strs: + assert s in result + + @pytest.mark.parametrize(*CMD_CASES) + def test_cmd(self, method, expected_output): tf = Terraform() - ret_code, out, err = tf.apply() + ret, out, err = method(tf) + assert expected_output in out + assert ret == 0 - print out - print err - # assert ret_code, 0 + def test_state_data(self): + tf = Terraform(working_dir='test_tfstate_file') + tf.read_state_file() + assert tf.tfstate.modules[0]['path'] == ['root'] - ret_code, out, err = tf.destroy() + def test_apply(self): + tf = Terraform(working_dir='apply_tf', variables={'test_var': 'test'}) + tf.apply(var={'test_var': 'test2'}) - assert ret_code, 0 + def test_get_output(self): + tf = Terraform(working_dir='apply_tf', variables={'test_var': 'test'}) + tf.apply() + assert tf.output('test_output') == 'test' From b739004102473fcd295ee1a1922705401c1a4ee7 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 15:46:05 +0800 Subject: [PATCH 02/33] add travis --- .travis.yml | 16 ++++++++ test/test_terraform.py | 2 +- test/test_tfstate_file/tfstate.test | 62 +++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 .travis.yml create mode 100644 test/test_tfstate_file/tfstate.test diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5735476 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: python +python: + - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" +# - "3.5-dev" # 3.5 development branch +# - "3.6-dev" # 3.6 development branch +# - "nightly" # currently points to 3.7-dev +# command to install dependencies +#install: "pip install -r requirements.txt" +# command to run tests +script: + - cd test + - pytest \ No newline at end of file diff --git a/test/test_terraform.py b/test/test_terraform.py index e0c8bb9..5ad0d15 100644 --- a/test/test_terraform.py +++ b/test/test_terraform.py @@ -69,7 +69,7 @@ class TestTerraform: assert ret == 0 def test_state_data(self): - tf = Terraform(working_dir='test_tfstate_file') + tf = Terraform(working_dir='test_tfstate_file', state='tfstate.test') tf.read_state_file() assert tf.tfstate.modules[0]['path'] == ['root'] diff --git a/test/test_tfstate_file/tfstate.test b/test/test_tfstate_file/tfstate.test new file mode 100644 index 0000000..2e07061 --- /dev/null +++ b/test/test_tfstate_file/tfstate.test @@ -0,0 +1,62 @@ +{ + "version": 3, + "terraform_version": "0.7.10", + "serial": 0, + "lineage": "d03ecdf7-8be0-4593-a952-1d8127875119", + "modules": [ + { + "path": [ + "root" + ], + "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": [] + } + ] +} From f8bbfed9f403fd9fa2b50dbd028cb3344e712f02 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 16:11:20 +0800 Subject: [PATCH 03/33] for travis --- .travis.yml | 8 ++++---- test/reuirements.txt | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 test/reuirements.txt diff --git a/.travis.yml b/.travis.yml index 5735476..bb930b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,15 @@ language: python python: - "2.7" - - "3.2" - - "3.3" - - "3.4" +# - "3.2" +# - "3.3" +# - "3.4" - "3.5" # - "3.5-dev" # 3.5 development branch # - "3.6-dev" # 3.6 development branch # - "nightly" # currently points to 3.7-dev # command to install dependencies -#install: "pip install -r requirements.txt" +install: "pip install -r test/requirements.txt" # command to run tests script: - cd test diff --git a/test/reuirements.txt b/test/reuirements.txt new file mode 100644 index 0000000..55b033e --- /dev/null +++ b/test/reuirements.txt @@ -0,0 +1 @@ +pytest \ No newline at end of file From 0776ae7db4134bc1d25e8d120ff92d77ff1e9d93 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 16:12:33 +0800 Subject: [PATCH 04/33] for travis --- test/{reuirements.txt => requirements.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{reuirements.txt => requirements.txt} (100%) diff --git a/test/reuirements.txt b/test/requirements.txt similarity index 100% rename from test/reuirements.txt rename to test/requirements.txt From bf9bd75e25c73f27b43d3d86f5ed0853b731fdad Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 16:58:02 +0800 Subject: [PATCH 05/33] install terraform --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bb930b3..44f09e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,15 @@ python: # - "3.6-dev" # 3.6 development branch # - "nightly" # currently points to 3.7-dev # command to install dependencies +before_install: 'sudo apt-get install unzip' +before_script: + - 'wget https://releases.hashicorp.com/terraform/0.7.11/terraform_0.7.11_linux_amd64.zip' + - 'mkdir terraform' + - 'unzip terraform_0.7.11_linux_amd64.zip -d terraform' + - 'sudo ln -s $PWD/terraform/terraform /usr/local/terraform' + install: "pip install -r test/requirements.txt" # command to run tests script: - cd test - - pytest \ No newline at end of file + - py.test \ No newline at end of file From 7b9a0491ca60f606b4dc39d25d8355c0953b100e Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 17:08:10 +0800 Subject: [PATCH 06/33] 1. remove aws case 2. update travis method --- .travis.yml | 4 +++- test/aws_tf/test.tf | 13 ------------- test/test_terraform.py | 6 ++---- 3 files changed, 5 insertions(+), 18 deletions(-) delete mode 100644 test/aws_tf/test.tf diff --git a/.travis.yml b/.travis.yml index 44f09e4..28d4bc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,9 @@ before_script: - 'unzip terraform_0.7.11_linux_amd64.zip -d terraform' - 'sudo ln -s $PWD/terraform/terraform /usr/local/terraform' -install: "pip install -r test/requirements.txt" +install: + - "pip install -r test/requirements.txt" + - "pip install ." # command to run tests script: - cd test diff --git a/test/aws_tf/test.tf b/test/aws_tf/test.tf deleted file mode 100644 index 1e79483..0000000 --- a/test/aws_tf/test.tf +++ /dev/null @@ -1,13 +0,0 @@ -variable "access_key" {} -variable "secret_key" {} - -provider "aws" { - access_key = "${var.access_key}" - secret_key = "${var.secret_key}" - region = "us-west-2" -} - -resource "aws_instance" "ubuntu-1404" { - ami = "ami-9abea4fb" - instance_type = "t2.micro" -} \ No newline at end of file diff --git a/test/test_terraform.py b/test/test_terraform.py index 5ad0d15..848b437 100644 --- a/test/test_terraform.py +++ b/test/test_terraform.py @@ -6,8 +6,6 @@ import re logging.basicConfig(level=logging.DEBUG) -ACCESS_KEY = os.environ['PACKER_access_key'] -SECRET_KEY = os.environ['PACKER_secret_key'] STRING_CASES = [ [ @@ -29,8 +27,8 @@ CMD_CASES = [ ['method', 'expected_output'], [ [ - lambda x: x.cmd('plan', 'aws_tf', no_color='', var={'access_key': ACCESS_KEY, 'secret_key': SECRET_KEY}) , - 'Plan: 1 to add, 0 to change, 0 to destroy' + lambda x: x.cmd('plan', 'apply_tf', no_color='', var={'test_var': 'test'}) , + "doesn't need to do anything" ] ] ] From 699c7bb17c70ecb1281cee94c040f48655608fb1 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 17:20:32 +0800 Subject: [PATCH 07/33] fix terraform path on travis --- .travis.yml | 5 ++--- python_terraform/__init__.py | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 28d4bc3..a310b33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,8 @@ python: before_install: 'sudo apt-get install unzip' before_script: - 'wget https://releases.hashicorp.com/terraform/0.7.11/terraform_0.7.11_linux_amd64.zip' - - 'mkdir terraform' - - 'unzip terraform_0.7.11_linux_amd64.zip -d terraform' - - 'sudo ln -s $PWD/terraform/terraform /usr/local/terraform' + - 'unzip terraform_0.7.11_linux_amd64.zip' + - './terraform --version' install: - "pip install -r test/requirements.txt" diff --git a/python_terraform/__init__.py b/python_terraform/__init__.py index d9067ac..5b947a3 100644 --- a/python_terraform/__init__.py +++ b/python_terraform/__init__.py @@ -19,13 +19,15 @@ class Terraform: state=None, variables=None, parallelism=None, - var_file=None): + var_file=None, + terraform_bin_path=None): self.working_dir = working_dir self.state = state self.targets = [] if targets is None else targets self.variables = dict() if variables is None else variables self.parallelism = parallelism - self.terraform_bin_path = 'terraform' + self.terraform_bin_path = terraform_bin_path \ + if terraform_bin_path else 'terraform' self.var_file = var_file self.input = False From 42c707b807a19038f9cb2e200521ec823fb91bbe Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 17:24:03 +0800 Subject: [PATCH 08/33] fix terraform path on travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a310b33..bb7c5db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,8 @@ python: before_install: 'sudo apt-get install unzip' before_script: - 'wget https://releases.hashicorp.com/terraform/0.7.11/terraform_0.7.11_linux_amd64.zip' - - 'unzip terraform_0.7.11_linux_amd64.zip' - - './terraform --version' + - 'unzip terraform_0.7.11_linux_amd64.zip -d test' + - './test/terraform --version' install: - "pip install -r test/requirements.txt" From fcd2883ce966279d273feedfeeb9b4444f9ff75e Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 17:31:04 +0800 Subject: [PATCH 09/33] fix terraform path on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index bb7c5db..35f5528 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,5 +20,6 @@ install: - "pip install ." # command to run tests script: + - "export PAHT=$PATH:$PWD/test" - cd test - py.test \ No newline at end of file From baf18a3e0b19c9f956d39e20b743df6425d383cb Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 17:31:23 +0800 Subject: [PATCH 10/33] fix typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 35f5528..1ce910d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,6 @@ install: - "pip install ." # command to run tests script: - - "export PAHT=$PATH:$PWD/test" + - "export PATH=$PATH:$PWD/test" - cd test - py.test \ No newline at end of file From fcdbc612a58bdb673b0b199c0d2e5aa71567cd8f Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 17:35:17 +0800 Subject: [PATCH 11/33] fix terraform path --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ce910d..55ce50f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,14 +12,14 @@ python: before_install: 'sudo apt-get install unzip' before_script: - 'wget https://releases.hashicorp.com/terraform/0.7.11/terraform_0.7.11_linux_amd64.zip' - - 'unzip terraform_0.7.11_linux_amd64.zip -d test' - - './test/terraform --version' + - 'mkdir tf_bin' + - 'unzip terraform_0.7.11_linux_amd64.zip -d tf_bin' install: - "pip install -r test/requirements.txt" - "pip install ." # command to run tests script: - - "export PATH=$PATH:$PWD/test" + - "export PATH=$PATH:$PWD/tf_bin" - cd test - py.test \ No newline at end of file From 201082fefb6fe255f269c3c332e4cac9d418a51d Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Fri, 18 Nov 2016 17:39:25 +0800 Subject: [PATCH 12/33] update readme with travis icon --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 64cb5d5..dd06cb6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ 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/ -## This project is not stable yet +### Status +[![Build Status](https://travis-ci.org/beelit94/python-terraform.svg?branch=develop)](https://travis-ci.org/beelit94/python-terraform) ## Installation pip install git+https://github.com/beelit94/python-terraform.git@develop From bd1d08af710851603b8270d2d72d0ae5364610fe Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 18:24:33 +0800 Subject: [PATCH 13/33] 1. update travis deploy 2. update README to adapt change 3. use IsFlagged class for flag option 4. add destroy method --- .travis.yml | 25 +++++++++- LICENSE.txt | 7 +++ README.md | 54 +++++++++++++++++++++ python_terraform/__init__.py | 94 +++++++++++++++++++++++++++--------- test/test_terraform.py | 17 +++++-- 5 files changed, 167 insertions(+), 30 deletions(-) create mode 100644 LICENSE.txt diff --git a/.travis.yml b/.travis.yml index 55ce50f..ec5af18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,4 +22,27 @@ install: script: - "export PATH=$PATH:$PWD/tf_bin" - cd test - - py.test \ No newline at end of file + - py.test + +deploy: + # test pypi + - provider: pypi + distributions: sdist + server: https://testpypi.python.org/pypi + user: "beelit94" + password: + secure: "sWxc+p/gdq3k2WbUGNG2F4TukFNkTkvq6OPaiOvyfgWThYNk6/juRkMd8flmTbh0VGhcjFbpDLeSApb2kFhfiokYJSH1hOOcmXf8xzYH8/+R4DDEiGa5Y/pR9TBvYu4S8eJEfFUFfb1BBpapykj7o43hcaqMExBJIdVJU7aeoEAC1jQeTJh8wWwdJKHy2dNSM+6RVhk3e5+b0LfK7Bk5sU5P+YdEMj79MJU450J4OmZXWzJgvBN5/2QfVa5LrUD00nYuGuiBniz2lVevIHWjUYawUzpPsTa7F0s2WemG9YcV7U8u06xNjY9Ce3CTbxNhc7OIKq+TCkOgR3qZFXVJ8A87G+AT2iQ01VslQ4DJCxnJNTnpqojWnwf6MFL9O8ONioWYO32bhQFKOQ806ASHP4lNMRDKqx8hXtP5In7/r0SARKscv6Bas83rp+FESkKD5vWgkZJG+yx96LlwRLUhSVnVyb/nOJ++zt5RR3BvY2O4p9YAZY3Qt8TQihOdBQKnY3UXsMyNaE25+yvyNWpmyJiePRbTUd+cpLnycnqG9Ll8v6TpFXb6ahFMjlAFfJNQYlREfseClTHSRjZNxfsXGQCsJh6TZAq7jOB5hCk3q41eOUFWARxbyj8j59NBV8fSQrrGJJ9/VZKQeYiQlBB9KpK4PrnH84oeQ8i+VSbVr5w=" + on: + branch: release + tags: false + condition: $TRAVIS_PYTHON_VERSION = "2.7" + # production pypi +- provider: pypi + distributions: sdist + user: "beelit94" + password: + secure: "QhCiTLrBvw/Uzt3eiLEmvMP3uHnayVCETqEDA+2+Q9vFavqj0CHA76zqYonBFqnh0a3HFCRIVVt+6ynpZ10kpQ3tAObIw+pY39ZPnpAhOjSpFzzMdpIF9Bhv9A93ng2iSESAZPAOwktHzUwjFx0Zvl0lSYD9rutHgttGgdU2CajiUtwTUhCTjOAVdR2Gm+15H808vzKWnMaKflXxZt+fkt279mQTYAtz6eBWtZwIKry/uAJCSrPSWtbi50O0HsWRMXLXWH5Jn/BVjWSDSM92DssUDq0D+tQyp4M5nQXJ9EyAvEdsKNLx3cvNruznh2ohI2jmcoIjwFiS6+wrEmUiXkP86iyzCSqL/EbcOG0xUh3vbfYtMBp7jENgD405+3SEhPY4PlqUmc+HDtB7FUcHz4y7wGWJRGyQzNnjJ6Tv0Ajdz5mfJubWVIvHjcRqkxTVtUKt50o00xZ62M0ZzQkDTIHQEsZly0XeHAgSvNzWkmjt9BiBrZ9OkoWVkRpSrCBy/EcpDNPCTSfSzOQ0Nq1ePFjkkW1n8QWDW9Pdb+/7/P2y9E2S8CT+nXBkRQeQiO86Qf1Ireg7k9TA5VYisVZ6bEXEc9UV0mAojpSsC7zWhVlbAoltN6ZbjKmqy/wqn2QIcJemcSie0JigzKpdw7l8FPT2lCRyTKlYLpRyKXzSkNI=" + on: + branch: master + tags: true + condition: $TRAVIS_PYTHON_VERSION = "2.7" \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..70afd37 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (c) 2016 beelit94@gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index dd06cb6..1735927 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,57 @@ python-terraform is a python module provide a wrapper of `terraform` command lin ## Installation pip install git+https://github.com/beelit94/python-terraform.git@develop + +## Usage +For any terraform command + + def cmd(self, cmd, *args, **kwargs): + """ + run a terraform command, if success, will try to read state file + :param cmd: command and sub-command of terraform, seperated with space + refer to https://www.terraform.io/docs/commands/index.html + :param args: arguments of a command + :param kwargs: any option flag with key value without prefixed dash character + if there's a dash in the option name, use under line instead of dash, + ex. -no-color --> no_color + if it's a simple flag with no value, value should be IsFlagged + ex. cmd('taint', allow_missing=IsFlagged) + if it's a boolean value flag, assign True or false + if it's a flag could be used multiple times, assign list to it's value + if it's a "var" variable flag, assign dictionary to it + if a value is None, will skip this option + :return: ret_code, out, err + +For apply/destroy method, the flag options, like, `-no-color` or `-force` +have been implemented as boolean argument. simply use `is_no_color=True/False` for +apply/destroy method + + +## Examples +### Have a test.tf file under folder "/home/test" +#### apply with variables a=b, c=d, refresh=False, no color in the output +In shell: + + cd /home/test + terraform apply -var='a=b' -var='c=d' -refresh=false -no-color + +In python-terraform: + + from python_terraform import Terraform + tf = terraform(working_dir='/home/test') + tf.apply(is_no_color=True, refresh=False, var={'a':'b', 'c':'d'}) +#### taint command, allow-missing and no color +In shell: + + cd /home/test + terraform taint -allow-missing -no-color + +In python-terraform: + + from python_terraform import Terraform + tf = terraform(working_dir='/home/test') + tf.cmd('taint', allow_missing=IsFlagged, no_color=IsFlagged) + + + + \ No newline at end of file diff --git a/python_terraform/__init__.py b/python_terraform/__init__.py index 5b947a3..3a183e0 100644 --- a/python_terraform/__init__.py +++ b/python_terraform/__init__.py @@ -8,6 +8,14 @@ from python_terraform.tfstate import Tfstate log = logging.getLogger(__name__) +class IsFlagged: + pass + + +class IsNotFlagged: + pass + + class Terraform: """ Wrapper of terraform command line tool @@ -21,6 +29,15 @@ class Terraform: parallelism=None, var_file=None, terraform_bin_path=None): + """ + :param working_dir: the folder of the working folder, if not given, will be where python + :param targets: list of target + :param state: path of state file relative to working folder + :param variables: variables for apply/destroy/plan command + :param parallelism: parallelism for apply/destroy command + :param var_file: if specified, variables will not be used + :param terraform_bin_path: binary path of terraform + """ self.working_dir = working_dir self.state = state self.targets = [] if targets is None else targets @@ -29,42 +46,62 @@ class Terraform: self.terraform_bin_path = terraform_bin_path \ if terraform_bin_path else 'terraform' self.var_file = var_file - self.input = False # store the tfstate data self.tfstate = dict() def apply(self, - working_dir=None, - no_color=True, + dir=None, + is_no_color=True, + is_input=False, **kwargs): """ refer to https://terraform.io/docs/commands/apply.html - :param working_dir: working folder - :param no_color: Disables output with coloring. + :raise RuntimeError when return code is not zero + :param is_no_color: if True, add flag -no-color + :param is_input: if True, add option -input=true + :param dir: folder relative to working folder + :param kwargs: same as kwags in method 'cmd' :returns return_code, stdout, stderr """ - if not working_dir: - working_dir = self.working_dir + args, option_dict = self._create_cmd_args(is_input, + is_no_color, + dir, + kwargs) + + return self.cmd('apply', *args, **option_dict) + + def _create_cmd_args(self, is_input, is_no_color, dir, kwargs): option_dict = dict() option_dict['state'] = self.state option_dict['target'] = self.targets option_dict['var'] = self.variables option_dict['var_file'] = self.var_file option_dict['parallelism'] = self.parallelism - if no_color: - option_dict['no_color'] = '' - option_dict['input'] = self.input - + if is_no_color: + option_dict['no_color'] = IsFlagged + option_dict['input'] = is_input option_dict.update(kwargs) + args = [dir] if dir else [] + return args, option_dict - args = [working_dir] if working_dir else [] + def destroy(self, working_dir=None, is_force=True, + is_no_color=True, is_input=False, **kwargs): + """ + refer to https://www.terraform.io/docs/commands/destroy.html + :raise RuntimeError when return code is not zero + :return: ret_code, stdout, stderr + """ - ret, out, err = self.cmd('apply', *args, **option_dict) + args, option_dict = self._create_cmd_args(is_input, + is_no_color, + working_dir, + kwargs) + if is_force: + option_dict['force'] = IsFlagged - if ret != 0: - raise RuntimeError(err) + return self.cmd('destroy', *args, **option_dict) def generate_cmd_string(self, cmd, *args, **kwargs): """ @@ -82,11 +119,11 @@ class Terraform: --> generate_cmd_string call: terraform apply -var='a=b' -var='c=d' -no-color the_folder --> python call: - tf.generate_cmd_string('apply', the_folder, no_color='', var={'a':'b', 'c':'d'}) + tf.generate_cmd_string('apply', the_folder, no_color=IsFlagged, var={'a':'b', 'c':'d'}) :param cmd: command and sub-command of terraform, seperated with space refer to https://www.terraform.io/docs/commands/index.html - :param args: argument other than options of a command + :param args: arguments of a command :param kwargs: same as kwags in method 'cmd' :return: string of valid terraform command """ @@ -110,10 +147,13 @@ class Terraform: continue # simple flag, - if v == '': + if v is IsFlagged: cmds += ['-{k}'.format(k=k)] continue + if v is IsNotFlagged: + continue + if not v: continue @@ -131,10 +171,12 @@ class Terraform: run a terraform command, if success, will try to read state file :param cmd: command and sub-command of terraform, seperated with space refer to https://www.terraform.io/docs/commands/index.html - :param args: argument other than options of a command - :param kwargs: any option flag with key value other than variables, - if there's a dash in the option name, use under line instead of dash, ex -no-color --> no_color - if it's a simple flag with no value, value should be empty string + :param args: arguments of a command + :param kwargs: any option flag with key value without prefixed dash character + if there's a dash in the option name, use under line instead of dash, + ex. -no-color --> no_color + if it's a simple flag with no value, value should be IsFlagged + ex. cmd('taint', allow_missing=IsFlagged) if it's a boolean value flag, assign True or false if it's a flag could be used multiple times, assign list to it's value if it's a "var" variable flag, assign dictionary to it @@ -144,8 +186,11 @@ class Terraform: cmd_string = self.generate_cmd_string(cmd, *args, **kwargs) log.debug('command: {c}'.format(c=cmd_string)) + working_folder = self.working_dir if self.working_dir else None + p = subprocess.Popen(cmd_string, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) + stderr=subprocess.PIPE, shell=True, + cwd=working_folder) out, err = p.communicate() ret_code = p.returncode log.debug('output: {o}'.format(o=out)) @@ -162,8 +207,9 @@ class Terraform: :param name: name of output :return: output value """ - ret, out, err = self.cmd('output', name, json='') + ret, out, err = self.cmd('output', name, json=IsFlagged) + log.debug('output raw string: {0}'.format(out)) if ret != 0: return None out = out.lstrip() diff --git a/test/test_terraform.py b/test/test_terraform.py index 848b437..d5a9c83 100644 --- a/test/test_terraform.py +++ b/test/test_terraform.py @@ -1,16 +1,16 @@ -from python_terraform import Terraform +from python_terraform import * import pytest import os import logging import re -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.WARN) STRING_CASES = [ [ lambda x: x.generate_cmd_string('apply', 'the_folder', - no_color='', + no_color=IsFlagged, var={'a': 'b', 'c': 'd'}), "terraform apply -var='a=b' -var='c=d' -no-color the_folder" ], @@ -27,7 +27,7 @@ CMD_CASES = [ ['method', 'expected_output'], [ [ - lambda x: x.cmd('plan', 'apply_tf', no_color='', var={'test_var': 'test'}) , + lambda x: x.cmd('plan', 'apply_tf', no_color=IsFlagged, var={'test_var': 'test'}) , "doesn't need to do anything" ] ] @@ -73,9 +73,16 @@ class TestTerraform: def test_apply(self): tf = Terraform(working_dir='apply_tf', variables={'test_var': 'test'}) - tf.apply(var={'test_var': 'test2'}) + ret, out, err = tf.apply(var={'test_var': 'test2'}) + assert ret == 0 def test_get_output(self): tf = Terraform(working_dir='apply_tf', variables={'test_var': 'test'}) tf.apply() assert tf.output('test_output') == 'test' + + def test_destroy(self): + tf = Terraform(working_dir='apply_tf', variables={'test_var': 'test'}) + ret, out, err = tf.destroy() + assert ret == 0 + assert 'Destroy complete! Resources: 0 destroyed.' in out From 8a522295f1eb11cc6d2eb7b8a680b30b45a0144f Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 18:25:35 +0800 Subject: [PATCH 14/33] 1. update README --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1735927..6efd1e2 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,21 @@ python-terraform is a python module provide a wrapper of `terraform` command lin For any terraform command def cmd(self, cmd, *args, **kwargs): - """ - run a terraform command, if success, will try to read state file - :param cmd: command and sub-command of terraform, seperated with space - refer to https://www.terraform.io/docs/commands/index.html - :param args: arguments of a command - :param kwargs: any option flag with key value without prefixed dash character - if there's a dash in the option name, use under line instead of dash, - ex. -no-color --> no_color - if it's a simple flag with no value, value should be IsFlagged - ex. cmd('taint', allow_missing=IsFlagged) - if it's a boolean value flag, assign True or false - if it's a flag could be used multiple times, assign list to it's value - if it's a "var" variable flag, assign dictionary to it - if a value is None, will skip this option - :return: ret_code, out, err + """ + run a terraform command, if success, will try to read state file + :param cmd: command and sub-command of terraform, seperated with space + refer to https://www.terraform.io/docs/commands/index.html + :param args: arguments of a command + :param kwargs: any option flag with key value without prefixed dash character + if there's a dash in the option name, use under line instead of dash, + ex. -no-color --> no_color + if it's a simple flag with no value, value should be IsFlagged + ex. cmd('taint', allow_missing=IsFlagged) + if it's a boolean value flag, assign True or false + if it's a flag could be used multiple times, assign list to it's value + if it's a "var" variable flag, assign dictionary to it + if a value is None, will skip this option + :return: ret_code, out, err For apply/destroy method, the flag options, like, `-no-color` or `-force` have been implemented as boolean argument. simply use `is_no_color=True/False` for From 035566c514273a1659712947aaef07e08a924d0c Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 18:29:47 +0800 Subject: [PATCH 15/33] 1. ignore .cache --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index db6d66b..09f993d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.tfstate.backup *.pyc *.egg-info -.idea \ No newline at end of file +.idea +.cache \ No newline at end of file From 9df157d247b2ff061cfb3e45d8f39801f9090a41 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 18:37:13 +0800 Subject: [PATCH 16/33] test travis with branch setting --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec5af18..9945aa6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ deploy: password: secure: "sWxc+p/gdq3k2WbUGNG2F4TukFNkTkvq6OPaiOvyfgWThYNk6/juRkMd8flmTbh0VGhcjFbpDLeSApb2kFhfiokYJSH1hOOcmXf8xzYH8/+R4DDEiGa5Y/pR9TBvYu4S8eJEfFUFfb1BBpapykj7o43hcaqMExBJIdVJU7aeoEAC1jQeTJh8wWwdJKHy2dNSM+6RVhk3e5+b0LfK7Bk5sU5P+YdEMj79MJU450J4OmZXWzJgvBN5/2QfVa5LrUD00nYuGuiBniz2lVevIHWjUYawUzpPsTa7F0s2WemG9YcV7U8u06xNjY9Ce3CTbxNhc7OIKq+TCkOgR3qZFXVJ8A87G+AT2iQ01VslQ4DJCxnJNTnpqojWnwf6MFL9O8ONioWYO32bhQFKOQ806ASHP4lNMRDKqx8hXtP5In7/r0SARKscv6Bas83rp+FESkKD5vWgkZJG+yx96LlwRLUhSVnVyb/nOJ++zt5RR3BvY2O4p9YAZY3Qt8TQihOdBQKnY3UXsMyNaE25+yvyNWpmyJiePRbTUd+cpLnycnqG9Ll8v6TpFXb6ahFMjlAFfJNQYlREfseClTHSRjZNxfsXGQCsJh6TZAq7jOB5hCk3q41eOUFWARxbyj8j59NBV8fSQrrGJJ9/VZKQeYiQlBB9KpK4PrnH84oeQ8i+VSbVr5w=" on: - branch: release + branch: release/* tags: false condition: $TRAVIS_PYTHON_VERSION = "2.7" # production pypi From 2ab2542c3e106e1683e20d9cc13ad1db811e51ce Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 18:39:37 +0800 Subject: [PATCH 17/33] test travis with branch setting --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9945aa6..ec5af18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ deploy: password: secure: "sWxc+p/gdq3k2WbUGNG2F4TukFNkTkvq6OPaiOvyfgWThYNk6/juRkMd8flmTbh0VGhcjFbpDLeSApb2kFhfiokYJSH1hOOcmXf8xzYH8/+R4DDEiGa5Y/pR9TBvYu4S8eJEfFUFfb1BBpapykj7o43hcaqMExBJIdVJU7aeoEAC1jQeTJh8wWwdJKHy2dNSM+6RVhk3e5+b0LfK7Bk5sU5P+YdEMj79MJU450J4OmZXWzJgvBN5/2QfVa5LrUD00nYuGuiBniz2lVevIHWjUYawUzpPsTa7F0s2WemG9YcV7U8u06xNjY9Ce3CTbxNhc7OIKq+TCkOgR3qZFXVJ8A87G+AT2iQ01VslQ4DJCxnJNTnpqojWnwf6MFL9O8ONioWYO32bhQFKOQ806ASHP4lNMRDKqx8hXtP5In7/r0SARKscv6Bas83rp+FESkKD5vWgkZJG+yx96LlwRLUhSVnVyb/nOJ++zt5RR3BvY2O4p9YAZY3Qt8TQihOdBQKnY3UXsMyNaE25+yvyNWpmyJiePRbTUd+cpLnycnqG9Ll8v6TpFXb6ahFMjlAFfJNQYlREfseClTHSRjZNxfsXGQCsJh6TZAq7jOB5hCk3q41eOUFWARxbyj8j59NBV8fSQrrGJJ9/VZKQeYiQlBB9KpK4PrnH84oeQ8i+VSbVr5w=" on: - branch: release/* + branch: release tags: false condition: $TRAVIS_PYTHON_VERSION = "2.7" # production pypi From 39d23caadb02f282d76ed6f81233480b9097f687 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 18:44:52 +0800 Subject: [PATCH 18/33] fix .travis.yml --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec5af18..a6522b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,12 +37,12 @@ deploy: tags: false condition: $TRAVIS_PYTHON_VERSION = "2.7" # production pypi -- provider: pypi - distributions: sdist - user: "beelit94" - password: - secure: "QhCiTLrBvw/Uzt3eiLEmvMP3uHnayVCETqEDA+2+Q9vFavqj0CHA76zqYonBFqnh0a3HFCRIVVt+6ynpZ10kpQ3tAObIw+pY39ZPnpAhOjSpFzzMdpIF9Bhv9A93ng2iSESAZPAOwktHzUwjFx0Zvl0lSYD9rutHgttGgdU2CajiUtwTUhCTjOAVdR2Gm+15H808vzKWnMaKflXxZt+fkt279mQTYAtz6eBWtZwIKry/uAJCSrPSWtbi50O0HsWRMXLXWH5Jn/BVjWSDSM92DssUDq0D+tQyp4M5nQXJ9EyAvEdsKNLx3cvNruznh2ohI2jmcoIjwFiS6+wrEmUiXkP86iyzCSqL/EbcOG0xUh3vbfYtMBp7jENgD405+3SEhPY4PlqUmc+HDtB7FUcHz4y7wGWJRGyQzNnjJ6Tv0Ajdz5mfJubWVIvHjcRqkxTVtUKt50o00xZ62M0ZzQkDTIHQEsZly0XeHAgSvNzWkmjt9BiBrZ9OkoWVkRpSrCBy/EcpDNPCTSfSzOQ0Nq1ePFjkkW1n8QWDW9Pdb+/7/P2y9E2S8CT+nXBkRQeQiO86Qf1Ireg7k9TA5VYisVZ6bEXEc9UV0mAojpSsC7zWhVlbAoltN6ZbjKmqy/wqn2QIcJemcSie0JigzKpdw7l8FPT2lCRyTKlYLpRyKXzSkNI=" - on: - branch: master - tags: true - condition: $TRAVIS_PYTHON_VERSION = "2.7" \ No newline at end of file + - provider: pypi + distributions: sdist + user: "beelit94" + password: + secure: "QhCiTLrBvw/Uzt3eiLEmvMP3uHnayVCETqEDA+2+Q9vFavqj0CHA76zqYonBFqnh0a3HFCRIVVt+6ynpZ10kpQ3tAObIw+pY39ZPnpAhOjSpFzzMdpIF9Bhv9A93ng2iSESAZPAOwktHzUwjFx0Zvl0lSYD9rutHgttGgdU2CajiUtwTUhCTjOAVdR2Gm+15H808vzKWnMaKflXxZt+fkt279mQTYAtz6eBWtZwIKry/uAJCSrPSWtbi50O0HsWRMXLXWH5Jn/BVjWSDSM92DssUDq0D+tQyp4M5nQXJ9EyAvEdsKNLx3cvNruznh2ohI2jmcoIjwFiS6+wrEmUiXkP86iyzCSqL/EbcOG0xUh3vbfYtMBp7jENgD405+3SEhPY4PlqUmc+HDtB7FUcHz4y7wGWJRGyQzNnjJ6Tv0Ajdz5mfJubWVIvHjcRqkxTVtUKt50o00xZ62M0ZzQkDTIHQEsZly0XeHAgSvNzWkmjt9BiBrZ9OkoWVkRpSrCBy/EcpDNPCTSfSzOQ0Nq1ePFjkkW1n8QWDW9Pdb+/7/P2y9E2S8CT+nXBkRQeQiO86Qf1Ireg7k9TA5VYisVZ6bEXEc9UV0mAojpSsC7zWhVlbAoltN6ZbjKmqy/wqn2QIcJemcSie0JigzKpdw7l8FPT2lCRyTKlYLpRyKXzSkNI=" + on: + branch: master + tags: true + condition: $TRAVIS_PYTHON_VERSION = "2.7" \ No newline at end of file From 486e5ad2541002c99b452fb1ca71e37b8be65429 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 18:53:22 +0800 Subject: [PATCH 19/33] test deploy trigger --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a6522b6..36cd804 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ deploy: password: secure: "sWxc+p/gdq3k2WbUGNG2F4TukFNkTkvq6OPaiOvyfgWThYNk6/juRkMd8flmTbh0VGhcjFbpDLeSApb2kFhfiokYJSH1hOOcmXf8xzYH8/+R4DDEiGa5Y/pR9TBvYu4S8eJEfFUFfb1BBpapykj7o43hcaqMExBJIdVJU7aeoEAC1jQeTJh8wWwdJKHy2dNSM+6RVhk3e5+b0LfK7Bk5sU5P+YdEMj79MJU450J4OmZXWzJgvBN5/2QfVa5LrUD00nYuGuiBniz2lVevIHWjUYawUzpPsTa7F0s2WemG9YcV7U8u06xNjY9Ce3CTbxNhc7OIKq+TCkOgR3qZFXVJ8A87G+AT2iQ01VslQ4DJCxnJNTnpqojWnwf6MFL9O8ONioWYO32bhQFKOQ806ASHP4lNMRDKqx8hXtP5In7/r0SARKscv6Bas83rp+FESkKD5vWgkZJG+yx96LlwRLUhSVnVyb/nOJ++zt5RR3BvY2O4p9YAZY3Qt8TQihOdBQKnY3UXsMyNaE25+yvyNWpmyJiePRbTUd+cpLnycnqG9Ll8v6TpFXb6ahFMjlAFfJNQYlREfseClTHSRjZNxfsXGQCsJh6TZAq7jOB5hCk3q41eOUFWARxbyj8j59NBV8fSQrrGJJ9/VZKQeYiQlBB9KpK4PrnH84oeQ8i+VSbVr5w=" on: - branch: release + branch: "release/**" tags: false condition: $TRAVIS_PYTHON_VERSION = "2.7" # production pypi From 2fc770faae05e69280ddd9fd9b0dbc4f2403526b Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 19:00:35 +0800 Subject: [PATCH 20/33] test deploy trigger --- .travis.yml | 3 +-- setup.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36cd804..3bb741b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,7 @@ install: # command to run tests script: - "export PATH=$PATH:$PWD/tf_bin" - - cd test - - py.test + - py.test test deploy: # test pypi diff --git a/setup.py b/setup.py index de4e272..1a847dd 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup( name=module_name, version=get_version(), url='https://github.com/beelit94/python-terraform', - license='BSD', + license='MIT', author='Freddy Tan', author_email='beelit94@gmail.com', description='This is a python module provide a wrapper of terraform command line tool', @@ -38,8 +38,8 @@ setup( # 'Development Status :: 1 - Planning', # 'Development Status :: 2 - Pre-Alpha', # 'Development Status :: 3 - Alpha', - # 'Development Status :: 4 - Beta', - 'Development Status :: 5 - Production/Stable', + 'Development Status :: 4 - Beta', + # 'Development Status :: 5 - Production/Stable', # 'Development Status :: 6 - Mature', # 'Development Status :: 7 - Inactive', 'Environment :: Console', From f4bec15d2b299f47362f6e46ee542bcc7a4f4e22 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 19:06:19 +0800 Subject: [PATCH 21/33] restore folder for build --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3bb741b..38c6ae6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,9 @@ install: # command to run tests script: - "export PATH=$PATH:$PWD/tf_bin" - - py.test test + - cd test + - py.test + - cd .. deploy: # test pypi From bac4f2270d4b4bd6c64309da0a811c194dc13081 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 19:17:08 +0800 Subject: [PATCH 22/33] try to build on travis --- .gitignore | 3 ++- setup.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 09f993d..9bdd69e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ *.pyc *.egg-info .idea -.cache \ No newline at end of file +.cache +/.pypirc diff --git a/setup.py b/setup.py index 1a847dd..32173c5 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ setup( 'Operating System :: POSIX', 'Operating System :: MacOS', 'Operating System :: Unix', - 'Operating System :: Windows', + # 'Operating System :: Windows', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', From 9b14fb38ab59249522ae661cac92c06081eddf2e Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 23:29:57 +0800 Subject: [PATCH 23/33] try to build on travis --- .travis.yml | 4 ++-- setup.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 38c6ae6..1515c44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ deploy: on: branch: "release/**" tags: false - condition: $TRAVIS_PYTHON_VERSION = "2.7" + condition: $TRAVIS_PYTHON_VERSION = "3.5" # production pypi - provider: pypi distributions: sdist @@ -46,4 +46,4 @@ deploy: on: branch: master tags: true - condition: $TRAVIS_PYTHON_VERSION = "2.7" \ No newline at end of file + condition: $TRAVIS_PYTHON_VERSION = "3.5" \ No newline at end of file diff --git a/setup.py b/setup.py index 32173c5..298a945 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ """ My Tool does one thing, and one thing well. """ -from distutils.core import setup +from setuptools import setup import os dependencies = [] @@ -28,9 +28,7 @@ setup( description='This is a python module provide a wrapper of terraform command line tool', long_description=__doc__, packages=['python_terraform'], - include_package_data=True, package_data={}, - zip_safe=False, platforms='any', install_requires=dependencies, classifiers=[ @@ -44,7 +42,7 @@ setup( # 'Development Status :: 7 - Inactive', 'Environment :: Console', 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', + 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', 'Operating System :: MacOS', 'Operating System :: Unix', From 37f46a1cd475bd07e888a68fe096c840efeb978e Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 23:30:25 +0800 Subject: [PATCH 24/33] try to build on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1515c44..2fc7f35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ before_script: - 'unzip terraform_0.7.11_linux_amd64.zip -d tf_bin' install: + - "curl https://bootstrap.pypa.io/ez_setup.py -o - | python" - "pip install -r test/requirements.txt" - "pip install ." # command to run tests From 88048d290d3310000d22968bd3b433abdbfce8c7 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 23:36:32 +0800 Subject: [PATCH 25/33] try to build on travis --- .travis.yml | 1 + setup.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2fc7f35..b30b973 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ before_script: install: - "curl https://bootstrap.pypa.io/ez_setup.py -o - | python" - "pip install -r test/requirements.txt" + - "pip install pypandoc" - "pip install ." # command to run tests script: diff --git a/setup.py b/setup.py index 298a945..dc32a6a 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,12 @@ import os dependencies = [] module_name = 'python-terraform' +try: + import pypandoc + long_description = pypandoc.convert('README.md', 'rst') +except(IOError, ImportError): + long_description = open('README.md').read() + def get_version(): p = os.path.join(os.path.dirname( @@ -26,7 +32,7 @@ setup( author='Freddy Tan', author_email='beelit94@gmail.com', description='This is a python module provide a wrapper of terraform command line tool', - long_description=__doc__, + long_description=long_description, packages=['python_terraform'], package_data={}, platforms='any', From 3c98d18c8629bd1099197297690d2dd424acc455 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 23:47:04 +0800 Subject: [PATCH 26/33] try to build on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b30b973..2b6d084 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ before_script: install: - "curl https://bootstrap.pypa.io/ez_setup.py -o - | python" - "pip install -r test/requirements.txt" - - "pip install pypandoc" + - "sudo apt-get -y install pypandoc" - "pip install ." # command to run tests script: From 1ba2ba3894822d0e8d8f329a39e5639e11974e76 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sat, 19 Nov 2016 23:56:00 +0800 Subject: [PATCH 27/33] try to build on travis --- .travis.yml | 2 +- setup.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2b6d084..c3bebc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ before_script: install: - "curl https://bootstrap.pypa.io/ez_setup.py -o - | python" - "pip install -r test/requirements.txt" - - "sudo apt-get -y install pypandoc" + - "sudo apt-get -y install pandoc" - "pip install ." # command to run tests script: diff --git a/setup.py b/setup.py index dc32a6a..a9a3acf 100644 --- a/setup.py +++ b/setup.py @@ -6,12 +6,14 @@ import os dependencies = [] module_name = 'python-terraform' +short_description = 'This is a python module provide a wrapper ' \ + 'of terraform command line tool' try: import pypandoc long_description = pypandoc.convert('README.md', 'rst') -except(IOError, ImportError): - long_description = open('README.md').read() +except(IOError, ImportError, OSError): + long_description = short_description def get_version(): @@ -31,7 +33,7 @@ setup( license='MIT', author='Freddy Tan', author_email='beelit94@gmail.com', - description='This is a python module provide a wrapper of terraform command line tool', + description=short_description, long_description=long_description, packages=['python_terraform'], package_data={}, From a3a75ecf64552cf785d348a745b9d331652c4866 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sun, 20 Nov 2016 00:00:00 +0800 Subject: [PATCH 28/33] add pypandoc --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c3bebc3..751fc23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ install: - "curl https://bootstrap.pypa.io/ez_setup.py -o - | python" - "pip install -r test/requirements.txt" - "sudo apt-get -y install pandoc" + - "pip install pypandoc" - "pip install ." # command to run tests script: From a17e3517c0c3f6cec5ce8086158f46146e5edc18 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sun, 20 Nov 2016 00:35:14 +0800 Subject: [PATCH 29/33] change version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7deb86f..d5cc44d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.1 \ No newline at end of file +0.7.2 \ No newline at end of file From 4989717379c24892343dab10af3e549a54121058 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sun, 20 Nov 2016 00:48:59 +0800 Subject: [PATCH 30/33] add description to pypi --- DESCRIPTION.rst | 3 +++ setup.py | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 DESCRIPTION.rst diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst new file mode 100644 index 0000000..2ed66b9 --- /dev/null +++ b/DESCRIPTION.rst @@ -0,0 +1,3 @@ +Please see README at github_ + +.. _github: https://github.com/beelit94/python-terraform \ No newline at end of file diff --git a/setup.py b/setup.py index a9a3acf..7498776 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """ -My Tool does one thing, and one thing well. +This is a python module provide a wrapper of terraform command line tool """ from setuptools import setup import os @@ -9,11 +9,8 @@ module_name = 'python-terraform' short_description = 'This is a python module provide a wrapper ' \ 'of terraform command line tool' -try: - import pypandoc - long_description = pypandoc.convert('README.md', 'rst') -except(IOError, ImportError, OSError): - long_description = short_description +with open('DESCRIPTION.rst') as f: + long_description = f.read() def get_version(): From 25f8721b69fadae4e058561b1c105bfdd7d0be74 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sun, 20 Nov 2016 01:05:19 +0800 Subject: [PATCH 31/33] add description to pypi --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index d5cc44d..b09a54c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.2 \ No newline at end of file +0.7.3 \ No newline at end of file From 1886025f78e88012ff7ffbbb266bec2e07581561 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sun, 20 Nov 2016 01:11:21 +0800 Subject: [PATCH 32/33] change description --- DESCRIPTION.rst | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst index 2ed66b9..f9deb59 100644 --- a/DESCRIPTION.rst +++ b/DESCRIPTION.rst @@ -1,3 +1,3 @@ Please see README at github_ -.. _github: https://github.com/beelit94/python-terraform \ No newline at end of file +.. _github: https://github.com/beelit94/python-terraform/blob/master/README.md \ No newline at end of file diff --git a/README.md b/README.md index 6efd1e2..01c7b0d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ python-terraform is a python module provide a wrapper of `terraform` command lin [![Build Status](https://travis-ci.org/beelit94/python-terraform.svg?branch=develop)](https://travis-ci.org/beelit94/python-terraform) ## Installation - pip install git+https://github.com/beelit94/python-terraform.git@develop + pip install python-terraform ## Usage For any terraform command From 59d203d15facbf6e4993417d5293ae044fc9cf07 Mon Sep 17 00:00:00 2001 From: Freddy Tan Date: Sun, 20 Nov 2016 01:12:32 +0800 Subject: [PATCH 33/33] change version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b09a54c..ef090a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.3 \ No newline at end of file +0.7.4 \ No newline at end of file