Compare commits

...

9 commits

Author SHA1 Message Date
a0dbb79d30 Add initial tests 2024-06-27 12:43:51 +02:00
26da85487f Add TODOs 2024-06-27 12:43:38 +02:00
d6b6cb7a72 Lint 2024-06-27 12:43:24 +02:00
ff61ff383e Rename to backend 2024-06-27 11:48:17 +02:00
170d9bc6d0 Implement missing methods 2024-06-27 11:42:09 +02:00
7f2c78ba97 Remove unused import, arguments 2024-06-27 11:41:42 +02:00
e72ac8882e Rename for better semantics 2024-06-27 11:40:01 +02:00
de23855d65 All upper case TODO 2024-06-27 11:39:45 +02:00
a6b6a6b8bc Typo 2024-06-27 10:18:44 +02:00
12 changed files with 152 additions and 95 deletions

View file

@ -9,12 +9,12 @@ from ..infrastructure import FileApi, ResourceApi, TerraformApi, TerraformBacken
# TODO: mv more fkt to Terraform_api ?
class TerraformService:
def __init__(
self, file_api: FileApi, resource_api: ResourceApi, terraform_api: TerraformApi, tf_backend_git_api: TerraformBackendGitApi
self, file_api: FileApi, resource_api: ResourceApi, terraform_api: TerraformApi, tf_backend_api: TerraformBackendGitApi
):
self.file_api = file_api
self.resource_api = resource_api
self.terraform_api = terraform_api
self.tf_backend_git_api = tf_backend_git_api
self.tf_backend_git_api = tf_backend_api
@classmethod
def prod(cls):
@ -38,13 +38,13 @@ class TerraformService:
self.file_api.cp_recursive("scripts", devops.build_path(), check=False)
def start_tf_backend_git_daemon(self, devops: Devops):
terraform = devops.specialized_builds[BuildType.TERRAFORM]
credentials = terraform.env_credentials()
terraform_domain = devops.specialized_builds[BuildType.TERRAFORM]
credentials = terraform_domain.env_credentials()
self.tf_backend_git_api.start(credentials)
def uses_backend_git(self, devops: Devops) -> bool:
terraform = devops.specialized_builds[BuildType.TERRAFORM]
return terraform.uses_backend_git()
terraform_domain = devops.specialized_builds[BuildType.TERRAFORM]
return terraform_domain.uses_backend_git()
def read_output(self, devops: Devops) -> map:
terraform_domain = devops.specialized_builds[BuildType.TERRAFORM]
@ -166,7 +166,6 @@ class TerraformService:
def post_build(self, devops: Devops):
self.__rescue_local_state__(devops)
self.tf_backend_git_api.stop()
def __copy_build_resource_file_from_package__(self, resource_name, devops: Devops):
data = self.resource_api.read_resource(

View file

@ -13,50 +13,51 @@ class DevopsTerraformBuild(DevopsBuild):
inp["mixin_types"] = config.get("mixin_types", [])
super().__init__(project, inp)
project.build_depends_on("dda-python-terraform")
self.teraform_service = TerraformService.prod()
# ToDo: we might want to make this private in the future, keeping this for compatibility
self.terraform_service = TerraformService.prod()
# TODO: we might want to make this private in the future, keeping this for compatibility
def initialize_build_dir(self):
super().initialize_build_dir()
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.initialize_build_dir(devops)
self.terraform_service.initialize_build_dir(devops)
def pre_build(self):
self.initialize_build_dir()
devops = self.devops_repo.get_devops(self.project)
if self.teraform_service.uses_backend_git(devops):
self.teraform_service.start_tf_backend_git_daemon()
if self.terraform_service.uses_backend_git(devops):
self.terraform_service.start_tf_backend_git_daemon(devops)
def post_build(self):
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.post_build(devops)
self.terraform_service.post_build(devops)
def read_output_json(self) -> map:
devops = self.devops_repo.get_devops(self.project)
return self.teraform_service.read_output(devops)
return self.terraform_service.read_output(devops)
def plan(self):
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.plan(devops)
self.terraform_service.plan(devops)
self.post_build()
def plan_fail_on_diff(self):
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.plan(devops, fail_on_diff=True)
self.terraform_service.plan(devops, fail_on_diff=True)
self.post_build()
def apply(self, auto_approve=False):
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.apply(devops, auto_approve=auto_approve)
self.terraform_service.apply(devops, auto_approve=auto_approve)
self.post_build()
def refresh(self):
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.refresh(devops)
self.terraform_service.refresh(devops)
self.post_build()
def destroy(self, auto_approve=False):
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.destroy(devops, auto_approve=auto_approve)
self.terraform_service.destroy(devops, auto_approve=auto_approve)
self.post_build()
def tf_import(
@ -65,5 +66,5 @@ class DevopsTerraformBuild(DevopsBuild):
tf_import_resource,
):
devops = self.devops_repo.get_devops(self.project)
self.teraform_service.tf_import(devops, tf_import_name, tf_import_resource)
self.terraform_service.tf_import(devops, tf_import_name, tf_import_resource)
self.post_build()

View file

@ -15,6 +15,7 @@ from .terraform import TerraformDomain
from .provider_digitalocean import Digitalocean
from .provider_hetzner import Hetzner
from .provider_aws import Aws
from .backend_tf_backend_git import TerraformBackendGit
from .provs_k3s import K3s
from .release import Release
from .artifact import Artifact

View file

@ -0,0 +1,72 @@
from typing import List, Dict, Set, Any
from .common import Validateable, CredentialMappingDefault
# TODO: Add a 'git_as_backend' option
# TODO: Check this option at the right places
class TerraformBackendGit(Validateable, CredentialMappingDefault):
def __init__(
self,
inp: dict,
):
self.stage = inp.get("stage")
self.module = inp.get("module")
self.git_backend_repo = inp.get("git_backend_repo")
self.git_backend_ref = inp.get("git_backend_ref")
self.git_backend_state = inp.get("git_backend_state")
self.git_backend_username = inp.get("git_backend_username")
self.git_backend_token = inp.get("git_backend_token")
def validate(self) -> List[str]:
result = []
result += self.__validate_is_not_empty__("stage")
result += self.__validate_is_not_empty__("module")
result += self.__validate_is_not_empty__("git_backend_repo")
result += self.__validate_is_not_empty__("git_backend_ref")
result += self.__validate_is_not_empty__("git_backend_state")
result += self.__validate_is_not_empty__("git_backend_username")
result += self.__validate_is_not_empty__("git_backend_token")
return result
# See: https://developer.hashicorp.com/terraform/language/settings/backends/configuration#command-line-key-value-pairs
# and https://github.com/plumber-cd/terraform-backend-git?tab=readme-ov-file#standalone-terraform-http-backend-mode
def backend_config(self) -> Dict[str, Any]:
return {
"address": self.__make_http_backend_address__(),
"lock_address": self.__make_http_backend_address__(),
"unlock_address": self.__make_http_backend_address__(),
}
def resources_from_package(self) -> Set[str]:
return {"tf_backend_git_backend.tf", "tf_backend_git_backend_vars.tf"}
# TODO: This can not be used for backend config, as the backend block can not reference vars.
def project_vars(self) -> Dict[str, Any]:
return {
"http_backend_address": self.__make_http_backend_address__()
}
def is_local_state(self):
return False
def __make_http_backend_address__(self) -> str:
# TODO Should we make this configurable?
base_string = "http://localhost:6061/?type=git"
state = f"{self.stage}/{self.module}/{self.git_backend_state}"
return f"{base_string}&repository={self.git_backend_repo}&ref={self.git_backend_ref}&state={state}"
# TODO: Implement ssh auth too
@classmethod
def get_mapping_default(cls) -> List[Dict[str, str]]:
return [
{
"gopass_path": "server/meissa/repo/terraform-backend-git-test",
"gopass_field": "user",
"name": "git_backend_username",
},
{
"gopass_path": "server/meissa/repo/terraform-backend-git-test",
"gopass_field": "token",
"name": "git_backend_token",
},
]

View file

@ -13,6 +13,7 @@ class BuildType(Enum):
TERRAFORM = 3
# TODO: We could follow domain implications and make a 'BackendType' enum
class ProviderType(Enum):
DIGITALOCEAN = 0
HETZNER = 1

View file

@ -6,7 +6,7 @@ from .devops_factory import DevopsFactory
from .terraform import TerraformDomain
from .provider_digitalocean import Digitalocean
from .provider_hetzner import Hetzner
from .provider_tf_backend_git import TerraformBackendGit
from .backend_tf_backend_git import TerraformBackendGit
from .c4k import C4k
from .image import Image
from .release import ReleaseType, Release

View file

@ -1,63 +0,0 @@
from typing import List, Dict, Set, Any
from .common import Validateable, CredentialMappingDefault
class TerraformBackendGit(Validateable, CredentialMappingDefault):
def __init__(
self,
inp: dict,
):
self.stage = inp.get("stage")
self.module = inp.get("module")
self.git_backend_repo = inp.get("git_backend_repo")
self.git_backend_ref = inp.get("git_backend_ref")
self.git_backend_state = inp.get("git_backend_state")
self.git_backend_username = inp.get("git_backend_username")
self.git_backend_token = inp.get("git_backend_token")
def validate(self) -> List[str]:
result = []
result += self.__validate_is_not_empty__("stage")
result += self.__validate_is_not_empty__("module")
result += self.__validate_is_not_empty__("git_backend_repo")
result += self.__validate_is_not_empty__("git_backend_ref")
result += self.__validate_is_not_empty__("git_backend_state")
result += self.__validate_is_not_empty__("git_backend_username")
result += self.__validate_is_not_empty__("git_backend_token")
return result
# See: https://developer.hashicorp.com/terraform/language/settings/backends/configuration#command-line-key-value-pairs
# and https://github.com/plumber-cd/terraform-backend-git?tab=readme-ov-file#standalone-terraform-http-backend-mode
def backend_config(self) -> Dict[str, Any]:
return {
"address": self.__make_http_backend_address__(self.git_backend_repo, self.git_backend_ref, self.git_backend_state),
"lock_address": self.__make_http_backend_address__(self.git_backend_repo, self.git_backend_ref, self.git_backend_state),
"unlock_address": self.__make_http_backend_address__(self.git_backend_repo, self.git_backend_ref, self.git_backend_state),
}
def resources_from_package(self) -> List[str]:
return {"tf_backend_git_backend.tf", "tf_backend_git_backend_vars.tf"}
# ToDo: This can not be used for backend config, as the backend block can not reference vars.
def project_vars(self) -> Dict[str, Any]:
return {
"http_backend_address": self.__make_http_backend_address__(self.git_backend_ref, self.git_backend_repo, self.git_backend_state)
}
def is_local_state(self):
return False
# ToDo:
def __bucket_key__(self):
pass
# ToDo:
def __make_http_backend_address__(self):
pass
# ToDo:
@classmethod
def get_mapping_default():
pass

View file

@ -8,7 +8,7 @@ from .common import (
from .provider_digitalocean import Digitalocean
from .provider_hetzner import Hetzner
from .provider_aws import Aws
from .provider_tf_backend_git import TerraformBackendGit
from .backend_tf_backend_git import TerraformBackendGit
class TerraformDomain(Validateable):
@ -88,20 +88,22 @@ class TerraformDomain(Validateable):
for provider in self.providers.values():
result = result and provider.is_local_state()
return result
def uses_backend_git(self) -> bool:
return ProviderType.TERRAFORM_BACKEND_GIT in self.providers.keys()
# ToDo: Add ssh method case and make this default
# ToDo: How do we get to the credentials?
if ProviderType.TERRAFORM_BACKEND_GIT in self.providers.keys():
return True
return False
# TODO: Add ssh method case and make this default
# TODO: How do we get to the credentials?
def env_credentials(self) -> Dict[str, str]:
tf_backend_git = self.providers[ProviderType.TERRAFORM_BACKEND_GIT]
if tf_backend_git.git_backend_token != "":
return {
"GITHUB_TOKEN" : tf_backend_git.git_backend_token,
"GIT_USERNAME" : tf_backend_git.git_backend_username,
}
return {"" : ""}
"GITHUB_TOKEN": tf_backend_git.git_backend_token,
"GIT_USERNAME": tf_backend_git.git_backend_username,
}
return {"": ""}
def backend_config(self) -> Dict[str, Any]:
result = {}

View file

@ -216,17 +216,21 @@ class GitApi:
class TerraformApi:
pass
class TerraformBackendGitApi:
def __init__(self):
self.execution_api = ExecutionApi()
def start(self, credentials: Dict[str, str]):
env = ""
for key in credentials:
env = env + f'{key}' + "=" + f'{credentials[key]}' + " "
self.execution_api.execute(f'{env}' + " " + "terraform-backend-git &")
def stop(self):
self.execution_api.execute("terraform-backend-git stop")
class ArtifactDeploymentApi:
def __init__(self):
self.execution_api = ExecutionApi()

View file

@ -24,7 +24,7 @@ def devops_config(overrides: dict) -> dict:
"k3s_letsencrypt_endpoint": "k3s_letsencrypt_endpoint",
"k3s_enable_echo": "false",
"k3s_app_filename_to_provision": "k3s_app.yaml",
"tf_provider_types": ["DIGITALOCEAN", "HETZNER", "AWS"],
"tf_provider_types": ["DIGITALOCEAN", "HETZNER", "AWS", "TERRAFORM_BACKEND_GIT"],
"tf_additional_vars": [],
"tf_output_json_name": "the_out.json",
"tf_use_workspace": None,
@ -58,6 +58,11 @@ def devops_config(overrides: dict) -> dict:
"release_artifact_server_url": None,
"release_organisation": None,
"release_repository_name": None,
"git_backend_repo": "https://repo.example.com/meissa/infra-states",
"git_backend_ref": "main",
"git_backend_state": "test.json",
"git_backend_username": "tf_backend_user",
"git_backend_token": "324asd234df435sfdgh",
"credentials_mappings": [
{
"gopass_path": "a/path",

View file

@ -0,0 +1,30 @@
from pybuilder.core import Project
from pathlib import Path
from src.main.python.ddadevops.domain import (
BuildType,
TerraformBackendGit,
)
from .helper import devops_config
def test_tf_backend_git_creation():
sut = TerraformBackendGit(
{
"module": "module",
"stage": "test",
"git_backend_repo": "https://repo.example.com/meissa/infra-states",
"git_backend_ref": "main",
"git_backend_state": "test.json",
"git_backend_username": "tf_backend_user",
"git_backend_token": "324asd234df435sfdgh",
}
)
assert sut is not None
assert sut.is_valid()
def test_should_calculate_backend_config():
sut = TerraformBackendGit(devops_config({}))
assert {
"address": "http://localhost:6061/?type=git&repository=https://repo.example.com/meissa/infra-states&ref=main&state=test/module/test.json",
"lock_address": "http://localhost:6061/?type=git&repository=https://repo.example.com/meissa/infra-states&ref=main&state=test/module/test.json",
"unlock_address": "http://localhost:6061/?type=git&repository=https://repo.example.com/meissa/infra-states&ref=main&state=test/module/test.json",
} == sut.backend_config()

View file

@ -17,6 +17,7 @@ def test_creation():
assert sut.providers[ProviderType.DIGITALOCEAN]
assert sut.providers[ProviderType.HETZNER]
assert sut.providers[ProviderType.AWS]
assert sut.providers[ProviderType.TERRAFORM_BACKEND_GIT]
def test_should_calculate_output_json_name():
@ -188,3 +189,7 @@ def test_should_calculate_local_state_handling():
)
)
assert not sut.is_local_state()
def test_should_use_backend_git():
sut = TerraformDomain(devops_config({}))
assert TerraformDomain.uses_backend_git()