mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
Add Kubernetes executor
This commit is contained in:
parent
7d8f94e5e8
commit
cca61971a8
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
29
server-core/src/main/java/io/onedev/server/util/Maps.java
Normal file
29
server-core/src/main/java/io/onedev/server/util/Maps.java
Normal 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]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>
|
||||
13
server-plugin/server-plugin-kubernetes/pom.xml
Normal file
13
server-plugin/server-plugin-kubernetes/pom.xml
Normal 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>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
13
server-plugin/server-plugin-serverdocker/pom.xml
Normal file
13
server-plugin/server-plugin-serverdocker/pom.xml
Normal 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>
|
||||
@ -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 + "/*");
|
||||
@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user