Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3f8d91b

Browse files
committedMar 13, 2019
Initial commit
0 parents  commit 3f8d91b

20 files changed

+1073
-0
lines changed
 

‎.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/.vscode
2+
/.classpath
3+
/.project
4+
/.settings
5+
/pom.xml.versionsBackup
6+
/.factorypath
7+
/target
8+
/work
9+
/bin

‎jnlp-docker-agent/Dockerfile

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Amazon Software License (the "License"). You may not use this file except in compliance with the License.
4+
# A copy of the License is located at
5+
#
6+
# http://aws.amazon.com/asl/
7+
#
8+
# or in the "license" file accompanying this file.
9+
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
10+
# See the License for the specific language governing permissions and limitations under the License.
11+
#
12+
13+
FROM jenkins/jnlp-slave:latest as build
14+
15+
FROM ubuntu:14.04.5
16+
17+
ENV DOCKER_BUCKET="download.docker.com" \
18+
DOCKER_VERSION="17.09.0-ce" \
19+
DOCKER_CHANNEL="stable" \
20+
DOCKER_SHA256="a9e90a73c3cdfbf238f148e1ec0eaff5eb181f92f35bdd938fd7dab18e1c4647" \
21+
DIND_COMMIT="3b5fac462d21ca164b3778647420016315289034" \
22+
DOCKER_COMPOSE_VERSION="1.21.2" \
23+
GITVERSION_VERSION="3.6.5"
24+
25+
# Install git, SSH, and other utilities
26+
RUN set -ex \
27+
&& echo 'Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/99use-gzip-compression \
28+
&& apt-get update \
29+
&& apt install -y apt-transport-https \
30+
&& apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF \
31+
&& echo "deb https://download.mono-project.com/repo/ubuntu stable-trusty main" | tee /etc/apt/sources.list.d/mono-official-stable.list \
32+
&& apt-get update \
33+
&& apt-get install software-properties-common -y --no-install-recommends \
34+
&& apt-add-repository ppa:git-core/ppa \
35+
&& apt-get update \
36+
&& apt-get install git=1:2.* -y --no-install-recommends \
37+
&& git version \
38+
&& apt-get install -y --no-install-recommends openssh-client=1:6.6* \
39+
&& mkdir ~/.ssh \
40+
&& touch ~/.ssh/known_hosts \
41+
&& ssh-keyscan -t rsa,dsa -H github.com >> ~/.ssh/known_hosts \
42+
&& ssh-keyscan -t rsa,dsa -H bitbucket.org >> ~/.ssh/known_hosts \
43+
&& chmod 600 ~/.ssh/known_hosts \
44+
&& apt-get install -y --no-install-recommends \
45+
wget=1.15-* python=2.7.* python2.7-dev=2.7.* fakeroot=1.20-* ca-certificates \
46+
tar=1.27.* gzip=1.6-* zip=3.0-* autoconf=2.69-* automake=1:1.14.* \
47+
bzip2=1.0.* file=1:5.14-* g++=4:4.8.* gcc=4:4.8.* imagemagick=8:6.7.* \
48+
libbz2-dev=1.0.* libc6-dev=2.19-* libcurl4-openssl-dev=7.35.* libdb-dev=1:5.3.* \
49+
libevent-dev=2.0.* libffi-dev=3.1~* libgeoip-dev=1.6.* libglib2.0-dev=2.40.* \
50+
libjpeg-dev=8c-* libkrb5-dev=1.12+* liblzma-dev=5.1.* \
51+
libmagickcore-dev=8:6.7.* libmagickwand-dev=8:6.7.* libmysqlclient-dev=5.5.* \
52+
libncurses5-dev=5.9+* libpng12-dev=1.2.* libpq-dev=9.3.* libreadline-dev=6.3-* \
53+
libsqlite3-dev=3.8.* libssl-dev=1.0.* libtool=2.4.* libwebp-dev=0.4.* \
54+
libxml2-dev=2.9.* libxslt1-dev=1.1.* libyaml-dev=0.1.* make=3.81-* \
55+
patch=2.7.* xz-utils=5.1.* zlib1g-dev=1:1.2.* unzip=6.0-* curl=7.35.* \
56+
e2fsprogs=1.42.* iptables=1.4.* xfsprogs=3.1.* xz-utils=5.1.* \
57+
mono-devel less=458-* groff=1.22.* liberror-perl=0.17-* \
58+
asciidoc=8.6.* build-essential=11.* bzr=2.6.* cvs=2:1.12.* cvsps=2.1-* docbook-xml=4.5-* docbook-xsl=1.78.* dpkg-dev=1.17.* \
59+
libdbd-sqlite3-perl=1.40-* libdbi-perl=1.630-* libdpkg-perl=1.17.* libhttp-date-perl=6.02-* \
60+
libio-pty-perl=1:1.08-* libserf-1-1=1.3.* libsvn-perl=1.8.* libsvn1=1.8.* libtcl8.6=8.6.* libtimedate-perl=2.3000-* \
61+
libunistring0=0.9.* libxml2-utils=2.9.* libyaml-perl=0.84-* python-bzrlib=2.6.* python-configobj=4.7.* \
62+
sgml-base=1.26+* sgml-data=2.0.* subversion=1.8.* tcl=8.6.* tcl8.6=8.6.* xml-core=0.13+* xmlto=0.0.* xsltproc=1.1.* \
63+
&& rm -rf /var/lib/apt/lists/* \
64+
&& apt-get clean
65+
66+
# Download and set up GitVersion
67+
RUN set -ex \
68+
&& wget "https://github.com/GitTools/GitVersion/releases/download/v${GITVERSION_VERSION}/GitVersion_${GITVERSION_VERSION}.zip" -O /tmp/GitVersion_${GITVERSION_VERSION}.zip \
69+
&& mkdir -p /usr/local/GitVersion_${GITVERSION_VERSION} \
70+
&& unzip /tmp/GitVersion_${GITVERSION_VERSION}.zip -d /usr/local/GitVersion_${GITVERSION_VERSION} \
71+
&& rm /tmp/GitVersion_${GITVERSION_VERSION}.zip \
72+
&& echo "mono /usr/local/GitVersion_${GITVERSION_VERSION}/GitVersion.exe \$@" >> /usr/local/bin/gitversion \
73+
&& chmod +x /usr/local/bin/gitversion
74+
75+
# Install Docker
76+
RUN set -ex \
77+
&& curl -fSL "https://${DOCKER_BUCKET}/linux/static/${DOCKER_CHANNEL}/x86_64/docker-${DOCKER_VERSION}.tgz" -o docker.tgz \
78+
&& echo "${DOCKER_SHA256} *docker.tgz" | sha256sum -c - \
79+
&& tar --extract --file docker.tgz --strip-components 1 --directory /usr/local/bin/ \
80+
&& rm docker.tgz \
81+
&& docker -v \
82+
# set up subuid/subgid so that "--userns-remap=default" works out-of-the-box
83+
&& addgroup dockremap \
84+
&& useradd -g dockremap dockremap \
85+
&& echo 'dockremap:165536:65536' >> /etc/subuid \
86+
&& echo 'dockremap:165536:65536' >> /etc/subgid \
87+
&& wget "https://raw.githubusercontent.com/docker/docker/${DIND_COMMIT}/hack/dind" -O /usr/local/bin/dind \
88+
&& curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-Linux-x86_64 > /usr/local/bin/docker-compose \
89+
&& chmod +x /usr/local/bin/dind /usr/local/bin/docker-compose \
90+
# Ensure docker-compose works
91+
&& docker-compose version
92+
93+
# Install dependencies by all python images equivalent to buildpack-deps:jessie
94+
# on the public repos.
95+
96+
RUN set -ex \
97+
&& wget "https://bootstrap.pypa.io/2.6/get-pip.py" -O /tmp/get-pip.py \
98+
&& python /tmp/get-pip.py \
99+
&& pip install awscli==1.* \
100+
&& rm -fr /var/lib/apt/lists/* /tmp/* /var/tmp/*
101+
102+
VOLUME /var/lib/docker
103+
104+
COPY dockerd-entrypoint.sh /usr/local/bin/
105+
106+
ENV JAVA_VERSION=8 \
107+
JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64" \
108+
JDK_HOME="/usr/lib/jvm/java-8-openjdk-amd64" \
109+
JRE_HOME="/usr/lib/jvm/java-8-openjdk-amd64" \
110+
ANT_VERSION=1.10.3 \
111+
MAVEN_HOME="/opt/maven" \
112+
MAVEN_VERSION=3.5.4 \
113+
MAVEN_CONFIG="/root/.m2" \
114+
GRADLE_VERSION=4.2.1 \
115+
PROPERTIES_COMMON_VERSION=0.92.37.8 \
116+
PYTHON_TOOL_VERSION="3.3-*" \
117+
JDK_VERSION="8u171-b11-2~14.04" \
118+
ANT_DOWNLOAD_SHA512="73f2193700b1d1e32eedf25fab1009e2a98fb2f6425413f5c9fa1b0f2f9f49f59cb8ed3f04931c808ae022a64ecfa2619e5fb77643fea6dbc29721e489eb3a07" \
119+
MAVEN_DOWNLOAD_SHA1="22cac91b3557586bb1eba326f2f7727543ff15e3" \
120+
GRADLE_DOWNLOAD_SHA256="b551cc04f2ca51c78dd14edb060621f0e5439bdfafa6fd167032a09ac708fbc0"
121+
122+
RUN set -ex \
123+
&& apt-get update \
124+
&& apt-get install -y software-properties-common=$PROPERTIES_COMMON_VERSION \
125+
&& add-apt-repository ppa:openjdk-r/ppa \
126+
&& apt-get update \
127+
&& apt-get install -y python-setuptools=$PYTHON_TOOL_VERSION \
128+
129+
# Install OpenJDK 8
130+
&& apt-get install -y openjdk-${JAVA_VERSION}-jdk=$JDK_VERSION \
131+
&& apt-get install -y --no-install-recommends ca-certificates-java \
132+
&& apt-get clean \
133+
# Ensure Java cacerts symlink points to valid location
134+
&& update-ca-certificates -f \
135+
136+
# Install Ant
137+
&& curl -LSso /var/tmp/apache-ant-$ANT_VERSION-bin.tar.gz https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz \
138+
&& echo "$ANT_DOWNLOAD_SHA512 /var/tmp/apache-ant-$ANT_VERSION-bin.tar.gz" | sha512sum -c - \
139+
&& tar -xzf /var/tmp/apache-ant-$ANT_VERSION-bin.tar.gz -C /opt \
140+
&& update-alternatives --install /usr/bin/ant ant /opt/apache-ant-$ANT_VERSION/bin/ant 10000 \
141+
142+
# Install Maven
143+
&& mkdir -p $MAVEN_HOME \
144+
&& curl -LSso /var/tmp/apache-maven-$MAVEN_VERSION-bin.tar.gz https://apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz \
145+
&& echo "$MAVEN_DOWNLOAD_SHA1 /var/tmp/apache-maven-$MAVEN_VERSION-bin.tar.gz" | sha1sum -c - \
146+
&& tar xzvf /var/tmp/apache-maven-$MAVEN_VERSION-bin.tar.gz -C $MAVEN_HOME --strip-components=1 \
147+
&& update-alternatives --install /usr/bin/mvn mvn /opt/maven/bin/mvn 10000 \
148+
&& mkdir -p $MAVEN_CONFIG \
149+
150+
# Install Gradle
151+
&& curl -LSso /var/tmp/gradle-$GRADLE_VERSION-bin.zip https://services.gradle.org/distributions/gradle-$GRADLE_VERSION-bin.zip \
152+
&& echo "$GRADLE_DOWNLOAD_SHA256 /var/tmp/gradle-$GRADLE_VERSION-bin.zip" | sha256sum -c - \
153+
&& unzip /var/tmp/gradle-$GRADLE_VERSION-bin.zip -d /opt \
154+
&& update-alternatives --install /usr/local/bin/gradle gradle /opt/gradle-$GRADLE_VERSION/bin/gradle 10000 \
155+
156+
# Cleanup
157+
&& rm -fr /var/lib/apt/lists/* /tmp/* /var/tmp/* \
158+
&& apt-get clean
159+
160+
COPY m2-settings.xml $MAVEN_CONFIG/settings.xml
161+
162+
# Jenkins Agent setup
163+
COPY --from=build /usr/local/bin/jenkins-slave /usr/bin/jenkins-agent
164+
COPY --from=build /usr/share/jenkins/slave.jar /usr/share/jenkins/slave.jar
165+
RUN mkdir -p /home/jenkins/.jenkins && mkdir -p /home/jenkins/agent
166+
RUN chmod +x /usr/bin/jenkins-agent
167+
WORKDIR /home/jenkins/agent
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/sh
2+
set -e
3+
4+
/usr/local/bin/dockerd \
5+
--host=unix:///var/run/docker.sock \
6+
--host=tcp://127.0.0.1:2375 \
7+
--storage-driver=overlay &>/var/log/docker.log &
8+
9+
10+
tries=0
11+
d_timeout=60
12+
until docker info >/dev/null 2>&1
13+
do
14+
if [ "$tries" -gt "$d_timeout" ]; then
15+
cat /var/log/docker.log
16+
echo 'Timed out trying to connect to internal docker host.' >&2
17+
exit 1
18+
fi
19+
tries=$(( $tries + 1 ))
20+
sleep 1
21+
done
22+
23+
eval "$@"

‎jnlp-docker-agent/m2-settings.xml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<settings>
2+
<profiles>
3+
<profile>
4+
<id>securecentral</id>
5+
<activation>
6+
<activeByDefault>true</activeByDefault>
7+
</activation>
8+
<!--Override the repository (and pluginRepository) "central" from the
9+
Maven Super POM -->
10+
<repositories>
11+
<repository>
12+
<id>central</id>
13+
<url>https://repo1.maven.org/maven2</url>
14+
<releases>
15+
<enabled>true</enabled>
16+
</releases>
17+
</repository>
18+
</repositories>
19+
<pluginRepositories>
20+
<pluginRepository>
21+
<id>central</id>
22+
<url>https://repo1.maven.org/maven2</url>
23+
<releases>
24+
<enabled>true</enabled>
25+
</releases>
26+
</pluginRepository>
27+
</pluginRepositories>
28+
</profile>
29+
</profiles>
30+
</settings>

‎pom.xml

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>org.jenkins-ci.plugins</groupId>
6+
<artifactId>plugin</artifactId>
7+
<version>3.25</version>
8+
<relativePath />
9+
</parent>
10+
<groupId>io.jenkins.plugins.codebuilder</groupId>
11+
<artifactId>codebuilder-cloud</artifactId>
12+
<version>1.0-SNAPSHOT</version>
13+
<packaging>hpi</packaging>
14+
<properties>
15+
<jenkins.version>2.107.3</jenkins.version>
16+
<java.level>8</java.level>
17+
</properties>
18+
<name>CodeBuilder: AWS CodeBuild Cloud Agents</name>
19+
<description>This plugin provisions cloud agents dynamically using AWS CodeBuild</description>
20+
<licenses>
21+
<license>
22+
<name>MIT License</name>
23+
<url>https://opensource.org/licenses/MIT</url>
24+
</license>
25+
</licenses>
26+
<!-- Assuming you want to host on @jenkinsci:
27+
<url>https://wiki.jenkins.io/display/JENKINS/TODO+Plugin</url>
28+
<scm>
29+
<connection>scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git</connection>
30+
<developerConnection>scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git</developerConnection>
31+
<url>https://github.com/jenkinsci/${project.artifactId}-plugin</url>
32+
</scm>
33+
-->
34+
35+
<repositories>
36+
<repository>
37+
<id>repo.jenkins-ci.org</id>
38+
<url>http://repo.jenkins-ci.org/public/</url>
39+
</repository>
40+
</repositories>
41+
<pluginRepositories>
42+
<pluginRepository>
43+
<id>repo.jenkins-ci.org</id>
44+
<url>http://repo.jenkins-ci.org/public/</url>
45+
</pluginRepository>
46+
</pluginRepositories>
47+
48+
<distributionManagement>
49+
<repository>
50+
<id>repo.jenkins-ci.org</id>
51+
<url>http://repo.jenkins-ci.org/releases/</url>
52+
</repository>
53+
<snapshotRepository>
54+
<id>repo.jenkins-ci.org</id>
55+
<url>http://repo.jenkins-ci.org/snapshots</url>
56+
</snapshotRepository>
57+
</distributionManagement>
58+
59+
<dependencies>
60+
<dependency>
61+
<groupId>org.jenkins-ci.plugins</groupId>
62+
<artifactId>aws-java-sdk</artifactId>
63+
<version>1.11.403</version>
64+
</dependency>
65+
<dependency>
66+
<groupId>org.jenkins-ci.plugins</groupId>
67+
<artifactId>aws-credentials</artifactId>
68+
<version>1.23</version>
69+
</dependency>
70+
</dependencies>
71+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package dev.lsegal.jenkins.codebuilder;
2+
3+
import java.io.IOException;
4+
import java.util.Collections;
5+
6+
import javax.annotation.Nonnull;
7+
8+
import com.amazonaws.services.codebuild.model.ResourceNotFoundException;
9+
import com.amazonaws.services.codebuild.model.StopBuildRequest;
10+
11+
import org.apache.commons.lang.StringUtils;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
import hudson.model.Descriptor;
16+
import hudson.model.TaskListener;
17+
import hudson.slaves.AbstractCloudComputer;
18+
import hudson.slaves.AbstractCloudSlave;
19+
import hudson.slaves.CloudRetentionStrategy;
20+
import hudson.slaves.ComputerLauncher;
21+
22+
class CodeBuilderAgent extends AbstractCloudSlave {
23+
private static final Logger LOGGER = LoggerFactory.getLogger(CodeBuilderAgent.class);
24+
private static final long serialVersionUID = -6722929807051421839L;
25+
private final CodeBuilderCloud cloud;
26+
27+
public CodeBuilderAgent(@Nonnull CodeBuilderCloud cloud, @Nonnull String name, @Nonnull ComputerLauncher launcher)
28+
throws Descriptor.FormException, IOException {
29+
super(name, "AWS CodeBuild Agent", "/build", 1, Mode.NORMAL, cloud.getLabel(), launcher,
30+
new CloudRetentionStrategy(cloud.getAgentTimeout() / 60 + 1), Collections.emptyList());
31+
this.cloud = cloud;
32+
}
33+
34+
public CodeBuilderCloud getCloud() {
35+
return cloud;
36+
}
37+
38+
@Override
39+
public AbstractCloudComputer<CodeBuilderAgent> createComputer() {
40+
return new CodeBuilderComputer(this);
41+
}
42+
43+
@Override
44+
protected void _terminate(TaskListener listener) throws IOException, InterruptedException {
45+
listener.getLogger().println("[CodeBuilder]: Terminating agent: " + getDisplayName());
46+
47+
if (getLauncher() instanceof CodeBuilderLauncher) {
48+
String buildId = ((CodeBuilderComputer) getComputer()).getBuildId();
49+
if (StringUtils.isBlank(buildId)) {
50+
return;
51+
}
52+
53+
try {
54+
LOGGER.info("[CodeBuilder]: Stopping build ID: {}", buildId);
55+
cloud.getClient().stopBuild(new StopBuildRequest().withId(buildId));
56+
} catch (ResourceNotFoundException e) {
57+
// this is fine. really.
58+
} catch (Exception e) {
59+
LOGGER.error("[CodeBuilder]: Failed to stop build ID: {}", buildId, e);
60+
}
61+
}
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
package dev.lsegal.jenkins.codebuilder;
2+
3+
import java.io.IOException;
4+
import java.util.ArrayList;
5+
import java.util.Arrays;
6+
import java.util.Collection;
7+
import java.util.Collections;
8+
import java.util.List;
9+
import java.util.concurrent.Future;
10+
11+
import javax.annotation.CheckForNull;
12+
import javax.annotation.Nonnull;
13+
import javax.annotation.Nullable;
14+
15+
import com.amazonaws.ClientConfiguration;
16+
import com.amazonaws.SdkClientException;
17+
import com.amazonaws.regions.DefaultAwsRegionProviderChain;
18+
import com.amazonaws.regions.Region;
19+
import com.amazonaws.regions.RegionUtils;
20+
import com.amazonaws.services.codebuild.AWSCodeBuild;
21+
import com.amazonaws.services.codebuild.AWSCodeBuildClientBuilder;
22+
import com.amazonaws.services.codebuild.model.ListProjectsRequest;
23+
import com.amazonaws.services.codebuild.model.ListProjectsResult;
24+
import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsHelper;
25+
import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials;
26+
27+
import org.apache.commons.lang.RandomStringUtils;
28+
import org.apache.commons.lang.StringUtils;
29+
import org.kohsuke.stapler.DataBoundConstructor;
30+
import org.kohsuke.stapler.DataBoundSetter;
31+
import org.kohsuke.stapler.QueryParameter;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
34+
35+
import hudson.Extension;
36+
import hudson.ProxyConfiguration;
37+
import hudson.model.Computer;
38+
import hudson.model.Descriptor;
39+
import hudson.model.Label;
40+
import hudson.model.Node;
41+
import hudson.model.labels.LabelAtom;
42+
import hudson.slaves.Cloud;
43+
import hudson.slaves.NodeProvisioner;
44+
import hudson.slaves.NodeProvisioner.PlannedNode;
45+
import hudson.util.ListBoxModel;
46+
import jenkins.model.Jenkins;
47+
import jenkins.model.JenkinsLocationConfiguration;
48+
49+
public class CodeBuilderCloud extends Cloud {
50+
private static final Logger LOGGER = LoggerFactory.getLogger(CodeBuilderCloud.class);
51+
private static final String DEFAULT_JNLP_IMAGE = "lsegal/jnlp-docker-agent:latest";
52+
private static final int DEFAULT_AGENT_TIMEOUT = 120;
53+
private static final String DEFAULT_COMPUTE_TYPE = "BUILD_GENERAL1_SMALL";
54+
55+
static {
56+
clearAllNodes();
57+
}
58+
59+
@Nonnull
60+
private final String projectName;
61+
62+
@Nonnull
63+
private final String credentialsId;
64+
65+
@Nonnull
66+
private final String region;
67+
68+
private String label;
69+
private String computeType;
70+
private String jenkinsUrl;
71+
private String jnlpImage;
72+
private int agentTimeout;
73+
74+
private transient AWSCodeBuild client;
75+
76+
@DataBoundConstructor
77+
public CodeBuilderCloud(String name, @Nonnull String projectName, @Nullable String credentialsId,
78+
@Nonnull String region) throws InterruptedException {
79+
super("codebuilder_" + Jenkins.get().clouds.size());
80+
81+
this.projectName = projectName;
82+
this.credentialsId = credentialsId;
83+
if (StringUtils.isBlank(region)) {
84+
this.region = getDefaultRegion();
85+
} else {
86+
this.region = region;
87+
}
88+
89+
LOGGER.info("[CodeBuilder]: Initializing Cloud: {}", this);
90+
}
91+
92+
/**
93+
* Clear all CodeBuilder nodes on boot-up because these cannot be permanent.
94+
*/
95+
private static void clearAllNodes() {
96+
List<Node> nodes = Jenkins.get().getNodes();
97+
if (nodes.size() == 0) {
98+
return;
99+
}
100+
101+
LOGGER.info("[CodeBuilder]: Clearing all previous CodeBuilder nodes...");
102+
for (final Node n : nodes) {
103+
if (n instanceof CodeBuilderAgent) {
104+
try {
105+
((CodeBuilderAgent) n).terminate();
106+
} catch (InterruptedException | IOException e) {
107+
LOGGER.error("[CodeBuilder]: Failed to terminate agent '{}'", n.getDisplayName(), e);
108+
}
109+
}
110+
}
111+
}
112+
113+
@Override
114+
public String toString() {
115+
return String.format("%s<%s>", name, projectName);
116+
}
117+
118+
@Nonnull
119+
public String getProjectName() {
120+
return projectName;
121+
}
122+
123+
@Nonnull
124+
public String getRegion() {
125+
return region;
126+
}
127+
128+
@Nonnull
129+
public String getLabel() {
130+
return StringUtils.isBlank(label) ? "" : label;
131+
}
132+
133+
@DataBoundSetter
134+
public void setLabel(String label) {
135+
this.label = label;
136+
}
137+
138+
@Nonnull
139+
public String getJenkinsUrl() {
140+
if (StringUtils.isNotBlank(jenkinsUrl)) {
141+
return jenkinsUrl;
142+
} else {
143+
JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get();
144+
if (config != null) {
145+
return config.getUrl();
146+
}
147+
}
148+
return "unknown";
149+
}
150+
151+
@DataBoundSetter
152+
public void setJenkinsUrl(String jenkinsUrl) {
153+
JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get();
154+
if (config != null && config.getUrl() == jenkinsUrl) {
155+
return;
156+
}
157+
this.jenkinsUrl = jenkinsUrl;
158+
}
159+
160+
@Nonnull
161+
public String getJnlpImage() {
162+
return StringUtils.isBlank(jnlpImage) ? DEFAULT_JNLP_IMAGE : jnlpImage;
163+
}
164+
165+
@DataBoundSetter
166+
public void setJnlpImage(String jnlpImage) {
167+
this.jnlpImage = jnlpImage;
168+
}
169+
170+
@Nonnull
171+
public int getAgentTimeout() {
172+
return agentTimeout == 0 ? DEFAULT_AGENT_TIMEOUT : agentTimeout;
173+
}
174+
175+
@DataBoundSetter
176+
public void setAgentTimeout(int agentTimeout) {
177+
this.agentTimeout = agentTimeout;
178+
}
179+
180+
@Nonnull
181+
public String getComputeType() {
182+
return StringUtils.isBlank(computeType) ? DEFAULT_COMPUTE_TYPE : computeType;
183+
}
184+
185+
@DataBoundSetter
186+
public void setComputeType(String computeType) {
187+
this.computeType = computeType;
188+
}
189+
190+
@CheckForNull
191+
private static AmazonWebServicesCredentials getCredentials(@Nullable String credentialsId) {
192+
return AWSCredentialsHelper.getCredentials(credentialsId, Jenkins.get());
193+
}
194+
195+
private static AWSCodeBuild buildClient(String credentialsId, String region) {
196+
ProxyConfiguration proxy = Jenkins.get().proxy;
197+
ClientConfiguration clientConfiguration = new ClientConfiguration();
198+
199+
if (proxy != null) {
200+
clientConfiguration.setProxyHost(proxy.name);
201+
clientConfiguration.setProxyPort(proxy.port);
202+
clientConfiguration.setProxyUsername(proxy.getUserName());
203+
clientConfiguration.setProxyPassword(proxy.getPassword());
204+
}
205+
206+
AWSCodeBuildClientBuilder builder = AWSCodeBuildClientBuilder.standard()
207+
.withClientConfiguration(clientConfiguration).withRegion(region);
208+
209+
AmazonWebServicesCredentials credentials = getCredentials(credentialsId);
210+
if (credentials != null) {
211+
String awsAccessKeyId = credentials.getCredentials().getAWSAccessKeyId();
212+
String obfuscatedAccessKeyId = StringUtils.left(awsAccessKeyId, 4)
213+
+ StringUtils.repeat("*", awsAccessKeyId.length() - 8) + StringUtils.right(awsAccessKeyId, 4);
214+
LOGGER.debug("[CodeBuilder]: Using credentials: {}", obfuscatedAccessKeyId);
215+
builder.withCredentials(credentials);
216+
}
217+
LOGGER.debug("[CodeBuilder]: Selected Region: {}", region);
218+
219+
return builder.build();
220+
}
221+
222+
public synchronized AWSCodeBuild getClient() {
223+
if (this.client == null) {
224+
this.client = CodeBuilderCloud.buildClient(credentialsId, region);
225+
}
226+
return this.client;
227+
}
228+
229+
private transient long lastProvisionTime = 0;
230+
231+
@Override
232+
public synchronized Collection<PlannedNode> provision(Label label, int excessWorkload) {
233+
List<NodeProvisioner.PlannedNode> list = new ArrayList<NodeProvisioner.PlannedNode>();
234+
235+
// guard against non-matching labels
236+
if (label != null && !label.matches(Arrays.asList(new LabelAtom(getLabel())))) {
237+
return list;
238+
}
239+
240+
// guard against double-provisioning with a 500ms cooldown clock
241+
long timeDiff = System.currentTimeMillis() - lastProvisionTime;
242+
if (timeDiff < 500) {
243+
LOGGER.info("[CodeBuilder]: Provision of {} skipped, still on cooldown ({}ms of 500ms)", excessWorkload,
244+
timeDiff);
245+
return list;
246+
}
247+
248+
String labelName = label == null ? getLabel() : label.getDisplayName();
249+
long stillProvisioning = numStillProvisioning();
250+
long numToLaunch = Math.max(excessWorkload - stillProvisioning, 0);
251+
LOGGER.info("[CodeBuilder]: Provisioning {} nodes for label '{}' ({} already provisioning)", numToLaunch, labelName,
252+
stillProvisioning);
253+
254+
for (int i = 0; i < numToLaunch; i++) {
255+
final String suffix = RandomStringUtils.randomAlphabetic(4);
256+
final String displayName = String.format("%s.cb-%s", projectName, suffix);
257+
final CodeBuilderCloud cloud = this;
258+
final Future<Node> nodeResolver = Computer.threadPoolForRemoting.submit(() -> {
259+
CodeBuilderLauncher launcher = new CodeBuilderLauncher(cloud);
260+
CodeBuilderAgent agent = new CodeBuilderAgent(cloud, displayName, launcher);
261+
Jenkins.get().addNode(agent);
262+
return agent;
263+
});
264+
list.add(new NodeProvisioner.PlannedNode(displayName, nodeResolver, 1));
265+
}
266+
267+
lastProvisionTime = System.currentTimeMillis();
268+
return list;
269+
}
270+
271+
/**
272+
* Find the number of {@link CodeBuilderAgent} instances still connecting to
273+
* Jenkins host.
274+
*/
275+
private long numStillProvisioning() {
276+
return Jenkins.get().getNodes().stream().filter(CodeBuilderAgent.class::isInstance)
277+
.map(CodeBuilderAgent.class::cast).filter(a -> a.getLauncher().isLaunchSupported()).count();
278+
}
279+
280+
@Override
281+
public boolean canProvision(Label label) {
282+
boolean canProvision = label == null ? true : label.matches(Arrays.asList(new LabelAtom(getLabel())));
283+
LOGGER.info("[CodeBuilder]: Check provisioning capabilities for label '{}': {}", label, canProvision);
284+
return canProvision;
285+
}
286+
287+
private static String getDefaultRegion() {
288+
try {
289+
return new DefaultAwsRegionProviderChain().getRegion();
290+
} catch (SdkClientException exc) {
291+
return null;
292+
}
293+
}
294+
295+
@Extension
296+
public static class DescriptorImpl extends Descriptor<Cloud> {
297+
@Override
298+
public String getDisplayName() {
299+
return Messages.displayName();
300+
}
301+
302+
public String getDefaultJnlpImage() {
303+
return DEFAULT_JNLP_IMAGE;
304+
}
305+
306+
public int getDefaultAgentTimeout() {
307+
return DEFAULT_AGENT_TIMEOUT;
308+
}
309+
310+
public String getDefaultComputeType() {
311+
return DEFAULT_COMPUTE_TYPE;
312+
}
313+
314+
public ListBoxModel doFillCredentialsIdItems() {
315+
return AWSCredentialsHelper.doFillCredentialsIdItems(Jenkins.get());
316+
}
317+
318+
public ListBoxModel doFillRegionItems() {
319+
final ListBoxModel options = new ListBoxModel();
320+
321+
String defaultRegion = getDefaultRegion();
322+
if (StringUtils.isNotBlank(defaultRegion)) {
323+
options.add(defaultRegion);
324+
}
325+
326+
for (Region r : RegionUtils.getRegionsForService(AWSCodeBuild.ENDPOINT_PREFIX)) {
327+
if (r.getName() == defaultRegion) {
328+
continue;
329+
}
330+
options.add(r.getName());
331+
}
332+
return options;
333+
}
334+
335+
public ListBoxModel doFillProjectNameItems(@QueryParameter String credentialsId, @QueryParameter String region) {
336+
if (StringUtils.isBlank(region)) {
337+
region = getDefaultRegion();
338+
if (StringUtils.isBlank(region)) {
339+
return new ListBoxModel();
340+
}
341+
}
342+
343+
try {
344+
final List<String> projects = new ArrayList<String>();
345+
String lastToken = null;
346+
do {
347+
ListProjectsResult result = CodeBuilderCloud.buildClient(credentialsId, region)
348+
.listProjects(new ListProjectsRequest().withNextToken(lastToken));
349+
projects.addAll(result.getProjects());
350+
lastToken = result.getNextToken();
351+
} while (lastToken != null);
352+
Collections.sort(projects);
353+
final ListBoxModel options = new ListBoxModel();
354+
for (final String arn : projects) {
355+
options.add(arn);
356+
}
357+
return options;
358+
} catch (RuntimeException e) {
359+
// missing credentials will throw an "AmazonClientException: Unable to load AWS
360+
// credentials from any provider in the chain"
361+
LOGGER.error("[CodeBuilder]: Exception listing projects (region={})", region, e);
362+
return new ListBoxModel();
363+
}
364+
}
365+
366+
public String getDefaultRegion() {
367+
return CodeBuilderCloud.getDefaultRegion();
368+
}
369+
}
370+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package dev.lsegal.jenkins.codebuilder;
2+
3+
import java.io.UnsupportedEncodingException;
4+
import java.net.URLEncoder;
5+
6+
import javax.annotation.Nonnull;
7+
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
import hudson.model.Computer;
12+
import hudson.model.Executor;
13+
import hudson.model.Queue;
14+
import hudson.slaves.AbstractCloudComputer;
15+
import jenkins.model.Jenkins;
16+
17+
public class CodeBuilderComputer extends AbstractCloudComputer<CodeBuilderAgent> {
18+
private static final Logger LOGGER = LoggerFactory.getLogger(CodeBuilderComputer.class);
19+
private String buildId;
20+
21+
@Nonnull
22+
private final CodeBuilderCloud cloud;
23+
24+
public CodeBuilderComputer(CodeBuilderAgent agent) {
25+
super(agent);
26+
this.cloud = agent.getCloud();
27+
}
28+
29+
public String getBuildId() {
30+
return buildId;
31+
}
32+
33+
/* package */ void setBuildId(String buildId) {
34+
this.buildId = buildId;
35+
}
36+
37+
public String getBuildUrl() {
38+
try {
39+
return String.format("https://%s.console.aws.amazon.com/codesuite/codebuild/projects/%s/build/%s",
40+
cloud.getRegion(), cloud.getProjectName(), URLEncoder.encode(buildId, "UTF-8"));
41+
} catch (UnsupportedEncodingException e) {
42+
return buildId;
43+
}
44+
}
45+
46+
@Override
47+
public void taskAccepted(Executor executor, Queue.Task task) {
48+
super.taskAccepted(executor, task);
49+
LOGGER.info("[CodeBuilder]: [{}]: Task in job '{}' accepted", this, task.getFullDisplayName());
50+
}
51+
52+
@Override
53+
public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
54+
super.taskCompleted(executor, task, durationMS);
55+
LOGGER.info("[CodeBuilder]: [{}]: Task in job '{}' completed in {}ms", this, task.getFullDisplayName(), durationMS);
56+
gracefulShutdown();
57+
}
58+
59+
@Override
60+
public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
61+
super.taskCompletedWithProblems(executor, task, durationMS, problems);
62+
LOGGER.error("[CodeBuilder]: [{}]: Task in job '{}' completed with problems in {}ms", this,
63+
task.getFullDisplayName(), durationMS, problems);
64+
gracefulShutdown();
65+
}
66+
67+
@Override
68+
public String toString() {
69+
return String.format("name: %s buildID: %s", getName(), getBuildId());
70+
}
71+
72+
private void gracefulShutdown() {
73+
setAcceptingTasks(false);
74+
75+
Computer.threadPoolForRemoting.submit(() -> {
76+
LOGGER.info("[CodeBuilder]: [{}]: Terminating agent after task.", this);
77+
try {
78+
Thread.sleep(500);
79+
Jenkins.get().removeNode(getNode());
80+
} catch (Exception e) {
81+
LOGGER.info("[CodeBuilder]: [{}]: Termination error: {}", this, e.getClass());
82+
}
83+
return null;
84+
});
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package dev.lsegal.jenkins.codebuilder;
2+
3+
import java.io.IOException;
4+
import java.util.concurrent.TimeoutException;
5+
6+
import com.amazonaws.services.codebuild.model.StartBuildRequest;
7+
import com.amazonaws.services.codebuild.model.StartBuildResult;
8+
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
import hudson.model.TaskListener;
13+
import hudson.slaves.JNLPLauncher;
14+
import hudson.slaves.SlaveComputer;
15+
import hudson.util.StreamTaskListener;
16+
import jenkins.model.Jenkins;
17+
18+
public class CodeBuilderLauncher extends JNLPLauncher {
19+
private static final int sleepMs = 500;
20+
private static final Logger LOGGER = LoggerFactory.getLogger(CodeBuilderLauncher.class);
21+
22+
private final CodeBuilderCloud cloud;
23+
private boolean launched;
24+
25+
public CodeBuilderLauncher(CodeBuilderCloud cloud) {
26+
super(true);
27+
this.cloud = cloud;
28+
}
29+
30+
@Override
31+
public boolean isLaunchSupported() {
32+
return !launched;
33+
}
34+
35+
@Override
36+
public void launch(SlaveComputer computer, TaskListener listener) {
37+
this.launched = false;
38+
LOGGER.info("[CodeBuilder]: Launching {} with {}", computer, listener);
39+
CodeBuilderComputer cbcpu = (CodeBuilderComputer) computer;
40+
StartBuildRequest req = new StartBuildRequest().withProjectName(cloud.getProjectName())
41+
.withImageOverride(cloud.getJnlpImage()).withPrivilegedModeOverride(true)
42+
.withComputeTypeOverride(cloud.getComputeType()).withBuildspecOverride(buildspec(computer));
43+
44+
try {
45+
StartBuildResult res = cloud.getClient().startBuild(req);
46+
String buildId = res.getBuild().getId();
47+
cbcpu.setBuildId(buildId);
48+
49+
LOGGER.info("[CodeBuilder]: Waiting for agent '{}' to connect to build ID: {}...", computer, buildId);
50+
for (int i = 0; i < cloud.getAgentTimeout() * (1000 / sleepMs); i++) {
51+
if (computer.isOnline() && computer.isAcceptingTasks()) {
52+
LOGGER.info("[CodeBuilder]: Agent '{}' connected to build ID: {}.", computer, buildId);
53+
launched = true;
54+
return;
55+
}
56+
Thread.sleep(sleepMs);
57+
}
58+
throw new TimeoutException(
59+
"Timed out while waiting for agent " + computer.getNode() + " to start for build ID: " + buildId);
60+
61+
} catch (Exception e) {
62+
cbcpu.setBuildId(null);
63+
LOGGER.error("[CodeBuilder]: Exception while starting build: {}", e.getMessage(), e);
64+
listener.fatalError("Exception while starting build: %s", e.getMessage());
65+
66+
if (computer.getNode() instanceof CodeBuilderAgent) {
67+
try {
68+
Jenkins.get().removeNode(computer.getNode());
69+
} catch (IOException e1) {
70+
LOGGER.error("Failed to terminate agent: {}", computer.getNode().getDisplayName(), e);
71+
}
72+
}
73+
}
74+
}
75+
76+
@Override
77+
public void beforeDisconnect(SlaveComputer computer, StreamTaskListener listener) {
78+
((CodeBuilderComputer) computer).setBuildId(null);
79+
}
80+
81+
private String buildspec(SlaveComputer computer) {
82+
String cmd = String.format("jenkins-agent -noreconnect -workDir \"$CODEBUILD_SRC_DIR\" -url \"%s\" \"%s\" \"%s\"",
83+
cloud.getJenkinsUrl(), computer.getJnlpMac(), computer.getNode().getDisplayName());
84+
StringBuilder builder = new StringBuilder();
85+
builder.append("version: 0.2\n");
86+
builder.append("phases:\n");
87+
builder.append(" pre_build:\n");
88+
builder.append(" commands:\n");
89+
builder.append(
90+
" - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2 >/dev/null &\n");
91+
builder.append(" - until docker info; do echo .; sleep 1; done\n");
92+
builder.append(" build:\n");
93+
builder.append(" commands:\n");
94+
builder.append(" - " + cmd + " || exit 0\n");
95+
96+
return builder.toString();
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package dev.lsegal.jenkins.codebuilder;
2+
3+
import java.io.IOException;
4+
import java.util.Arrays;
5+
6+
import org.kohsuke.stapler.DataBoundConstructor;
7+
8+
import hudson.Extension;
9+
import hudson.Launcher;
10+
import hudson.model.AbstractBuild;
11+
import hudson.model.AbstractProject;
12+
import hudson.model.BuildListener;
13+
import hudson.model.Computer;
14+
import hudson.tasks.BuildWrapper;
15+
import hudson.tasks.BuildWrapperDescriptor;
16+
import jenkins.model.Jenkins;
17+
18+
public final class CodeBuilderLogger extends BuildWrapper {
19+
@DataBoundConstructor
20+
public CodeBuilderLogger() {
21+
super();
22+
}
23+
24+
@Extension
25+
public static class DescriptorImpl extends BuildWrapperDescriptor {
26+
@Override
27+
public String getDisplayName() {
28+
return Messages.loggerName();
29+
}
30+
31+
@Override
32+
public boolean isApplicable(final AbstractProject<?, ?> item) {
33+
return true;
34+
}
35+
}
36+
37+
@Override
38+
public Environment setUp(@SuppressWarnings("rawtypes") AbstractBuild build, Launcher launcher, BuildListener listener)
39+
throws IOException, InterruptedException {
40+
Computer cpu = Arrays.asList(Jenkins.get().getComputers()).stream()
41+
.filter(c -> c.getChannel() == launcher.getChannel()).findFirst().get();
42+
if (cpu instanceof CodeBuilderComputer) {
43+
CodeBuilderComputer cbCpu = (CodeBuilderComputer) cpu;
44+
listener.getLogger().print("[CodeBuilder]: " + Messages.loggerStarted() + ": ");
45+
listener.hyperlink(cbCpu.getBuildUrl(), cbCpu.getBuildId());
46+
listener.getLogger().println();
47+
}
48+
return new Environment() {
49+
/* empty implementation */
50+
};
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?jelly escape-by-default='true'?>
2+
3+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:c="/lib/credentials">
4+
<f:entry field="credentialsId" title="${%AWS Credentials}">
5+
<c:select />
6+
</f:entry>
7+
8+
<f:entry field="region" title="${%Region}">
9+
<f:select />
10+
</f:entry>
11+
12+
<f:entry field="projectName" title="${%Project Name}">
13+
<f:select />
14+
</f:entry>
15+
16+
<f:entry field="label" title="${%Label}">
17+
<f:textbox />
18+
</f:entry>
19+
20+
<f:entry field="computeType" title="${%Compute Type}">
21+
<select>
22+
<option value="BUILD_GENERAL1_SMALL">Small (2 vCPUs, 3GB RAM, 64GB Disk)</option>
23+
<option value="BUILD_GENERAL1_MEDIUM">Medium (4 vCPUs, 7GB RAM, 128GB Disk)</option>
24+
<option value="BUILD_GENERAL1_LARGE">Large (8 vCPUs, 15GB RAM, 128GB Disk)</option>
25+
</select>
26+
</f:entry>
27+
28+
<f:advanced>
29+
<f:entry field="jenkinsUrl" title="${%Alternative Jenkins URL}">
30+
<f:textbox />
31+
</f:entry>
32+
33+
<f:entry field="jnlpImage" title="${%JNLP Docker Image}">
34+
<f:textbox default="${descriptor.defaultJnlpImage}" />
35+
</f:entry>
36+
37+
<f:entry field="agentTimeout" title="${%Agent Connection Timeout}">
38+
<f:number default="${descriptor.defaultAgentTimeout}" />
39+
</f:entry>
40+
</f:advanced>
41+
</j:jelly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<p>
2+
The time in seconds to wait before giving up on an agent connection. Default
3+
value is 120 seconds.
4+
</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<p>AWS credentials for accessing AWS CodeBuild.</p>
2+
<p>If you configured an IAM role, ensure that it includes the following policy statements
3+
(where <code>{PROJECTNAME}</code> is replaced with the name of your project):</p>
4+
<pre><code>[
5+
{
6+
"Effect": "Allow",
7+
"Resource": "*",
8+
"Action": ["codebuild:ListProjects"]
9+
}
10+
{
11+
"Effect": "Allow",
12+
"Resource": "arn:aws:codebuild:*:*:project/{PROJECTNAME}",
13+
"Action": ["codebuild:StartBuild", "codebuild:StopBuild"]
14+
}
15+
]</code></pre>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>Alternative Jenkins URL, set if you cannot access the Jenkins host directly. Leave blank for default.</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<p>
2+
The JNLP Docker image override to use on AWS CodeBuild. The image should provide
3+
a <code>jenkins-agent</code> JNLP binary and Docker access. Default value is
4+
<code>lsegal/jnlp-docker-agent:latest</code>.
5+
</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<p>
2+
Labels (or tags) are used to group multiple agents into one logical group. For
3+
example, if you have multiple Windows agents and you have a job that must run
4+
on Windows, then you could configure all your Windows agents to have the label
5+
windows, and then tie that job to this label. This would ensure that your job
6+
runs on one of your Windows agents, but not on any agents without this label.
7+
</p>
8+
9+
<p>
10+
Labels do not necessarily have to represent the operating system on the agent;
11+
you can also use labels to note the CPU architecture, or that a certain tool
12+
is installed on the agent.
13+
</p>
14+
15+
<p>
16+
Labels may contain any non-space characters, but you should avoid special
17+
characters such as any of these: <code>!&|&lt;&gt;()</code>, as other Jenkins features allow for
18+
defining label expressions, where these characters may be used.
19+
</p>
20+
21+
<p>
22+
Note that you can only provide a single label in this box.
23+
</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<p>The name of the AWS CodeBuild project to run builds in.</p>
2+
<p>
3+
Note that this project does not need to be unique for each job. This plugin
4+
can run builds for multiple jobs using the same project name. The easiest
5+
configuration involves creating a bare project with no source or build
6+
definitions and using that for your entire Jenkins cluster.
7+
</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>The AWS region to find AWS CodeBuild resources in. Defaults to <code>us-east-1</code>.</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
displayName=CodeBuilder: AWS CodeBuild Cloud Agents
2+
loggerName=Add Build ID Link to CodeBuilder Jobs
3+
loggerStarted=Started build ID

‎src/main/resources/index.jelly

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?jelly escape-by-default='true'?>
2+
<div>
3+
CodeBuilder Plugin: Dynamic Agents Running on AWS CodeBuild
4+
</div>

0 commit comments

Comments
 (0)
Please sign in to comment.