Add Kubernetes executor

This commit is contained in:
robin 2019-06-17 12:12:36 +08:00
parent 7d8f94e5e8
commit cca61971a8
23 changed files with 603 additions and 90 deletions

View File

@ -100,6 +100,11 @@
<artifactId>jetty-servlets</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
@ -346,9 +351,9 @@
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>

View File

@ -65,9 +65,9 @@ import io.onedev.server.model.BuildParam;
import io.onedev.server.model.Project;
import io.onedev.server.model.Setting;
import io.onedev.server.model.Setting.Key;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.model.support.SourceSnapshot;
import io.onedev.server.model.User;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.model.support.jobexecutor.SourceSnapshot;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;

View File

@ -29,7 +29,7 @@ public class JobCache implements Serializable {
private String path;
@Editable(order=100, description="Specify key of the cache. Caches with same key can be shared")
@Editable(order=100, description="Specify key of the cache. Caches with same key can be reused by different builds")
@PathSegment
@NotEmpty
public String getKey() {

View File

@ -5,8 +5,8 @@ import java.util.List;
import javax.annotation.Nullable;
import io.onedev.server.model.Setting;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.model.support.authenticator.Authenticator;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.model.support.setting.BackupSetting;
import io.onedev.server.model.support.setting.GlobalIssueSetting;
import io.onedev.server.model.support.setting.MailSetting;

View File

@ -51,8 +51,8 @@ import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.UserAuthorization;
import io.onedev.server.model.support.BranchProtection;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.model.support.TagProtection;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Transactional;

View File

@ -14,8 +14,8 @@ import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.maintenance.DataManager;
import io.onedev.server.model.Setting;
import io.onedev.server.model.Setting.Key;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.model.support.authenticator.Authenticator;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.model.support.setting.BackupSetting;
import io.onedev.server.model.support.setting.GlobalIssueSetting;
import io.onedev.server.model.support.setting.MailSetting;

View File

@ -1,4 +1,4 @@
package io.onedev.server.model.support.jobexecutor;
package io.onedev.server.model.support;
import java.io.File;
import java.io.Serializable;
@ -12,6 +12,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import io.onedev.commons.launcher.loader.ExtensionPoint;
import io.onedev.commons.utils.stringmatch.ChildAwareMatcher;
import io.onedev.commons.utils.stringmatch.Matcher;
import io.onedev.server.ci.job.cache.JobCache;
@ -25,6 +26,7 @@ import io.onedev.server.web.editable.annotation.NameOfEmptyValue;
import io.onedev.server.web.editable.annotation.Patterns;
import io.onedev.server.web.editable.annotation.ProjectPatterns;
@ExtensionPoint
@Editable
public abstract class JobExecutor implements Serializable {

View File

@ -1,4 +1,4 @@
package io.onedev.server.model.support.jobexecutor;
package io.onedev.server.model.support;
import java.io.File;
import java.io.IOException;

View File

@ -1,44 +0,0 @@
package io.onedev.server.model.support.jobexecutor;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import io.onedev.commons.utils.FileUtils;
import io.onedev.server.ci.job.cache.JobCache;
import io.onedev.server.util.patternset.PatternSet;
import io.onedev.server.web.editable.annotation.Editable;
@Editable(order=300)
public class KubernetesExecutor extends JobExecutor {
private static final long serialVersionUID = 1L;
@Override
public void execute(String environment, File workspace, Map<String, String> envVars,
List<String> commands, SourceSnapshot snapshot, Collection<JobCache> caches,
PatternSet collectFiles, Logger logger) {
logger.info("run_type: " + envVars.get("run_type"));
logger.info("deploy_to_production_environment: " + envVars.get("deploy_to_production_environment"));
logger.info("production_token: " + envVars.get("production_token"));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void checkCaches() {
}
@Override
public void cleanDir(File dir) {
FileUtils.cleanDir(dir);
}
}

View File

@ -0,0 +1,29 @@
package io.onedev.server.util;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.common.base.Preconditions;
public class Maps {
public static Map<Object, Object> newLinkedHashMap(Object...args) {
Map<Object, Object> map = new LinkedHashMap<>();
initMap(map, args);
return map;
}
public static Map<Object, Object> newHashMap(Object...args) {
Map<Object, Object> map = new HashMap<>();
initMap(map, args);
return map;
}
private static void initMap(Map<Object, Object> map, Object...args) {
Preconditions.checkArgument(args.length % 2 == 0, "Arguments should be key/value pairs");
for (int i=0; i<args.length/2; i++)
map.put(args[i*2], args[i*2+1]);
}
}

View File

@ -9,7 +9,7 @@ import io.onedev.server.util.validation.annotation.ExecutorName;
public class ExecutorNameValidator implements ConstraintValidator<ExecutorName, String> {
private static final Pattern PATTERN = Pattern.compile("[\\w-\\s\\.]+");
private static final Pattern PATTERN = Pattern.compile("[\\w-\\.]+");
@Override
public void initialize(ExecutorName constaintAnnotation) {

View File

@ -4,7 +4,7 @@ import java.io.Serializable;
import javax.validation.constraints.NotNull;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.web.editable.annotation.Editable;
@Editable

View File

@ -20,7 +20,7 @@ import org.slf4j.LoggerFactory;
import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
import io.onedev.commons.utils.ExceptionUtils;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.web.behavior.testform.TestFormBehavior;
import io.onedev.server.web.behavior.testform.TestResult;
import io.onedev.server.web.component.beaneditmodal.BeanEditModalPanel;

View File

@ -14,7 +14,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.web.behavior.sortable.SortBehavior;
import io.onedev.server.web.behavior.sortable.SortPosition;
import io.onedev.server.web.page.admin.AdministrationPage;

View File

@ -14,7 +14,7 @@ import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import io.onedev.server.model.support.jobexecutor.JobExecutor;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.web.ajaxlistener.ConfirmListener;
import io.onedev.server.web.editable.BeanContext;

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<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">
<modelVersion>4.0.0</modelVersion>
<artifactId>server-plugin</artifactId>
<packaging>pom</packaging>
@ -12,13 +14,16 @@
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-core</artifactId>
<version>${project.version}</version>
<version>${project.version}</version>
</dependency>
</dependencies>
<modules>
<module>server-plugin-archetype</module>
<module>server-plugin-artifact</module>
<module>server-plugin-htmlreport</module>
<module>server-plugin-maven</module>
</modules>
<module>server-plugin-maven</module>
<module>server-plugin-kubernetes</module>
<module>server-plugin-localdocker</module>
<module>server-plugin-serverdocker</module>
</modules>
</project>

View File

@ -0,0 +1,13 @@
<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">
<modelVersion>4.0.0</modelVersion>
<artifactId>server-plugin-kubernetes</artifactId>
<parent>
<groupId>io.onedev</groupId>
<artifactId>server-plugin</artifactId>
<version>3.0.0</version>
</parent>
<properties>
<moduleClass>io.onedev.server.plugin.kubernetes.KubernetesModule</moduleClass>
</properties>
</project>

View File

@ -0,0 +1,404 @@
package io.onedev.server.plugin.kubernetes;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.codec.Charsets;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.StringUtils;
import io.onedev.commons.utils.command.Commandline;
import io.onedev.commons.utils.command.LineConsumer;
import io.onedev.server.ci.job.cache.JobCache;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.model.support.SourceSnapshot;
import io.onedev.server.plugin.kubernetes.KubernetesExecutor.TestData;
import io.onedev.server.util.Maps;
import io.onedev.server.util.patternset.PatternSet;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.OmitName;
import io.onedev.server.web.util.Testable;
@Editable(order=300)
public class KubernetesExecutor extends JobExecutor implements Testable<TestData> {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(KubernetesExecutor.class);
private String configFile;
private String kubeCtlPath;
private String namespace = "onedev-ci";
private List<NodeSelectorEntry> nodeSelector = new ArrayList<>();
private String imagePullSecrets;
private String serviceAccount;
@Editable(name="Kubectl Config File", order=100, description=
"Specify absolute path to the config file used by kubectl to access the "
+ "cluster. Leave empty to have kubectl determining cluster access "
+ "information automatically")
public String getConfigFile() {
return configFile;
}
public void setConfigFile(String configFile) {
this.configFile = configFile;
}
@Editable(name="Path to kubectl", order=200, description=
"Specify absolute path to the kubectl utility, for instance: <i>/usr/bin/kubectl</i>. "
+ "If left empty, OneDev will try to find the utility from system path")
public String getKubeCtlPath() {
return kubeCtlPath;
}
public void setKubeCtlPath(String kubeCtlPath) {
this.kubeCtlPath = kubeCtlPath;
}
@Editable(order=20000, group="More Settings", description="Optionally specify Kubernetes namespace "
+ "used by this executor to place created Kubernetes resources (such as job pods)")
@NotEmpty
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
@Editable(order=21000, group="More Settings", description="Optionally specify node selectors of the "
+ "job pods created by this executor")
public List<NodeSelectorEntry> getNodeSelector() {
return nodeSelector;
}
public void setNodeSelector(List<NodeSelectorEntry> nodeSelector) {
this.nodeSelector = nodeSelector;
}
@Editable(order=22000, group="More Settings", description="Optionally specify space-separated image "
+ "pull secrets in above namespace for job pods to access private docker registries. "
+ "Refer to <a href='https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/'>kubernetes "
+ "documentation</a> on how to set up image pull secrets")
public String getImagePullSecrets() {
return imagePullSecrets;
}
public void setImagePullSecrets(String imagePullSecrets) {
this.imagePullSecrets = imagePullSecrets;
}
@Editable(order=23000, group="More Settings", description="Optionally specify a service account in above namespace to run the job "
+ "pod. Refer to <a href='https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/'>"
+ "kubernetes documentation</a> on how to set up service accounts")
public String getServiceAccount() {
return serviceAccount;
}
public void setServiceAccount(String serviceAccount) {
this.serviceAccount = serviceAccount;
}
@Override
public void execute(String environment, File workspace, Map<String, String> envVars,
List<String> commands, SourceSnapshot snapshot, Collection<JobCache> caches,
PatternSet collectFiles, Logger logger) {
}
@Override
public void checkCaches() {
}
@Override
public void cleanDir(File dir) {
FileUtils.cleanDir(dir);
}
private Commandline newKubeCtl() {
String kubectl = getKubeCtlPath();
if (kubectl == null)
kubectl = "kubectl";
Commandline cmdline = new Commandline(kubectl);
if (getConfigFile() != null)
cmdline.addArgs("--kubeconfig", getConfigFile());
return cmdline;
}
private String createResource(Map<Object, Object> resourceData, Logger logger) {
Commandline kubectl = newKubeCtl();
File file = null;
try {
AtomicReference<String> resourceNameRef = new AtomicReference<String>(null);
file = File.createTempFile("k8s", ".yaml");
FileUtils.writeFile(file, new Yaml().dump(resourceData), Charsets.UTF_8.name());
kubectl.addArgs("create", "-f", file.getAbsolutePath());
kubectl.execute(new LineConsumer() {
@Override
public void consume(String line) {
logger.info(line);
line = StringUtils.substringAfter(line, "/");
resourceNameRef.set(StringUtils.substringBefore(line, " "));
}
}, new LineConsumer() {
@Override
public void consume(String line) {
logger.error(line);
}
}).checkReturnCode();
return Preconditions.checkNotNull(resourceNameRef.get());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (file != null)
file.delete();
}
}
private void deleteResource(String resourceType, String resourceName, Logger logger) {
Commandline cmd = newKubeCtl();
cmd.addArgs("delete", resourceType, resourceName, "--namespace=" + getNamespace());
cmd.execute(new LineConsumer() {
@Override
public void consume(String line) {
logger.info(line);
}
}, new LineConsumer() {
@Override
public void consume(String line) {
logger.error(line);
}
}).checkReturnCode();
}
private void createNamespaceIfNotExist(Logger logger) {
Commandline cmd = newKubeCtl();
cmd.addArgs("get", "namespaces");
AtomicBoolean hasNamespace = new AtomicBoolean(false);
cmd.execute(new LineConsumer() {
@Override
public void consume(String line) {
logger.debug(line);
if (line.startsWith(getNamespace() + " "))
hasNamespace.set(true);
}
}, new LineConsumer() {
@Override
public void consume(String line) {
logger.error(line);
}
}).checkReturnCode();
if (!hasNamespace.get()) {
cmd = newKubeCtl();
cmd.addArgs("create", "namespace", getNamespace());
cmd.execute(new LineConsumer() {
@Override
public void consume(String line) {
logger.debug(line);
}
}, new LineConsumer() {
@Override
public void consume(String line) {
logger.error(line);
}
}).checkReturnCode();
}
}
private String getResourceNamePrefix() {
try {
return "onedev-ci-" + InetAddress.getLocalHost().getHostName() + "-" + getName() + "-";
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
private List<Object> getImagePullSecretsData() {
List<Object> data = new ArrayList<>();
if (getImagePullSecrets() != null) {
for (String imagePullSecret: Splitter.on(" ").trimResults().omitEmptyStrings().split(getImagePullSecrets()))
data.add(Maps.newLinkedHashMap("name", imagePullSecret));
}
return data;
}
private Map<String, String> getNodeSelectorData() {
Map<String, String> data = new LinkedHashMap<>();
for (NodeSelectorEntry selector: getNodeSelector())
data.put(selector.getLabelName(), selector.getLabelValue());
return data;
}
private String getPodNodeOS(String podImage, Logger logger) {
createNamespaceIfNotExist(logger);
Map<String, Object> podSpec = new LinkedHashMap<>();
podSpec.put("containers", Lists.<Object>newArrayList(
Maps.newLinkedHashMap(
"name", "test",
"image", podImage)));
Map<String, String> nodeSelectorData = getNodeSelectorData();
if (!nodeSelectorData.isEmpty())
podSpec.put("nodeSelector", nodeSelectorData);
List<Object> imagePullSecretsData = getImagePullSecretsData();
if (!imagePullSecretsData.isEmpty())
podSpec.put("imagePullSecrets", imagePullSecretsData);
if (getServiceAccount() != null)
podSpec.put("serviceAccountName", getServiceAccount());
podSpec.put("restartPolicy", "Never");
Map<Object, Object> podData = Maps.newLinkedHashMap(
"apiVersion", "v1",
"kind", "Pod",
"metadata", Maps.newLinkedHashMap(
"generateName", getResourceNamePrefix() + "test-",
"namespace", getNamespace()),
"spec", podSpec);
String podName = createResource(podData, logger);
try {
AtomicReference<String> nodeNameRef = new AtomicReference<>(null);
Commandline kubectl = newKubeCtl();
kubectl.addArgs("get", "pods/" + podName, "-n", getNamespace(), "-o=jsonpath={..spec.nodeName}");
kubectl.execute(new LineConsumer() {
@Override
public void consume(String line) {
nodeNameRef.set(line);
}
}, new LineConsumer() {
@Override
public void consume(String line) {
logger.error(line);
}
}).checkReturnCode();
Preconditions.checkNotNull(nodeNameRef.get());
AtomicReference<String> nodeOSRef = new AtomicReference<String>(null);
kubectl = newKubeCtl();
kubectl.addArgs("get", "nodes/" + nodeNameRef.get(), "-o=jsonpath={..nodeInfo.operatingSystem}");
kubectl.execute(new LineConsumer() {
@Override
public void consume(String line) {
nodeOSRef.set(line);
}
}, new LineConsumer() {
@Override
public void consume(String line) {
logger.error(line);
}
}).checkReturnCode();
return nodeOSRef.get();
} finally {
deleteResource("pod", podName, logger);
}
}
@Override
public void test(TestData data) {
String podNodeOS = getPodNodeOS(data.getDockerImage(), logger);
logger.info("OS of the node running test pod is " + podNodeOS);
}
@Editable
public static class NodeSelectorEntry implements Serializable {
private static final long serialVersionUID = 1L;
private String labelName;
private String labelValue;
@Editable(order=100)
public String getLabelName() {
return labelName;
}
public void setLabelName(String labelName) {
this.labelName = labelName;
}
@Editable(order=200)
public String getLabelValue() {
return labelValue;
}
public void setLabelValue(String labelValue) {
this.labelValue = labelValue;
}
}
@Editable(name="Specify a Docker Image to Test Against")
public static class TestData implements Serializable {
private static final long serialVersionUID = 1L;
private String dockerImage;
@Editable
@OmitName
@NotEmpty
public String getDockerImage() {
return dockerImage;
}
public void setDockerImage(String dockerImage) {
this.dockerImage = dockerImage;
}
}
}

View File

@ -0,0 +1,37 @@
package io.onedev.server.plugin.kubernetes;
import java.util.Collection;
import com.google.common.collect.Sets;
import io.onedev.commons.launcher.loader.AbstractPluginModule;
import io.onedev.commons.launcher.loader.ImplementationProvider;
import io.onedev.server.model.support.JobExecutor;
/**
* NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class.
*
*/
public class KubernetesModule extends AbstractPluginModule {
@Override
protected void configure() {
super.configure();
// put your guice bindings here
contribute(ImplementationProvider.class, new ImplementationProvider() {
@Override
public Class<?> getAbstractClass() {
return JobExecutor.class;
}
@Override
public Collection<Class<?>> getImplementations() {
return Sets.newHashSet(KubernetesExecutor.class);
}
});
}
}

View File

@ -0,0 +1,13 @@
<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">
<modelVersion>4.0.0</modelVersion>
<artifactId>server-plugin-serverdocker</artifactId>
<parent>
<groupId>io.onedev</groupId>
<artifactId>server-plugin</artifactId>
<version>3.0.0</version>
</parent>
<properties>
<moduleClass>io.onedev.server.plugin.serverdocker.ServerDockerModule</moduleClass>
</properties>
</project>

View File

@ -1,4 +1,4 @@
package io.onedev.server.model.support.jobexecutor;
package io.onedev.server.plugin.serverdocker;
import java.io.ByteArrayInputStream;
import java.io.File;
@ -37,7 +37,9 @@ import io.onedev.server.ci.job.cache.CacheAllocation;
import io.onedev.server.ci.job.cache.CacheCallable;
import io.onedev.server.ci.job.cache.CacheRunner;
import io.onedev.server.ci.job.cache.JobCache;
import io.onedev.server.model.support.jobexecutor.ServerDockerExecutor.TestData;
import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.model.support.SourceSnapshot;
import io.onedev.server.plugin.serverdocker.ServerDockerExecutor.TestData;
import io.onedev.server.util.OneContext;
import io.onedev.server.util.patternset.PatternSet;
import io.onedev.server.util.validation.Validatable;
@ -113,7 +115,7 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
this.registryLogins = registryLogins;
}
private Commandline getDockerCmd() {
private Commandline getDocker() {
if (getDockerExecutable() != null)
return new Commandline(getDockerExecutable());
else
@ -123,11 +125,11 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
@SuppressWarnings("unchecked")
private String getImageOS(Logger logger, String image) {
logger.info("Checking image OS...");
Commandline cmd = getDockerCmd();
cmd.addArgs("inspect", image);
Commandline docker = getDocker();
docker.addArgs("inspect", image);
StringBuilder output = new StringBuilder();
cmd.execute(new LineConsumer(Charsets.UTF_8.name()) {
docker.execute(new LineConsumer(Charsets.UTF_8.name()) {
@Override
public void consume(String line) {
@ -177,17 +179,17 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
login(logger);
logger.info("Pulling image...") ;
Commandline cmd = getDockerCmd();
cmd.addArgs("pull", environment);
cmd.execute(newInfoLogger(logger), newErrorLogger(logger)).checkReturnCode();
Commandline docker = getDocker();
docker.addArgs("pull", environment);
docker.execute(newInfoLogger(logger), newErrorLogger(logger)).checkReturnCode();
cmd.clearArgs();
docker.clearArgs();
String jobInstance = UUID.randomUUID().toString();
cmd.addArgs("run", "--rm", "--name", jobInstance);
docker.addArgs("run", "--rm", "--name", jobInstance);
for (Map.Entry<String, String> entry: envVars.entrySet())
cmd.addArgs("--env", entry.getKey() + "=" + entry.getValue());
docker.addArgs("--env", entry.getKey() + "=" + entry.getValue());
if (getRunOptions() != null)
cmd.addArgs(StringUtils.parseQuoteTokens(getRunOptions()));
docker.addArgs(StringUtils.parseQuoteTokens(getRunOptions()));
String imageOS = getImageOS(logger, environment);
logger.info("Detected image OS: " + imageOS);
@ -223,12 +225,12 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
}
}
cmd.addArgs("-v", effectiveWorkspace.getAbsolutePath() + ":" + dockerWorkspacePath);
docker.addArgs("-v", effectiveWorkspace.getAbsolutePath() + ":" + dockerWorkspacePath);
for (CacheAllocation allocation: allocations) {
if (!allocation.isWorkspace())
cmd.addArgs("-v", allocation.getInstance().getAbsolutePath() + ":" + allocation.resolvePath(dockerWorkspacePath));
docker.addArgs("-v", allocation.getInstance().getAbsolutePath() + ":" + allocation.resolvePath(dockerWorkspacePath));
}
cmd.addArgs("-w", dockerWorkspacePath);
docker.addArgs("-w", dockerWorkspacePath);
if (windows) {
File scriptFile = new File(effectiveWorkspace, "onedev-job-commands.bat");
@ -237,8 +239,8 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
} catch (IOException e) {
throw new RuntimeException(e);
}
cmd.addArgs(environment);
cmd.addArgs("cmd", "/c", dockerWorkspacePath + "\\onedev-job-commands.bat");
docker.addArgs(environment);
docker.addArgs("cmd", "/c", dockerWorkspacePath + "\\onedev-job-commands.bat");
} else {
File scriptFile = new File(effectiveWorkspace, "onedev-job-commands.sh");
try {
@ -246,19 +248,19 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
} catch (IOException e) {
throw new RuntimeException(e);
}
cmd.addArgs(environment);
cmd.addArgs("sh", dockerWorkspacePath + "/onedev-job-commands.sh");
docker.addArgs(environment);
docker.addArgs("sh", dockerWorkspacePath + "/onedev-job-commands.sh");
}
logger.info("Running container to execute job...");
try {
cmd.execute(newInfoLogger(logger), newErrorLogger(logger), null, new ProcessKiller() {
docker.execute(newInfoLogger(logger), newErrorLogger(logger), null, new ProcessKiller() {
@Override
public void kill(Process process) {
logger.info("Stopping container...");
Commandline cmd = getDockerCmd();
Commandline cmd = getDocker();
cmd.addArgs("stop", jobInstance);
cmd.execute(newInfoLogger(logger), newErrorLogger(logger));
}
@ -314,7 +316,7 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
logger.info("Login to docker registry '{}'...", login.getRegistryUrl());
else
logger.info("Login to official docker registry...");
Commandline cmd = getDockerCmd();
Commandline cmd = getDocker();
cmd.addArgs("login", "-u", login.getUserName(), "--password-stdin");
if (login.getRegistryUrl() != null)
cmd.addArgs(login.getRegistryUrl());
@ -417,7 +419,7 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
logger.info("Pulling image...");
Commandline cmd = getDockerCmd();
Commandline cmd = getDocker();
cmd.addArgs("pull", testData.getDockerImage());
cmd.execute(newInfoLogger(logger), newErrorLogger(logger)).checkReturnCode();
@ -469,7 +471,7 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
if (!SystemUtils.IS_OS_WINDOWS) {
logger.info("Checking busybox...");
cmd = getDockerCmd();
cmd = getDocker();
cmd.addArgs("run", "--rm", "busybox", "sh", "-c", "echo hello from busybox");
cmd.execute(newInfoLogger(logger), newErrorLogger(logger)).checkReturnCode();
}
@ -480,7 +482,7 @@ public class ServerDockerExecutor extends JobExecutor implements Testable<TestDa
if (SystemUtils.IS_OS_WINDOWS) {
FileUtils.cleanDir(dir);
} else {
Commandline cmd = getDockerCmd();
Commandline cmd = getDocker();
String containerPath = "/onedev_dir_to_clean";
cmd.addArgs("run", "-v", dir.getAbsolutePath() + ":" + containerPath, "--rm",
"busybox", "sh", "-c", "rm -rf " + containerPath + "/*");

View File

@ -0,0 +1,37 @@
package io.onedev.server.plugin.serverdocker;
import java.util.Collection;
import com.google.common.collect.Sets;
import io.onedev.commons.launcher.loader.AbstractPluginModule;
import io.onedev.commons.launcher.loader.ImplementationProvider;
import io.onedev.server.model.support.JobExecutor;
/**
* NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class.
*
*/
public class ServerDockerModule extends AbstractPluginModule {
@Override
protected void configure() {
super.configure();
// put your guice bindings here
contribute(ImplementationProvider.class, new ImplementationProvider() {
@Override
public Class<?> getAbstractClass() {
return JobExecutor.class;
}
@Override
public Collection<Class<?>> getImplementations() {
return Sets.newHashSet(ServerDockerExecutor.class);
}
});
}
}

View File

@ -28,6 +28,16 @@
<artifactId>server-plugin-maven</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-kubernetes</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-serverdocker</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<executables>bin/*.sh, boot/wrapper-*</executables>