diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..46889c9fc0057c14161f2a0b403d437749b23c04
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,60 @@
+~*
+*~
+*.sw[op]
+*.py[cod]
+.DS_Store
+__pycache__/
+__pypackages__/
+.vagrant/
+vendor/
+Thumbs.db
+*.retry
+.svn/
+.sass-cache/
+*.log
+*.out
+*.so
+node_modules/
+.npm/
+nbproject/
+*.ipynb
+.idea/
+*.egg-info/
+*.[ao]
+.classpath
+.cache/
+bower_components/
+*.class
+*.[ewj]ar
+secring.*
+.*.kate-swp
+.swp.*
+.directory
+.Trash-*
+build/
+_build/
+dist/
+.tox/
+*.pdf
+*.exe
+*.dll
+*.gz
+*.tgz
+*.tar
+*.rar
+*.zip
+*.xz
+*.pid
+*.lock
+*.env
+.bundle/
+!Pipfile.lock
+!pdm.lock
+!Gemfile.lock
+.terraform
+.terraform.*
+!.terraform.lock.hcl
+tfplan
+*.tfstate*
+*.venv
+reports/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ebf3774c62fd1726967205b3833019eb5c1b825b
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,21 @@
+---
+include:
+  - project: shore/ci-stuff
+    file: templates/pre-commit.yml
+  - project: shore/ci-stuff
+    file: templates/python.yml
+
+build-wheel:
+  extends: .python3
+  stage: build
+  script:
+    - python3 -m build
+  after_script:
+    - &twine_check twine check dist/*.whl
+  artifacts:
+    paths:
+      - dist/*.tar.gz
+      - dist/*.whl
+
+install-wheel:
+  extends: .install-wheel
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c274ad4aa27672616b67490cfde8208ec2eb30d9
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,59 @@
+---
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks.git
+    rev: v4.0.1
+    hooks:
+      - id: check-merge-conflict
+      - id: check-toml
+      - id: check-yaml
+      - id: end-of-file-fixer
+      - id: trailing-whitespace
+        exclude: \.diff$
+
+  - repo: https://github.com/codespell-project/codespell.git
+    rev: v2.1.0
+    hooks:
+      - id: codespell
+
+  - repo: https://github.com/amperser/proselint.git
+    rev: 0.10.2
+    hooks:
+      - id: proselint
+        types: [plain-text]
+        exclude: LICENSE
+
+  - repo: https://gitlab.com/devopshq/gitlab-ci-linter.git
+    rev: v1.0.2
+    hooks:
+      - id: gitlab-ci-linter
+        args:
+          - "--server"
+          - https://git.shore.co.il
+
+  - repo: https://git.shore.co.il/nimrod/yamltool.git
+    rev: v0.1.2
+    hooks:
+      - id: yamltool
+
+  - repo: https://github.com/adrienverge/yamllint.git
+    rev: v1.26.3
+    hooks:
+      - id: yamllint
+
+  - repo: https://github.com/Lucas-C/pre-commit-hooks-markup.git
+    rev: v1.0.1
+    hooks:
+      - id: rst-linter
+
+  - repo: https://github.com/myint/rstcheck.git
+    rev: v6.0.0.post1
+    hooks:
+      - id: rstcheck
+
+  - repo: https://github.com/ambv/black.git
+    rev: 22.3.0
+    hooks:
+      - id: black
+        args:
+          - |
+            --line-length=79
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9410b2874ea3ef47fc6d79c107d3ee7905d75c54
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Adar Nimrod
+
+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.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ef44faf83d96125b92bd78b5233b0f2087b50620
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,66 @@
+tf
+##
+
+.. image:: https://git.shore.co.il/nimrod/tf/badges/main/pipeline.svg
+    :target: https://git.shore.co.il/nimrod/tf/-/commits/main
+    :alt: pipeline status
+
+A simple Terraform wrapper to use variable definition files if they match the
+workspace name.
+
+Rationale
+---------
+
+With workspaces, one can use the same module in different environments with
+minor changes like different domain name, size of a cluster, instance type.
+One can use variable definition files to store the values for each workspace in
+a dedicated file. This wrapper replaces the following:
+
+.. code:: shell
+
+   terraform workspace select prod
+   terraform plan -var-files=prod.tfvars -out tfplan
+
+to:
+
+.. code:: shell
+
+   terraform workspace select prod
+   tf plan -out tfplan
+
+Installation
+------------
+
+.. code:: shell
+
+   python3 -m pip install terraformation
+
+The wrapper is a single Python3 script with no external dependencies. If you
+prefer, you can download the :code:`tf.py` and use that instead.
+
+Usage
+-----
+
+Replace :code:`terraform` with :code:`tf`. In case there's a variable
+definitions file (that ends with :code:`.tfvars`) that matches the current
+workspace name (if the current workspace name is :code:`prod` and a file named
+:code:`prod.tfvars` exists) than a :code:`-var-file=prod.tfvars` argument is
+added to the relevant commands (like :code:`plan` and :code:`import`). All
+other arguments are kept as they were. Similarly, if a directory exists with
+the same name as the workspace, for all the files inside that directory that
+end with :code:`.tfvars`, a :code:`-var-file` argument is added. For example:
+:code:`-var-file=prod/a.tfvars` and :code:`-var-file=prod/b.tfvars`.
+
+License
+-------
+
+This software is licensed under the MIT license (see the :code:`LICENSE.txt`
+file).
+
+Author
+------
+
+Nimrod Adar, `contact me <nimrod@shore.co.il>`_ or visit my `website
+<https://www.shore.co.il/>`_. Patches are welcome via `git send-email
+<http://git-scm.com/book/en/v2/Git-Commands-Email>`_. The repository is located
+at: https://git.shore.co.il/nimrod/.
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..a2b0562c602c7a969d555b00f903a457e5b91edf
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,36 @@
+[build-system]
+build-backend = "setuptools.build_meta"
+requires = [
+  "setuptools >= 61.0.0",
+  "wheel",
+]
+
+[project]
+authors = [
+  { name = "Nimrod Adar", email = "nimrod@shore.co.il"},
+]
+classifiers = [
+  "License :: OSI Approved :: MIT License",
+  "Programming Language :: Python",
+]
+dependencies = [
+]
+description = "A simple Terraform wrapper to use variable definition files if they match the workspace name."
+dynamic = [
+  "version",
+]
+keywords = [
+  "terraform",
+]
+license = {file = "LICENSE.txt"}
+name = "terraformation"
+readme = "README.rst"
+
+[project.scripts]
+tf = "tf:wrapper"
+
+[project.urls]
+homepage = "https://git.shore.co.il/nimrod/tf"
+
+[tool.setuptools.dynamic]
+version = {attr = "tf.__version__"}
diff --git a/tf.py b/tf.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ecdbd76135f9a7a324ad69cb61a0792bb9a926e
--- /dev/null
+++ b/tf.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+"""A simple Terraform wrapper to use variable definition files if they match
+the workspace name."""
+
+import glob
+import os
+import pathlib
+import subprocess  # nosec
+import sys
+
+__version__ = "0.1.0"
+
+TF_COMMANDS_TO_MODIFY = ["plan", "console", "import", "refresh"]
+
+
+def get_workspace():
+    """Return the Terraform workspace."""
+    proc = subprocess.run(  # nosec
+        ["terraform", "workspace", "show"],
+        capture_output=True,
+        check=True,
+        text=True,
+    )
+    return proc.stdout.strip()
+
+
+def is_debug_set():
+    """Check if the debug environment variable is set."""
+    debug = os.getenv("TF_DEBUG", "").lower()
+    return debug in ["1", "true"]
+
+
+def wrapper():
+    # In case tf was run with no arguments.
+    if len(sys.argv) == 1:
+        os.execlp("terraform", "terraform")  # nosec
+
+    # Build a new argument list.
+    args = sys.argv[:]
+    args[0] = "terraform"
+
+    # Check if the Terraform command is one that we need to modify (include
+    # tfvars files). If not, execute Terraform with the same arguments.
+    for command in TF_COMMANDS_TO_MODIFY:
+        if command in sys.argv:
+            break
+    else:
+        os.execvp("terraform", args)  # nosec
+
+    # We need to add the var files after the Terraform command (if we add it
+    # before Terraform doesn't accept them) but not at the end (not to modify
+    # other argument that accept optional values). So we add them right after
+    # the Terraform command.
+    command_index = args.index(command)
+    workspace = get_workspace()
+    var_file = pathlib.Path(f"{workspace}.tfvars")
+    var_dir = pathlib.Path(workspace)
+    if var_file.exists() and var_file.is_file():
+        args.insert(command_index + 1, f"-var-file={var_file}")
+    elif var_dir.exists() and var_dir.is_dir():
+        for var_file in glob.glob(f"{var_dir}/*.tfvars"):
+            args.insert(command_index + 1, f"-var-file={var_file}")
+
+    # Print the new argument list to stderr if debugging is enabled.
+    if is_debug_set():
+        print(args, file=sys.stderr)
+
+    os.execvp("terraform", args)  # nosec
+
+
+if __name__ == "__main__":
+    wrapper()