From 6e79ba11536b9c6b6497163a611a09ccc94e8b68 Mon Sep 17 00:00:00 2001 From: Caleb Van Dyke Date: Mon, 22 Jan 2024 20:03:40 -0600 Subject: [PATCH] add docs (#5) --- .github/workflows/python_ci.yaml | 2 +- .pre-commit-config.yaml | 2 +- .readthedocs.yaml | 13 + .vscode/settings.json | 3 + README.md | 244 -------------- README.rst | 291 +++++++++++++++++ docs/Makefile | 20 ++ docs/requirements.txt | 3 + docs/source/conf.py | 37 +++ docs/source/gcp-resources/bigquery.rst | 5 + docs/source/gcp-resources/bigtable.rst | 5 + docs/source/gcp-resources/cloud_sql.rst | 5 + docs/source/gcp-resources/cloud_tasks.rst | 5 + docs/source/gcp-resources/gcs_bucket.rst | 5 + docs/source/gcp-resources/pubsub.rst | 5 + docs/source/gcp-resources/pubsub_lite.rst | 5 + docs/source/gcp-resources/secret_manager.rst | 5 + docs/source/index.rst | 305 ++++++++++++++++++ sdks/python/README.md | 3 - sdks/python/README.rst | 291 +++++++++++++++++ sdks/python/pyproject.toml | 10 +- sdks/python/terrabridge/base.py | 12 +- sdks/python/terrabridge/gcp/base.py | 20 +- sdks/python/terrabridge/gcp/bigquery.py | 67 ++-- sdks/python/terrabridge/gcp/bigtable.py | 72 +++-- sdks/python/terrabridge/gcp/cloud_sql.py | 133 +++++--- sdks/python/terrabridge/gcp/cloud_sql.pyi | 23 ++ sdks/python/terrabridge/gcp/cloud_tasks.py | 34 +- sdks/python/terrabridge/gcp/gcs_bucket.py | 39 ++- sdks/python/terrabridge/gcp/gcs_bucket.pyi | 6 +- sdks/python/terrabridge/gcp/pubsub.py | 70 ++-- sdks/python/terrabridge/gcp/pubsub_lite.py | 72 +++-- sdks/python/terrabridge/gcp/secret_manager.py | 28 +- sdks/python/terrabridge/parser.py | 23 +- sdks/python/tests/data/terraform.tfstate | 43 +++ sdks/python/tests/gcp/gcs_bucket_test.py | 13 + 36 files changed, 1499 insertions(+), 420 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 .vscode/settings.json delete mode 100644 README.md create mode 100644 README.rst create mode 100644 docs/Makefile create mode 100644 docs/requirements.txt create mode 100644 docs/source/conf.py create mode 100644 docs/source/gcp-resources/bigquery.rst create mode 100644 docs/source/gcp-resources/bigtable.rst create mode 100644 docs/source/gcp-resources/cloud_sql.rst create mode 100644 docs/source/gcp-resources/cloud_tasks.rst create mode 100644 docs/source/gcp-resources/gcs_bucket.rst create mode 100644 docs/source/gcp-resources/pubsub.rst create mode 100644 docs/source/gcp-resources/pubsub_lite.rst create mode 100644 docs/source/gcp-resources/secret_manager.rst create mode 100644 docs/source/index.rst delete mode 100644 sdks/python/README.md create mode 100644 sdks/python/README.rst diff --git a/.github/workflows/python_ci.yaml b/.github/workflows/python_ci.yaml index 507fff8..c007f1d 100644 --- a/.github/workflows/python_ci.yaml +++ b/.github/workflows/python_ci.yaml @@ -29,7 +29,7 @@ jobs: uses: chartboost/ruff-action@v1 with: src: "sdks/python" - version: 0.0.265 + version: 0.1.14 checkName: "test_and_lint" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a2870b..9192e53 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: rev: v0.1.14 hooks: - id: ruff - args: ["--fix", "sdks/python"] + args: ["sdks/python"] - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..d528014 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "3.10" + +python: + install: + - requirements: docs/requirements.txt + +sphinx: + configuration: docs/source/conf.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..070cfda --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "esbonio.sphinx.confDir": "" +} diff --git a/README.md b/README.md deleted file mode 100644 index 034655c..0000000 --- a/README.md +++ /dev/null @@ -1,244 +0,0 @@ -# terrabridge - -![CI](https://github.com/launchflow/terrabridge/actions/workflows/python_ci.yaml/badge.svg) -[![Python version](https://badge.fury.io/py/terrabridge.svg)](https://pypi.org/project/terrabridge) -[![codecov](https://codecov.io/gh/launchflow/terrabridge/graph/badge.svg?token=slFk4lUP2h)](https://codecov.io/gh/launchflow/terrabridge) - -Terrabridge bridges the gap between Terraform and applicatoin code. With Terrabridge you simply provide your terraform state file to the library and all information will be loaded allowing you to easily access your resources. This allows you to truly keep your infrastructure configuration in one place. - -Once your terraform and application code are in sync Terrabridge makes it dead simple to connect to your resource by providing easy ready to use clients for your resources such as SQLAlchemy for relational databases, or reading secret values from secret storage. - -```python -from terrabridge.gcp import SecretManagerSecret - -sec = SecretManagerSecret("secret", state_file="terraform.tfstate") -print(sec.version().decode("utf-8")) - -``` - -## Installation - -```bash -pip install terrabridge -``` - -To use the GCP clients you will need to install the GCP extras: - -```bash -pip install terrabridge[gcp] -``` - -To use the AWS clients you will need to install the AWS extras: - -```bash -pip install terrabridge[aws] -``` - -## Usage - -### Basic Usage - -Terrabridge can be used by providing your state file to the library. The state file can be local, stored in an S3 bucket, or in a GCS bucket. Terrabridge will then parse the state file into a python object that can be consumed by your application code. - -For example if you had the below terraform code that creates and manages a GCS bucket you can easily access the bucket from your application code using the `GCSBucket` class. All terrabridge objects take in a state file and the name of the resource you assigned in terraform. All attributes that are available in terraform are now available on your terrabridge object. - -```python -from terrabridge.gcp import GCSBucket - -bucket = GCSBucket("bucket", state_file="terraform.tfstate") -# Fetches the remote bucket. -print(bucket.url) -print(bucket.id) -print(bucket.name) -bucket = bucket.bucket() -print(bucket.get_iam_policy().bindings) -``` - -```hcl -resource "google_storage_bucket" "bucket" { - name = var.bucket_name - location = "US" -} -``` - -### Global State File - -If all of your terrabridge objects use the same state file you can set a global state file, and avoid passing it to each object: - -```python -import terrabridge -from terrabridge.gcp import GCSBucket - -terrabridge.state_file="terraform.tfstate" - -bucket = GCSBucket("bucket") -``` - -### Remote State File - -If your state file is stored in an S3 bucket or GCS bucket you can pass the bucket name and key to the `state_file` argument. Terrabridge will then download the state file and parse it. - -```python - -from terrabridge.gcp import GCSBucket -from terrabridge.aws import S3Bucket - -gcs_bucket = GCSBucket("bucket", state_file="gs://my-bucket/terraform.tfstate") -s3_bucket = S3Bucket("bucket", state_file="s3://my-bucket/terraform.tfstate") -``` - - -## Examples - -### S3 Bucket - -Easily connect and read data from a S3 bucket, that is defined in terraform. - -TODO - -### GCS Bucket - -Easily connect and read data from a GCS bucket, that is defined in terraform. - -**Python:** - -```python -from terrabridge.gcp import GCSBucket - -bucket = GCSBucket("bucket", state_file="terraform.tfstate") -bucket = bucket.bucket() -print(bucket.get_iam_policy().bindings) -``` - -**Terraform:** -```hcl -variable "gcp_project_id" { - type = string - description = "The GCP project to deploy resources into." -} - -variable "bucket_name" { - type = string - description = "Name of the bucket." -} - - -provider "google" { - project = var.gcp_project_id - region = "us-central1" - zone = "us-central1-a" -} - -resource "google_storage_bucket" "bucket" { - name = var.bucket_name - location = "US" -} -``` - -### Cloud SQL Postgres Database - -Use SQLAlchemy to connect to a managed Cloud SQL Postgres database with one function call. - -**Python:** - -```python -import datetime -import uuid - -from sqlalchemy import Column, DateTime, Integer, select -from sqlalchemy.ext.asyncio import AsyncAttrs -from sqlalchemy.orm import DeclarativeBase - -from terrabridge.gcp import CloudSQLDatabase, CloudSQLUser - - -class Base(AsyncAttrs, DeclarativeBase): - pass - - -class StorageUser(Base): - __tablename__ = "users" - - # Autopoluated fields - id = Column(Integer, primary_key=True, default=uuid.uuid4) - created_at = Column(DateTime, default=datetime.datetime.utcnow) - - -db = CloudSQLDatabase("postgres_database", state_file="terraform.tfstate") -user = CloudSQLUser("postgres_user", state_file="terraform.tfstate") -engine = db.sqlalchemy_engine(user) - -Base.metadata.create_all(engine) - -conn = engine.connect() -print(conn.execute(select(StorageUser)).all()) -``` - -**Terraform:** - -```hcl -variable "gcp_project_id" { - type = string - description = "The GCP project to deploy resources into." -} - -provider "google" { - project = var.gcp_project_id - region = "us-central1" - zone = "us-central1-a" -} - -resource "google_sql_database_instance" "postgres_sql_instance" { - name = "terrabridge-testing-instance-mysql" - project = var.gcp_project_id - database_version = "POSTGRES_15" - region = "us-central1" - settings { - tier = "db-custom-1-3840" - } -} - -resource "google_sql_database" "postgres_database" { - name = "terrabridge-testing-database" - project = var.gcp_project_id - instance = google_sql_database_instance.postgres_sql_instance.name -} - -resource "google_sql_user" "postgres_user" { - name = "terrabridge-testing-user" - project = var.gcp_project_id - instance = google_sql_database_instance.postgres_sql_instance.name - password = "terrabridge-testing-password" -} -``` - -## Supported Providers and Languages - -Python in the first language we support however we plan to support more languages in the future. We are always happy to accept contributions for new languages and providers. - -| | **python** | **golang** | **java** | **typescript** | -|-----------|------------|------------|----------|----------------| -| **gcp** | ✅ | ❌ | ❌ | ❌ | -| **aws** | 🚧 | ❌ | ❌ | ❌ | -| **azure** | ❌ | ❌ | ❌ | ❌ | - -### GCP Supported Resources - -TODO: add links to docs - -- BigQuery Dataset -- BigQuery Instance -- BigTable Instance -- BigTable Table -- Cloud SQL Database Instance -- Cloud SQL Database -- Cloud SQL User -- Cloud Tasks Queue -- GCS Bucket -- Pub/Sub Lite Topic -- Pub/Sub Lite Subscription -- Pub/Sub Topic -- Pub/Sub Subscription -- Secret Manager Secret - -### AWS Supported Resources diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..9caca1c --- /dev/null +++ b/README.rst @@ -0,0 +1,291 @@ +terrabridge +=========== + +|license| |CI| |Python version| |codecov| + +.. |license| image:: https://img.shields.io/pypi/l/terrabridge.svg + :target: https://pypi.python.org/pypi/terrabridge +.. |CI| image:: https://github.com/launchflow/terrabridge/actions/workflows/python_ci.yaml/badge.svg + :target: https://github.com/launchflow/terrabridge/actions/workflows/python_ci.yaml +.. |Python version| image:: https://badge.fury.io/py/terrabridge.svg + :target: https://pypi.org/project/terrabridge +.. |codecov| image:: https://codecov.io/gh/launchflow/terrabridge/graph/badge.svg?token=slFk4lUP2h + :target: https://codecov.io/gh/launchflow/terrabridge + + +Terrabridge bridges the gap between Terraform and applicatoin code. With +Terrabridge you simply provide your terraform state file to the library +and all information will be loaded allowing you to easily access your +resources. This allows you to truly keep your infrastructure +configuration in one place. + +Once your terraform and application code are in sync Terrabridge makes +it dead simple to connect to your resource by providing easy ready to +use clients for your resources such as SQLAlchemy for relational +databases, or reading secret values from secret storage. + +.. code:: python + + from terrabridge.gcp import SecretManagerSecret + + sec = SecretManagerSecret("secret", state_file="terraform.tfstate") + print(sec.version().decode("utf-8")) + +Installation +------------ + +.. code:: bash + + pip install terrabridge + +To use the GCP clients you will need to install the GCP extras: + +.. code:: bash + + pip install terrabridge[gcp] + +To use the AWS clients you will need to install the AWS extras: + +.. code:: bash + + pip install terrabridge[aws] + +Usage +----- + +Basic Usage +~~~~~~~~~~~ + +Terrabridge can be used by providing your state file to the library. The +state file can be local, stored in an S3 bucket, or in a GCS bucket. +Terrabridge will then parse the state file into a python object that can +be consumed by your application code. + +For example if you had the below terraform code that creates and manages +a GCS bucket you can easily access the bucket from your application code +using the ``GCSBucket`` class. All terrabridge objects take in a state +file and the name of the resource you assigned in terraform. All +attributes that are available in terraform are now available on your +terrabridge object. + +.. code:: python + + from terrabridge.gcp import GCSBucket + + bucket = GCSBucket("bucket", state_file="terraform.tfstate") + # Fetches the remote bucket. + print(bucket.url) + print(bucket.id) + print(bucket.name) + bucket = bucket.bucket() + print(bucket.get_iam_policy().bindings) + +.. code:: hcl + + resource "google_storage_bucket" "bucket" { + name = var.bucket_name + location = "US" + } + +Global State File +~~~~~~~~~~~~~~~~~ + +If all of your terrabridge objects use the same state file you can set a +global state file, and avoid passing it to each object: + +.. code:: python + + import terrabridge + from terrabridge.gcp import GCSBucket + + terrabridge.state_file="terraform.tfstate" + + bucket = GCSBucket("bucket") + +Remote State File +~~~~~~~~~~~~~~~~~ + +If your state file is stored in an S3 bucket or GCS bucket you can pass +the bucket name and key to the ``state_file`` argument. Terrabridge will +then download the state file and parse it. + +.. code:: python + + + from terrabridge.gcp import GCSBucket + from terrabridge.aws import S3Bucket + + gcs_bucket = GCSBucket("bucket", state_file="gs://my-bucket/terraform.tfstate") + s3_bucket = S3Bucket("bucket", state_file="s3://my-bucket/terraform.tfstate") + +Examples +-------- + +S3 Bucket +~~~~~~~~~ + +Easily connect and read data from a S3 bucket, that is defined in +terraform. + +TODO + +GCS Bucket +~~~~~~~~~~ + +Easily connect and read data from a GCS bucket, that is defined in +terraform. + +**Python:** + +.. code:: python + + from terrabridge.gcp import GCSBucket + + bucket = GCSBucket("bucket", state_file="terraform.tfstate") + bucket = bucket.bucket() + print(bucket.get_iam_policy().bindings) + +**Terraform:** + +.. code:: hcl + + variable "gcp_project_id" { + type = string + description = "The GCP project to deploy resources into." + } + + variable "bucket_name" { + type = string + description = "Name of the bucket." + } + + + provider "google" { + project = var.gcp_project_id + region = "us-central1" + zone = "us-central1-a" + } + + resource "google_storage_bucket" "bucket" { + name = var.bucket_name + location = "US" + } + +Cloud SQL Postgres Database +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use SQLAlchemy to connect to a managed Cloud SQL Postgres database with +one function call. + +**Python:** + +.. code:: python + + import datetime + import uuid + + from sqlalchemy import Column, DateTime, Integer, select + from sqlalchemy.ext.asyncio import AsyncAttrs + from sqlalchemy.orm import DeclarativeBase + + from terrabridge.gcp import CloudSQLDatabase, CloudSQLUser + + + class Base(AsyncAttrs, DeclarativeBase): + pass + + + class StorageUser(Base): + __tablename__ = "users" + + # Autopoluated fields + id = Column(Integer, primary_key=True, default=uuid.uuid4) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + + + db = CloudSQLDatabase("postgres_database", state_file="terraform.tfstate") + user = CloudSQLUser("postgres_user", state_file="terraform.tfstate") + engine = db.sqlalchemy_engine(user) + + Base.metadata.create_all(engine) + + conn = engine.connect() + print(conn.execute(select(StorageUser)).all()) + +**Terraform:** + +.. code:: hcl + + variable "gcp_project_id" { + type = string + description = "The GCP project to deploy resources into." + } + + provider "google" { + project = var.gcp_project_id + region = "us-central1" + zone = "us-central1-a" + } + + resource "google_sql_database_instance" "postgres_sql_instance" { + name = "terrabridge-testing-instance-mysql" + project = var.gcp_project_id + database_version = "POSTGRES_15" + region = "us-central1" + settings { + tier = "db-custom-1-3840" + } + } + + resource "google_sql_database" "postgres_database" { + name = "terrabridge-testing-database" + project = var.gcp_project_id + instance = google_sql_database_instance.postgres_sql_instance.name + } + + resource "google_sql_user" "postgres_user" { + name = "terrabridge-testing-user" + project = var.gcp_project_id + instance = google_sql_database_instance.postgres_sql_instance.name + password = "terrabridge-testing-password" + } + +Supported Providers and Languages +--------------------------------- + +Python in the first language we support however we plan to support more +languages in the future. We are always happy to accept contributions for +new languages and providers. + +========= ========== ========== ======== ============== +\ **python** **golang** **java** **typescript** +========= ========== ========== ======== ============== +**gcp** ✅ ❌ ❌ ❌ +**aws** 🚧 ❌ ❌ ❌ +**azure** ❌ ❌ ❌ ❌ +========= ========== ========== ======== ============== + +GCP Supported Resources +~~~~~~~~~~~~~~~~~~~~~~~ + +TODO: add links to docs + +- BigQuery Dataset +- BigQuery Instance +- BigTable Instance +- BigTable Table +- Cloud SQL Database Instance +- Cloud SQL Database +- Cloud SQL User +- Cloud Tasks Queue +- GCS Bucket +- Pub/Sub Lite Topic +- Pub/Sub Lite Subscription +- Pub/Sub Topic +- Pub/Sub Subscription +- Secret Manager Secret + +AWS Supported Resources +~~~~~~~~~~~~~~~~~~~~~~~ + + TODO diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..d09661e --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +furo +sphinx +sphinx-autobuild diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..2799f30 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,37 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import os +import sys + +python_code_path = os.path.join(os.path.abspath("../.."), "sdks/python") +sys.path.insert(0, python_code_path) + + +project = "terrabridge" +copyright = "2024, LaunchFlow" +author = "LaunchFlow" +release = "0.0.1.dev1" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", +] + +templates_path = ["_templates"] +exclude_patterns = [] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +html_static_path = ["_static"] +html_title = "terrabridge" diff --git a/docs/source/gcp-resources/bigquery.rst b/docs/source/gcp-resources/bigquery.rst new file mode 100644 index 0000000..e56a84f --- /dev/null +++ b/docs/source/gcp-resources/bigquery.rst @@ -0,0 +1,5 @@ +BigQuery +======================================= + +.. automodule:: terrabridge.gcp.bigquery + :members: diff --git a/docs/source/gcp-resources/bigtable.rst b/docs/source/gcp-resources/bigtable.rst new file mode 100644 index 0000000..9412e1f --- /dev/null +++ b/docs/source/gcp-resources/bigtable.rst @@ -0,0 +1,5 @@ +BigTable +======================================= + +.. automodule:: terrabridge.gcp.bigtable + :members: diff --git a/docs/source/gcp-resources/cloud_sql.rst b/docs/source/gcp-resources/cloud_sql.rst new file mode 100644 index 0000000..8896d19 --- /dev/null +++ b/docs/source/gcp-resources/cloud_sql.rst @@ -0,0 +1,5 @@ +Cloud SQL +======================================= + +.. automodule:: terrabridge.gcp.cloud_sql + :members: diff --git a/docs/source/gcp-resources/cloud_tasks.rst b/docs/source/gcp-resources/cloud_tasks.rst new file mode 100644 index 0000000..cf0e426 --- /dev/null +++ b/docs/source/gcp-resources/cloud_tasks.rst @@ -0,0 +1,5 @@ +Cloud Tasks +======================================= + +.. automodule:: terrabridge.gcp.cloud_tasks + :members: diff --git a/docs/source/gcp-resources/gcs_bucket.rst b/docs/source/gcp-resources/gcs_bucket.rst new file mode 100644 index 0000000..8eab941 --- /dev/null +++ b/docs/source/gcp-resources/gcs_bucket.rst @@ -0,0 +1,5 @@ +GCS Bucket +======================================= + +.. automodule:: terrabridge.gcp.gcs_bucket + :members: diff --git a/docs/source/gcp-resources/pubsub.rst b/docs/source/gcp-resources/pubsub.rst new file mode 100644 index 0000000..a3ab943 --- /dev/null +++ b/docs/source/gcp-resources/pubsub.rst @@ -0,0 +1,5 @@ +Pub/Sub +======================================= + +.. automodule:: terrabridge.gcp.pubsub_lite + :members: diff --git a/docs/source/gcp-resources/pubsub_lite.rst b/docs/source/gcp-resources/pubsub_lite.rst new file mode 100644 index 0000000..316ef1f --- /dev/null +++ b/docs/source/gcp-resources/pubsub_lite.rst @@ -0,0 +1,5 @@ +Pub/Sub Lite +======================================= + +.. automodule:: terrabridge.gcp.pubsub_lite + :members: diff --git a/docs/source/gcp-resources/secret_manager.rst b/docs/source/gcp-resources/secret_manager.rst new file mode 100644 index 0000000..ea82257 --- /dev/null +++ b/docs/source/gcp-resources/secret_manager.rst @@ -0,0 +1,5 @@ +Secret Manager +======================================= + +.. automodule:: terrabridge.gcp.secret_manager + :members: diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..5668269 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,305 @@ +terrabridge +======================================= + +.. toctree:: + :hidden: + :caption: GCP Resources + + gcp-resources/bigquery + gcp-resources/bigtable + gcp-resources/cloud_sql + gcp-resources/cloud_tasks + gcp-resources/gcs_bucket + gcp-resources/pubsub_lite + gcp-resources/pubsub + gcp-resources/secret_manager + + +|license| |CI| |Python version| |codecov| + +.. |license| image:: https://img.shields.io/pypi/l/terrabridge.svg + :target: https://pypi.python.org/pypi/terrabridge +.. |CI| image:: https://github.com/launchflow/terrabridge/actions/workflows/python_ci.yaml/badge.svg + :target: https://github.com/launchflow/terrabridge/actions/workflows/python_ci.yaml +.. |Python version| image:: https://badge.fury.io/py/terrabridge.svg + :target: https://pypi.org/project/terrabridge +.. |codecov| image:: https://codecov.io/gh/launchflow/terrabridge/graph/badge.svg?token=slFk4lUP2h + :target: https://codecov.io/gh/launchflow/terrabridge + + +Terrabridge bridges the gap between Terraform and applicatoin code. With +Terrabridge you simply provide your terraform state file to the library +and all information will be loaded allowing you to easily access your +resources. This allows you to truly keep your infrastructure +configuration in one place. + +Once your terraform and application code are in sync Terrabridge makes +it dead simple to connect to your resource by providing easy ready to +use clients for your resources such as SQLAlchemy for relational +databases, or reading secret values from secret storage. + +.. code:: python + + from terrabridge.gcp import SecretManagerSecret + + sec = SecretManagerSecret("secret", state_file="terraform.tfstate") + print(sec.version().decode("utf-8")) + +Installation +------------ + +.. code:: bash + + pip install terrabridge + +To use the GCP clients you will need to install the GCP extras: + +.. code:: bash + + pip install terrabridge[gcp] + +To use the AWS clients you will need to install the AWS extras: + +.. code:: bash + + pip install terrabridge[aws] + +Usage +----- + +Basic Usage +~~~~~~~~~~~ + +Terrabridge can be used by providing your state file to the library. The +state file can be local, stored in an S3 bucket, or in a GCS bucket. +Terrabridge will then parse the state file into a python object that can +be consumed by your application code. + +For example if you had the below terraform code that creates and manages +a GCS bucket you can easily access the bucket from your application code +using the ``GCSBucket`` class. All terrabridge objects take in a state +file and the name of the resource you assigned in terraform. All +attributes that are available in terraform are now available on your +terrabridge object. + +.. code:: python + + from terrabridge.gcp import GCSBucket + + bucket = GCSBucket("bucket", state_file="terraform.tfstate") + # Fetches the remote bucket. + print(bucket.url) + print(bucket.id) + print(bucket.name) + bucket = bucket.bucket() + print(bucket.get_iam_policy().bindings) + +.. code:: hcl + + resource "google_storage_bucket" "bucket" { + name = var.bucket_name + location = "US" + } + +Global State File +~~~~~~~~~~~~~~~~~ + +If all of your terrabridge objects use the same state file you can set a +global state file, and avoid passing it to each object: + +.. code:: python + + import terrabridge + from terrabridge.gcp import GCSBucket + + terrabridge.state_file="terraform.tfstate" + + bucket = GCSBucket("bucket") + +Remote State File +~~~~~~~~~~~~~~~~~ + +If your state file is stored in an S3 bucket or GCS bucket you can pass +the bucket name and key to the ``state_file`` argument. Terrabridge will +then download the state file and parse it. + +.. code:: python + + + from terrabridge.gcp import GCSBucket + from terrabridge.aws import S3Bucket + + gcs_bucket = GCSBucket("bucket", state_file="gs://my-bucket/terraform.tfstate") + s3_bucket = S3Bucket("bucket", state_file="s3://my-bucket/terraform.tfstate") + +Examples +-------- + +S3 Bucket +~~~~~~~~~ + +Easily connect and read data from a S3 bucket, that is defined in +terraform. + +TODO + +GCS Bucket +~~~~~~~~~~ + +Easily connect and read data from a GCS bucket, that is defined in +terraform. + +**Python:** + +.. code:: python + + from terrabridge.gcp import GCSBucket + + bucket = GCSBucket("bucket", state_file="terraform.tfstate") + bucket = bucket.bucket() + print(bucket.get_iam_policy().bindings) + +**Terraform:** + +.. code:: hcl + + variable "gcp_project_id" { + type = string + description = "The GCP project to deploy resources into." + } + + variable "bucket_name" { + type = string + description = "Name of the bucket." + } + + + provider "google" { + project = var.gcp_project_id + region = "us-central1" + zone = "us-central1-a" + } + + resource "google_storage_bucket" "bucket" { + name = var.bucket_name + location = "US" + } + +Cloud SQL Postgres Database +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use SQLAlchemy to connect to a managed Cloud SQL Postgres database with +one function call. + +**Python:** + +.. code:: python + + import datetime + import uuid + + from sqlalchemy import Column, DateTime, Integer, select + from sqlalchemy.ext.asyncio import AsyncAttrs + from sqlalchemy.orm import DeclarativeBase + + from terrabridge.gcp import CloudSQLDatabase, CloudSQLUser + + + class Base(AsyncAttrs, DeclarativeBase): + pass + + + class StorageUser(Base): + __tablename__ = "users" + + # Autopoluated fields + id = Column(Integer, primary_key=True, default=uuid.uuid4) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + + + db = CloudSQLDatabase("postgres_database", state_file="terraform.tfstate") + user = CloudSQLUser("postgres_user", state_file="terraform.tfstate") + engine = db.sqlalchemy_engine(user) + + Base.metadata.create_all(engine) + + conn = engine.connect() + print(conn.execute(select(StorageUser)).all()) + +**Terraform:** + +.. code:: hcl + + variable "gcp_project_id" { + type = string + description = "The GCP project to deploy resources into." + } + + provider "google" { + project = var.gcp_project_id + region = "us-central1" + zone = "us-central1-a" + } + + resource "google_sql_database_instance" "postgres_sql_instance" { + name = "terrabridge-testing-instance-mysql" + project = var.gcp_project_id + database_version = "POSTGRES_15" + region = "us-central1" + settings { + tier = "db-custom-1-3840" + } + } + + resource "google_sql_database" "postgres_database" { + name = "terrabridge-testing-database" + project = var.gcp_project_id + instance = google_sql_database_instance.postgres_sql_instance.name + } + + resource "google_sql_user" "postgres_user" { + name = "terrabridge-testing-user" + project = var.gcp_project_id + instance = google_sql_database_instance.postgres_sql_instance.name + password = "terrabridge-testing-password" + } + +Supported Providers and Languages +--------------------------------- + +Python in the first language we support however we plan to support more +languages in the future. We are always happy to accept contributions for +new languages and providers. + +========= ========== ========== ======== ============== +\ **python** **golang** **java** **typescript** +========= ========== ========== ======== ============== +**gcp** ✅ ❌ ❌ ❌ +**aws** 🚧 ❌ ❌ ❌ +**azure** ❌ ❌ ❌ ❌ +========= ========== ========== ======== ============== + +GCP Supported Resources +~~~~~~~~~~~~~~~~~~~~~~~ + +TODO: add links to docs + +- BigQuery Dataset +- BigQuery Instance +- BigTable Instance +- BigTable Table +- Cloud SQL Database Instance +- Cloud SQL Database +- Cloud SQL User +- Cloud Tasks Queue +- GCS Bucket +- Pub/Sub Lite Topic +- Pub/Sub Lite Subscription +- Pub/Sub Topic +- Pub/Sub Subscription +- Secret Manager Secret + +AWS Supported Resources +~~~~~~~~~~~~~~~~~~~~~~~ + + TODO diff --git a/sdks/python/README.md b/sdks/python/README.md deleted file mode 100644 index 912f458..0000000 --- a/sdks/python/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# terrabridge - -terrabridge is the simplest way to connect your terraform managed resources to your application. diff --git a/sdks/python/README.rst b/sdks/python/README.rst new file mode 100644 index 0000000..9caca1c --- /dev/null +++ b/sdks/python/README.rst @@ -0,0 +1,291 @@ +terrabridge +=========== + +|license| |CI| |Python version| |codecov| + +.. |license| image:: https://img.shields.io/pypi/l/terrabridge.svg + :target: https://pypi.python.org/pypi/terrabridge +.. |CI| image:: https://github.com/launchflow/terrabridge/actions/workflows/python_ci.yaml/badge.svg + :target: https://github.com/launchflow/terrabridge/actions/workflows/python_ci.yaml +.. |Python version| image:: https://badge.fury.io/py/terrabridge.svg + :target: https://pypi.org/project/terrabridge +.. |codecov| image:: https://codecov.io/gh/launchflow/terrabridge/graph/badge.svg?token=slFk4lUP2h + :target: https://codecov.io/gh/launchflow/terrabridge + + +Terrabridge bridges the gap between Terraform and applicatoin code. With +Terrabridge you simply provide your terraform state file to the library +and all information will be loaded allowing you to easily access your +resources. This allows you to truly keep your infrastructure +configuration in one place. + +Once your terraform and application code are in sync Terrabridge makes +it dead simple to connect to your resource by providing easy ready to +use clients for your resources such as SQLAlchemy for relational +databases, or reading secret values from secret storage. + +.. code:: python + + from terrabridge.gcp import SecretManagerSecret + + sec = SecretManagerSecret("secret", state_file="terraform.tfstate") + print(sec.version().decode("utf-8")) + +Installation +------------ + +.. code:: bash + + pip install terrabridge + +To use the GCP clients you will need to install the GCP extras: + +.. code:: bash + + pip install terrabridge[gcp] + +To use the AWS clients you will need to install the AWS extras: + +.. code:: bash + + pip install terrabridge[aws] + +Usage +----- + +Basic Usage +~~~~~~~~~~~ + +Terrabridge can be used by providing your state file to the library. The +state file can be local, stored in an S3 bucket, or in a GCS bucket. +Terrabridge will then parse the state file into a python object that can +be consumed by your application code. + +For example if you had the below terraform code that creates and manages +a GCS bucket you can easily access the bucket from your application code +using the ``GCSBucket`` class. All terrabridge objects take in a state +file and the name of the resource you assigned in terraform. All +attributes that are available in terraform are now available on your +terrabridge object. + +.. code:: python + + from terrabridge.gcp import GCSBucket + + bucket = GCSBucket("bucket", state_file="terraform.tfstate") + # Fetches the remote bucket. + print(bucket.url) + print(bucket.id) + print(bucket.name) + bucket = bucket.bucket() + print(bucket.get_iam_policy().bindings) + +.. code:: hcl + + resource "google_storage_bucket" "bucket" { + name = var.bucket_name + location = "US" + } + +Global State File +~~~~~~~~~~~~~~~~~ + +If all of your terrabridge objects use the same state file you can set a +global state file, and avoid passing it to each object: + +.. code:: python + + import terrabridge + from terrabridge.gcp import GCSBucket + + terrabridge.state_file="terraform.tfstate" + + bucket = GCSBucket("bucket") + +Remote State File +~~~~~~~~~~~~~~~~~ + +If your state file is stored in an S3 bucket or GCS bucket you can pass +the bucket name and key to the ``state_file`` argument. Terrabridge will +then download the state file and parse it. + +.. code:: python + + + from terrabridge.gcp import GCSBucket + from terrabridge.aws import S3Bucket + + gcs_bucket = GCSBucket("bucket", state_file="gs://my-bucket/terraform.tfstate") + s3_bucket = S3Bucket("bucket", state_file="s3://my-bucket/terraform.tfstate") + +Examples +-------- + +S3 Bucket +~~~~~~~~~ + +Easily connect and read data from a S3 bucket, that is defined in +terraform. + +TODO + +GCS Bucket +~~~~~~~~~~ + +Easily connect and read data from a GCS bucket, that is defined in +terraform. + +**Python:** + +.. code:: python + + from terrabridge.gcp import GCSBucket + + bucket = GCSBucket("bucket", state_file="terraform.tfstate") + bucket = bucket.bucket() + print(bucket.get_iam_policy().bindings) + +**Terraform:** + +.. code:: hcl + + variable "gcp_project_id" { + type = string + description = "The GCP project to deploy resources into." + } + + variable "bucket_name" { + type = string + description = "Name of the bucket." + } + + + provider "google" { + project = var.gcp_project_id + region = "us-central1" + zone = "us-central1-a" + } + + resource "google_storage_bucket" "bucket" { + name = var.bucket_name + location = "US" + } + +Cloud SQL Postgres Database +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use SQLAlchemy to connect to a managed Cloud SQL Postgres database with +one function call. + +**Python:** + +.. code:: python + + import datetime + import uuid + + from sqlalchemy import Column, DateTime, Integer, select + from sqlalchemy.ext.asyncio import AsyncAttrs + from sqlalchemy.orm import DeclarativeBase + + from terrabridge.gcp import CloudSQLDatabase, CloudSQLUser + + + class Base(AsyncAttrs, DeclarativeBase): + pass + + + class StorageUser(Base): + __tablename__ = "users" + + # Autopoluated fields + id = Column(Integer, primary_key=True, default=uuid.uuid4) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + + + db = CloudSQLDatabase("postgres_database", state_file="terraform.tfstate") + user = CloudSQLUser("postgres_user", state_file="terraform.tfstate") + engine = db.sqlalchemy_engine(user) + + Base.metadata.create_all(engine) + + conn = engine.connect() + print(conn.execute(select(StorageUser)).all()) + +**Terraform:** + +.. code:: hcl + + variable "gcp_project_id" { + type = string + description = "The GCP project to deploy resources into." + } + + provider "google" { + project = var.gcp_project_id + region = "us-central1" + zone = "us-central1-a" + } + + resource "google_sql_database_instance" "postgres_sql_instance" { + name = "terrabridge-testing-instance-mysql" + project = var.gcp_project_id + database_version = "POSTGRES_15" + region = "us-central1" + settings { + tier = "db-custom-1-3840" + } + } + + resource "google_sql_database" "postgres_database" { + name = "terrabridge-testing-database" + project = var.gcp_project_id + instance = google_sql_database_instance.postgres_sql_instance.name + } + + resource "google_sql_user" "postgres_user" { + name = "terrabridge-testing-user" + project = var.gcp_project_id + instance = google_sql_database_instance.postgres_sql_instance.name + password = "terrabridge-testing-password" + } + +Supported Providers and Languages +--------------------------------- + +Python in the first language we support however we plan to support more +languages in the future. We are always happy to accept contributions for +new languages and providers. + +========= ========== ========== ======== ============== +\ **python** **golang** **java** **typescript** +========= ========== ========== ======== ============== +**gcp** ✅ ❌ ❌ ❌ +**aws** 🚧 ❌ ❌ ❌ +**azure** ❌ ❌ ❌ ❌ +========= ========== ========== ======== ============== + +GCP Supported Resources +~~~~~~~~~~~~~~~~~~~~~~~ + +TODO: add links to docs + +- BigQuery Dataset +- BigQuery Instance +- BigTable Instance +- BigTable Table +- Cloud SQL Database Instance +- Cloud SQL Database +- Cloud SQL User +- Cloud Tasks Queue +- GCS Bucket +- Pub/Sub Lite Topic +- Pub/Sub Lite Subscription +- Pub/Sub Topic +- Pub/Sub Subscription +- Secret Manager Secret + +AWS Supported Resources +~~~~~~~~~~~~~~~~~~~~~~~ + + TODO diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index 952752d..143750b 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -1,9 +1,11 @@ [tool.poetry] description = "terrabridge connects your terraform managed resources to your application." name = "terrabridge" -version = "0.0.1.dev0" +version = "0.0.1.dev1" authors = ["CalebTVanDyke "] -readme = "README.md" +readme = "README.rst" +exclude = ["tests", "examples"] +license = "Apache-2.0" [tool.poetry.dependencies] python = "^3.8" @@ -21,11 +23,11 @@ pymysql = { version = "^1.1.0", extras = ["dev"] } sqlalchemy = { extras = ["dev"], version = "^2.0.25" } sqlalchemy-pytds = { version = "^1.0.0", extras = ["dev"] } coverage = { version = "^7.4.0", extras = ["dev"] } -pytest-asyncio = {version = "^0.23.3", extras = ["dev"]} +pytest-asyncio = { version = "^0.23.3", extras = ["dev"] } gcsfs = "*" s3fs = "*" fsspec = "*" -asyncpg = {version = "^0.29.0", extras = ["dev"]} +asyncpg = { version = "^0.29.0", extras = ["dev"] } [build-system] diff --git a/sdks/python/terrabridge/base.py b/sdks/python/terrabridge/base.py index 1ba1431..a47db0a 100644 --- a/sdks/python/terrabridge/base.py +++ b/sdks/python/terrabridge/base.py @@ -8,13 +8,21 @@ class Resource: _attributes = {} _terraform_type = None - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, + ) -> None: if terrabridge.state_file is None and state_file is None: raise ValueError( "state_file must be specified if terrabridge.state_file is not set." ) self.resource_name = resource_name - resource = get_resource(resource_name, state_file or terrabridge.state_file) + resource = get_resource( + resource_name, module_name, state_file or terrabridge.state_file + ) if resource["type"] != self._terraform_type: raise ValueError( f"Resource {resource_name} is of type {resource['type']}, " diff --git a/sdks/python/terrabridge/gcp/base.py b/sdks/python/terrabridge/gcp/base.py index a625b4b..7fa1f7a 100644 --- a/sdks/python/terrabridge/gcp/base.py +++ b/sdks/python/terrabridge/gcp/base.py @@ -4,9 +4,23 @@ class GCPResource(Resource): - """Base class for all GCP resources""" + """Base class for all GCP resources. - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + """ + + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.project: str = self._attributes["project"] self.id: str = self._attributes["id"] diff --git a/sdks/python/terrabridge/gcp/bigquery.py b/sdks/python/terrabridge/gcp/bigquery.py index 610d589..b523c7c 100644 --- a/sdks/python/terrabridge/gcp/bigquery.py +++ b/sdks/python/terrabridge/gcp/bigquery.py @@ -6,46 +6,71 @@ class BigQueryDataset(GCPResource): """Represents a BigQuery Dataset - Parsed from the terraform resource: `google_bigquery_dataset`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_dataset). + Parsed from the terraform resource: ``google_bigquery_dataset``. For all + available attributes, see the `Terraform documentation `_. - Example usage: + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - ```python - from terrabridge.gcp import BigQueryDataset + Example + ------- + .. code:: python - dataset = BigQueryDataset("dataset", state_file="gs://my-bucket/terraform.tfstate") - print(dataset.id) - ``` + from terrabridge.gcp import BigQueryDataset + + dataset = BigQueryDataset("dataset", state_file="gs://my-bucket/terraform.tfstate") + print(dataset.id) + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. """ _terraform_type = "google_bigquery_dataset" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) class BigQueryTable(GCPResource): """Represents a BigQuery Table - Parsed from the terraform resource: `google_bigquery_table`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_table). + Parsed from the terraform resource: ``google_bigquery_table``. For all + available attributes, see the `Terraform documentation `_. + + Example + ------- + .. code:: python - Example usage: + from terrabridge.gcp import BigQueryTable - ```python - from terrabridge.gcp import BigQueryTable + table = BigQueryTable("table", state_file="gs://my-bucket/terraform.tfstate") + print(table.id) + print(table.dataset.id) - table = BigQueryTable("table", state_file="gs://my-bucket/terraform.tfstate") - print(table.id) - print(table.dataset.id) - ``` + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + dataset (BigQueryDataset): The dataset the table belongs to. Will only be + populated if the dataset also exists in the state file. """ _terraform_type = "google_bigquery_table" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.dataset: Optional[BigQueryDataset] = None for dependency in self._dependencies: if dependency.startswith(BigQueryDataset._terraform_type): diff --git a/sdks/python/terrabridge/gcp/bigtable.py b/sdks/python/terrabridge/gcp/bigtable.py index cf470c6..4537958 100644 --- a/sdks/python/terrabridge/gcp/bigtable.py +++ b/sdks/python/terrabridge/gcp/bigtable.py @@ -6,47 +6,77 @@ class BigTableInstance(GCPResource): """Represents a BigTable Instance - Parsed from the terraform resource: `google_bigtable_instance`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigtable_instance). + Parsed from the terraform resource: ``google_bigtable_instance``. For all + available attributes, see the `Terraform documentation `_. - Example usage: + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - ```python - from terrabridge.gcp import BigTableInstance + Example + ------- + .. code:: python - bt_instance = BigTableInstance("instance", state_file="gs://my-bucket/terraform.tfstate") - print(instance.name) - ``` + from terrabridge.gcp import BigTableInstance + + bt_instance = BigTableInstance("instance", state_file="gs://my-bucket/terraform.tfstate") + print(instance.name) + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the instance. """ _terraform_type = "google_bigtable_instance" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.name: str = self._attributes["name"] class BigTableTable(GCPResource): """Represents a BigTable Table - Parsed from the terraform resource: `google_bigtable_table`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigtable_table). + Parsed from the terraform resource: ``google_bigtable_table``. For all + available attributes, see the `Terraform documentation _`. + + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. + + Example + ------- + .. code:: python - Example usage: + from terrabridge.gcp import BigTableTable - ```python - from terrabridge.gcp import BigTableTable + table = BigTableTable("table", state_file="gs://my-bucket/terraform.tfstate") + print(table.name) + print(table.instance.name) - table = BigTableTable("table", state_file="gs://my-bucket/terraform.tfstate") - print(table.name) - print(table.instance.name) - ``` + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the table. + instance (BigTableInstance): The instance the table belongs to. Will only be + populated if the instance also exists in the state file. """ _terraform_type = "google_bigtable_table" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, state_file=state_file, module_name=module_name) self.instance: Optional[BigTableInstance] = None self.name: str = self._attributes["name"] for dependency in self._dependencies: diff --git a/sdks/python/terrabridge/gcp/cloud_sql.py b/sdks/python/terrabridge/gcp/cloud_sql.py index a40b144..009520b 100644 --- a/sdks/python/terrabridge/gcp/cloud_sql.py +++ b/sdks/python/terrabridge/gcp/cloud_sql.py @@ -37,24 +37,39 @@ class CloudSQLInstance(GCPResource): """Represents a CloudSQL Instance - Parsed from the terraform resource: `google_sql_database_instance`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance). + Parsed from the terraform resource: ``google_sql_database_instance``. For all + available attributes, see the `Terraform documentation `_. + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - Example usage: + Example + ------- + .. code:: python - ```python - from terrabridge.gcp import CloudSQLInstance + from terrabridge.gcp import CloudSQLInstance - sql_instance = CloudSQLInstance("instance", state_file="gs://my-bucket/terraform.tfstate") - print(sql_instance.connection_name) - ``` + sql_instance = CloudSQLInstance("instance", state_file="gs://my-bucket/terraform.tfstate") + print(sql_instance.connection_name) + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + connection_name (str): The name of the instance. + database_version (str): The database version. + name (str): The name of the instance. """ _terraform_type = "google_sql_database_instance" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.connection_name: str = self._attributes["connection_name"] self.database_version: str = self._attributes["database_version"] self.name: str = self._attributes["name"] @@ -63,25 +78,42 @@ def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> N class CloudSQLUser(GCPResource): """Represents a CloudSQL User - Parsed from the terraform resource: `google_sql_user`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_user). + Parsed from the terraform resource: ``google_sql_user``. For all + available attributes, see the `Terraform documentation `_. + + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. + + Example + ------- + .. code:: python - Example usage: + from terrabridge.gcp import CloudSQLUser - ```python - from terrabridge.gcp import CloudSQLUser + sql_user = CloudSQLUser("user", state_file="gs://my-bucket/terraform.tfstate") + print(sql_user.name) + print(sql_user.password) + print(sql_user.cloud_sql_instance.name) - sql_user = CloudSQLUser("user", state_file="gs://my-bucket/terraform.tfstate") - print(sql_user.name) - print(sql_user.password) - print(sql_user.cloud_sql_instance.name) - ``` + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the user. + password (str): The password of the user. + cloud_sql_instance (str): The instance the user belongs to. Will only be populated + if the instance also exists in the state file. """ _terraform_type = "google_sql_user" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.name = self._attributes["name"] self.password = self._attributes["password"] self.cloud_sql_instance: Optional[CloudSQLInstance] = None @@ -95,28 +127,44 @@ def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> N class CloudSQLDatabase(GCPResource): """Represents a CloudSQL Database - Parsed from the terraform resource: `google_sql_database`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database). + Parsed from the terraform resource: ``google_sql_database``. For all + available attributes, see the `Terraform documentation `_. + + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. + + Example + ------- + .. code:: python - Example usage: + from terrabridge.gcp import CloudSQLDatabase - ```python - from terrabridge.gcp import CloudSQLDatabase + database = CloudSQLDatabase("db", state_file="gs://my-bucket/terraform.tfstate") + print(database.name) + print(database.cloud_sql_instance.name) - database = CloudSQLDatabase("db", state_file="gs://my-bucket/terraform.tfstate") - print(database.name) - print(database.cloud_sql_instance.name) + user = CloudSQLUser("user", state_file="gs://my-bucket/terraform.tfstate") + engine = database.sqlalchemy_engine(user) + async_engine = await database.async_sqlalchemy_engine(user) - user = CloudSQLUser("user", state_file="gs://my-bucket/terraform.tfstate") - engine = database.sqlalchemy_engine(user) - async_engine = await database.async_sqlalchemy_engine(user) - ``` + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the database. + cloud_sql_instance (str): The instance the user belongs to. Will only be populated + if the instance also exists in the state file. """ _terraform_type = "google_sql_database" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.name: str = self._attributes["name"] self.cloud_sql_instance: Optional[CloudSQLInstance] = None for dependency in self._dependencies: @@ -130,17 +178,18 @@ def sqlalchemy_engine( ) -> Engine: """Returns a SQLAlchemy engine for the database. - Requires terrabridge[gcp] and sqlalchemy to be installed, and whatever + Requires ``terrabridge[gcp]`` and ``sqlalchemy`` to be installed, and whatever driver is needed for the database version. - POSTGRES: pg8000 - SQLSERVER: pytds + * ``POSTGRES``: ``pg8000`` + * ``SQLSERVER``: ``pytds`` + * ``MYSQL``: ``pymysql`` Parameters: user: The user to connect to the database with. ip_type: The type of IP address to connect with and. engine_params: Additional parameters to pass to the SQLAlchemy engine. - returns: + Returns: A SQLAlchemy engine. """ if create_engine is None: @@ -200,14 +249,14 @@ async def async_sqlalchemy_engine( ) -> AsyncEngine: """Returns a SQLAlchemy engine for the database. - Requires terrabridge[gcp] and sqlalchemy[asyncio] to be installed. + Requires ``terrabridge[gcp]`` and ``sqlalchemy[asyncio]`` to be installed. Parameters: user: The user to connect to the database with. ip_type: The type of IP address to connect with and. engine_params: Additional parameters to pass to the SQLAlchemy engine. - returns: + Returns: A SQLAlchemy engine. """ if create_async_engine is None: diff --git a/sdks/python/terrabridge/gcp/cloud_sql.pyi b/sdks/python/terrabridge/gcp/cloud_sql.pyi index 13cf273..1af92bb 100644 --- a/sdks/python/terrabridge/gcp/cloud_sql.pyi +++ b/sdks/python/terrabridge/gcp/cloud_sql.pyi @@ -8,15 +8,38 @@ class CloudSQLInstance: database_version: str name: str + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, + ) -> None: ... + class CloudSQLUser: name: str password: str cloud_sql_instance: Optional[CloudSQLInstance] + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, + ) -> None: ... + class CloudSQLDatabase: name: str cloud_sql_instance: Optional[CloudSQLInstance] + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, + ) -> None: ... def sqlalchemy_engine( self, user: CloudSQLUser, ip_type: IPTypes = IPTypes.PUBLIC, **engine_params ) -> sqlalchemy.engine.base.Engine: ... diff --git a/sdks/python/terrabridge/gcp/cloud_tasks.py b/sdks/python/terrabridge/gcp/cloud_tasks.py index 04a57b7..4ce495e 100644 --- a/sdks/python/terrabridge/gcp/cloud_tasks.py +++ b/sdks/python/terrabridge/gcp/cloud_tasks.py @@ -6,21 +6,35 @@ class CloudTasksQueue(GCPResource): """Represents a Cloud Tasks Queue - Parsed from the terraform resource: `google_cloud_tasks_queue`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_tasks_queue). + Parsed from the terraform resource: ``google_cloud_tasks_queue``. For all + available attributes, see the `Terraform documentation `_. - Example usage: + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - ```python - from terrabridge.gcp import CloudTasksQueue + Example + ------- + .. code:: python - queue = CloudTasksQueue("queue", state_file="gs://my-bucket/terraform.tfstate") - print(queue.name) - ``` + from terrabridge.gcp import CloudTasksQueue + + queue = CloudTasksQueue("queue", state_file="gs://my-bucket/terraform.tfstate") + print(queue.name) + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the cloud tasks queue. """ _terraform_type = "google_cloud_tasks_queue" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.name = self._attributes["name"] diff --git a/sdks/python/terrabridge/gcp/gcs_bucket.py b/sdks/python/terrabridge/gcp/gcs_bucket.py index 6f36931..b3d55da 100644 --- a/sdks/python/terrabridge/gcp/gcs_bucket.py +++ b/sdks/python/terrabridge/gcp/gcs_bucket.py @@ -11,33 +11,48 @@ class GCSBucket(GCPResource): """Represents a GCS Bucket - Parsed from the terraform resource: `google_storage_bucket`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket). + Parsed from the terraform resource: ``google_storage_bucket``. For all + available attributes, see the `Terraform documentation `_. - Example usage: + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - ```python - from terrabridge.gcp import GCSBucket + Example + ------- + .. code:: python - bucket = GCSBucket("queue", state_file="gs://my-bucket/terraform.tfstate") - print(bucket.name) + from terrabridge.gcp import GCSBucket - bucket.bucket().upload_from_filename("local_file.txt") - ``` + bucket = GCSBucket("queue", state_file="gs://my-bucket/terraform.tfstate") + print(bucket.name) + + bucket.bucket().upload_from_filename("local_file.txt") + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the bucket. + url (str): The url of the bucket (e.g. gs://BUCKET_NAME). """ _terraform_type = "google_storage_bucket" _client = None - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.url: str = self._attributes["url"] self.name: str = self._attributes["name"] def bucket(self) -> storage.Bucket: """Fetches the remote storage bucket. - Requires terrabridge[gcp] to be installed. + Requires ``terrabridge[gcp]`` to be installed. """ if storage is None: raise ImportError( diff --git a/sdks/python/terrabridge/gcp/gcs_bucket.pyi b/sdks/python/terrabridge/gcp/gcs_bucket.pyi index 7774b07..c24a819 100644 --- a/sdks/python/terrabridge/gcp/gcs_bucket.pyi +++ b/sdks/python/terrabridge/gcp/gcs_bucket.pyi @@ -8,6 +8,10 @@ class GCSBucket: name: str def __init__( - self, resource_name: str, *, state_file: Optional[str] = None + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None, ) -> None: ... def bucket(self) -> storage.Bucket: ... diff --git a/sdks/python/terrabridge/gcp/pubsub.py b/sdks/python/terrabridge/gcp/pubsub.py index 4a1ab77..9cf7e79 100644 --- a/sdks/python/terrabridge/gcp/pubsub.py +++ b/sdks/python/terrabridge/gcp/pubsub.py @@ -12,28 +12,44 @@ class PubSubTopic(GCPResource): """Represents a PubSub Topic Parsed from the terraform resource: `google_pubsub_topic`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_topic). + available attributes, see the `Terraform documentation `. - Example usage: + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - ```python - from terrabridge.gcp import PubSubTopic + Example + ------- + .. code:: python - topic = PubSubTopic("topic", state_file="gs://my-bucket/terraform.tfstate") - print(topic.name) + from terrabridge.gcp import PubSubTopic - topic.publish(b"Hello, world!") - ``` + topic = PubSubTopic("topic", state_file="gs://my-bucket/terraform.tfstate") + print(topic.name) + + topic.publish(b"Hello, world!") + + Attributes: + name (str): The name of the pub/sub topic. """ _publisher = None _terraform_type = "google_pubsub_topic" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.name = self._attributes["name"] def publish(self, message: bytes, ordering_key: str = "", **attributes): + """Publish a message to the topic. + + Requires ``terrabridge[gcp]`` to be installed. + """ if pubsub_v1 is None: raise ImportError( "google-cloud-pubsub is not installed. " @@ -49,21 +65,35 @@ def publish(self, message: bytes, ordering_key: str = "", **attributes): class PubSubSubscription(GCPResource): """Represents a PubSub Subscription - Parsed from the terraform resource: `google_pubsub_subscription`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription). + Parsed from the terraform resource: ``google_pubsub_subscription``. For all + available attributes, see the `Terraform documentation `_. + + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. + + Example + ------- + .. code:: python - Example usage: + from terrabridge.gcp import PubSubSubscription - ```python - from terrabridge.gcp import PubSubSubscription + subscription = PubSubSubscription("subscription", state_file="gs://my-bucket/terraform.tfstate") + print(subscription.name) - subscription = PubSubSubscription("subscription", state_file="gs://my-bucket/terraform.tfstate") - print(subscription.name) - ``` + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the pub/sub subscription. """ _terraform_type = "google_pubsub_subscription" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.id = self._attributes["id"] diff --git a/sdks/python/terrabridge/gcp/pubsub_lite.py b/sdks/python/terrabridge/gcp/pubsub_lite.py index 910389d..a136b80 100644 --- a/sdks/python/terrabridge/gcp/pubsub_lite.py +++ b/sdks/python/terrabridge/gcp/pubsub_lite.py @@ -11,31 +11,49 @@ class PubSubLiteTopic(GCPResource): """Represents a PubSub Lite Topic - Parsed from the terraform resource: `google_pubsub_lite_topic`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_lite_topic). + Parsed from the terraform resource: ``google_pubsub_lite_topic``. For all + available attributes, see the `Terraform documentation `_. - Example usage: + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - ```python - from terrabridge.gcp import PubSubLiteTopic + Example + ------- + .. code:: python - topic = PubSubLiteTopic("lite_topic", state_file="gs://my-bucket/terraform.tfstate") - print(topic.name) + from terrabridge.gcp import PubSubLiteTopic - topic.publish(b"Hello, world!") - ``` + topic = PubSubLiteTopic("lite_topic", state_file="gs://my-bucket/terraform.tfstate") + print(topic.name) + + topic.publish(b"Hello, world!") + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the pub/sub lite topic. """ _terraform_type = "google_pubsub_lite_topic" _publisher = None - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.name: str = self._attributes["name"] def publish( self, message: bytes, ordering_key: str = "", metadata: Dict[str, str] = {} ): + """Publish a message to the topic. + + Requires ``terrabridge[gcp]`` to be installed. + """ if pubsublite is None: raise ImportError( "google-cloud-pubsub is not installed. " @@ -55,20 +73,34 @@ class PubSubLiteSubscription(GCPResource): """Represents a PubSub Lite Subscription Parsed from the terraform resource: `google_pubsub_lite_subscription`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_lite_subscription). + available attributes, see the `Terraform documentation `_. + + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. + + Example + ------- + .. code:: python - Example usage: + from terrabridge.gcp import PubSubLiteSubscription - ```python - from terrabridge.gcp import PubSubLiteSubscription + sub = PubSubLiteSubscription("lite_subscription", state_file="gs://my-bucket/terraform.tfstate") + print(sub.name) - sub = PubSubLiteSubscription("lite_subscription", state_file="gs://my-bucket/terraform.tfstate") - print(sub.name) - ``` + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the pub/sub lite subscription. """ _terraform_type = "google_pubsub_lite_subscription" - def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> None: - super().__init__(resource_name, state_file=state_file) + def __init__( + self, + resource_name: str, + *, + module_name: Optional[str] = None, + state_file: Optional[str] = None + ) -> None: + super().__init__(resource_name, module_name=module_name, state_file=state_file) self.name = self._attributes["name"] diff --git a/sdks/python/terrabridge/gcp/secret_manager.py b/sdks/python/terrabridge/gcp/secret_manager.py index 63eb773..2d2a5bc 100644 --- a/sdks/python/terrabridge/gcp/secret_manager.py +++ b/sdks/python/terrabridge/gcp/secret_manager.py @@ -11,19 +11,27 @@ class SecretManagerSecret(GCPResource): """Represents a Secret Manager Secret - Parsed from the terraform resource: `google_secret_manager_secret`. For all - available attributes, see the [Terraform documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/secret_manager_secret). + Parsed from the terraform resource: ``google_secret_manager_secret``. For all + available attributes, see the `Terraform documentation `_. - Example usage: + Some attributes are pulled up to be top-level attributes for convenience for type hints. + However all attributes that are available in the Terraform state file are available. - ```python - from terrabridge.gcp import SecretManagerSecret + Example + ------- + .. code:: python - secret = SecretManagerSecret("subscription", state_file="gs://my-bucket/terraform.tfstate") - print(secret.name) + from terrabridge.gcp import SecretManagerSecret - print(secret.version().decode("utf-8")) - ``` + secret = SecretManagerSecret("subscription", state_file="gs://my-bucket/terraform.tfstate") + print(secret.name) + + print(secret.version().decode("utf-8")) + + Attributes: + project (str): The project the resource belongs to. + id (str): The id of the resource. + name (str): The name of the secret. """ _client = None @@ -36,7 +44,7 @@ def __init__(self, resource_name: str, *, state_file: Optional[str] = None) -> N def version(self, version: str = "latest") -> bytes: """Fetches the secret version. - Requires terrabridge[gcp] to be installed. + Requires ``terrabridge[gcp]`` to be installed. """ if secretmanager is None: raise ImportError( diff --git a/sdks/python/terrabridge/parser.py b/sdks/python/terrabridge/parser.py index 62cc4b3..b78eecb 100644 --- a/sdks/python/terrabridge/parser.py +++ b/sdks/python/terrabridge/parser.py @@ -1,6 +1,6 @@ -# Maps a terraform type to a resource type. import json -from typing import Any, Dict +from dataclasses import dataclass +from typing import Any, Dict, Optional import gcsfs import s3fs @@ -12,12 +12,23 @@ tf_state_cache: Dict[str, Dict[str, Dict[str, Any]]] = {} -def get_resource(resource_name: str, tf_state_path: str): +@dataclass(frozen=True) +class _ResourceKey: + resource_name: str + module_name: Optional[str] + + def __repr__(self) -> str: + if self.module_name is None: + return self.resource_name + return f"{self.resource_name} in {self.module_name}" + + +def get_resource(resource_name: str, module_name: Optional[str], tf_state_path: str): """Return the attributes of a resource.""" if tf_state_path not in tf_state_cache: _parse_terraform_state(tf_state_path) try: - return tf_state_cache[tf_state_path][resource_name] + return tf_state_cache[tf_state_path][_ResourceKey(resource_name, module_name)] except KeyError: raise ValueError( f"Resource {resource_name} not found in {tf_state_path}. " @@ -41,7 +52,9 @@ def _parse_terraform_state(tf_state_path: str): if tf_state_path not in tf_state_cache: tf_state_cache[tf_state_path] = {} for resource in tf_state["resources"]: - tf_state_cache[tf_state_path][resource["name"]] = { + tf_state_cache[tf_state_path][ + _ResourceKey(resource["name"], resource.get("module")) + ] = { "attributes": resource["instances"][0].get("attributes", {}), "dependencies": resource["instances"][0].get("dependencies", {}), "type": resource["type"], diff --git a/sdks/python/tests/data/terraform.tfstate b/sdks/python/tests/data/terraform.tfstate index f1dacf9..af39a96 100644 --- a/sdks/python/tests/data/terraform.tfstate +++ b/sdks/python/tests/data/terraform.tfstate @@ -692,6 +692,49 @@ } ] }, + { + "mode": "managed", + "type": "google_storage_bucket", + "module": "module.bucket", + "name": "bucket", + "provider": "provider[\"registry.opentofu.org/hashicorp/google\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "autoclass": [], + "cors": [], + "custom_placement_config": [], + "default_event_based_hold": false, + "effective_labels": {}, + "enable_object_retention": false, + "encryption": [], + "force_destroy": false, + "id": "terrabridge-testing-terrabridge-testing-module", + "labels": {}, + "lifecycle_rule": [], + "location": "US", + "logging": [], + "name": "terrabridge-testing-terrabridge-testing-module", + "project": "terrabridge-testing", + "public_access_prevention": "inherited", + "requester_pays": false, + "retention_policy": [], + "rpo": "DEFAULT", + "self_link": "https://www.googleapis.com/storage/v1/b/terrabridge-testing-terrabridge-testing-module", + "storage_class": "STANDARD", + "terraform_labels": {}, + "timeouts": null, + "uniform_bucket_level_access": false, + "url": "gs://terrabridge-testing-terrabridge-testing-module", + "versioning": [], + "website": [] + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsInJlYWQiOjI0MDAwMDAwMDAwMCwidXBkYXRlIjoyNDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjEifQ==" + } + ] + }, { "mode": "managed", "type": "google_sql_database_instance", diff --git a/sdks/python/tests/gcp/gcs_bucket_test.py b/sdks/python/tests/gcp/gcs_bucket_test.py index 18c2fb4..82a67cb 100644 --- a/sdks/python/tests/gcp/gcs_bucket_test.py +++ b/sdks/python/tests/gcp/gcs_bucket_test.py @@ -31,6 +31,19 @@ def test_gcs_bucket(): ) +def test_gcs_bucket_module(): + bucket = GCSBucket( + resource_name="bucket", + state_file="tests/data/terraform.tfstate", + module_name="module.bucket", + ) + + assert bucket.project == "terrabridge-testing" + assert bucket.url == "gs://terrabridge-testing-terrabridge-testing-module" + assert bucket.resource_name == "bucket" + assert bucket.name == "terrabridge-testing-terrabridge-testing-module" + + def test_gcs_bucket_global_state_file(): try: terrabridge.state_file = "tests/data/terraform.tfstate"