Compare commits

...

6 commits

11 changed files with 122 additions and 47 deletions

View file

@ -3,17 +3,18 @@ from dda_python_terraform import Terraform, IsFlagged
from packaging import version from packaging import version
from ..domain import Devops, BuildType from ..domain import Devops, BuildType
from ..infrastructure import FileApi, ResourceApi, TerraformApi from ..infrastructure import FileApi, ResourceApi, TerraformApi, TerraformBackendGitApi
# TODO: mv more fkt to Terraform_api ? # TODO: mv more fkt to Terraform_api ?
class TerraformService: class TerraformService:
def __init__( def __init__(
self, file_api: FileApi, resource_api: ResourceApi, terraform_api: TerraformApi self, file_api: FileApi, resource_api: ResourceApi, terraform_api: TerraformApi, tf_backend_git_api: TerraformBackendGitApi
): ):
self.file_api = file_api self.file_api = file_api
self.resource_api = resource_api self.resource_api = resource_api
self.terraform_api = terraform_api self.terraform_api = terraform_api
self.tf_backend_git_api = tf_backend_git_api
@classmethod @classmethod
def prod(cls): def prod(cls):
@ -21,6 +22,7 @@ class TerraformService:
FileApi(), FileApi(),
ResourceApi(), ResourceApi(),
TerraformApi(), TerraformApi(),
TerraformBackendGitApi(),
) )
def initialize_build_dir(self, devops: Devops): def initialize_build_dir(self, devops: Devops):
@ -35,6 +37,15 @@ class TerraformService:
self.file_api.cp("*.tfvars", devops.build_path(), check=False) self.file_api.cp("*.tfvars", devops.build_path(), check=False)
self.file_api.cp_recursive("scripts", devops.build_path(), check=False) 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()
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()
def read_output(self, devops: Devops) -> map: def read_output(self, devops: Devops) -> map:
terraform_domain = devops.specialized_builds[BuildType.TERRAFORM] terraform_domain = devops.specialized_builds[BuildType.TERRAFORM]
return self.file_api.read_json_fro_file( return self.file_api.read_json_fro_file(
@ -154,6 +165,8 @@ class TerraformService:
def post_build(self, devops: Devops): def post_build(self, devops: Devops):
self.__rescue_local_state__(devops) self.__rescue_local_state__(devops)
self.tf_backend_git_api.stop()
def __copy_build_resource_file_from_package__(self, resource_name, devops: Devops): def __copy_build_resource_file_from_package__(self, resource_name, devops: Devops):
data = self.resource_api.read_resource( data = self.resource_api.read_resource(

View file

@ -14,12 +14,18 @@ class DevopsTerraformBuild(DevopsBuild):
super().__init__(project, inp) super().__init__(project, inp)
project.build_depends_on("dda-python-terraform") project.build_depends_on("dda-python-terraform")
self.teraform_service = TerraformService.prod() self.teraform_service = TerraformService.prod()
# ToDo: we might want to make this private in the future, keeping this for compatibility
def initialize_build_dir(self): def initialize_build_dir(self):
super().initialize_build_dir() super().initialize_build_dir()
devops = self.devops_repo.get_devops(self.project) devops = self.devops_repo.get_devops(self.project)
self.teraform_service.initialize_build_dir(devops) self.teraform_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()
def post_build(self): def post_build(self):
devops = self.devops_repo.get_devops(self.project) devops = self.devops_repo.get_devops(self.project)
self.teraform_service.post_build(devops) self.teraform_service.post_build(devops)

View file

@ -17,6 +17,7 @@ class ProviderType(Enum):
DIGITALOCEAN = 0 DIGITALOCEAN = 0
HETZNER = 1 HETZNER = 1
AWS = 2 AWS = 2
TERRAFORM_BACKEND_GIT = 3
class MixinType(Enum): class MixinType(Enum):

View file

@ -6,6 +6,7 @@ from .devops_factory import DevopsFactory
from .terraform import TerraformDomain from .terraform import TerraformDomain
from .provider_digitalocean import Digitalocean from .provider_digitalocean import Digitalocean
from .provider_hetzner import Hetzner from .provider_hetzner import Hetzner
from .provider_tf_backend_git import TerraformBackendGit
from .c4k import C4k from .c4k import C4k
from .image import Image from .image import Image
from .release import ReleaseType, Release from .release import ReleaseType, Release
@ -60,6 +61,8 @@ class InitService:
default_mappings += Digitalocean.get_mapping_default() default_mappings += Digitalocean.get_mapping_default()
if ProviderType.HETZNER in provider_types: if ProviderType.HETZNER in provider_types:
default_mappings += Hetzner.get_mapping_default() default_mappings += Hetzner.get_mapping_default()
if ProviderType.TERRAFORM_BACKEND_GIT in provider_types:
default_mappings += TerraformBackendGit.get_mapping_default()
if MixinType.RELEASE in mixin_types: if MixinType.RELEASE in mixin_types:
primary_build_file_id = inp.get( primary_build_file_id = inp.get(

View file

@ -1,44 +0,0 @@
from typing import List, Dict, Set, Any
from .common import Validateable, CredentialMappingDefault
class TerraformGitBackend(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
def backend_config(self) -> Dict[str, Any]:
return {
"git_backend_repo": self.git_backend_repo,
"git_backend_ref": self.git_backend_ref,
"git_backend_state": self.git_backend_state,
"git_backend_username": self.git_backend_username,
"git_backend_token": self.git_backend_token,
}
def project_vars(self):
return {
"git_backend_token": self.git_backend_token,
"git_backend_username": self.git_backend_username,
}

View file

@ -0,0 +1,63 @@
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,6 +8,7 @@ from .common import (
from .provider_digitalocean import Digitalocean from .provider_digitalocean import Digitalocean
from .provider_hetzner import Hetzner from .provider_hetzner import Hetzner
from .provider_aws import Aws from .provider_aws import Aws
from .provider_tf_backend_git import TerraformBackendGit
class TerraformDomain(Validateable): class TerraformDomain(Validateable):
@ -42,6 +43,8 @@ class TerraformDomain(Validateable):
self.providers[ProviderType.HETZNER] = Hetzner(inp) self.providers[ProviderType.HETZNER] = Hetzner(inp)
if ProviderType.AWS in provider_types: if ProviderType.AWS in provider_types:
self.providers[ProviderType.AWS] = Aws(inp) self.providers[ProviderType.AWS] = Aws(inp)
if ProviderType.TERRAFORM_BACKEND_GIT in provider_types:
self.providers[ProviderType.TERRAFORM_BACKEND_GIT] = TerraformBackendGit(inp)
def validate(self) -> List[str]: def validate(self) -> List[str]:
result = [] result = []
@ -85,6 +88,20 @@ class TerraformDomain(Validateable):
for provider in self.providers.values(): for provider in self.providers.values():
result = result and provider.is_local_state() result = result and provider.is_local_state()
return result 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?
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 {"" : ""}
def backend_config(self) -> Dict[str, Any]: def backend_config(self) -> Dict[str, Any]:
result = {} result = {}

View file

@ -7,6 +7,7 @@ from .infrastructure import (
CredentialsApi, CredentialsApi,
GitApi, GitApi,
TerraformApi, TerraformApi,
TerraformBackendGitApi,
ArtifactDeploymentApi, ArtifactDeploymentApi,
) )
from .repository import DevopsRepository, BuildFileRepository from .repository import DevopsRepository, BuildFileRepository

View file

@ -1,4 +1,5 @@
from subprocess import Popen, PIPE, run, CalledProcessError from subprocess import Popen, PIPE, run, CalledProcessError
from typing import Dict
from pathlib import Path from pathlib import Path
from sys import stdout from sys import stdout
from os import chmod, environ from os import chmod, environ
@ -215,6 +216,16 @@ class GitApi:
class TerraformApi: class TerraformApi:
pass 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: class ArtifactDeploymentApi:
def __init__(self): def __init__(self):

View file

@ -0,0 +1,3 @@
terraform {
backend "http" {}
}

View file

@ -0,0 +1 @@
variable http_backend_address {}