forked from jenkinsci/ec2-fleet-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Label based configuration (jenkinsci#160)
Label based configuration
- Loading branch information
Showing
26 changed files
with
1,862 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
[Back to README](../README.md) | ||
|
||
# Label Based Configuration | ||
|
||
* [Overview](#overview) | ||
* [How it works](#how-it-works) | ||
* [Supported Parameters](#supported-parameters) | ||
* [Configuration](#configuration) | ||
|
||
# Overview | ||
|
||
Feature in *beta* mode. Please report all problem [here](https://github.com/jenkinsci/ec2-fleet-plugin/issues/new) | ||
|
||
This feature auto manages EC2 Spot Fleet or ASG based Fleets for Jenkins based on | ||
label attached to Jenkins Jobs. | ||
|
||
With this feature user of EC2 Fleet Plugin doesn't need to have pre-created AWS resources | ||
to start configuration and run Jobs. Plugin required just AWS Credentials | ||
with permissions to be able create resources. | ||
|
||
# How It Works | ||
|
||
- Plugin detects all labeled Jobs where Label starts from Name configured in plugin configuration ```Cloud Name``` | ||
- Plugin parses Label to get Fleet configuration | ||
- Plugin creates dedicated fleet for each unique Label | ||
- Plugin uses [CloudFormation Stacks](https://aws.amazon.com/cloudformation/) to provision Fleet and all required resources | ||
- When Label is not used by any Job Plugin deletes Stack and release resources | ||
|
||
Label format | ||
``` | ||
<CloudName>_parameter1=value1,parameter2=value2 | ||
``` | ||
|
||
# Supported Parameters | ||
|
||
*Note* Parameter name is case insensitive | ||
|
||
| Parameter | Value Example | Value | | ||
| --- | ---| ---- | | ||
| imageId | ```ami-0080e4c5bc078760e``` | *Required* AMI ID https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html | | ||
| max | ```12``` | Fleet Max Size, positive value or zero. If not specified plugin configuration Max will be used | | ||
| min | ```1``` | Fleet Min Size, positive value or zero. If not specified plugin configuration Min will be used | | ||
| instanceType | ```c4.large``` | EC2 Instance Type https://aws.amazon.com/ec2/instance-types/. If not specified ```m4.large``` will be used | | ||
| spotPrice | ```0.4``` | Max Spot Price, if not specified EC2 Spot Fleet API will use default price. | | ||
|
||
### Examples | ||
|
||
Minimum configuration just Image ID | ||
``` | ||
<FleetName>_imageId=ami-0080e4c5bc078760e | ||
``` | ||
|
||
# Configuration | ||
|
||
1. Create AWS User | ||
1. Add Inline User Permissions | ||
```json | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [{ | ||
"Effect": "Allow", | ||
"Action": [ | ||
"cloudformation:*", | ||
"ec2:*", | ||
"autoscaling:*", | ||
"iam:ListRoles", | ||
"iam:PassRole", | ||
"iam:ListInstanceProfiles", | ||
"iam:CreateRole", | ||
"iam:AttachRolePolicy", | ||
"iam:GetRole" | ||
], | ||
"Resource": "*" | ||
}] | ||
} | ||
``` | ||
1. Goto ```Manage Jenkins > Configure Jenkins``` | ||
1. Add Cloud ```Amazon EC2 Fleet label based``` | ||
1. Specify ```AWS Credentials``` | ||
1. Specify ```SSH Credentials``` | ||
- Jenkins need to be able to connect to EC2 Instances to run Jobs | ||
1. Set ```Region``` | ||
1. Provide base configuration | ||
- Note ```Cloud Name``` | ||
1. Goto to Jenkins Job which you want to run on this Fleet | ||
1. Goto Job ```Configuration``` | ||
1. Enable ```Restrict where this project can be run``` | ||
1. Set Label value to ```<Cloud Name>_parameterName=paremeterValue,p2=v2``` | ||
1. Click ```Save``` | ||
|
||
In some short time plugin will detect Job and will create required resources to be able | ||
run it in future. | ||
|
||
That's all, you can repeat this for other Jobs. |
21 changes: 21 additions & 0 deletions
21
src/main/java/com/amazon/jenkins/ec2fleet/AbstractEC2FleetCloud.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.amazon.jenkins.ec2fleet; | ||
|
||
import hudson.slaves.Cloud; | ||
|
||
public abstract class AbstractEC2FleetCloud extends Cloud { | ||
|
||
protected AbstractEC2FleetCloud(String name) { | ||
super(name); | ||
} | ||
|
||
public abstract boolean isDisableTaskResubmit(); | ||
|
||
public abstract int getIdleMinutes(); | ||
|
||
public abstract boolean isAlwaysReconnect(); | ||
|
||
public abstract boolean scheduleToTerminate(String instanceId); | ||
|
||
public abstract String getOldId(); | ||
|
||
} |
133 changes: 133 additions & 0 deletions
133
src/main/java/com/amazon/jenkins/ec2fleet/CloudFormationApi.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package com.amazon.jenkins.ec2fleet; | ||
|
||
import com.amazonaws.ClientConfiguration; | ||
import com.amazonaws.regions.Region; | ||
import com.amazonaws.regions.RegionUtils; | ||
import com.amazonaws.services.cloudformation.AmazonCloudFormation; | ||
import com.amazonaws.services.cloudformation.AmazonCloudFormationClient; | ||
import com.amazonaws.services.cloudformation.model.Capability; | ||
import com.amazonaws.services.cloudformation.model.CreateStackRequest; | ||
import com.amazonaws.services.cloudformation.model.DeleteStackRequest; | ||
import com.amazonaws.services.cloudformation.model.DescribeStacksRequest; | ||
import com.amazonaws.services.cloudformation.model.DescribeStacksResult; | ||
import com.amazonaws.services.cloudformation.model.Parameter; | ||
import com.amazonaws.services.cloudformation.model.Stack; | ||
import com.amazonaws.services.cloudformation.model.StackStatus; | ||
import com.amazonaws.services.cloudformation.model.Tag; | ||
import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsHelper; | ||
import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials; | ||
import jenkins.model.Jenkins; | ||
import org.apache.commons.io.IOUtils; | ||
import org.apache.commons.lang.StringUtils; | ||
|
||
import javax.annotation.Nullable; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class CloudFormationApi { | ||
|
||
public AmazonCloudFormation connect(final String awsCredentialsId, final String regionName, final String endpoint) { | ||
final ClientConfiguration clientConfiguration = AWSUtils.getClientConfiguration(); | ||
final AmazonWebServicesCredentials credentials = AWSCredentialsHelper.getCredentials(awsCredentialsId, Jenkins.getInstance()); | ||
final AmazonCloudFormation client = | ||
credentials != null ? | ||
new AmazonCloudFormationClient(credentials, clientConfiguration) : | ||
new AmazonCloudFormationClient(clientConfiguration); | ||
|
||
final String effectiveEndpoint = getEndpoint(regionName, endpoint); | ||
if (effectiveEndpoint != null) client.setEndpoint(effectiveEndpoint); | ||
return client; | ||
} | ||
|
||
// todo do we want to merge with EC2Api#getEndpoint | ||
@Nullable | ||
private String getEndpoint(@Nullable final String regionName, @Nullable final String endpoint) { | ||
if (StringUtils.isNotEmpty(endpoint)) { | ||
return endpoint; | ||
} else if (StringUtils.isNotEmpty(regionName)) { | ||
final Region region = RegionUtils.getRegion(regionName); | ||
if (region != null && region.isServiceSupported(endpoint)) { | ||
return region.getServiceEndpoint(endpoint); | ||
} else { | ||
final String domain = regionName.startsWith("cn-") ? "amazonaws.com.cn" : "amazonaws.com"; | ||
return "https://cloudformation." + regionName + "." + domain; | ||
} | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
public void delete(final AmazonCloudFormation client, final String stackId) { | ||
client.deleteStack(new DeleteStackRequest().withStackName(stackId)); | ||
} | ||
|
||
public void create( | ||
final AmazonCloudFormation client, final String fleetName, final String keyName, final String parametersString) { | ||
final EC2FleetLabelParameters parameters = new EC2FleetLabelParameters(parametersString); | ||
|
||
try { | ||
final String type = parameters.getOrDefault("type", "ec2-spot-fleet"); | ||
final String imageId = parameters.get("imageId"); //"ami-0080e4c5bc078760e"; | ||
final int maxSize = parameters.getIntOrDefault("maxSize", 10); | ||
final int minSize = parameters.getIntOrDefault("minSize", 0); | ||
final String instanceType = parameters.getOrDefault("instanceType", "m4.large"); | ||
final String spotPrice = parameters.get("spotPrice"); // "0.04" | ||
|
||
final String template = "/com/amazon/jenkins/ec2fleet/" + (type.equals("asg") ? "auto-scaling-group.yml" : "ec2-spot-fleet.yml"); | ||
client.createStack( | ||
new CreateStackRequest() | ||
.withStackName(fleetName + "-" + System.currentTimeMillis()) | ||
.withTags( | ||
new Tag().withKey("ec2-fleet-plugin") | ||
.withValue(parametersString) | ||
) | ||
.withTemplateBody(IOUtils.toString(CloudFormationApi.class.getResourceAsStream(template))) | ||
// to allow some of templates create iam | ||
.withCapabilities(Capability.CAPABILITY_IAM) | ||
.withParameters( | ||
new Parameter().withParameterKey("ImageId").withParameterValue(imageId), | ||
new Parameter().withParameterKey("InstanceType").withParameterValue(instanceType), | ||
new Parameter().withParameterKey("MaxSize").withParameterValue(Integer.toString(maxSize)), | ||
new Parameter().withParameterKey("MinSize").withParameterValue(Integer.toString(minSize)), | ||
new Parameter().withParameterKey("SpotPrice").withParameterValue(spotPrice), | ||
new Parameter().withParameterKey("KeyName").withParameterValue(keyName) | ||
)); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
public static class StackInfo { | ||
public final String stackId; | ||
public final String fleetId; | ||
public final StackStatus stackStatus; | ||
|
||
public StackInfo(String stackId, String fleetId, StackStatus stackStatus) { | ||
this.stackId = stackId; | ||
this.fleetId = fleetId; | ||
this.stackStatus = stackStatus; | ||
} | ||
} | ||
|
||
public Map<String, StackInfo> describe( | ||
final AmazonCloudFormation client, final String fleetName) { | ||
Map<String, StackInfo> r = new HashMap<>(); | ||
|
||
String nextToken = null; | ||
do { | ||
DescribeStacksResult describeStacksResult = client.describeStacks( | ||
new DescribeStacksRequest().withNextToken(nextToken)); | ||
for (Stack stack : describeStacksResult.getStacks()) { | ||
if (stack.getStackName().startsWith(fleetName)) { | ||
final String fleetId = stack.getOutputs().isEmpty() ? null : stack.getOutputs().get(0).getOutputValue(); | ||
r.put(stack.getTags().get(0).getValue(), new StackInfo( | ||
stack.getStackId(), fleetId, StackStatus.valueOf(stack.getStackStatus()))); | ||
} | ||
} | ||
nextToken = describeStacksResult.getNextToken(); | ||
} while (nextToken != null); | ||
|
||
return r; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.