Skip to content

Commit 474af8d

Browse files
authored
Merge pull request #7 from GaneshSPatil/master
Use JobIdentifier Information provided as part of elastic agent extension V2
2 parents 092f2c9 + 5e83c8e commit 474af8d

17 files changed

+969
-266
lines changed

src/main/java/cd/go/contrib/elasticagent/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public interface Constants {
4545
// internal use only
4646
String CREATED_BY_LABEL_KEY = "Elastic-Agent-Created-By";
4747
String ENVIRONMENT_LABEL_KEY = "Elastic-Agent-Environment-Name";
48+
String JOB_ID_LABEL_KEY = "Elastic-Agent-Job-Identifier";
49+
4850
String KUBERNETES_NAMESPACE_KEY = "default";
4951
String KUBERNETES_POD_KIND_LABEL_KEY = "kind";
5052
String KUBERNETES_POD_KIND_LABEL_VALUE = "kubernetes-elastic-agent";

src/main/java/cd/go/contrib/elasticagent/KubernetesAgentInstances.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package cd.go.contrib.elasticagent;
1818

19+
import cd.go.contrib.elasticagent.model.JobIdentifier;
1920
import cd.go.contrib.elasticagent.requests.CreateAgentRequest;
2021
import io.fabric8.kubernetes.api.model.Pod;
2122
import io.fabric8.kubernetes.api.model.PodList;
@@ -38,30 +39,46 @@ public class KubernetesAgentInstances implements AgentInstances<KubernetesInstan
3839
public Clock clock = Clock.DEFAULT;
3940
private boolean refreshed;
4041
private KubernetesClientFactory factory;
42+
private KubernetesInstanceFactory kubernetesInstanceFactory;
4143

4244
public KubernetesAgentInstances() {
43-
this(KubernetesClientFactory.instance());
45+
this(KubernetesClientFactory.instance(), new KubernetesInstanceFactory());
4446
}
4547

4648
public KubernetesAgentInstances(KubernetesClientFactory factory) {
49+
this(factory, new KubernetesInstanceFactory());
50+
}
51+
52+
public KubernetesAgentInstances(KubernetesClientFactory factory, KubernetesInstanceFactory kubernetesInstanceFactory) {
4753
this.factory = factory;
54+
this.kubernetesInstanceFactory = kubernetesInstanceFactory;
4855
}
4956

5057
@Override
5158
public KubernetesInstance create(CreateAgentRequest request, PluginSettings settings, PluginRequest pluginRequest) throws Exception {
52-
KubernetesClient client = factory.kubernetes(settings);
53-
KubernetesInstance instance;
54-
if(isUsingPodYaml(request)) {
55-
instance = KubernetesInstance.createUsingPodYaml(request, settings, client, pluginRequest);
56-
} else {
57-
instance = KubernetesInstance.create(request, settings, client, pluginRequest);
59+
JobIdentifier jobIdentifier = request.jobIdentifier();
60+
if (isAgentCreatedForJob(jobIdentifier.getJobId())) {
61+
LOG.warn("[Create Agent Request] Request for creating an agent for Job Identifier [" + jobIdentifier + "] has already been scheduled. Skipping current request.");
62+
return null;
5863
}
5964

65+
KubernetesClient client = factory.kubernetes(settings);
66+
KubernetesInstance instance = kubernetesInstanceFactory.create(request, settings, client, pluginRequest, isUsingPodYaml(request));
6067
register(instance);
6168

6269
return instance;
6370
}
6471

72+
private boolean isAgentCreatedForJob(Long jobId) {
73+
for (KubernetesInstance instance : instances.values()) {
74+
if (instance.jobId().equals(jobId)) {
75+
return true;
76+
}
77+
}
78+
79+
return false;
80+
}
81+
6582
private boolean isUsingPodYaml(CreateAgentRequest request) {
6683
return Boolean.valueOf(request.properties().get(SPECIFIED_USING_POD_CONFIGURATION.getKey()));
6784
}
@@ -119,7 +136,7 @@ public void refreshAll(PluginRequest pluginRequest) throws Exception {
119136
Map<String, String> podLabels = pod.getMetadata().getLabels();
120137
if (podLabels != null) {
121138
if (StringUtils.equals(Constants.KUBERNETES_POD_KIND_LABEL_VALUE, podLabels.get(Constants.KUBERNETES_POD_KIND_LABEL_KEY))) {
122-
register(KubernetesInstance.fromInstanceInfo(pod));
139+
register(kubernetesInstanceFactory.fromKubernetesPod(pod));
123140
}
124141
}
125142
}
@@ -133,10 +150,6 @@ public KubernetesInstance find(String agentId) {
133150
return instances.get(agentId);
134151
}
135152

136-
public boolean hasInstance(String agentId) {
137-
return instances.containsKey(agentId);
138-
}
139-
140153
private void register(KubernetesInstance instance) {
141154
instances.put(instance.name(), instance);
142155
}
@@ -155,9 +168,13 @@ private KubernetesAgentInstances unregisteredAfterTimeout(PluginSettings setting
155168
DateTime dateTimeCreated = new DateTime(createdAt);
156169

157170
if (clock.now().isAfter(dateTimeCreated.plus(period))) {
158-
unregisteredInstances.register(KubernetesInstance.fromInstanceInfo(pod));
171+
unregisteredInstances.register(kubernetesInstanceFactory.fromKubernetesPod(pod));
159172
}
160173
}
161174
return unregisteredInstances;
162175
}
176+
177+
public boolean instanceExists(KubernetesInstance instance) {
178+
return instances.contains(instance);
179+
}
163180
}

