Compare commits

...

6 commits

6 changed files with 97 additions and 41 deletions

View file

@ -8,8 +8,8 @@ class ReleaseService:
def __init__( def __init__(
self, self,
git_api: GitApi, git_api: GitApi,
build_file_repository: BuildFileRepository,
artifact_deployment_api: ArtifactDeploymentApi, artifact_deployment_api: ArtifactDeploymentApi,
build_file_repository: BuildFileRepository,
): ):
self.git_api = git_api self.git_api = git_api
self.artifact_deployment_api = artifact_deployment_api self.artifact_deployment_api = artifact_deployment_api
@ -63,7 +63,12 @@ class ReleaseService:
def publish_artifacts(self, release: Release): def publish_artifacts(self, release: Release):
for artifact_path in release.release_artifacts: for artifact_path in release.release_artifacts:
self.artifact_deployment_api.calculate_checksums(artifact_path) self.artifact_deployment_api.calculate_checksums(artifact_path)
self.artifact_deployment_api.create_forgejo_release(release.forgejo_release_api_endpoint) # create release self.artifact_deployment_api.create_forgejo_release(
release.forgejo_release_api_endpoint(),
release.version.to_string(),
release.release_artifact_token
)
# create release
# add artifacts to release # add artifacts to release
pass pass

View file

@ -8,7 +8,7 @@ from .provider_digitalocean import Digitalocean
from .provider_hetzner import Hetzner from .provider_hetzner import Hetzner
from .c4k import C4k from .c4k import C4k
from .image import Image from .image import Image
from .release import ReleaseType from .release import ReleaseType, Release
from ..infrastructure import BuildFileRepository, CredentialsApi, EnvironmentApi, GitApi from ..infrastructure import BuildFileRepository, CredentialsApi, EnvironmentApi, GitApi
@ -69,6 +69,7 @@ class InitService:
Path(primary_build_file_id) Path(primary_build_file_id)
) )
version = primary_build_file.get_version() version = primary_build_file.get_version()
default_mappings += Release.get_mapping_default()
credentials = Credentials(inp, default_mappings) credentials = Credentials(inp, default_mappings)
authorization = self.authorization(credentials) authorization = self.authorization(credentials)

View file

@ -1,4 +1,4 @@
from typing import Optional, List from typing import Optional, List, Dict
from pathlib import Path from pathlib import Path
from .common import ( from .common import (
Validateable, Validateable,
@ -17,9 +17,7 @@ class Release(Validateable):
self.release_primary_build_file = inp.get( self.release_primary_build_file = inp.get(
"release_primary_build_file", "./project.clj" "release_primary_build_file", "./project.clj"
) )
self.release_artifacts = inp.get( self.release_artifacts = inp.get("release_artifacts", [])
"release_artifacts", []
)
self.release_secondary_build_files = inp.get( self.release_secondary_build_files = inp.get(
"release_secondary_build_files", [] "release_secondary_build_files", []
) )
@ -27,6 +25,7 @@ class Release(Validateable):
self.release_artifact_server_url = inp.get("release_artifact_server_url") self.release_artifact_server_url = inp.get("release_artifact_server_url")
self.release_organisation = inp.get("release_organisation") self.release_organisation = inp.get("release_organisation")
self.release_repository_name = inp.get("release_repository_name") self.release_repository_name = inp.get("release_repository_name")
self.release_artifact_token = inp.get("release_artifact_token")
def update_release_type(self, release_type: ReleaseType): def update_release_type(self, release_type: ReleaseType):
self.release_type = release_type self.release_type = release_type
@ -59,7 +58,18 @@ class Release(Validateable):
and self.release_type != ReleaseType.NONE and self.release_type != ReleaseType.NONE
and self.release_main_branch != self.release_current_branch and self.release_main_branch != self.release_current_branch
): ):
result.append(f"Releases are allowed only on {self.release_main_branch}") result.append(
f"Releases are allowed only on {self.release_main_branch}"
)
return result
def validate_for_artifact(self):
result = []
result += self.__validate_is_not_empty__("release_artifact_server_url")
result += self.__validate_is_not_empty__("release_organisation")
result += self.__validate_is_not_empty__("release_repository_name")
result += self.__validate_is_not_empty__("release_artifacts")
result += self.__validate_is_not_empty__("release_artifact_token")
return result return result
def build_files(self) -> List[str]: def build_files(self) -> List[str]:
@ -68,10 +78,22 @@ class Release(Validateable):
return result return result
def forgejo_release_api_endpoint(self): def forgejo_release_api_endpoint(self):
if self.release_artifact_server_url == None or self.release_organisation == None or self.release_repository_name == None: validation = self.validate_for_artifact()
raise RuntimeError("when doing artifact release, release_artifact_server_url, release_organisation, release_repository_name may not be None.") if validation != []:
raise RuntimeError(f"not valid for creating artifacts: {validation}")
server_url = self.release_artifact_server_url.removeprefix("/").removesuffix("/") server_url = self.release_artifact_server_url.removeprefix("/").removesuffix(
"/"
)
organisation = self.release_organisation.removeprefix("/").removesuffix("/") organisation = self.release_organisation.removeprefix("/").removesuffix("/")
repository = self.release_repository_name.removeprefix("/").removesuffix("/") repository = self.release_repository_name.removeprefix("/").removesuffix("/")
return f"{server_url}/api/v1/repos/{organisation}/{repository}/releases" return f"{server_url}/api/v1/repos/{organisation}/{repository}/releases"
@classmethod
def get_mapping_default(cls) -> List[Dict[str, str]]:
return [
{
"gopass_path": "server/meissa/repo/buero-rw",
"name": "release_artifact_token",
}
]

