diff --git a/README.md b/README.md index 8ad9e7e..95d43ca 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ jenkins: image: name=jenkins mode: NORMAL numExecutors: 1 + placementGroup: "key1=value1&key2=value2" connector: root: sshCredentialsId: 'ssh-private-key' @@ -199,6 +200,7 @@ jenkins: network: subsystem=cd labelStr: java numExecutors: 3 + placementGroup: "1000656" connectivity: "public-only" connector: root: diff --git a/pom.xml b/pom.xml index ae91b3c..590f586 100644 --- a/pom.xml +++ b/pom.xml @@ -122,7 +122,7 @@ cloud.dnation.integration hetzner-cloud-client-java - 1.1.0 + 1.3.0 org.jenkins-ci.plugins diff --git a/src/main/java/cloud/dnation/jenkins/plugins/hetzner/ConfigurationValidator.java b/src/main/java/cloud/dnation/jenkins/plugins/hetzner/ConfigurationValidator.java index 309119e..d81ea66 100644 --- a/src/main/java/cloud/dnation/jenkins/plugins/hetzner/ConfigurationValidator.java +++ b/src/main/java/cloud/dnation/jenkins/plugins/hetzner/ConfigurationValidator.java @@ -22,6 +22,8 @@ import cloud.dnation.hetznerclient.GetLocationsResponse; import cloud.dnation.hetznerclient.GetNetworkByIdResponse; import cloud.dnation.hetznerclient.GetNetworksBySelectorResponse; +import cloud.dnation.hetznerclient.GetPlacementGroupByIdResponse; +import cloud.dnation.hetznerclient.GetPlacementGroupsResponse; import cloud.dnation.hetznerclient.GetServerTypesResponse; import cloud.dnation.hetznerclient.HetznerApi; import com.google.common.base.Preconditions; @@ -136,6 +138,28 @@ static ValidationResult verifyNetwork(String network, String credentialsId) { }, credentialsId); } + static ValidationResult verifyPlacementGroup(String placementGroup, String credentialsId) { + if (Strings.isNullOrEmpty(placementGroup)) { + return new ValidationResult(false, "Placement group expression is empty"); + } + return validateWithClient(api -> { + if (Helper.isLabelExpression(placementGroup)) { + final GetPlacementGroupsResponse result = api.getPlacementGroups(placementGroup).execute().body(); + Preconditions.checkArgument(result.getPlacementGroups().size() == 1, + "Expected exactly one result, got %s", result.getPlacementGroups().size()); + return new ValidationResult(true, "Found: " + + result.getPlacementGroups().get(0).getName() + " " + + result.getPlacementGroups().get(0).getId()); + } else if (Helper.isPossiblyInteger(placementGroup)) { + final GetPlacementGroupByIdResponse result = api.getPlacementGroupById(Integer.parseInt(placementGroup)).execute().body(); + return new ValidationResult(true, "Found: " + + result.getPlacementGroup().getName() + " " + result.getPlacementGroup().getId()); + } else { + return new ValidationResult(false, "Placement group expression unsupported : " + placementGroup); + } + }, credentialsId); + } + /** * Attempt to validate given server type name. * diff --git a/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java b/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java index dacba73..d174490 100644 --- a/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java +++ b/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java @@ -23,6 +23,7 @@ import cloud.dnation.hetznerclient.CreateSshKeyResponse; import cloud.dnation.hetznerclient.GetImagesBySelectorResponse; import cloud.dnation.hetznerclient.GetNetworksBySelectorResponse; +import cloud.dnation.hetznerclient.GetPlacementGroupsResponse; import cloud.dnation.hetznerclient.GetServerByIdResponse; import cloud.dnation.hetznerclient.GetServersBySelectorResponse; import cloud.dnation.hetznerclient.GetSshKeysBySelectorResponse; @@ -136,6 +137,21 @@ private int getNetworkIdForLabelExpression(String labelExpression) throws IOExce GetNetworksBySelectorResponse::getNetworks); } + /** + * Attempt to obtain placement group based on label expression. + * It's expected that provided label expression resolves to single placement group. + * + * @param labelExpression label expression used to filter placement group + * @return placement group ID + * @throws IOException if fails to make API call + * @throws IllegalStateException if there was invalid response from API server + * @throws IllegalArgumentException if label expression didn't yield single placement group + */ + private int getPlacementGroupForLabelExpression(String labelExpression) throws IOException { + return searchResourceByLabelExpression(labelExpression, proxy()::getPlacementGroups, + GetPlacementGroupsResponse::getPlacementGroups); + } + private int searchResourceByLabelExpression( String labelExpression, Function> searchFunction, @@ -254,6 +270,14 @@ public HetznerServerInfo createServer(HetznerServerAgent agent) { createServerRequest.setNetworks(Lists.newArrayList(networkId)); } } + final String placementGroup = agent.getTemplate().getPlacementGroup(); + if (!Strings.isNullOrEmpty(placementGroup)) { + if(Helper.isPossiblyInteger(placementGroup)) { + createServerRequest.setPlacementGroup(Integer.parseInt(placementGroup)); + } else { + createServerRequest.setPlacementGroup(getPlacementGroupForLabelExpression(placementGroup)); + } + } createServerRequest.setServerType(agent.getTemplate().getServerType()); createServerRequest.setImage(imageId); createServerRequest.setName(agent.getNodeName()); diff --git a/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerServerTemplate.java b/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerServerTemplate.java index a0f114c..364dbe2 100644 --- a/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerServerTemplate.java +++ b/src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerServerTemplate.java @@ -51,6 +51,7 @@ import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyImage; import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyLocation; import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyNetwork; +import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyPlacementGroup; import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyServerType; import static cloud.dnation.jenkins.plugins.hetzner.Helper.getStringOrDefault; import static cloud.dnation.jenkins.plugins.hetzner.HetznerConstants.DEFAULT_REMOTE_FS; @@ -89,6 +90,10 @@ public class HetznerServerTemplate extends AbstractDescribableImpl + + + + diff --git a/src/main/resources/cloud/dnation/jenkins/plugins/hetzner/HetznerServerTemplate/help-placementGroup.html b/src/main/resources/cloud/dnation/jenkins/plugins/hetzner/HetznerServerTemplate/help-placementGroup.html new file mode 100644 index 0000000..a8575ed --- /dev/null +++ b/src/main/resources/cloud/dnation/jenkins/plugins/hetzner/HetznerServerTemplate/help-placementGroup.html @@ -0,0 +1,27 @@ + +
+ ID of placement group + or label expression that resolves to single placement group + To obtain list of all placement groups, use following curl command: +

+

+    curl -H "Authorization: Bearer $API_TOKEN" https://api.hetzner.cloud/v1/placement_groups
+    
+

+ API documentation + +