1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package com.jcabi.beanstalk.maven.plugin;
31
32 import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalk;
33 import com.amazonaws.services.elasticbeanstalk.model.ConfigurationOptionSetting;
34 import com.amazonaws.services.elasticbeanstalk.model.ConfigurationSettingsDescription;
35 import com.amazonaws.services.elasticbeanstalk.model.DescribeConfigurationSettingsRequest;
36 import com.amazonaws.services.elasticbeanstalk.model.DescribeConfigurationSettingsResult;
37 import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsRequest;
38 import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsResult;
39 import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsRequest;
40 import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsResult;
41 import com.amazonaws.services.elasticbeanstalk.model.EnvironmentDescription;
42 import com.amazonaws.services.elasticbeanstalk.model.EnvironmentInfoDescription;
43 import com.amazonaws.services.elasticbeanstalk.model.EnvironmentInfoType;
44 import com.amazonaws.services.elasticbeanstalk.model.EventDescription;
45 import com.amazonaws.services.elasticbeanstalk.model.RequestEnvironmentInfoRequest;
46 import com.amazonaws.services.elasticbeanstalk.model.RetrieveEnvironmentInfoRequest;
47 import com.amazonaws.services.elasticbeanstalk.model.TerminateEnvironmentRequest;
48 import com.amazonaws.services.elasticbeanstalk.model.TerminateEnvironmentResult;
49 import com.amazonaws.services.elasticbeanstalk.model.UpdateEnvironmentRequest;
50 import com.amazonaws.services.elasticbeanstalk.model.UpdateEnvironmentResult;
51 import com.jcabi.aspects.Loggable;
52 import com.jcabi.aspects.Tv;
53 import com.jcabi.log.Logger;
54 import java.io.IOException;
55 import java.net.URL;
56 import java.util.Collection;
57 import java.util.LinkedList;
58 import java.util.List;
59 import java.util.concurrent.TimeUnit;
60 import javax.validation.constraints.NotNull;
61 import lombok.EqualsAndHashCode;
62 import org.apache.commons.io.IOUtils;
63
64
65
66
67
68
69
70
71
72 @EqualsAndHashCode(of = { "client", "eid" })
73 @SuppressWarnings({ "PMD.TooManyMethods", "PMD.ExcessiveImports" })
74 @Loggable(Loggable.DEBUG)
75 final class Environment {
76
77
78
79
80 private static final long DELAY_MS = TimeUnit.MINUTES.toMillis(Tv.THIRTY);
81
82
83
84
85 private final transient AWSElasticBeanstalk client;
86
87
88
89
90 private final transient String eid;
91
92
93
94
95
96
97 protected Environment(@NotNull final AWSElasticBeanstalk clnt,
98 @NotNull final String idnt) {
99 this.client = clnt;
100 this.eid = idnt;
101 final EnvironmentDescription desc = this.description();
102 final String template = desc.getTemplateName();
103 if (template != null) {
104 final DescribeConfigurationSettingsResult res =
105 this.client.describeConfigurationSettings(
106 new DescribeConfigurationSettingsRequest()
107 .withApplicationName(desc.getApplicationName())
108 .withTemplateName(template)
109 );
110 for (final ConfigurationSettingsDescription config
111 : res.getConfigurationSettings()) {
112 Logger.debug(
113 Environment.class,
114 "Environment '%s/%s/%s' settings:",
115 config.getApplicationName(), config.getEnvironmentName()
116 );
117 for (final ConfigurationOptionSetting opt
118 : config.getOptionSettings()) {
119 Logger.debug(
120 Environment.class,
121 " %s/%s: %s",
122 opt.getNamespace(), opt.getOptionName(), opt.getValue()
123 );
124 }
125 }
126 }
127 }
128
129
130
131
132 @Override
133 public String toString() {
134 final EnvironmentDescription desc = this.description();
135 return String.format(
136 "%s/%s/%s",
137 desc.getEnvironmentName(), desc.getEnvironmentId(), desc.getCNAME()
138 );
139 }
140
141
142
143
144
145 public boolean primary() {
146 final EnvironmentDescription desc = this.description();
147 final String prefix = String.format("%s.", desc.getApplicationName());
148 final boolean primary = this.stable()
149 && desc.getCNAME().startsWith(prefix);
150 if (primary) {
151 Logger.info(
152 this,
153 "Environment '%s' considered primary", this
154 );
155 } else {
156 Logger.info(
157 this,
158
159 "Environment '%s' considered secondary since its CNAME doesn't start with '%s'",
160 this, prefix
161 );
162 }
163 return primary;
164 }
165
166
167
168
169
170 public String name() {
171 return this.description().getEnvironmentName();
172 }
173
174
175
176
177
178 public boolean green() {
179 return this.stable() && "Green".equals(this.description().getHealth());
180 }
181
182
183
184
185
186 public boolean stable() {
187 return this.until(
188 new Environment.Barrier() {
189 @Override
190 public String message() {
191 return "stable state";
192 }
193 @Override
194 public boolean allow(final EnvironmentDescription desc) {
195 return !desc.getStatus().matches(".*ing$");
196 }
197 }
198 );
199 }
200
201
202
203
204
205 public boolean terminated() {
206 return this.stable()
207 && "Terminated".equals(this.description().getStatus());
208 }
209
210
211
212
213 public void terminate() {
214 if (!this.stable()) {
215 throw new DeploymentException(
216 String.format(
217 "env '%s' is not stable, can't terminate",
218 this.eid
219 )
220 );
221 }
222 if (!this.terminated()) {
223 final TerminateEnvironmentResult res =
224 this.client.terminateEnvironment(
225 new TerminateEnvironmentRequest()
226 .withEnvironmentId(this.eid)
227 .withTerminateResources(true)
228 );
229 Logger.info(
230 this,
231 "Environment '%s/%s/%s' is terminated (label:'%s', status:%s)",
232 res.getApplicationName(), res.getEnvironmentName(),
233 res.getEnvironmentId(),
234 res.getVersionLabel(), res.getStatus()
235 );
236 }
237 }
238
239
240
241
242
243 public String[] events() {
244 if (!this.stable()) {
245 throw new DeploymentException(
246 String.format(
247 "env '%s' is not stable, can't get list of events",
248 this.eid
249 )
250 );
251 }
252 final DescribeEventsResult res = this.client.describeEvents(
253 new DescribeEventsRequest().withEnvironmentId(this.eid)
254 );
255 final Collection<String> events = new LinkedList<String>();
256 for (final EventDescription desc : res.getEvents()) {
257 events.add(
258 String.format("[%s]: %s", desc.getSeverity(), desc.getMessage())
259 );
260 }
261 return events.toArray(new String[events.size()]);
262 }
263
264
265
266
267
268 public String tail() {
269 if (!this.stable()) {
270 throw new DeploymentException(
271 String.format(
272 "env '%s' is not stable, can't get TAIL report",
273 this.eid
274 )
275 );
276 }
277 if (this.terminated()) {
278 throw new DeploymentException(
279 String.format(
280 "env '%s' is terminated, can't get TAIL report",
281 this.eid
282 )
283 );
284 }
285 this.client.requestEnvironmentInfo(
286 new RequestEnvironmentInfoRequest()
287 .withEnvironmentId(this.eid)
288 .withInfoType(EnvironmentInfoType.Tail)
289 );
290 final RetrieveEnvironmentInfoRequest req =
291 new RetrieveEnvironmentInfoRequest()
292 .withEnvironmentId(this.eid)
293 .withInfoType(EnvironmentInfoType.Tail);
294 List<EnvironmentInfoDescription> infos;
295 final long start = System.currentTimeMillis();
296 do {
297 if (System.currentTimeMillis() - start > Environment.DELAY_MS) {
298 throw new DeploymentException(
299 String.format(
300 "env '%s' doesn't report its TAIL, time out",
301 this.eid
302 )
303 );
304 }
305 Logger.info(
306 this,
307 "Waiting for TAIL report of %s",
308 this.eid
309 );
310 infos = this.client
311 .retrieveEnvironmentInfo(req)
312 .getEnvironmentInfo();
313 } while (infos.isEmpty());
314 final EnvironmentInfoDescription desc = infos.get(0);
315 try {
316 return IOUtils.toString(new URL(desc.getMessage()).openStream());
317 } catch (final IOException ex) {
318 throw new IllegalStateException(ex);
319 }
320 }
321
322
323
324
325
326 public void update(final Version version) {
327 final UpdateEnvironmentResult res = this.client.updateEnvironment(
328 new UpdateEnvironmentRequest()
329 .withEnvironmentId(this.eid)
330 .withVersionLabel(version.label())
331 );
332 Logger.info(
333 this,
334 "Environment '%s' updated to '%s'",
335 res.getEnvironmentId(), res.getVersionLabel()
336 );
337 }
338
339
340
341
342
343 private EnvironmentDescription description() {
344 final DescribeEnvironmentsResult res = this.client.describeEnvironments(
345 new DescribeEnvironmentsRequest()
346 .withEnvironmentIds(this.eid)
347 );
348 if (res.getEnvironments().isEmpty()) {
349 throw new DeploymentException(
350 String.format("environment '%s' not found", this.eid)
351 );
352 }
353 final EnvironmentDescription desc = res.getEnvironments().get(0);
354 Logger.debug(
355 this,
356
357 "ID=%s, env=%s, app=%s, CNAME=%s, label=%s, template=%s, status=%s, health=%s",
358 desc.getEnvironmentId(), desc.getEnvironmentName(),
359 desc.getApplicationName(), desc.getCNAME(),
360 desc.getVersionLabel(), desc.getTemplateName(), desc.getStatus(),
361 desc.getHealth()
362 );
363 return desc;
364 }
365
366
367
368
369
370
371 private boolean until(final Environment.Barrier barrier) {
372 boolean passed = false;
373 final long start = System.currentTimeMillis();
374 while (true) {
375 final EnvironmentDescription desc = this.description();
376 if (barrier.allow(desc)) {
377 passed = true;
378 Logger.info(
379 this,
380 "Environment '%s/%s/%s': health=%s, status=%s",
381 desc.getApplicationName(), desc.getEnvironmentName(),
382 desc.getEnvironmentId(), desc.getHealth(),
383 desc.getStatus()
384 );
385 break;
386 }
387 Logger.info(
388 this,
389
390 "Environment '%s/%s/%s': health=%s, status=%s (waiting for %s, %[ms]s)",
391 desc.getApplicationName(), desc.getEnvironmentName(),
392 desc.getEnvironmentId(), desc.getHealth(),
393 desc.getStatus(), barrier.message(),
394 System.currentTimeMillis() - start
395 );
396 if (System.currentTimeMillis() - start > Environment.DELAY_MS) {
397 Logger.warn(
398 this,
399 "Environment failed to reach '%s' after %[ms]s",
400 barrier.message(), System.currentTimeMillis() - start
401 );
402 break;
403 }
404 try {
405 TimeUnit.MINUTES.sleep(1);
406 } catch (final InterruptedException ex) {
407 Thread.currentThread().interrupt();
408 throw new DeploymentException(ex);
409 }
410 }
411 return passed;
412 }
413
414
415
416
417 private interface Barrier {
418
419
420
421
422
423
424 boolean allow(EnvironmentDescription desc);
425
426
427
428
429 String message();
430 }
431
432 }