Skip to content
This repository was archived by the owner on Sep 13, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions tests/test_terraform.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import os

import pytest
from mock import ANY, MagicMock
Expand Down Expand Up @@ -66,7 +65,7 @@ def terraform(tmp_path, mocker):
@pytest.fixture
def resource(tmp_path):
name = "test-resource"
path = tmp_path / name / "terraform.tfstate"
path = tmp_path / "terraform.tfstate"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(TEST_RESOURCE_STATE, encoding="utf-8")
yield name
Expand All @@ -92,21 +91,15 @@ def test_make_pemfile():
assert fobj.read() == content


def test_make_tf(terraform):
name = "foo"
with terraform.make_tf(name) as tf:
assert tf.working_dir == os.path.join(terraform.tmp_dir, name)


def test_create(tmp_path, terraform):
name = "foo"
terraform.create(name=name, cloud="aws")
terraform.cmd_mock.assert_any_call("init", capture_output=False)
terraform.cmd_mock.assert_any_call(
"apply", capture_output=False, auto_approve=IsFlagged
)
assert (tmp_path / name / "main.tf.json").exists()
with open(tmp_path / name / "main.tf.json") as fobj:
assert (tmp_path / "main.tf.json").exists()
with open(tmp_path / "main.tf.json") as fobj:
data = json.load(fobj)
assert data["resource"]["iterative_machine"] == {
name: {"name": name, "cloud": "aws"},
Expand Down
44 changes: 16 additions & 28 deletions tpi/terraform.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asyncio
import os
import sys
from contextlib import contextmanager
from itertools import repeat
from typing import TYPE_CHECKING, Iterator, Optional, Union

Expand All @@ -18,22 +17,14 @@ class TPIException(Exception):


class TerraformBackend:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like we should maybe rename this? The base/terraform backend naming is another leftover from dvc-machine and doesn't really have any meaning in TPI.

def __init__(self, tmp_dir: StrPath, **kwargs):
self.tmp_dir = tmp_dir
os.makedirs(self.tmp_dir, exist_ok=True)
"""Class for managing a named TPI iterative-machine resource."""

@contextmanager
def make_tf(self, name: str):
from tpi import TerraformProviderIterative, TPIError
def __init__(self, working_dir: StrPath, **kwargs):
from tpi import TerraformProviderIterative

try:
working_dir = os.path.join(self.tmp_dir, name)
os.makedirs(working_dir, exist_ok=True)
yield TerraformProviderIterative(working_dir=working_dir)
except TPIError:
raise
except Exception as exc:
raise TPIError("terraform failed") from exc
self.working_dir = working_dir
os.makedirs(self.working_dir, exist_ok=True)
self.tf = TerraformProviderIterative(working_dir=self.working_dir)

def create(self, name: Optional[str] = None, **config):
"""Create and start an instance of the specified machine."""
Expand All @@ -42,29 +33,26 @@ def create(self, name: Optional[str] = None, **config):
from tpi import render_json

assert name and "cloud" in config
with self.make_tf(name) as tf:
tf_file = os.path.join(tf.working_dir, "main.tf.json")
with open(tf_file, "w", encoding="utf-8") as fobj:
fobj.write(render_json(name=name, **config, indent=2))
tf.cmd("init")
tf.cmd("apply", auto_approve=IsFlagged)
tf_file = os.path.join(self.working_dir, "main.tf.json")
with open(tf_file, "w", encoding="utf-8") as fobj:
fobj.write(render_json(name=name, **config, indent=2))
self.tf.cmd("init")
self.tf.cmd("apply", auto_approve=IsFlagged)
Comment on lines +36 to +40
Copy link
Contributor Author

@pmrowla pmrowla Oct 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one thing to note now is that since the init'ed working_dir is always used (instead of separate dirs per resource name), this class is currently only useful for managing a single iterative-machine resource at a time (since we just rewrite the entire main.tf.json any time a config change is made).

A proper TPI release should parse the entire existing main.tf.json and only add/modify/delete the appropriate named resource entries so that you can actually manage multiple resources at a time.


def destroy(self, name: Optional[str] = None, **config):
"""Stop and destroy all instances of the specified machine."""
from python_terraform import IsFlagged

assert name

with self.make_tf(name) as tf:
if first(tf.iter_instances(name)):
tf.cmd("destroy", auto_approve=IsFlagged)
if first(self.tf.iter_instances(name)):
self.tf.cmd("destroy", auto_approve=IsFlagged)

def instances(self, name: Optional[str] = None, **config) -> Iterator[dict]:
"""Iterate over status of all instances of the specified machine."""
assert name

with self.make_tf(name) as tf:
yield from tf.iter_instances(name)
yield from self.tf.iter_instances(name)

def close(self):
pass
Expand All @@ -73,7 +61,7 @@ def run_shell(self, name: Optional[str] = None, **config):
"""Spawn an interactive SSH shell for the specified machine."""
from tpi import TerraformProviderIterative

resource = self._default_resource(name)
resource = self.default_resource(name)
with TerraformProviderIterative.pemfile(resource) as pem:
self._shell(
host=resource["instance_ip"],
Expand All @@ -82,7 +70,7 @@ def run_shell(self, name: Optional[str] = None, **config):
known_hosts=None,
)

def _default_resource(self, name):
def default_resource(self, name):
from tpi import TPIError

resource = first(self.instances(name))
Expand Down