Skip to content

Commit

Permalink
feat: refactor module to chaosterraform
Browse files Browse the repository at this point in the history
  • Loading branch information
mcastellin committed Jun 1, 2023
1 parent c5cf881 commit 70de17c
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- run: ls -laR chaostf/
- run: ls -laR chaosterraform/

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- run: ls -laR chaostf/
- run: ls -laR chaosterraform/

- name: Install dependencies
run: |
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,10 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Terraform state files
.terraform*
terraform.tfstate*

# ChaosToolkit files
journal.json
3 changes: 1 addition & 2 deletions chaosterraform/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ def configure_control(
str(params.get("retain")),
)

driver = Terraform()
driver.configure(**params, args=tf_vars)
driver = Terraform(**params, args=tf_vars)
driver.terraform_init()


Expand Down
49 changes: 21 additions & 28 deletions chaosterraform/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import subprocess
from copy import deepcopy
from itertools import chain
from typing import Any, Dict, List

from chaoslib.exceptions import InterruptExecution
Expand All @@ -18,6 +19,11 @@
)


def _run(*cmd: List[List[str]], capture_output: bool = False, text: bool = False):
_cmd = list(chain(*cmd))
return subprocess.run(_cmd, shell=False, capture_output=capture_output, text=text)


def singleton(cls):
"""Creates a singleton wrapper for any class"""

Expand All @@ -33,20 +39,14 @@ def wrapper_singleton(*args, **kwargs):

@singleton
class Terraform:
def __init__(self):
super().__init__()
self.retain = False
self.silent = False
self.chdir = None
self.args = {}

def configure(
def __init__(
self,
retain: bool = False,
silent: bool = False,
chdir: str = None,
args: Dict = None,
):
super().__init__()
self.retain = retain
self.silent = silent
self.chdir = chdir
Expand All @@ -63,16 +63,12 @@ def _terraform(self):
raise InterruptExecution(
f"Terraform: chdir [{self.chdir}] is not a directory"
)
return f"terraform -chdir={self.chdir}"
return "terraform"
return ["terraform", f"-chdir={self.chdir}"]
return ["terraform"]

def terraform_init(self):
if not os.path.exists(".terraform"):
result = subprocess.run(
f"{self._terraform} init",
capture_output=self.silent,
shell=True,
)
result = _run(self._terraform, ["init"], capture_output=self.silent)
if result.returncode != 0:
raise InterruptExecution("Failed to initialize terraform")

Expand All @@ -85,30 +81,27 @@ def apply(self, **kwargs):
string_value = value
if isinstance(value, bool):
string_value = str(value).lower()
var_overrides.append(f"-var {key}='{string_value}'")
opts = " ".join(var_overrides)
var_overrides.extend(["-var", f"{key}='{string_value}'"])

result = subprocess.run(
f"{self._terraform} apply {opts} -auto-approve",
result = _run(
self._terraform,
["apply", "-auto-approve"],
var_overrides,
capture_output=self.silent,
shell=True,
)
if result.returncode != 0:
raise InterruptExecution("Failed to apply terraform stack terraform")

def output(self):
result = subprocess.run(
f"{self._terraform} output -json",
shell=True,
capture_output=True,
text=True,
result = _run(
self._terraform, ["output", "-json"], capture_output=True, text=True
)
outputs = json.loads(result.stdout)
return outputs

def destroy(self):
subprocess.run(
f"{self._terraform} destroy -auto-approve",
_run(
self._terraform,
["destroy", "-auto-approve"],
capture_output=self.silent,
shell=True,
)
16 changes: 16 additions & 0 deletions examples/experiment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
title: Example of chaostoolkit-terraform control
description: Use chaosterraform to deploy terraform code

controls:
- name: chaosterraform
provider:
type: python
module: chaosterraform.control
arguments:
silent: false

steady-state-hypothesis:
title: empty
probes: []

method: []
23 changes: 23 additions & 0 deletions examples/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "3.0.2"
}
}
}

provider "docker" {
host = "unix:///var/run/docker.sock"
}

# Pulls the image
resource "docker_image" "nginx" {
name = "nginx:latest"
}

# Create a container
resource "docker_container" "foo" {
image = docker_image.nginx.image_id
name = "foo"
}
97 changes: 97 additions & 0 deletions tests/test_driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from unittest.mock import patch
from chaosterraform import driver


class MockShellResponse:
def __init__(self, returncode=0, text=""):
self.returncode = returncode
self.stdout = text


@patch("chaosterraform.driver.os.path")
def test_chdir(mocked_path):
mocked_path.exists.return_value = True

driver.Terraform.instance_ = None
tf_driver = driver.Terraform(chdir="../testfolder/one")

assert tf_driver._terraform == ["terraform", "-chdir=../testfolder/one"]


@patch("subprocess.run")
def test_init_default_args(mocked_run):
mocked_run.return_value = MockShellResponse()
driver.Terraform.instance_ = None
tf_driver = driver.Terraform(silent=True)
tf_driver.terraform_init()

mocked_run.assert_called_once_with(
["terraform", "init"],
shell=False,
capture_output=True,
text=False,
)


@patch("subprocess.run")
def test_apply_default_args(mocked_run):
mocked_run.return_value = MockShellResponse()
driver.Terraform.instance_ = None
tf_driver = driver.Terraform()
tf_driver.apply()

mocked_run.assert_called_once_with(
["terraform", "apply", "-auto-approve"],
shell=False,
capture_output=False,
text=False,
)


@patch("subprocess.run")
def test_apply_verbose(mocked_run):
mocked_run.return_value = MockShellResponse()
driver.Terraform.instance_ = None
tf_driver = driver.Terraform(silent=True)
tf_driver.apply()

mocked_run.assert_called_once_with(
["terraform", "apply", "-auto-approve"],
shell=False,
capture_output=True,
text=False,
)


@patch("subprocess.run")
def test_destroy_default_args(mocked_run):
driver.Terraform.instance_ = None
tf_driver = driver.Terraform()
tf_driver.destroy()

mocked_run.assert_called_once_with(
["terraform", "destroy", "-auto-approve"],
shell=False,
capture_output=False,
text=False,
)


@patch("subprocess.run")
def test_output_strings(mocked_run):
return_text = '{"expected":"value","one": "1","boolean":true,"number":999}'
mocked_run.return_value = MockShellResponse(text=return_text)
driver.Terraform.instance_ = None
tf_driver = driver.Terraform()
result = tf_driver.output()

mocked_run.assert_called_once_with(
["terraform", "output", "-json"],
shell=False,
capture_output=True,
text=True,
)
assert result.get("expected") == "value"
assert result.get("one") == "1"
assert result.get("boolean") is True
assert result.get("number") == 999

0 comments on commit 70de17c

Please sign in to comment.