View file

@ -65,16 +65,12 @@ class ImageApi:
def dockerhub_login(self, username: str, password: str): def dockerhub_login(self, username: str, password: str):
self.execution_api.execute_secure( self.execution_api.execute_secure(
f"docker login --username {username} --password {password}", f"docker login --username {username} --password {password}",
"docker login --username ***** --password *****" "docker login --username ***** --password *****",
) )
def dockerhub_publish(self, name: str, username: str, tag: str): def dockerhub_publish(self, name: str, username: str, tag: str):
self.execution_api.execute_live( self.execution_api.execute_live(f"docker tag {name} {username}/{name}:{tag}")
f"docker tag {name} {username}/{name}:{tag}" self.execution_api.execute_live(f"docker push {username}/{name}:{tag}")
)
self.execution_api.execute_live(
f"docker push {username}/{name}:{tag}"
)
def test(self, name: str, path: Path): def test(self, name: str, path: Path):
self.execution_api.execute_live( self.execution_api.execute_live(
@ -95,14 +91,24 @@ class ExecutionApi:
check=check, check=check,
stdout=PIPE, stdout=PIPE,
stderr=PIPE, stderr=PIPE,
text=True).stdout text=True,
).stdout
output = output.rstrip() output = output.rstrip()
except CalledProcessError as exc: except CalledProcessError as exc:
print(f"Command failed with code: {exc.returncode} and message: {exc.stderr}") print(
f"Command failed with code: {exc.returncode} and message: {exc.stderr}"
)
raise exc raise exc
return output return output
def execute_secure(self, command: str, sanitized_command: str, dry_run=False, shell=True, check=True): def execute_secure(
self,
command: str,
sanitized_command: str,
dry_run=False,
shell=True,
check=True,
):
try: try:
output = self.execute(command, dry_run, shell, check) output = self.execute(command, dry_run, shell, check)
return output return output
@ -213,25 +219,42 @@ class ArtifactDeploymentApi:
self.execution_api = ExecutionApi() self.execution_api = ExecutionApi()
def create_forgejo_release(self, target_url: str, tag: str, token: str): def create_forgejo_release(self, target_url: str, tag: str, token: str):
return self.execution_api.execute_secure(f'curl -X "POST" "{target_url}" ' return self.execution_api.execute_secure(
+ '-H "accept: application/json" -H "Content-Type: application/json" ' f'curl -X "POST" "{target_url}" '
+ f'-d "{{ "body": "Provides files for release {tag} Attention: The "Source Code"-files below are not up-to-date!", "tag_name": "{tag}"}}" ' # noqa: E501 + '-H "accept: application/json" -H "Content-Type: application/json" '
+ f'-H "Authorization: token {token}"', + f'-d "{{ "body": "Provides files for release {tag} Attention: The "Source Code"-files below are not up-to-date!", "tag_name": "{tag}"}}" ' # noqa: E501
sanitized_command=f'curl -X "POST" "{target_url}" ' + f'-H "Authorization: token {token}"',
+ '-H "accept: application/json" -H "Content-Type: application/json" ' sanitized_command=f'curl -X "POST" "{target_url}" '
+ f'-d "{{ "body": "Provides files for release {tag} Attention: The "Source Code"-files below are not up-to-date!", "tag_name": "{tag}"}}" ') # noqa: E501 + '-H "accept: application/json" -H "Content-Type: application/json" '
+ f'-d "{{ "body": "Provides files for release {tag} Attention: The "Source Code"-files below are not up-to-date!", "tag_name": "{tag}"}}" ',
) # noqa: E501
def post_asset(self, target_url: str, release_id: str, attachment: str, attachment_type: str, token: str): def post_asset(
return self.execution_api.execute_secure(f'curl -X "POST" "{target_url}/{release_id}/assets" ' # {target_url}/{release_id}/assets move to Domain self,
+ f'-H "accept: application/json" -H "Authorization: token {token}" ' target_url: str,
+ '-H "Content-Type: multipart/form-data" ' release_id: str,
+ f'-F "attachment=@{attachment};type={attachment_type}"', attachment: str,
sanitized_command=f'curl -X "POST" "{target_url}/{release_id}/assets" ' # see above attachment_type: str,
+ '-H "accept: application/json" ' token: str,
+ '-H "Content-Type: multipart/form-data" ' ):
+ f'-F "attachment=@{attachment};type={attachment_type}"') return self.execution_api.execute_secure(
f'curl -X "POST" "{target_url}/{release_id}/assets" ' # {target_url}/{release_id}/assets move to Domain
+ f'-H "accept: application/json" -H "Authorization: token {token}" '
+ '-H "Content-Type: multipart/form-data" '
+ f'-F "attachment=@{attachment};type={attachment_type}"',
sanitized_command=f'curl -X "POST" "{target_url}/{release_id}/assets" ' # see above
+ '-H "accept: application/json" '
+ '-H "Content-Type: multipart/form-data" '
+ f'-F "attachment=@{attachment};type={attachment_type}"',
)
def calculate_checksums(self, artifact_path: str): def calculate_checksums(self, artifact_path: str):
# self.execution_api.execute(f"find {artifact_path} -type f -exec sha256sum {{}}; | sort > {artifact_path} sha256sum.lst") relevant für provs # self.execution_api.execute(f"find {artifact_path} -type f
self.execution_api(f"sha256sum {artifact_path} > {artifact_path}.sha256",) # -exec sha256sum {{}}; | sort > {artifact_path} sha256sum.lst")
self.execution_api(f"sha512sum {artifact_path} > {artifact_path}.sha512",) # relevant für provs
self.execution_api(
f"sha256sum {artifact_path} > {artifact_path}.sha256",
)
self.execution_api(
f"sha512sum {artifact_path} > {artifact_path}.sha512",
)

