Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge upstream #43

Merged
merged 26 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e8aa063
Refac/github_actions (#1)
Noahnc Nov 24, 2023
f76c395
Feat/migrate git logic to python (#5)
Noahnc Nov 27, 2023
115379d
Feat/refac_core (#10)
Noahnc Nov 28, 2023
7809e75
fix(cicd): Disable checks on merge event.
Noahnc Nov 29, 2023
46a50ba
refac(action): Rename registry_secrets to terraform_registry_secrets
Noahnc Nov 29, 2023
cb9d633
Fix/action_file_references (#23)
Noahnc Nov 29, 2023
77ae74f
dic(README): Update documentation and add pr image.
Noahnc Nov 29, 2023
e3bb166
doc(README): Change infrapatch Name to Upper-Case.
Noahnc Nov 29, 2023
229c644
Feat/add_tests (#27)
Noahnc Nov 29, 2023
52fb4ba
fix(hcl_handler): Skip modules without version attribute.
Noahnc Nov 29, 2023
67adea4
fix(action_integration_test): Fix repo name to source repo. (#37)
Noahnc Nov 29, 2023
6b98fc6
Revert "fix(action_integration_test): Fix repo name to source repo. (…
Noahnc Nov 29, 2023
0ea227c
fix(action_integration_test): Switch update test to secrets.github_to…
Noahnc Nov 30, 2023
e99cc37
feat(action_integration_test): Add condition to use token from secret…
Noahnc Nov 30, 2023
d488e16
fix(action_integration_test): Fix token variable assignment.
Noahnc Nov 30, 2023
08820a3
Revert "fix(action_integration_test): Fix token variable assignment."
Noahnc Nov 30, 2023
e0d55d8
Revert "feat(action_integration_test): Add condition to use token fro…
Noahnc Nov 30, 2023
75640ea
Revert "fix(action_integration_test): Switch update test to secrets.g…
Noahnc Nov 30, 2023
5a23aee
Merge pull request #39 from Noahnc/main
Noahnc Nov 30, 2023
0f17930
feat(unit_tests): Add some unit tests.
Noahnc Dec 1, 2023
9b786af
feat(dev_container): Add devcontainer extension to recommendations
Noahnc Dec 1, 2023
0589071
doc(README): Add chapter for dev environment and contribution.
Noahnc Dec 1, 2023
8443b76
Merge branch 'Noahnc:main' into main
Noahnc Dec 5, 2023
0c2eda5
Merge remote-tracking branch 'origin/main' into feat/add_unit_tests
Noahnc Dec 5, 2023
6df8995
refac(versioned_terraofrm_resource): Remove no longer needed attributes.
Noahnc Dec 5, 2023
7da18c6
Merge pull request #40 from CMInformatik/feat/add_unit_tests
Noahnc Dec 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"ms-vscode-remote.remote-containers"
]
}
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ The CLI works by scanning your .tf files for versioned providers and modules and
- [Authentication](#authentication-1)
- [.terraformrc file:](#terraformrc-file)
- [infrapatch\_credentials.json file:](#infrapatch_credentialsjson-file)
- [Setup Development Environment for InfraPatch](#setup-development-environment-for-infrapatch)
- [Contributing](#contributing)


## GitHub Action
Expand Down Expand Up @@ -182,3 +184,16 @@ You can also specify the path to the credentials file with the `--credentials-fi
infrapatch --credentials-file-path "path/to/credentials/file" update
```

### Setup Development Environment for InfraPatch

This repository contains a devcontainer configuration for VSCode. To use it, you need to install the following tools:
* ["Dev Containers VSCode Extension"](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) for VSCode.
* A local Docker installation like [Docker Desktop](https://www.docker.com/products/docker-desktop).

After installation, you can open the repository in the devcontainer by clicking on the green "Open in Container" button in the bottom left corner of VSCode.
During the first start, the devcontainer will build the container image and install all dependencies.

### Contributing

If you have any ideas for improvements or find any bugs, feel free to open an issue or create a pull request.

Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ def test_find():

def test_to_dict():
module = TerraformModule(name="test_resource", current_version="1.0.0", _source_file="test_file.py", _source="test/test_module/test_provider")
provider = TerraformProvider(name="test_resource", current_version="1.0.0", _source_file="test_file.py", _source="test_provider/test_provider")
provider = TerraformProvider(
name="test_resource",
current_version="1.0.0",
_source_file="test_file.py",
_source="test_provider/test_provider",
)

module_dict = module.to_dict()
provider_dict = provider.to_dict()
Expand Down
9 changes: 6 additions & 3 deletions infrapatch/core/utils/terraform/hcl_edit_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ def update_hcl_value(self, resource: str, file: Path, value: str):
self._run_hcl_edit_command("update", resource, file, value)

def get_hcl_value(self, resource: str, file: Path) -> str:
result = self._run_hcl_edit_command("get", resource, file)
if result is None:
result = self._run_hcl_edit_command("read", resource, file)
if result is None or result == "":
raise HclEditCliException(f"Could not get value for resource '{resource}' from file '{file}'.")
return result
resource_id, value = result.split(" ")
if resource_id != resource:
raise HclEditCliException(f"Could not get value for resource '{resource}' from file '{file}'.")
return value

def _run_hcl_edit_command(self, action: str, resource: str, file: Path, value: Union[str, None] = None) -> Optional[str]:
command = [self._binary_path.absolute().as_posix(), action, resource]
Expand Down
4 changes: 2 additions & 2 deletions infrapatch/core/utils/terraform/hcl_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pygohcl

from infrapatch.core.models.versioned_terraform_resources import TerraformModule, TerraformProvider, VersionedTerraformResource
from infrapatch.core.utils.terraform.hcl_edit_cli import HclEditCli
from infrapatch.core.utils.terraform.hcl_edit_cli import HclEditCliInterface


class HclParserException(Exception):
Expand All @@ -29,7 +29,7 @@ def get_credentials_form_user_rc_file(self) -> dict[str, str]:


class HclHandler(HclHandlerInterface):
def __init__(self, hcl_edit_cli: HclEditCli):
def __init__(self, hcl_edit_cli: HclEditCliInterface):
self.hcl_edit_cli = hcl_edit_cli
pass

Expand Down
84 changes: 84 additions & 0 deletions infrapatch/core/utils/terraform/tests/test_hcl_edit_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from pathlib import Path
from unittest.mock import patch

import pytest

from infrapatch.core.utils.terraform.hcl_edit_cli import HclEditCli, HclEditCliException


@pytest.fixture
def hcl_edit_cli():
return HclEditCli()


def test_init_with_existing_binary_path(hcl_edit_cli):
assert hcl_edit_cli._binary_path.exists()


def test_get_binary_path_windows():
with patch("platform.system", return_value="Windows"):
hcl_edit_cli = HclEditCli()
assert hcl_edit_cli._get_binary_path().name == "hcledit_windows.exe"


def test_get_binary_path_linux():
with patch("platform.system", return_value="Linux"):
hcl_edit_cli = HclEditCli()
assert hcl_edit_cli._get_binary_path().name == "hcledit_linux"


def test_get_binary_path_darwin():
with patch("platform.system", return_value="Darwin"):
hcl_edit_cli = HclEditCli()
assert hcl_edit_cli._get_binary_path().name == "hcledit_darwin"


def test_get_binary_path_unsupported_platform():
with patch("platform.system", return_value="Unsupported"):
with pytest.raises(Exception):
HclEditCli()


def test_update_hcl_value(hcl_edit_cli, tmp_path):
file_path = tmp_path / "test_file.hcl"
file_path.write_text('resource "test_resource" {\n value = "old_value"\n}')

hcl_edit_cli.update_hcl_value("resource.test_resource.value", file_path, "new_value")

assert file_path.read_text() == 'resource "test_resource" {\n value = "new_value"\n}'


def test_get_hcl_value(hcl_edit_cli, tmp_path):
file_path = tmp_path / "test_file.hcl"
file_path.write_text('resource "test_resource" {\n value = "test_value"\n}')

value = hcl_edit_cli.get_hcl_value("resource.test_resource.value", file_path)

assert value == "test_value"


def test_get_hcl_value_non_existing_resource(hcl_edit_cli, tmp_path):
file_path = tmp_path / "test_file.hcl"
file_path.write_text('resource "test_resource" {\n value = "test_value"\n}')

with pytest.raises(HclEditCliException):
hcl_edit_cli.get_hcl_value("non_existing_resource.value", file_path)


def test_run_hcl_edit_command_success(hcl_edit_cli):
with patch("subprocess.run") as mock_run:
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = "command_output"

result = hcl_edit_cli._run_hcl_edit_command("get", "test_resource.value", Path("test_file.hcl"))

assert result == "command_output"


def test_run_hcl_edit_command_failure(hcl_edit_cli):
with patch("subprocess.run") as mock_run:
mock_run.return_value.returncode = 1
mock_run.return_value.stdout = "command_output"

with pytest.raises(HclEditCliException):
hcl_edit_cli._run_hcl_edit_command("get", "test_resource.value", Path("test_file.hcl"))
217 changes: 217 additions & 0 deletions infrapatch/core/utils/terraform/tests/test_hcl_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
from pathlib import Path
from unittest.mock import patch

import pytest

from infrapatch.core.models.versioned_terraform_resources import TerraformModule, TerraformProvider
from infrapatch.core.utils.terraform.hcl_edit_cli import HclEditCli
from infrapatch.core.utils.terraform.hcl_handler import HclHandler, HclParserException


@pytest.fixture
def tmp_user_home(tmp_path: Path):
return tmp_path


@pytest.fixture
def hcl_handler(tmp_user_home: Path):
return HclHandler(hcl_edit_cli=HclEditCli())


@pytest.fixture
def valid_terraform_code():
return """
terraform {
required_providers {
test_provider = {
source = "test_provider/test_provider"
version = ">1.0.0"
}
test_provider2 = {
source = "spacelift.io/test_provider/test_provider2"
version = "1.0.5"
}
}
}
module "test_module" {
source = "test/test_module/test_provider"
version = "2.0.0"
name = "Test_module"
}
module "test_module2" {
source = "spacelift.io/test/test_module/test_provider"
version = "1.0.2"
name = "Test_module2"
}
# This module should be ignored since it has no version
module "test_module3" {
source = "C:/test/test_module/test_provider"
name = "Test_module3"
}
"""


@pytest.fixture
def invalid_terraform_code():
return """
terraform {
required_providers {
test_provider = {
source = "test_provider/test_provider"
version = ">1.0.0"
}
test_provider = {
source = "spacelift.io/test_provider/test_provider2"
version = "1.0.5
}
}
}
module "test_module" {
source = "test/test_module/test_provider"
version = "2.0.0"
name = Test_module"
}
}
"""


def test_get_terraform_resources_from_file(hcl_handler: HclHandler, valid_terraform_code: str, tmp_path: Path):
# Create a temporary Terraform file for testing
tf_file = tmp_path.joinpath("test_file.tf")
tf_file.write_text(valid_terraform_code)
resouces = hcl_handler.get_terraform_resources_from_file(tf_file, get_modules=True, get_providers=True)
modules = hcl_handler.get_terraform_resources_from_file(tf_file, get_modules=True, get_providers=False)
providers = hcl_handler.get_terraform_resources_from_file(tf_file, get_modules=False, get_providers=True)

modules_filtered = [resource for resource in resouces if isinstance(resource, TerraformModule)]
providers_filtered = [resource for resource in resouces if isinstance(resource, TerraformProvider)]

assert len(resouces) == 4
assert len(modules) == 2
assert len(providers) == 2
assert len(modules_filtered) == len(modules)
assert len(providers_filtered) == len(providers)

for resource in resouces:
assert resource._source_file == tf_file.absolute().as_posix()
if resource.name == "test_module":
assert isinstance(resource, TerraformModule)
assert resource.current_version == "2.0.0"
assert resource.source == "test/test_module/test_provider"
assert resource.identifier == "test/test_module/test_provider"
assert resource.base_domain is None
elif resource.name == "test_module2":
assert isinstance(resource, TerraformModule)
assert resource.current_version == "1.0.2"
assert resource.source == "spacelift.io/test/test_module/test_provider"
assert resource.identifier == "test/test_module/test_provider"
assert resource.base_domain == "spacelift.io"
elif resource.name == "test_provider":
assert isinstance(resource, TerraformProvider)
assert resource.current_version == ">1.0.0"
assert resource.source == "test_provider/test_provider"
assert resource.identifier == "test_provider/test_provider"
assert resource.base_domain is None
elif resource.name == "test_provider2":
assert isinstance(resource, TerraformProvider)
assert resource.current_version == "1.0.5"
assert resource.source == "spacelift.io/test_provider/test_provider2"
assert resource.identifier == "test_provider/test_provider2"
assert resource.base_domain == "spacelift.io"
else:
raise Exception(f"Unknown resource '{resource.name}'.")


def test_invalid_terraform_code_parse_error(hcl_handler: HclHandler, invalid_terraform_code: str, tmp_path: Path):
# Create a temporary Terraform file for testing
tf_file = tmp_path.joinpath("test_file.tf")
tf_file.write_text(invalid_terraform_code)
with pytest.raises(HclParserException):
hcl_handler.get_terraform_resources_from_file(tf_file, get_modules=True, get_providers=True)


def test_bump_resource_version(hcl_handler, valid_terraform_code: str, tmp_path: Path):
# Create a TerraformModule resource for testing
tf_file = tmp_path.joinpath("test_file.tf")
tf_file.write_text(valid_terraform_code)
resouces = hcl_handler.get_terraform_resources_from_file(tf_file, get_modules=True, get_providers=True)

# bump versions
for resource in resouces:
if resource.name == "test_module":
resource.newest_version = "4.0.1"
elif resource.name == "test_module2":
resource.newest_version = "4.0.2"

elif resource.name == "test_provider":
resource.newest_version = "4.0.3"
elif resource.name == "test_provider2":
resource.newest_version = "4.0.4"
hcl_handler.bump_resource_version(resource)

resouces = hcl_handler.get_terraform_resources_from_file(tf_file, get_modules=True, get_providers=True)
# check if versions are bumped
for resource in resouces:
if resource.name == "test_module":
assert resource.current_version == "4.0.1"
elif resource.name == "test_module2":
assert resource.current_version == "4.0.2"
elif resource.name == "test_provider":
assert resource.current_version == ">1.0.0" # should not be bumped since it defines newer version
elif resource.name == "test_provider2":
assert resource.current_version == "4.0.4"


def test_get_all_terraform_files(hcl_handler):
# Create a temporary directory with Terraform files for testing
root_dir = Path("test_dir")
root_dir.mkdir()
tf_file1 = root_dir / "file1.tf"
tf_file1.touch()
tf_file2 = root_dir / "file2.tf"
tf_file2.touch()

# Test getting all Terraform files in the directory
files = hcl_handler.get_all_terraform_files(root_dir)
assert len(files) == 2
assert tf_file1 in files
assert tf_file2 in files

# Clean up the temporary directory
tf_file1.unlink()
tf_file2.unlink()
root_dir.rmdir()


def test_get_credentials_form_user_rc_file(hcl_handler, tmp_user_home: Path):
# Create a temporary terraformrc file for testing

# Create an instance of HclHandler with a mock HclEditCli
with patch("pathlib.Path.home", return_value=tmp_user_home):
# test without file
credentials = hcl_handler.get_credentials_form_user_rc_file()
assert len(credentials) == 0

# Create a temporary terraformrc file for testing
terraform_rc_file = tmp_user_home.joinpath(".terraformrc")
terraform_rc_file.write_text(
"""
credentials {
test1 = {
token = "token1"
}
test2 = {
token = "token2"
}
}
"""
)

# Test getting credentials from the terraformrc file
credentials = hcl_handler.get_credentials_form_user_rc_file()
assert len(credentials) == 2
assert credentials["test1"] == "token1"
assert credentials["test2"] == "token2"

# Clean up the temporary file
terraform_rc_file.unlink()
Loading