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

WIP Adding a toggleable additional Gitea external mirror #2071

Draft
wants to merge 48 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e4a00ae
add gitea_external_server
craddm Aug 1, 2024
ce965cf
add additional files for external gitea
craddm Aug 1, 2024
fa62af6
Add gitea_external_mirror config flag
craddm Aug 1, 2024
cb70be7
Create enum for GiteaServerAvailability
craddm Aug 1, 2024
73259e4
Create GiteaServerAvailability type
craddm Aug 1, 2024
34b25cd
Add gitea_external_mirror to config template
craddm Aug 1, 2024
b218499
add gitea_server field to GiteaServerProps
craddm Aug 1, 2024
15dcbf0
change gitea_external_mirror field entry to list
craddm Aug 1, 2024
7f5c04c
add gitea_servers field to SREUserServiceProps
craddm Aug 1, 2024
2acba92
change gitea enum name and possible responses
craddm Aug 1, 2024
39845fe
change GiteaServer type
craddm Aug 1, 2024
700f6f2
Change use of GiteaServers enum
craddm Aug 1, 2024
4042d1e
Change GiteaServer Enum possible values
craddm Aug 1, 2024
27f0c00
Iterate over gitea_servers to deploy multiple types
craddm Aug 1, 2024
26b5f47
Modify config template
craddm Aug 1, 2024
ebcc502
Remove seperate gitea component for external mirror
craddm Aug 1, 2024
a9643f1
Switch to boolean toggle for external mirror
craddm Aug 1, 2024
5fa097b
Remove GiteaServers enum
craddm Aug 2, 2024
3adc136
Change gitea flag to boolean
craddm Aug 2, 2024
3a20cc8
Use new fieldname
craddm Aug 2, 2024
dfbaf54
Switch to boolean toggle for external mirror
craddm Aug 2, 2024
d9067f9
Run lint:fmt
craddm Aug 2, 2024
4705e22
Switch to boolean toggle and change logic for deploying multiple servers
craddm Aug 2, 2024
5ab174d
Merge branch 'alan-turing-institute:develop' into gitea_external_mirror
craddm Aug 2, 2024
b6f3a50
remove deprecated enum
craddm Aug 2, 2024
d8e7ee5
switch to external_git_mirror
craddm Aug 2, 2024
486261e
rename gitea property
craddm Aug 2, 2024
da2b231
comment out external subnet while testing
craddm Aug 2, 2024
cb242ac
comment out external subnet while testing
craddm Aug 2, 2024
d499684
use separate fileshares for external and internal gitea servers
craddm Aug 2, 2024
599f771
don't setup ldap on external gitea server
craddm Aug 2, 2024
a575e71
Merge remote-tracking branch 'upstream/develop' into gitea_external_m…
craddm Sep 13, 2024
0d32339
fix linting error
craddm Sep 13, 2024
426c6d6
Add ip range for external git mirror
craddm Sep 13, 2024
424b18b
add subnet and nsg for external git mirror
craddm Sep 13, 2024
c86beec
add enum for git mirror nsg rule
craddm Sep 16, 2024
52badd4
differentiate between internal and external gitea hosts
craddm Sep 16, 2024
92669fc
add some test NSG rules
craddm Sep 16, 2024
343b20c
differentiate between internal and external gitea servers
craddm Sep 16, 2024
3b7e1f9
create list of gitea servers
craddm Sep 16, 2024
e8d3488
add type hint
craddm Sep 16, 2024
72e7cc1
swap gitea over
craddm Sep 16, 2024
638982b
Merge remote-tracking branch 'upstream/develop' into gitea_external_m…
craddm Sep 23, 2024
cf31e76
Capture gitea hostnames correctly
craddm Sep 23, 2024
dfb6d7e
Use dicts rather than lists for gitea server related variables
craddm Sep 23, 2024
b53ac70
Fix setting hostnames for external and internal gitea
craddm Sep 24, 2024
36a8cd6
populate gitea_hostnames
craddm Sep 24, 2024
53c88a7
Merge branch 'alan-turing-institute:develop' into gitea_external_mirror
craddm Oct 11, 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
2 changes: 2 additions & 0 deletions data_safe_haven/config/config_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
DatabaseSystem,
EmailAddress,
Fqdn,
GiteaServers,
Guid,
IpAddress,
SafeString,
Expand Down Expand Up @@ -53,6 +54,7 @@ class ConfigSectionSRE(BaseModel, validate_assignment=True):
data_provider_ip_addresses: list[IpAddress] = Field(
..., default_factory=list[IpAddress]
)
gitea_servers: GiteaServers = GiteaServers.INTERNAL
remote_desktop: ConfigSubsectionRemoteDesktopOpts = Field(
..., default_factory=ConfigSubsectionRemoteDesktopOpts
)
Expand Down
1 change: 1 addition & 0 deletions data_safe_haven/config/sre_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def template(cls: type[Self]) -> SREConfig:
data_provider_ip_addresses=[
"List of IP addresses belonging to data providers"
],
gitea_servers="both/internal: whether to deploy both external and internal Gitea servers, or only an internal server",
craddm marked this conversation as resolved.
Show resolved Hide resolved
remote_desktop=ConfigSubsectionRemoteDesktopOpts.model_construct(
allow_copy="True/False: whether to allow copying text out of the environment",
allow_paste="True/False: whether to allow pasting text into the environment",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ def __call__(self) -> None:
dns_server_ip=dns.ip_address,
dockerhub_credentials=dockerhub_credentials,
gitea_database_password=data.password_gitea_database_admin,
gitea_servers=self.config.sre.gitea_external_mirrors,
hedgedoc_database_password=data.password_hedgedoc_database_admin,
ldap_server_hostname=identity.hostname,
ldap_server_port=identity.server_port,
Expand Down
349 changes: 349 additions & 0 deletions data_safe_haven/infrastructure/programs/sre/gitea_external_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
from collections.abc import Mapping

from pulumi import ComponentResource, Input, Output, ResourceOptions
from pulumi_azure_native import containerinstance, storage

from data_safe_haven.infrastructure.common import (
DockerHubCredentials,
get_ip_address_from_container_group,
)
from data_safe_haven.infrastructure.components import (
FileShareFile,
FileShareFileProps,
LocalDnsRecordComponent,
LocalDnsRecordProps,
PostgresqlDatabaseComponent,
PostgresqlDatabaseProps,
)
from data_safe_haven.resources import resources_path
from data_safe_haven.utility import FileReader


class SREGiteaExternalServerProps:
"""Properties for SREGiteaExternalServerComponent"""

def __init__(
self,
containers_subnet_id: Input[str],
database_password: Input[str],
database_subnet_id: Input[str],
dns_server_ip: Input[str],
dockerhub_credentials: DockerHubCredentials,
ldap_server_hostname: Input[str],
ldap_server_port: Input[int],
ldap_username_attribute: Input[str],
ldap_user_filter: Input[str],
ldap_user_search_base: Input[str],
location: Input[str],
resource_group_name: Input[str],
sre_fqdn: Input[str],
storage_account_key: Input[str],
storage_account_name: Input[str],
database_username: Input[str] | None = None,
) -> None:
self.containers_subnet_id = containers_subnet_id
self.database_password = database_password
self.database_subnet_id = database_subnet_id
self.database_username = (
database_username if database_username else "postgresadmin"
)
self.dns_server_ip = dns_server_ip
self.dockerhub_credentials = dockerhub_credentials
self.ldap_server_hostname = ldap_server_hostname
self.ldap_server_port = ldap_server_port
self.ldap_username_attribute = ldap_username_attribute
self.ldap_user_filter = ldap_user_filter
self.ldap_user_search_base = ldap_user_search_base
self.location = location
self.resource_group_name = resource_group_name
self.sre_fqdn = sre_fqdn
self.storage_account_key = storage_account_key
self.storage_account_name = storage_account_name


class SREGiteaExternalServerComponent(ComponentResource):
"""Deploy Gitea server with Pulumi"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of overlap between this and the SREGiteaServerComponent. Can we combine both into a single reusable component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what I'm actually doing, I just haven't pushed yet


def __init__(
self,
name: str,
stack_name: str,
props: SREGiteaExternalServerProps,
opts: ResourceOptions | None = None,
tags: Input[Mapping[str, Input[str]]] | None = None,
) -> None:
super().__init__("dsh:sre:GiteaExternalServerComponent", name, {}, opts)
child_opts = ResourceOptions.merge(opts, ResourceOptions(parent=self))
child_tags = tags if tags else {}

# Define configuration file shares
file_share_gitea_caddy = storage.FileShare(
f"{self._name}_file_share_gitea_caddy",
access_tier=storage.ShareAccessTier.COOL,
account_name=props.storage_account_name,
resource_group_name=props.resource_group_name,
share_name="gitea-caddy",
share_quota=1,
signed_identifiers=[],
opts=child_opts,
)
file_share_gitea_gitea = storage.FileShare(
f"{self._name}_file_share_gitea_gitea",
access_tier=storage.ShareAccessTier.COOL,
account_name=props.storage_account_name,
resource_group_name=props.resource_group_name,
share_name="gitea-gitea",
share_quota=1,
signed_identifiers=[],
opts=child_opts,
)

# Upload caddy file
caddy_caddyfile_reader = FileReader(
resources_path / "gitea" / "caddy" / "Caddyfile"
)
file_share_gitea_caddy_caddyfile = FileShareFile(
f"{self._name}_file_share_gitea_caddy_caddyfile",
FileShareFileProps(
destination_path=caddy_caddyfile_reader.name,
share_name=file_share_gitea_caddy.name,
file_contents=Output.secret(caddy_caddyfile_reader.file_contents()),
storage_account_key=props.storage_account_key,
storage_account_name=props.storage_account_name,
),
opts=ResourceOptions.merge(
child_opts, ResourceOptions(parent=file_share_gitea_caddy)
),
)

# Upload Gitea configuration script
gitea_configure_sh_reader = FileReader(
resources_path / "gitea" / "gitea" / "configure.mustache.sh"
)
gitea_configure_sh = Output.all(
admin_email="[email protected]",
admin_username="dshadmin",
ldap_username_attribute=props.ldap_username_attribute,
ldap_user_filter=props.ldap_user_filter,
ldap_server_hostname=props.ldap_server_hostname,
ldap_server_port=props.ldap_server_port,
ldap_user_search_base=props.ldap_user_search_base,
).apply(
lambda mustache_values: gitea_configure_sh_reader.file_contents(
mustache_values
)
)
file_share_gitea_gitea_configure_sh = FileShareFile(
f"{self._name}_file_share_gitea_gitea_configure_sh",
FileShareFileProps(
destination_path=gitea_configure_sh_reader.name,
share_name=file_share_gitea_gitea.name,
file_contents=Output.secret(gitea_configure_sh),
storage_account_key=props.storage_account_key,
storage_account_name=props.storage_account_name,
),
opts=ResourceOptions.merge(
child_opts, ResourceOptions(parent=file_share_gitea_gitea)
),
)
# Upload Gitea entrypoint script
gitea_entrypoint_sh_reader = FileReader(
resources_path / "gitea" / "gitea" / "entrypoint.sh"
)
file_share_gitea_gitea_entrypoint_sh = FileShareFile(
f"{self._name}_file_share_gitea_gitea_entrypoint_sh",
FileShareFileProps(
destination_path=gitea_entrypoint_sh_reader.name,
share_name=file_share_gitea_gitea.name,
file_contents=Output.secret(gitea_entrypoint_sh_reader.file_contents()),
storage_account_key=props.storage_account_key,
storage_account_name=props.storage_account_name,
),
opts=ResourceOptions.merge(
child_opts, ResourceOptions(parent=file_share_gitea_gitea)
),
)

# Define a PostgreSQL server and default database
db_gitea_repository_name = "gitea"
db_server_gitea = PostgresqlDatabaseComponent(
f"{self._name}_db_gitea",
PostgresqlDatabaseProps(
database_names=[db_gitea_repository_name],
database_password=props.database_password,
database_resource_group_name=props.resource_group_name,
database_server_name=f"{stack_name}-db-server-gitea",
database_subnet_id=props.database_subnet_id,
database_username=props.database_username,
location=props.location,
),
opts=child_opts,
tags=child_tags,
)

# Define the container group with guacd, guacamole and caddy
container_group = containerinstance.ContainerGroup(
f"{self._name}_container_group",
container_group_name=f"{stack_name}-container-group-gitea",
containers=[
containerinstance.ContainerArgs(
image="caddy:2.8.4",
name="caddy"[:63],
ports=[
containerinstance.ContainerPortArgs(
port=80,
protocol=containerinstance.ContainerGroupNetworkProtocol.TCP,
),
],
resources=containerinstance.ResourceRequirementsArgs(
requests=containerinstance.ResourceRequestsArgs(
cpu=0.5,
memory_in_gb=0.5,
),
),
volume_mounts=[
containerinstance.VolumeMountArgs(
mount_path="/etc/caddy",
name="caddy-etc-caddy",
read_only=True,
),
],
),
containerinstance.ContainerArgs(
image="gitea/gitea:1.22.1",
name="gitea"[:63],
command=["/app/custom/entrypoint.sh"],
environment_variables=[
containerinstance.EnvironmentVariableArgs(
name="APP_NAME", value="Data Safe Haven Git server"
),
containerinstance.EnvironmentVariableArgs(
name="RUN_MODE", value="dev"
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__database__DB_TYPE", value="postgres"
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__database__HOST",
value=db_server_gitea.private_ip_address,
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__database__NAME", value=db_gitea_repository_name
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__database__USER",
value=props.database_username,
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__database__PASSWD",
secure_value=props.database_password,
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__database__SSL_MODE", value="require"
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__log__LEVEL",
# Options are: "Trace", "Debug", "Info" [default], "Warn", "Error", "Critical" or "None".
value="Debug",
),
containerinstance.EnvironmentVariableArgs(
name="GITEA__security__INSTALL_LOCK", value="true"
),
],
ports=[
containerinstance.ContainerPortArgs(
port=22,
protocol=containerinstance.ContainerGroupNetworkProtocol.TCP,
),
],
resources=containerinstance.ResourceRequirementsArgs(
requests=containerinstance.ResourceRequestsArgs(
cpu=2,
memory_in_gb=2,
),
),
volume_mounts=[
containerinstance.VolumeMountArgs(
mount_path="/app/custom",
name="gitea-app-custom",
read_only=True,
),
],
),
],
dns_config=containerinstance.DnsConfigurationArgs(
name_servers=[props.dns_server_ip],
),
# Required due to DockerHub rate-limit: https://docs.docker.com/docker-hub/download-rate-limit/
image_registry_credentials=[
{
"password": Output.secret(props.dockerhub_credentials.access_token),
"server": props.dockerhub_credentials.server,
"username": props.dockerhub_credentials.username,
}
],
ip_address=containerinstance.IpAddressArgs(
ports=[
containerinstance.PortArgs(
port=80,
protocol=containerinstance.ContainerGroupNetworkProtocol.TCP,
)
],
type=containerinstance.ContainerGroupIpAddressType.PRIVATE,
),
location=props.location,
os_type=containerinstance.OperatingSystemTypes.LINUX,
resource_group_name=props.resource_group_name,
restart_policy=containerinstance.ContainerGroupRestartPolicy.ALWAYS,
sku=containerinstance.ContainerGroupSku.STANDARD,
subnet_ids=[
containerinstance.ContainerGroupSubnetIdArgs(
id=props.containers_subnet_id
)
],
volumes=[
containerinstance.VolumeArgs(
azure_file=containerinstance.AzureFileVolumeArgs(
share_name=file_share_gitea_caddy.name,
storage_account_key=props.storage_account_key,
storage_account_name=props.storage_account_name,
),
name="caddy-etc-caddy",
),
containerinstance.VolumeArgs(
azure_file=containerinstance.AzureFileVolumeArgs(
share_name=file_share_gitea_gitea.name,
storage_account_key=props.storage_account_key,
storage_account_name=props.storage_account_name,
),
name="gitea-app-custom",
),
],
opts=ResourceOptions.merge(
child_opts,
ResourceOptions(
delete_before_replace=True,
depends_on=[
file_share_gitea_caddy_caddyfile,
file_share_gitea_gitea_configure_sh,
file_share_gitea_gitea_entrypoint_sh,
],
replace_on_changes=["containers"],
),
),
tags=child_tags,
)

# Register the container group in the SRE DNS zone
LocalDnsRecordComponent(
f"{self._name}_gitea_dns_record_set",
LocalDnsRecordProps(
base_fqdn=props.sre_fqdn,
private_ip_address=get_ip_address_from_container_group(container_group),
record_name="gitea",
resource_group_name=props.resource_group_name,
),
opts=ResourceOptions.merge(
child_opts, ResourceOptions(parent=container_group)
),
)
Loading
Loading