View file

@ -54,6 +54,7 @@ def devops_config(overrides: dict) -> dict:
"release_primary_build_file": "./package.json", "release_primary_build_file": "./package.json",
"release_secondary_build_file": [], "release_secondary_build_file": [],
"release_artifacts": [], "release_artifacts": [],
"release_artifact_token": "release_artifact_token",
"release_artifact_server_url": None, "release_artifact_server_url": None,
"release_organisation": None, "release_organisation": None,
"release_repository_name": None, "release_repository_name": None,

View file

@ -68,6 +68,8 @@ def test_should_calculate_forgejo_release_api_endpoint():
sut = Release( sut = Release(
devops_config( devops_config(
{ {
"release_artifacts": ["x"],
"release_artifact_token": "y",
"release_artifact_server_url": "https://repo.prod.meissa.de", "release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa", "release_organisation": "meissa",
"release_repository_name": "provs", "release_repository_name": "provs",
@ -83,6 +85,8 @@ def test_should_calculate_forgejo_release_api_endpoint():
sut = Release( sut = Release(
devops_config( devops_config(
{ {
"release_artifacts": ["x"],
"release_artifact_token": "y",
"release_artifact_server_url": "https://repo.prod.meissa.de/", "release_artifact_server_url": "https://repo.prod.meissa.de/",
"release_organisation": "/meissa/", "release_organisation": "/meissa/",
"release_repository_name": "provs", "release_repository_name": "provs",