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

Feat/add resource options #42

Merged
merged 20 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ae3bae7
feat(versioned_resource): Add line number and options attribute.
Noahnc Dec 22, 2023
ecc5f98
test(versioned_resources): Add new attributes to tests.
Noahnc Dec 22, 2023
ac0f450
refac(tf_test_projects): Move resources to get different line numbers.
Noahnc Dec 22, 2023
3fa64e0
feat(hcl_handler): Extract start line number for terraform providers …
Noahnc Dec 22, 2023
751b7e8
test(hcl_handler): Change hcl_handler tests to check line numbers.
Noahnc Dec 22, 2023
78a205d
fix(ruff): Format code.
Noahnc Dec 22, 2023
1e6dc41
feat(packages): Add pydantic as requirement.
Noahnc Dec 22, 2023
9f66fb3
feat(cli): Bump version.
Noahnc Dec 22, 2023
a9593ce
feat(options_processor): Add helper to process resource options.
Noahnc Dec 22, 2023
4142451
feat(constants): Add new constant for option prefix
Noahnc Dec 22, 2023
4caed86
refac(hcl_handler_tests): Edit tests to include check for line numbers.
Noahnc Dec 22, 2023
f2116ca
refac(resource_options): Switch from dataclass to pydantic.
Noahnc Dec 22, 2023
28bf3dc
feat(vscode): Update code actions on save.
Noahnc Dec 22, 2023
d77b44f
doc(README): Add documentation regarding resource options.
Noahnc Dec 22, 2023
5ea81cd
feat(tf_test_files): Add module that should be ignored by infrapatch.
Noahnc Dec 22, 2023
2fe37a2
refac(pydantic): Switch models from dataclass to pydantic BaseModel.
Noahnc Dec 22, 2023
bcdc06b
refac(Pydantic): Fix Code and tests to work with new pydantic models.
Noahnc Dec 22, 2023
68806b0
feat(resource_options): Implement ignore_resource in provider handler.
Noahnc Dec 22, 2023
535a3e2
feat(vscode): Set markdown formatter.
Noahnc Jan 3, 2024
af803e6
doc(README): Move development chapter to bottom and add multiple opti…
Noahnc Jan 3, 2024
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
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"editor.defaultFormatter": "charliermarsh.ruff"
},
Expand Down Expand Up @@ -53,5 +53,8 @@
"python.analysis.autoSearchPaths": false,
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[markdown]": {
"editor.defaultFormatter": "yzhang.markdown-all-in-one"
}
}
67 changes: 63 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ 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)
- [Global](#global)
- [Resource Options](#resource-options)
- [Available Options](#available-options)
- [Example](#example)
- [Setup Development Environment for InfraPatch](#setup-development-environment-for-infrapatch)
- [Contributing](#contributing)


## GitHub Action
Expand Down Expand Up @@ -184,7 +188,62 @@ 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
## Global

The following section describes configurations and behaviors that are applicable to the Github Action and the CLI.

### Resource Options

InfraPatch supports individual resource options to change the behavior for a specific resource.
Resource options can be specified one line obove your resource definition with the following syntax:

```hcl
# infrapatch_options: <option_name1>=<option_value1>, <option_name2>=<option_value2>
module "example" {
source = "terraform-aws-modules/example"
name: "demo"
}

terraform {
required_providers {
# infrapatch_options: <option_name1>=<option_value1>,<option_name2>=<option_value2>
aws = {
source = "hashicorp/aws"
}
}
}
```

#### Available Options

Currently, the following options are available:

| Option Name | Description | Default Value |
| ----------------- | ------------------------------------------------------------- | ------------- |
| `ignore_resource` | If set to `true`, the resource will be ignored by InfraPatch. | `false` |

#### Example

The following example shows how to ignore a terraform module and a terraform provider:

```hcl
# infrapatch_options: ignore_resource=true
module "example" {
source = "terraform-aws-modules/example"
name: "demo"
}

terraform {
required_providers {
# infrapatch_options: ignore_resource=true
aws = {
source = "hashicorp/aws"
}
}
}
```

## 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.
Expand All @@ -193,7 +252,7 @@ This repository contains a devcontainer configuration for VSCode. To use it, you
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
## Contributing

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

2 changes: 1 addition & 1 deletion infrapatch/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.5.0"
__version__ = "0.6.0"
2 changes: 2 additions & 0 deletions infrapatch/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
APP_NAME = "InfraPatch"

DEFAULT_CREDENTIALS_FILE_NAME = "infrapatch_credentials.json"

infrapatch_options_prefix = "# infrapatch_options:"
10 changes: 3 additions & 7 deletions infrapatch/core/models/statistics.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
from dataclasses import dataclass
import dataclasses
from typing import Any, Sequence
from pydantic import BaseModel
from pytablewriter import MarkdownTableWriter
from rich.table import Table
from infrapatch.core.models.versioned_resource import VersionedResource


@dataclass
class BaseStatistics:
class BaseStatistics(BaseModel):
errors: int
resources_patched: int
resources_pending_update: int
total_resources: int

def to_dict(self) -> dict[str, Any]:
return dataclasses.asdict(self)
return self.model_dump()


@dataclass
class ProviderStatistics(BaseStatistics):
resources: Sequence[VersionedResource]


@dataclass
class Statistics(BaseStatistics):
providers: dict[str, ProviderStatistics]

Expand Down
62 changes: 33 additions & 29 deletions infrapatch/core/models/tests/test_versioned_resource.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pathlib import Path
from pathlib import Path, PosixPath

import pytest

Expand All @@ -7,7 +7,7 @@

def test_version_management():
# Create new resource with newer version
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.newest_version = "2.0.0"

assert resource.status == ResourceStatus.UNPATCHED
Expand All @@ -17,103 +17,107 @@ def test_version_management():
assert resource.status == ResourceStatus.PATCHED

# Check new_version the same as current_version
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.newest_version = "1.0.0"

assert resource.status == ResourceStatus.UP_TO_DATE
assert resource.installed_version_equal_or_newer_than_new_version() is True

# Check new_version older than current_version
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.newest_version = "0.1.0"

assert resource.status == ResourceStatus.UP_TO_DATE
assert resource.installed_version_equal_or_newer_than_new_version() is True


def test_tile_constraint():
resource = VersionedResource(name="test_resource", current_version="~>1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="~>1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.newest_version = "~>1.0.1"
assert resource.has_tile_constraint() is True
assert resource.installed_version_equal_or_newer_than_new_version() is True

resource.newest_version = "~>1.1.0"
assert resource.installed_version_equal_or_newer_than_new_version() is False

resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
assert resource.has_tile_constraint() is False

resource = VersionedResource(name="test_resource", current_version="~>1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="~>1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.newest_version = "1.1.0"
assert resource.newest_version == "~>1.1.0"


def test_git_repo():
resource = VersionedResource(name="test_resource", current_version="~>1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="~>1.0.0", source_file=Path("test_file.py"), start_line_number=1)

assert resource.github_repo is None

resource.set_github_repo("https://github.com/noahnc/test_repo.git")
resource.github_repo = "https://github.com/noahnc/test_repo.git"
assert resource.github_repo == "noahnc/test_repo"

resource.set_github_repo("https://github.com/noahnc/test_repo")
resource.github_repo = "https://github.com/noahnc/test_repo"
assert resource.github_repo == "noahnc/test_repo"

with pytest.raises(Exception):
resource.set_github_repo("https://github.com/")
resource.github_repo = "https://github.com/"

with pytest.raises(Exception):
resource.set_github_repo("https://github.com")
resource.github_repo = "https://github.com"


def test_patch_error():
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.set_patch_error()
assert resource.status == ResourceStatus.PATCH_ERROR


def test_version_not_found():
# Test manual setting
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.set_no_version_found()
assert resource.status == ResourceStatus.NO_VERSION_FOUND
assert resource.installed_version_equal_or_newer_than_new_version() is True

# Test by setting None as new version
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
resource.newest_version = None
assert resource.status == ResourceStatus.NO_VERSION_FOUND
assert resource.installed_version_equal_or_newer_than_new_version() is True


def test_path():
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="/var/testdir/test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("/var/testdir/test_file.py"), start_line_number=1)
assert resource.source_file == Path("/var/testdir/test_file.py")


def test_find():
findably_resource = VersionedResource(name="test_resource3", current_version="1.0.0", _source_file="test_file3.py")
unfindably_resource = VersionedResource(name="test_resource6", current_version="1.0.0", _source_file="test_file8.py")
findably_resource = VersionedResource(name="test_resource3", current_version="1.0.0", source_file=Path("test_file3.py"), start_line_number=1)
unfindably_resource = VersionedResource(name="test_resource6", current_version="1.0.0", source_file=Path("test_file8.py"), start_line_number=1)
resources = [
VersionedResource(name="test_resource1", current_version="1.0.0", _source_file="test_file1.py"),
VersionedResource(name="test_resource2", current_version="1.0.0", _source_file="test_file2.py"),
VersionedResource(name="test_resource3", current_version="1.0.0", _source_file="test_file3.py"),
VersionedResource(name="test_resource4", current_version="1.0.0", _source_file="test_file4.py"),
VersionedResource(name="test_resource5", current_version="1.0.0", _source_file="test_file5.py"),
VersionedResource(name="test_resource1", current_version="1.0.0", source_file=Path("test_file1.py"), start_line_number=1),
VersionedResource(name="test_resource2", current_version="1.0.0", source_file=Path("test_file2.py"), start_line_number=1),
VersionedResource(name="test_resource3", current_version="1.0.0", source_file=Path("test_file3.py"), start_line_number=1),
VersionedResource(name="test_resource4", current_version="1.0.0", source_file=Path("test_file4.py"), start_line_number=1),
VersionedResource(name="test_resource5", current_version="1.0.0", source_file=Path("test_file5.py"), start_line_number=1),
]
assert len(findably_resource.find(resources)) == 1
assert findably_resource.find(resources) == [resources[2]]
assert len(unfindably_resource.find(resources)) == 0


def test_versioned_resource_to_dict():
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
resource = VersionedResource(name="test_resource", current_version="1.0.0", source_file=Path("test_file.py"), start_line_number=1)
expected_dict = {
"name": "test_resource",
"current_version": "1.0.0",
"_source_file": "test_file.py",
"_newest_version": None,
"_status": ResourceStatus.UNPATCHED,
"_github_repo": None,
"source_file": PosixPath("test_file.py"),
"newest_version_string": None,
"status": ResourceStatus.UNPATCHED,
"github_repo_string": None,
"start_line_number": 1,
"options": {
"ignore_resource": False,
},
}
assert resource.to_dict() == expected_dict
assert resource.model_dump() == expected_dict
Loading
Loading