Skip to content

Commit

Permalink
Merge branch 'Noahnc:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Noahnc authored Dec 5, 2023
2 parents 5a23aee + 91cd335 commit 8443b76
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 95 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ __pycache__/
# C extensions
*.so

Pipfile

**/.terraform/*

# Distribution / packaging
Expand Down
Binary file modified asset/infrapatch_report.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified asset/infrapatch_update.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 16 additions & 5 deletions infrapatch/action/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ def main(debug: bool):
if "terraform_modules" in config.enabled_providers or "terraform_providers" in config.enabled_providers:
builder.add_terraform_registry_configuration(config.default_registry_domain, config.terraform_registry_secrets)
if "terraform_modules" in config.enabled_providers:
builder.with_terraform_module_provider()
builder.with_terraform_module_provider(github)
if "terraform_providers" in config.enabled_providers:
builder.with_terraform_provider_provider()
builder.with_terraform_provider_provider(github)

provider_handler = builder.build()

Expand Down Expand Up @@ -107,9 +107,20 @@ def update_pr_body(pr, provider_handler):
def get_pr_body(provider_handler: ProviderHandler) -> str:
body = ""
markdown_tables = provider_handler.get_markdown_table_for_changed_resources()
for table in markdown_tables:
body += table.dumps()
body += "\n"
patched_resources = provider_handler.get_patched_resources()
release_notes = provider_handler.get_release_notes(patched_resources)
for provider_name in provider_handler.providers:
if provider_name in markdown_tables:
body += markdown_tables[provider_name].dumps()
body += "\n"
if provider_name in release_notes:
log.debug(f"Adding release notes for provider '{provider_name}' to pull request body.")
body += "## Changelog\n"
for release_note in release_notes[provider_name]:
body += "<details>\n"
body += f"<summary>{release_note.name} - {release_note.version}</summary>\n"
body += f"{release_note.body}\n"
body += "</details>\n\n"

body += provider_handler._get_statistics().get_markdown_table().dumps()
body += "\n"
Expand Down
2 changes: 1 addition & 1 deletion infrapatch/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.0.3"
__version__ = "0.5.0"
53 changes: 51 additions & 2 deletions infrapatch/core/models/tests/test_versioned_resource.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from pathlib import Path

import pytest

from infrapatch.core.models.versioned_resource import ResourceStatus, VersionedResource


Expand All @@ -14,10 +16,18 @@ def test_version_management():
resource.set_patched()
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.newest_version = "1.0.0"

assert resource.status == ResourceStatus.UNPATCHED
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.newest_version = "0.1.0"

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


Expand All @@ -38,12 +48,44 @@ def test_tile_constraint():
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")

assert resource.github_repo is None

resource.set_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")
assert resource.github_repo == "noahnc/test_repo"

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

with pytest.raises(Exception):
resource.set_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.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.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.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")
assert resource.source_file == Path("/var/testdir/test_file.py")
Expand All @@ -66,5 +108,12 @@ def test_find():

def test_versioned_resource_to_dict():
resource = VersionedResource(name="test_resource", current_version="1.0.0", _source_file="test_file.py")
expected_dict = {"name": "test_resource", "current_version": "1.0.0", "_source_file": "test_file.py", "_newest_version": None, "_status": ResourceStatus.UNPATCHED}
expected_dict = {
"name": "test_resource",
"current_version": "1.0.0",
"_source_file": "test_file.py",
"_newest_version": None,
"_status": ResourceStatus.UNPATCHED,
"_github_repo": None,
}
assert resource.to_dict() == expected_dict
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def test_to_dict():
"_source": "test/test_module/test_provider",
"_base_domain": None,
"_identifier": "test/test_module/test_provider",
"_github_repo": None,
}
assert provider_dict == {
"name": "test_resource",
Expand All @@ -79,4 +80,5 @@ def test_to_dict():
"_source": "test_provider/test_provider",
"_base_domain": None,
"_identifier": "test_provider/test_provider",
"_github_repo": None,
}
49 changes: 46 additions & 3 deletions infrapatch/core/models/versioned_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional, Union
from urllib.parse import urlparse
import logging as log
from git import Sequence

import semantic_version


class ResourceStatus:
UNPATCHED = "unpatched"
UP_TO_DATE = "up_to_date"
PATCHED = "patched"
PATCH_ERROR = "patch_error"
NO_VERSION_FOUND = "no_version_found"


@dataclass
class VersionedResource:
name: str
current_version: str
_source_file: str
_newest_version: Union[str, None] = None
_newest_version: Optional[str] = None
_status: str = ResourceStatus.UNPATCHED
_github_repo: Optional[str] = None

@property
def source_file(self) -> Path:
Expand All @@ -29,6 +35,10 @@ def source_file(self) -> Path:
def status(self) -> str:
return self._status

@property
def github_repo(self) -> Union[str, None]:
return self._github_repo

@property
def resource_name(self):
raise NotImplementedError()
Expand All @@ -46,15 +56,38 @@ def newest_version_base(self):
return self.newest_version

@newest_version.setter
def newest_version(self, version: str):
def newest_version(self, version: Optional[str]):
if self.has_tile_constraint():
self._newest_version = f"~>{version}"
else:
self._newest_version = version

if version is None:
self.set_no_version_found()
return
self._newest_version = version
if self.installed_version_equal_or_newer_than_new_version():
self.set_up_to_date()

def set_github_repo(self, github_repo_url: str):
url = urlparse(github_repo_url)
if url.path is None or url.path == "" or url.path == "/":
raise Exception(f"Invalid github repo url '{github_repo_url}'.")
path = url.path
if path.endswith(".git"):
path = path[:-4]
repo = "/".join(path.split("/")[1:3])
log.debug(f"Setting github repo for resource '{self.name}' to '{repo}'")
self._github_repo = repo

def set_patched(self):
self._status = ResourceStatus.PATCHED

def set_no_version_found(self):
self._status = ResourceStatus.NO_VERSION_FOUND

def set_up_to_date(self):
self._status = ResourceStatus.UP_TO_DATE

def has_tile_constraint(self) -> bool:
result = re.match(r"^~>[0-9]+\.[0-9]+\.[0-9]+$", self.current_version)
if result is None:
Expand All @@ -69,6 +102,8 @@ def find(self, resources):
return result

def installed_version_equal_or_newer_than_new_version(self):
if self._status == ResourceStatus.NO_VERSION_FOUND:
return True
if self.newest_version is None:
raise Exception(f"Newest version of resource '{self.name}' is not set.")

Expand Down Expand Up @@ -106,3 +141,11 @@ def check_if_up_to_date(self):

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


@dataclass
class VersionedResourceReleaseNotes:
resources: Sequence[VersionedResource]
name: str
body: str
version: str
20 changes: 9 additions & 11 deletions infrapatch/core/models/versioned_terraform_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
from infrapatch.core.models.versioned_resource import VersionedResource


@dataclass
@dataclass(kw_only=True)
class VersionedTerraformResource(VersionedResource):
_base_domain: Union[str, None] = None
_identifier: Union[str, None] = None
_source: Union[str, None] = None
_source: str
_base_domain: Optional[str] = None
_identifier: Optional[str] = None

@property
def source(self) -> Union[str, None]:
def source(self) -> str:
if self._source is None:
raise Exception("Source is None.")
return self._source

@property
Expand All @@ -36,12 +38,10 @@ def find(self, resources):
@dataclass
class TerraformModule(VersionedTerraformResource):
def __post_init__(self):
if self._source is None:
raise Exception("Source is None.")
self.source = self._source

@property
def source(self) -> Union[str, None]:
def source(self) -> str:
return self._source

@property
Expand All @@ -67,12 +67,10 @@ def source(self, source: str):
@dataclass
class TerraformProvider(VersionedTerraformResource):
def __post_init__(self):
if self._source is None:
raise Exception("Source is None.")
self.source = self._source

@property
def source(self) -> Union[str, None]:
def source(self) -> str:
return self._source

@property
Expand Down
33 changes: 29 additions & 4 deletions infrapatch/core/provider_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from rich.console import Console

from infrapatch.core.models.statistics import ProviderStatistics, Statistics
from infrapatch.core.models.versioned_resource import ResourceStatus, VersionedResource
from infrapatch.core.models.versioned_resource import ResourceStatus, VersionedResource, VersionedResourceReleaseNotes
from infrapatch.core.providers.base_provider_interface import BaseProviderInterface


Expand Down Expand Up @@ -38,6 +38,13 @@ def get_resources(self, disable_cache: bool = False) -> dict[str, Sequence[Versi
log.debug(f"Using cached resources for provider {provider.get_provider_name()}.")
return self._resource_cache

def get_patched_resources(self) -> dict[str, Sequence[VersionedResource]]:
resources = self.get_resources()
patched_resources: dict[str, Sequence[VersionedResource]] = {}
for provider_name, provider in self.providers.items():
patched_resources[provider.get_provider_name()] = [resource for resource in resources[provider_name] if resource.status == ResourceStatus.PATCHED]
return patched_resources

def get_upgradable_resources(self, disable_cache: bool = False) -> dict[str, Sequence[VersionedResource]]:
upgradable_resources: dict[str, Sequence[VersionedResource]] = {}
resources = self.get_resources(disable_cache)
Expand Down Expand Up @@ -128,19 +135,19 @@ def print_statistics_table(self, disable_cache: bool = False):
table = self._get_statistics(disable_cache).get_rich_table()
self.console.print(table)

def get_markdown_table_for_changed_resources(self) -> list[MarkdownTableWriter]:
def get_markdown_table_for_changed_resources(self) -> dict[str, MarkdownTableWriter]:
if self._resource_cache is None:
raise Exception("No resources found. Run get_resources() first.")

markdown_tables = []
markdown_tables = {}
for provider_name, provider in self.providers.items():
changed_resources = [
resource for resource in self._resource_cache[provider_name] if resource.status == ResourceStatus.PATCHED or resource.status == ResourceStatus.PATCH_ERROR
]
if len(changed_resources) == 0:
log.debug(f"No changed resources found for provider {provider_name}. Skipping.")
continue
markdown_tables.append(provider.get_markdown_table(changed_resources))
markdown_tables[provider_name] = provider.get_markdown_table(changed_resources)
return markdown_tables

def set_resources_patched_based_on_existing_resources(self, original_resources: dict[str, Sequence[VersionedResource]]) -> None:
Expand All @@ -157,3 +164,21 @@ def set_resources_patched_based_on_existing_resources(self, original_resources:
found_resource = found_resources[0]
found_resource.set_patched()
self._resource_cache[provider_name][i] = found_resource # type: ignore

def get_release_notes(self, resources: dict[str, Sequence[VersionedResource]]) -> dict[str, Sequence[VersionedResourceReleaseNotes]]:
release_notes: dict[str, Sequence[VersionedResourceReleaseNotes]] = {}
for provider_name, provider in self.providers.items():
provider_release_notes: list[VersionedResourceReleaseNotes] = []
patched_resources = [resource for resource in resources[provider_name] if resource.status == ResourceStatus.PATCHED]
grouped_resources = provider.get_grouped_by_identifier(patched_resources)
for identifier in progress.track(grouped_resources, description=f"Getting release notes for resources of Provider {provider.get_provider_display_name()}..."):
identifier_resources = grouped_resources[identifier]
if identifier_resources[0].status == ResourceStatus.NO_VERSION_FOUND:
log.debug(f"Skipping resource '{identifier_resources[0].name}' since no version was found.")
continue
resource_release_note = provider.get_resource_release_notes(grouped_resources[identifier][0])
if resource_release_note is not None:
resource_release_note.resources = grouped_resources[identifier]
provider_release_notes.append(resource_release_note)
release_notes[provider_name] = provider_release_notes
return release_notes
Loading

0 comments on commit 8443b76

Please sign in to comment.