src/main/java/cd/go/contrib/elasticagent/KubernetesInstance.java

Lines changed: 11 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -16,175 +16,29 @@
1616

1717
package cd.go.contrib.elasticagent;
1818

19-
import cd.go.contrib.elasticagent.requests.CreateAgentRequest;
20-
import cd.go.contrib.elasticagent.utils.Size;
21-
import cd.go.contrib.elasticagent.utils.Util;
22-
import com.fasterxml.jackson.databind.ObjectMapper;
23-
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
24-
import com.github.mustachejava.DefaultMustacheFactory;
25-
import com.github.mustachejava.Mustache;
26-
import com.github.mustachejava.MustacheFactory;
27-
import io.fabric8.kubernetes.api.model.*;
2819
import io.fabric8.kubernetes.client.KubernetesClient;
29-
import org.apache.commons.lang3.StringUtils;
3020
import org.joda.time.DateTime;
3121
import org.joda.time.DateTimeZone;
3222

33-
import java.io.IOException;
34-
import java.io.StringReader;
35-
import java.io.StringWriter;
36-
import java.text.ParseException;
37-
import java.text.SimpleDateFormat;
38-
import java.util.*;
39-
40-
import static cd.go.contrib.elasticagent.Constants.KUBERNETES_POD_CREATION_TIME_FORMAT;
41-
import static cd.go.contrib.elasticagent.KubernetesPlugin.LOG;
42-
import static cd.go.contrib.elasticagent.executors.GetProfileMetadataExecutor.POD_CONFIGURATION;
43-
import static cd.go.contrib.elasticagent.utils.Util.getSimpleDateFormat;
44-
import static org.apache.commons.lang3.StringUtils.isBlank;
23+
import java.util.Map;
4524

