Skip to content

Commit a202f87

Browse files
authored
Add Hetzner Support (#212)
* Add Hetzner Support * Fix Hetzner support * Using only token for Hetzner
1 parent 8b3d9ab commit a202f87

21 files changed

Lines changed: 587 additions & 3 deletions

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class Constants {
1717
public static final String X_GCP_SERVICE_ACCOUNT_CREDENTIALS_HEADER = "X-GCP-Service-Account-Credentials";
1818
public static final String X_OCI_SERVICE_ACCOUNT_ID_HEADER = "X-OCI-Service-Account-Id";
1919
public static final String X_OCI_SERVICE_ACCOUNT_CREDENTIALS_HEADER = "X-OCI-Service-Account-Credentials";
20+
public static final String X_HETZNER_TOKEN_HEADER = "X-Hetzner-Token";
2021

2122
public static final String CI_CD_SERVICE_ACCOUNT_NAME_KEY = "CI_CD_SERVICE_ACCOUNT_NAME";
2223
public static final String CI_CD_SERVICE_ACCOUNT_SECRET_KEY = "CI_CD_SERVICE_ACCOUNT_SECRET";
@@ -29,6 +30,7 @@ public class Constants {
2930
public static final String GCP_SERVICE_ACCOUNT_CREDENTIALS_KEY = "GCP_SERVICE_ACCOUNT_CREDENTIALS";
3031
public static final String OCI_SERVICE_ACCOUNT_ID_KEY = "OCI_SERVICE_ACCOUNT_ID";
3132
public static final String OCI_SERVICE_ACCOUNT_CREDENTIALS_KEY = "OCI_SERVICE_ACCOUNT_CREDENTIALS";
33+
public static final String HETZNER_TOKEN_KEY = "HETZNER_TOKEN";
3234
public static final String BLUEPRINT_TYPE = "blueprintType";
3335

3436
public static final String COMPONENT_TYPE = "type";

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ public String getOciServiceAccountCredentials() {
206206
return System.getenv(OCI_SERVICE_ACCOUNT_CREDENTIALS_KEY);
207207
}
208208

209+
@Override
210+
public String getHetznerToken() {
211+
return System.getenv(HETZNER_TOKEN_KEY);
212+
}
213+
209214
private URI checkAndReturnUri(String endpointEnvKey, URI defaultValue) {
210215
String endpoint = System.getenv(endpointEnvKey);
211216
if (isBlank(endpoint)) {

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/SdkConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,11 @@ public interface SdkConfiguration {
104104
* @return the OCI Service Account Credentials
105105
*/
106106
String getOciServiceAccountCredentials();
107+
108+
/**
109+
* Gets the Hetzner Token.
110+
*
111+
* @return the Hetzner Token
112+
*/
113+
String getHetznerToken();
107114
}

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/environment/BaseEnvironment.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import com.yanchware.fractal.sdk.domain.environment.aws.AwsCloudAgent;
77
import com.yanchware.fractal.sdk.domain.environment.azure.AzureCloudAgent;
88
import com.yanchware.fractal.sdk.domain.environment.gcp.GcpCloudAgent;
9+
import com.yanchware.fractal.sdk.domain.environment.hetzner.HetznerCloudAgent;
910
import com.yanchware.fractal.sdk.domain.environment.oci.OciCloudAgent;
1011
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.aws.AwsRegion;
1112
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.azure.AzureRegion;
1213
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.gcp.GcpRegion;
14+
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.hetzner.HetznerRegion;
1315
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.oci.OciRegion;
1416
import com.yanchware.fractal.sdk.domain.livesystem.service.dtos.ProviderType;
1517
import com.yanchware.fractal.sdk.utils.CollectionUtils;
@@ -115,6 +117,11 @@ protected void registerOciCloudAgent(OciRegion region, String tenancyId, String
115117
registerCloudAgent(agent);
116118
}
117119

120+
protected void registerHetznerCloudAgent(HetznerRegion region, String projectId) {
121+
var agent = new HetznerCloudAgent(getId(), region, projectId, getTags());
122+
registerCloudAgent(agent);
123+
}
124+
118125
private void registerCloudAgent(CloudAgentEntity cloudAgent){
119126
if (cloudAgentByProviderType.containsKey(cloudAgent.getProvider()))
120127
{

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/environment/ManagementEnvironment.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.aws.AwsRegion;
44
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.azure.AzureRegion;
55
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.gcp.GcpRegion;
6+
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.hetzner.HetznerRegion;
67
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.oci.OciRegion;
78
import com.yanchware.fractal.sdk.domain.livesystem.service.dtos.EnvironmentDto;
89
import com.yanchware.fractal.sdk.domain.livesystem.service.dtos.ProviderType;
@@ -102,6 +103,11 @@ public ManagementEnvironmentBuilder withOciCloudAgent(OciRegion region, String t
102103
return builder;
103104
}
104105

106+
public ManagementEnvironmentBuilder withHetznerCloudAgent(HetznerRegion region, String projectId) {
107+
environment.registerHetznerCloudAgent(region, projectId);
108+
return builder;
109+
}
110+
105111
@Override
106112
protected ManagementEnvironment createEnvironment() {
107113
return new ManagementEnvironment();

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/environment/OperationalEnvironment.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.aws.AwsRegion;
55
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.azure.AzureRegion;
66
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.gcp.GcpRegion;
7+
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.hetzner.HetznerRegion;
78
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.oci.OciRegion;
89
import com.yanchware.fractal.sdk.domain.livesystem.service.dtos.EnvironmentDto;
910
import com.yanchware.fractal.sdk.domain.livesystem.service.dtos.ProviderType;
@@ -74,6 +75,14 @@ public OperationalEnvironmentBuilder withShortName(String shortName) {
7475
return builder;
7576
}
7677

78+
public OperationalEnvironmentBuilder withHetznerProject(HetznerRegion region, String projectId) {
79+
var config = new HashMap<String, Object>();
80+
config.put("region", region);
81+
config.put("projectId", projectId);
82+
environment.cloudProviders.put(ProviderType.HETZNER, config);
83+
return builder;
84+
}
85+
7786
public OperationalEnvironmentBuilder withAwsAccount(AwsRegion region, String accountId) {
7887
var config = new HashMap<String, Object>();
7988
config.put("region", region);
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.yanchware.fractal.sdk.domain.environment.hetzner;
2+
3+
import com.yanchware.fractal.sdk.domain.environment.CloudAgentEntity;
4+
import com.yanchware.fractal.sdk.domain.environment.EnvironmentIdValue;
5+
import com.yanchware.fractal.sdk.domain.environment.service.EnvironmentService;
6+
import com.yanchware.fractal.sdk.domain.environment.service.dtos.InitializationRunResponse;
7+
import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException;
8+
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.hetzner.HetznerRegion;
9+
import com.yanchware.fractal.sdk.domain.livesystem.service.dtos.ProviderType;
10+
import lombok.Getter;
11+
import lombok.extern.slf4j.Slf4j;
12+
13+
import java.util.Map;
14+
import java.util.Objects;
15+
16+
import static com.yanchware.fractal.sdk.domain.environment.EnvironmentAggregate.REGION_PARAM_KEY;
17+
18+
@Slf4j
19+
@Getter
20+
public class HetznerCloudAgent extends CloudAgentEntity {
21+
public static final String PROJECT_ID_PARAM_KEY = "projectId";
22+
23+
private final HetznerRegion region;
24+
private final String projectId;
25+
26+
public HetznerCloudAgent(
27+
EnvironmentIdValue environmentId,
28+
HetznerRegion region,
29+
String projectId,
30+
Map<String, String> tags)
31+
{
32+
super(environmentId, tags);
33+
this.region = region;
34+
this.projectId = projectId;
35+
}
36+
37+
@Override
38+
public ProviderType getProvider() {
39+
return ProviderType.HETZNER;
40+
}
41+
42+
@Override
43+
public void initialize(EnvironmentService environmentService) throws InstantiatorException {
44+
initialize(environmentService, null);
45+
}
46+
47+
@Override
48+
public void initialize(EnvironmentService environmentService, EnvironmentIdValue managementEnvironmentId) throws InstantiatorException {
49+
var currentInitialization = environmentService.fetchCurrentHetznerInitialization(environmentId);
50+
51+
if (currentInitialization == null ||
52+
"Failed".equals(currentInitialization.status()) ||
53+
"Cancelled".equals(currentInitialization.status())) {
54+
55+
environmentService.startHetznerCloudAgentInitialization(
56+
environmentId,
57+
projectId,
58+
region,
59+
tags);
60+
61+
log.info("New initialization started, checking initialization status for environment [id: '{}']", environmentId);
62+
checkInitializationStatus(() -> fetchCurrentHetznerInitialization(environmentService));
63+
} else {
64+
checkInitializationStatus(() -> fetchCurrentHetznerInitialization(environmentService));
65+
}
66+
}
67+
@Override
68+
protected Map<String, Object> getConfigurationForEnvironmentParameters() {
69+
return Map.of(
70+
PROVIDER_PARAM_KEY, getProvider(),
71+
REGION_PARAM_KEY, region,
72+
PROJECT_ID_PARAM_KEY, projectId);
73+
}
74+
75+
private InitializationRunResponse fetchCurrentHetznerInitialization(EnvironmentService environmentService) {
76+
try {
77+
return environmentService.fetchCurrentHetznerInitialization(environmentId);
78+
} catch (InstantiatorException e) {
79+
throw new RuntimeException(e);
80+
}
81+
}
82+
83+
@Override
84+
public boolean equals(Object o) {
85+
if (this == o) return true;
86+
if (o == null || getClass() != o.getClass()) return false;
87+
88+
HetznerCloudAgent member = (HetznerCloudAgent) o;
89+
90+
return region.equals(member.region)
91+
&& projectId.equals(member.projectId)
92+
&& environmentId.equals(member.environmentId);
93+
}
94+
95+
@Override
96+
public int hashCode() {
97+
return Objects.hash(region, projectId, environmentId);
98+
}
99+
}

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/environment/service/EnvironmentService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.aws.AwsRegion;
1212
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.azure.AzureRegion;
1313
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.gcp.GcpRegion;
14+
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.hetzner.HetznerRegion;
1415
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.oci.OciRegion;
1516

1617
import java.util.Collection;
@@ -66,10 +67,17 @@ void startOciCloudAgentInitialization(
6667
OciRegion region,
6768
Map<String, String> tags) throws InstantiatorException;
6869

70+
void startHetznerCloudAgentInitialization(
71+
EnvironmentIdValue environmentId,
72+
String projectId,
73+
HetznerRegion region,
74+
Map<String, String> tags) throws InstantiatorException;
75+
6976
InitializationRunResponse fetchCurrentAwsInitialization(EnvironmentIdValue environmentId) throws InstantiatorException;
7077
InitializationRunResponse fetchCurrentAzureInitialization(EnvironmentIdValue environmentId) throws InstantiatorException;
7178
InitializationRunResponse fetchCurrentGcpInitialization(EnvironmentIdValue environmentId) throws InstantiatorException;
7279
InitializationRunResponse fetchCurrentOciInitialization(EnvironmentIdValue environmentId) throws InstantiatorException;
80+
InitializationRunResponse fetchCurrentHetznerInitialization(EnvironmentIdValue environmentId) throws InstantiatorException;
7381
CiCdProfileResponse[] manageCiCdProfiles(EnvironmentIdValue environmentId, Collection<CreateCiCdProfileRequest> ciCdProfiles) throws InstantiatorException;
7482
SecretResponse[] manageSecrets(EnvironmentIdValue environmentId, Collection<Secret> secrets) throws InstantiatorException;
7583
}

fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/environment/service/RestEnvironmentService.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.aws.AwsRegion;
1212
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.azure.AzureRegion;
1313
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.gcp.GcpRegion;
14+
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.hetzner.HetznerRegion;
1415
import com.yanchware.fractal.sdk.domain.livesystem.paas.providers.oci.OciRegion;
1516
import com.yanchware.fractal.sdk.domain.livesystem.service.dtos.ProviderType;
1617
import com.yanchware.fractal.sdk.utils.HttpUtils;
@@ -287,7 +288,7 @@ public void startOciCloudAgentInitialization(
287288
HttpUtils.buildPostRequest(
288289
getEnvironmentsUri(environmentId, "initializer/oci/initialize"),
289290
sdkConfiguration,
290-
serializeSafely(new GcpProjectInitializationRequest(
291+
serializeSafely(new OciCompartmentInitializationRequest(
291292
tenancyId,
292293
compartmentId,
293294
region.toString(),
@@ -297,6 +298,39 @@ public void startOciCloudAgentInitialization(
297298
EnvironmentResponse.class);
298299
}
299300

301+
@Override
302+
public void startHetznerCloudAgentInitialization(
303+
EnvironmentIdValue environmentId,
304+
String projectId,
305+
HetznerRegion region,
306+
Map<String, String> tags) throws InstantiatorException
307+
{
308+
var hetznerToken = sdkConfiguration.getHetznerToken();
309+
if (isBlank(hetznerToken)) {
310+
throw new IllegalArgumentException(
311+
String.format("The environment variable %s is required and it has not been defined", HETZNER_TOKEN_KEY));
312+
}
313+
314+
Map<String, String> additionalHeaders = new HashMap<>();
315+
additionalHeaders.put(X_HETZNER_TOKEN_HEADER, hetznerToken);
316+
317+
executeRequestWithRetries(
318+
"InitializeHetznerProject",
319+
environmentId.toString(),
320+
client,
321+
retryRegistry,
322+
HttpUtils.buildPostRequest(
323+
getEnvironmentsUri(environmentId, "initializer/hetzner/initialize"),
324+
sdkConfiguration,
325+
serializeSafely(new HetznerProjectInitializationRequest(
326+
projectId,
327+
region.toString(),
328+
tags)),
329+
additionalHeaders),
330+
new int[]{202},
331+
EnvironmentResponse.class);
332+
}
333+
300334
@Override
301335
public InitializationRunResponse fetchCurrentAwsInitialization(EnvironmentIdValue environmentId) throws InstantiatorException {
302336
return fetchCurrentInitialization(environmentId, ProviderType.AWS);
@@ -317,6 +351,11 @@ public InitializationRunResponse fetchCurrentOciInitialization(EnvironmentIdValu
317351
return fetchCurrentInitialization(environmentId, ProviderType.OCI);
318352
}
319353

354+
@Override
355+
public InitializationRunResponse fetchCurrentHetznerInitialization(EnvironmentIdValue environmentId) throws InstantiatorException {
356+
return fetchCurrentInitialization(environmentId, ProviderType.HETZNER);
357+
}
358+
320359
@Override
321360
public CiCdProfileResponse[] manageCiCdProfiles(EnvironmentIdValue environmentId, Collection<CreateCiCdProfileRequest> ciCdProfiles) throws InstantiatorException {
322361
return executeRequestWithRetries(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.yanchware.fractal.sdk.domain.environment.service.commands;
2+
3+
import java.util.Map;
4+
5+
public record HetznerProjectInitializationRequest(
6+
String projectId,
7+
String region,
8+
Map<String, String> tags){}

0 commit comments

Comments
 (0)