Skip to content

Commit

Permalink
add terraform-init extension (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
thrau authored Jul 10, 2024
1 parent 0c5c0c2 commit ecbffaa
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ You can install the respective extension by calling `localstack install <Install
| [MailHog](https://github.com/localstack/localstack-extensions/tree/main/mailhog) | localstack-extension-mailhog | 0.1.0 | Stable |
| [Miniflare](https://github.com/localstack/localstack-extensions/tree/main/miniflare) | localstack-extension-miniflare | 0.1.0 | Experimental |
| [Stripe](https://github.com/localstack/localstack-extensions/tree/main/stripe) | localstack-extension-stripe | 0.1.0 | Stable |
| [Terraform Init](https://github.com/localstack/localstack-extensions/tree/main/terraform-init) | localstack-extension-terraform-init | 0.2.0 | Experimental |


## Developing Extensions
Expand Down
36 changes: 36 additions & 0 deletions terraform-init/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
VENV_BIN = python3 -m venv
VENV_DIR ?= .venv
VENV_ACTIVATE = $(VENV_DIR)/bin/activate
VENV_RUN = . $(VENV_ACTIVATE)

venv: $(VENV_ACTIVATE)

$(VENV_ACTIVATE): setup.py setup.cfg
test -d .venv || $(VENV_BIN) .venv
$(VENV_RUN); pip install --upgrade pip setuptools plux build
$(VENV_RUN); pip install --upgrade black isort
$(VENV_RUN); pip install -e .
touch $(VENV_DIR)/bin/activate

clean:
rm -rf .venv/
rm -rf build/
rm -rf .eggs/
rm -rf *.egg-info/

install: venv
$(VENV_RUN); python setup.py develop

format: venv
$(VENV_RUN); python -m isort .; python -m black .

dist: venv
$(VENV_RUN); python -m build

publish: clean-dist venv dist
$(VENV_RUN); pip install --upgrade twine; twine upload dist/*

clean-dist: clean
rm -rf dist/

.PHONY: clean clean-dist dist install publish
110 changes: 110 additions & 0 deletions terraform-init/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
Use Terraform files in LocalStack init hooks
============================================

LocalStack Extension for using Terraform files in [init hooks](https://docs.localstack.cloud/references/init-hooks/).

> [!WARNING]
> This extension is experimental and subject to change.
> [!NOTE]
> The extension is designed for simple self-contained terraform files, not complex projects or modules.
> If you have larger projects, then we recommend running them from the host.
## Usage

* Start localstack with `EXTENSION_AUTO_INSTALL="localstack-extension-terraform-init"`
* Mount a `main.tf` file into `/etc/localstack/init/ready.d`

When LocalStack starts up, it will install the extension, which in turn install `terraform` and `tflocal` into the container.
If one of the init stage directories contain a `main.tf`, the extension will run `tflocal init` and `tflocal apply` on that directory.

> [!NOTE]
> Terraform state files will be created in your host directory if you mounted an entire folder into `/etc/localstack/init/ready.d`.
> These files are created from within the container using the container user, so you may need `sudo` to remove the files from your host.
> If you only mount the `main.tf` file, not an entire directory, localstack will have to download the AWS terraform provider every time during `tflocal init`.
>
### Example

Example `main.tf`:
```hcl
resource "aws_s3_bucket" "example" {
bucket = "my-tf-test-bucket"
tags = {
Name = "My bucket"
Environment = "Dev"
}
}
```

Start LocalStack Pro with mounted `main.tf`:

```console
localstack start \
-e EXTENSION_AUTO_INSTALL="localstack-extension-terraform-init" \
-v ./main.tf:/etc/localstack/init/ready.d/main.tf
```

Or, if you use a docker-compose file:

```yaml
services:
localstack:
container_name: "localstack-main"
image: localstack/localstack-pro # required for Pro
ports:
- "127.0.0.1:4566:4566" # LocalStack Gateway
environment:
# Activate LocalStack Pro: https://docs.localstack.cloud/getting-started/auth-token/
- LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:?}
- EXTENSION_AUTO_LOAD=localstack-extension-terraform-init"
volumes:
# you could also place your main.tf in `./ready.d` and set "./ready.d:/etc/localstack/init/ready.d"
- "./main.tf:/etc/localstack/init/ready.d/main.tf"
- "./volume:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
```
In a new terminal window, you can wait for localstack to complete and then print the created s3 buckets.
```console
localstack wait && awslocal s3 ls
```

The logs should show something like:

```
2024-06-26T20:36:19.946 INFO --- [ady_monitor)] l.extension : Applying terraform project from file /etc/localstack/init/ready.d/main.tf
2024-06-26T20:36:19.946 DEBUG --- [ady_monitor)] localstack.utils.run : Executing command: ['tflocal', '-chdir=/etc/localstack/init/ready.d', 'init', '-input=false']
2024-06-26T20:36:26.864 DEBUG --- [ady_monitor)] localstack.utils.run : Executing command: ['tflocal', '-chdir=/etc/localstack/init/ready.d', 'apply', '-auto-approve']
```

## Install local development version

To install the extension into localstack in developer mode, you will need Python 3.10, and create a virtual environment in the extensions project.

In the newly generated project, simply run

```bash
make install
```

Then, to enable the extension for LocalStack, run

```bash
localstack extensions dev enable .
```

You can then start LocalStack with `EXTENSION_DEV_MODE=1` to load all enabled extensions:

```bash
EXTENSION_DEV_MODE=1 localstack start
```

## Install from GitHub repository

To distribute your extension, simply upload it to your github account. Your extension can then be installed via:

```bash
localstack extensions install "git+https://github.com/localstack/localstack-extensions/#egg=localstack-extension-terraform-init&subdirectory=terraform-init"
```
1 change: 1 addition & 0 deletions terraform-init/localstack_terraform_init/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name = "localstack_terraform_init"
79 changes: 79 additions & 0 deletions terraform-init/localstack_terraform_init/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import logging
import os
from typing import List

from localstack import config
from localstack.extensions.api import Extension
from localstack.packages import InstallTarget, Package, PackageInstaller
from localstack.packages.core import PythonPackageInstaller
from localstack.packages.terraform import terraform_package
from localstack.runtime.init import ScriptRunner
from localstack.utils.run import run

LOG = logging.getLogger(__name__)


class TflocalInitExtension(Extension):
# the extension itself is just used for discoverability
name = "localstack-terraform-init"

def on_extension_load(self):
logging.getLogger("localstack_terraform_init").setLevel(
logging.DEBUG if config.DEBUG else logging.INFO
)


class TflocalPackage(Package):
def __init__(self, default_version: str = "0.18.2"):
super().__init__(name="terraform_local", default_version=default_version)

def _get_installer(self, version: str) -> PackageInstaller:
return TflocalPackageInstaller(version)

def get_versions(self) -> List[str]:
return [self.default_version]


class TflocalPackageInstaller(PythonPackageInstaller):
def __init__(self, version: str):
super().__init__("terraform_local", version)


tflocal_package = TflocalPackage()


class TflocalScriptRunner(ScriptRunner):
name = "tflocal"

def load(self, *args, **kwargs):
terraform_package.install()
tflocal_package.install()

def should_run(self, script_file: str) -> bool:
if os.path.basename(script_file) == "main.tf":
return True
return False

def run(self, path: str) -> None:
# create path to find ``terraform`` and ``tflocal`` binaries
# TODO: better way to define path
tf_path = terraform_package.get_installed_dir()
install_dir = tflocal_package.get_installer()._get_install_dir(
InstallTarget.VAR_LIBS
)
tflocal_path = f"{install_dir}/bin"
env_path = f"{tflocal_path}:{tf_path}:{os.getenv('PATH')}"

LOG.info("Applying terraform project from file %s", path)
# run tflocal
workdir = os.path.dirname(path)
LOG.debug("Initializing terraform provider in %s", workdir)
run(
["tflocal", f"-chdir={workdir}", "init", "-input=false"],
env_vars={"PATH": env_path},
)
LOG.debug("Applying terraform file %s", path)
run(
["tflocal", f"-chdir={workdir}", "apply", "-auto-approve"],
env_vars={"PATH": env_path},
)
23 changes: 23 additions & 0 deletions terraform-init/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[metadata]
name = localstack-extension-terraform-init
version = 0.2.0
summary = LocalStack Extension: LocalStack Terraform Init
url = https://github.com/localstack/localstack-extensions/tree/main/terraform-init
author = Thomas Rausch
author_email = thomas@localstack.cloud
description = LocalStack Extension for using Terraform files in init hooks
long_description = file: README.md
long_description_content_type = text/markdown; charset=UTF-8

[options]
zip_safe = False
packages = find:
install_requires =
localstack-core>=3.4
plux

[options.entry_points]
localstack.extensions =
localstack-terraform-init = localstack_terraform_init.extension:TflocalInitExtension
localstack.init.runner=
tflocal = localstack_terraform_init.extension:TflocalScriptRunner
4 changes: 4 additions & 0 deletions terraform-init/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env python
from setuptools import setup

setup()

0 comments on commit ecbffaa

Please sign in to comment.