4625
public class KubernetesInstance {
4726
private final DateTime createdAt;
4827
private final String environment;
28+
private final String name;
4929
private final Map<String, String> properties;
50-
private String name;
30+
private final Long jobId;
5131

52-
private KubernetesInstance(String name, Date createdAt, String environment, Map<String, String> properties) {
53-
this.name = name;
54-
this.createdAt = new DateTime(createdAt).withZone(DateTimeZone.UTC);
32+
public KubernetesInstance(DateTime createdAt, String environment, String name, Map<String, String> properties, Long jobId) {
33+
this.createdAt = createdAt.withZone(DateTimeZone.UTC);
5534
this.environment = environment;
35+
this.name = name;
5636
this.properties = properties;
37+
this.jobId = jobId;
5738
}
5839

59-
public static KubernetesInstance create(CreateAgentRequest request, PluginSettings settings, KubernetesClient client, PluginRequest pluginRequest) {
60-
String containerName = Constants.KUBERNETES_POD_NAME + UUID.randomUUID().toString();
61-
62-
Container container = new Container();
63-
container.setName(containerName);
64-
container.setImage(image(request.properties()));
65-
container.setImagePullPolicy("IfNotPresent");
66-
67-
ResourceRequirements resources = new ResourceRequirements();
68-
resources.setLimits(new HashMap<String, Quantity>() {{
69-
String maxMemory = request.properties().get("MaxMemory");
70-
if (StringUtils.isNotBlank(maxMemory)) {
71-
LOG.debug(String.format("[Create Agent] Setting memory resource limit on k8s pod:%s", maxMemory));
72-
Size mem = Size.parse(maxMemory);
73-
put("memory", new Quantity(String.valueOf(mem.toMegabytes()), "Mi"));
74-
}
75-
76-
String maxCPU = request.properties().get("MaxCPU");
77-
if (StringUtils.isNotBlank(maxCPU)) {
78-
LOG.debug(String.format("[Create Agent] Setting cpu resource limit on k8s pod:%s", maxCPU));
79-
put("cpu", new Quantity(maxCPU));
80-
}
81-
}});
82-
container.setResources(resources);
83-
84-
ObjectMeta podMetadata = new ObjectMeta();
85-
podMetadata.setName(containerName);
86-
87-
PodSpec podSpec = new PodSpec();
88-
podSpec.setContainers(Arrays.asList(container));
89-
90-
Pod elasticAgentPod = new Pod("v1", "Pod", podMetadata, podSpec, new PodStatus());
91-
92-
setContainerEnvVariables(elasticAgentPod, request, settings, pluginRequest);
93-
setAnnotations(elasticAgentPod, request);
94-
setLabels(elasticAgentPod, request);
95-
96-
return createKubernetesPod(client, elasticAgentPod);
97-
}
98-
99-
private static void setLabels(Pod pod, CreateAgentRequest request) {
100-
Map<String, String> existingLabels = (pod.getMetadata().getLabels() != null) ? pod.getMetadata().getLabels() : new HashMap<>();
101-
existingLabels.putAll(labelsFrom(request));
102-
pod.getMetadata().setLabels(existingLabels);
103-
}
104-
105-
private static void setAnnotations(Pod pod, CreateAgentRequest request) {
106-
Map<String, String> existingAnnotations = (pod.getMetadata().getAnnotations() != null) ? pod.getMetadata().getAnnotations() : new HashMap<>();
107-
existingAnnotations.putAll(request.properties());
108-
pod.getMetadata().setAnnotations(existingAnnotations);
109-
}
110-
111-
private static KubernetesInstance createKubernetesPod(KubernetesClient client, Pod elasticAgentPod) {
112-
LOG.info(String.format("[Create Agent] Creating K8s pod with spec:%s", elasticAgentPod.toString()));
113-
client.pods().inNamespace(Constants.KUBERNETES_NAMESPACE_KEY).create(elasticAgentPod);
114-
return fromInstanceInfo(elasticAgentPod);
115-
}
116-
117-
static KubernetesInstance fromInstanceInfo(Pod elasticAgentPod) {
118-
try {
119-
ObjectMeta metadata = elasticAgentPod.getMetadata();
120-
String containerName = metadata.getName();
121-
String environment = metadata.getLabels().get(Constants.ENVIRONMENT_LABEL_KEY);
122-
123-
Date date = new Date();
124-
if(StringUtils.isNotBlank(metadata.getCreationTimestamp())) {
125-
date = getSimpleDateFormat().parse(metadata.getCreationTimestamp());
126-
}
127-
return new KubernetesInstance(containerName, date, environment, metadata.getAnnotations());
128-
} catch (ParseException e) {
129-
throw new RuntimeException(e);
130-
}
131-
}
132-
133-
private static List<EnvVar> environmentFrom(CreateAgentRequest request, PluginSettings settings, String podName, PluginRequest pluginRequest) {
134-
ArrayList<EnvVar> env = new ArrayList<>();
135-
String goServerUrl = StringUtils.isBlank(settings.getGoServerUrl()) ? pluginRequest.getSeverInfo().getSecureSiteUrl() : settings.getGoServerUrl();
136-
env.add(new EnvVar("GO_EA_SERVER_URL", goServerUrl, null));
137-
String environment = request.properties().get("Environment");
138-
if (StringUtils.isNotBlank(environment)) {
139-
env.addAll(parseEnvironments(environment));
140-
}
141-
env.addAll(request.autoregisterPropertiesAsEnvironmentVars(podName));
142-
143-
return new ArrayList<>(env);
144-
}
145-
146-
private static void setContainerEnvVariables(Pod pod, CreateAgentRequest request, PluginSettings settings, PluginRequest pluginRequest) {
147-
for (Container container : pod.getSpec().getContainers()) {
148-
List<EnvVar> existingEnv = (container.getEnv() != null) ? container.getEnv() : new ArrayList<>();
149-
existingEnv.addAll(environmentFrom(request, settings, pod.getMetadata().getName(), pluginRequest));
150-
container.setEnv(existingEnv);
151-
}
152-
}
153-
154-
private static Collection<? extends EnvVar> parseEnvironments(String environment) {
155-
ArrayList<EnvVar> envVars = new ArrayList<>();
156-
for (String env : environment.split("\n")) {
157-
String[] parts = env.split("=");
158-
envVars.add(new EnvVar(parts[0], parts[1], null));
159-
}
160-
161-
return envVars;
162-
}
163-
164-
private static HashMap<String, String> labelsFrom(CreateAgentRequest request) {
165-
HashMap<String, String> labels = new HashMap<>();
166-
167-
labels.put(Constants.CREATED_BY_LABEL_KEY, Constants.PLUGIN_ID);
168-
if (StringUtils.isNotBlank(request.environment())) {
169-
labels.put(Constants.ENVIRONMENT_LABEL_KEY, request.environment());
170-
}
171-
172-
labels.put(Constants.KUBERNETES_POD_KIND_LABEL_KEY, Constants.KUBERNETES_POD_KIND_LABEL_VALUE);
173-
174-
return labels;
175-
}
176-
177-
private static String image(Map<String, String> properties) {
178-
String image = properties.get("Image");
179-
180-
if (isBlank(image)) {
181-
throw new IllegalArgumentException("Must provide `Image` attribute.");
182-
}
183-
184-
if (!image.contains(":")) {
185-
return image + ":latest";
186-
}
187-
return image;
40+
public void terminate(KubernetesClient client) {
41+
client.pods().inNamespace(Constants.KUBERNETES_NAMESPACE_KEY).withName(name).delete();
18842
}
18943

19044
public String name() {
@@ -199,62 +53,11 @@ public String environment() {
19953
return environment;
20054
}
20155

202-
@Override
203-
public boolean equals(Object o) {
204-
if (this == o) return true;
205-
if (o == null || getClass() != o.getClass()) return false;
206-
207-
KubernetesInstance that = (KubernetesInstance) o;
208-
209-
return name != null ? name.equals(that.name) : that.name == null;
210-
}
211-
212-
@Override
213-
public int hashCode() {
214-
return name != null ? name.hashCode() : 0;
215-
}
216-
217-
public void terminate(KubernetesClient client) {
218-
client.pods().inNamespace(Constants.KUBERNETES_NAMESPACE_KEY).withName(name).delete();
219-
}
220-
22156
public Map<String, String> getInstanceProperties() {
22257
return properties;
22358
}
22459

225-
public static KubernetesInstance createUsingPodYaml(CreateAgentRequest request, PluginSettings settings, KubernetesClient client, PluginRequest pluginRequest) {
226-
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
227-
String podYaml = request.properties().get(POD_CONFIGURATION.getKey());
228-
229-
StringWriter writer = new StringWriter();
230-
MustacheFactory mf = new DefaultMustacheFactory();
231-
Mustache mustache = mf.compile(new StringReader(podYaml), "templatePod");
232-
mustache.execute(writer, KubernetesInstance.getJinJavaContext());
233-
String templatizedPodYaml = writer.toString();
234-
235-
Pod elasticAgentPod = new Pod();
236-
try {
237-
elasticAgentPod = mapper.readValue(templatizedPodYaml, Pod.class);
238-
} catch (IOException e) {
239-
//ignore error here, handle this inside validate profile!
240-
e.printStackTrace();
241-
}
242-
243-
elasticAgentPod.getMetadata().setCreationTimestamp(getSimpleDateFormat().format(new Date()));
244-
245-
setContainerEnvVariables(elasticAgentPod, request, settings, pluginRequest);
246-
setAnnotations(elasticAgentPod, request);
247-
setLabels(elasticAgentPod, request);
248-
249-
return createKubernetesPod(client, elasticAgentPod);
250-
}
251-
252-
public static Map<String, String> getJinJavaContext() {
253-
HashMap<String, String> context = new HashMap<>();
254-
context.put(Constants.POD_POSTFIX, UUID.randomUUID().toString());
255-
context.put(Constants.CONTAINER_POSTFIX, UUID.randomUUID().toString());
256-
context.put(Constants.GOCD_AGENT_IMAGE, "gocd/gocd-agent-alpine-3.5");
257-
context.put(Constants.LATEST_VERSION, "v17.10.0");
258-
return context;
60+
public Long jobId() {
61+
return jobId;
25962
}
26063
}

0 commit comments

Comments
 (0)