From f59866916bd65a20ab1fbf135094c4e78c2c98e7 Mon Sep 17 00:00:00 2001 From: Yegor Bugayenko Date: Wed, 28 Feb 2024 05:56:34 +0300 Subject: [PATCH] #1906 DescribesInstance --- src/main/java/com/rultor/agents/Agents.java | 3 +- .../rultor/agents/aws/ConnectsInstance.java | 90 ++++++++++-------- .../rultor/agents/aws/DescribesInstance.java | 93 +++++++++++++++++++ .../com/rultor/agents/shells/PfShell.java | 20 ++++ .../com/rultor/agents/shells/TalkShells.java | 7 +- 5 files changed, 168 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/rultor/agents/aws/DescribesInstance.java diff --git a/src/main/java/com/rultor/agents/Agents.java b/src/main/java/com/rultor/agents/Agents.java index 366a84411f..90db6548b8 100644 --- a/src/main/java/com/rultor/agents/Agents.java +++ b/src/main/java/com/rultor/agents/Agents.java @@ -40,6 +40,7 @@ import com.jcabi.ssh.Ssh; import com.rultor.agents.aws.AwsEc2; import com.rultor.agents.aws.ConnectsInstance; +import com.rultor.agents.aws.DescribesInstance; import com.rultor.agents.aws.KillsInstance; import com.rultor.agents.aws.PingsInstance; import com.rultor.agents.aws.StartsInstance; @@ -284,9 +285,9 @@ public Agent agent(final Talk talk, final Profile profile) false ) ), + new Agent.Quiet(new DescribesInstance(aws)), new Agent.Quiet( new ConnectsInstance( - aws, new PfShell( profile, "none", diff --git a/src/main/java/com/rultor/agents/aws/ConnectsInstance.java b/src/main/java/com/rultor/agents/aws/ConnectsInstance.java index 5666fda6cc..a34dc69e9e 100644 --- a/src/main/java/com/rultor/agents/aws/ConnectsInstance.java +++ b/src/main/java/com/rultor/agents/aws/ConnectsInstance.java @@ -29,13 +29,9 @@ */ package com.rultor.agents.aws; -import com.amazonaws.services.ec2.model.DescribeInstanceStatusRequest; -import com.amazonaws.services.ec2.model.DescribeInstanceStatusResult; -import com.amazonaws.services.ec2.model.DescribeInstancesRequest; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceState; import com.jcabi.aspects.Immutable; import com.jcabi.log.Logger; +import com.jcabi.ssh.Shell; import com.jcabi.xml.XML; import com.rultor.agents.AbstractAgent; import com.rultor.agents.shells.PfShell; @@ -53,11 +49,6 @@ @ToString public final class ConnectsInstance extends AbstractAgent { - /** - * AWS Client. - */ - private final transient AwsEc2 api; - /** * Shell. */ @@ -65,47 +56,64 @@ public final class ConnectsInstance extends AbstractAgent { /** * Ctor. - * @param aws API * @param shll The shell */ - public ConnectsInstance(final AwsEc2 aws, final PfShell shll) { - super("/talk[daemon and ec2 and not(shell)]"); - this.api = aws; + public ConnectsInstance(final PfShell shll) { + super("/talk[daemon and ec2/host and not(shell)]"); this.shell = shll; } @Override public Iterable process(final XML xml) throws IOException { final String instance = xml.xpath("/talk/ec2/@id").get(0); - while (true) { - final DescribeInstanceStatusResult res = this.api.aws().describeInstanceStatus( - new DescribeInstanceStatusRequest() - .withIncludeAllInstances(true) - .withInstanceIds(instance) + final String host = xml.xpath("/talk/ec2/host").get(0); + final Directives dirs = new Directives(); + if (this.alive(host)) { + Logger.warn( + this, "AWS instance %s is alive at %s for %s", + instance, host, xml.xpath("/talk/@name").get(0) + ); + dirs.xpath("/talk").add("shell") + .attr("id", xml.xpath("/talk/daemon/@id").get(0)) + .add("host").set(host).up() + .add("port").set(Integer.toString(this.shell.port())).up() + .add("login").set(this.shell.login()).up() + .add("key").set(this.shell.key()); + Logger.info( + this, "AWS instance %s launched and running at %s", + instance, host + ); + } else { + Logger.warn( + this, "Can't connect to AWS instance %s at %s", + instance, host ); - final InstanceState state = res.getInstanceStatuses().get(0).getInstanceState(); - Logger.info(this, "AWS instance %s state: %s", instance, state.getName()); - if ("running".equals(state.getName())) { - break; - } - new Sleep(5L).now(); } - final Instance ready = this.api.aws().describeInstances( - new DescribeInstancesRequest() - .withInstanceIds(instance) - ).getReservations().get(0).getInstances().get(0); - Logger.info( - this, "AWS instance %s launched and running at %s", - ready.getInstanceId(), ready.getPublicIpAddress() - ); - new Sleep(60L).now(); - final Directives dirs = new Directives(); - dirs.xpath("/talk").add("shell") - .attr("id", xml.xpath("/talk/daemon/@id").get(0)) - .add("host").set(ready.getPublicIpAddress()).up() - .add("port").set(Integer.toString(this.shell.port())).up() - .add("login").set(this.shell.login()).up() - .add("key").set(this.shell.key()); return dirs; } + + /** + * Tries to connect to it via SSH and returns TRUE if it's possible. + * @param host IP of the host + * @return TRUE if alive + */ + @SuppressWarnings("PMD.AvoidCatchingGenericException") + private boolean alive(final String host) { + boolean alive = false; + try { + new Shell.Empty( + new Shell.Safe( + this.shell.withHost(host).toSsh() + ) + ).exec("whoami"); + alive = true; + // @checkstyle IllegalCatchCheck (1 line) + } catch (final Exception ex) { + Logger.warn( + this, "Failed to SSH-connect to %s: %s", + host, ex.getMessage() + ); + } + return alive; + } } diff --git a/src/main/java/com/rultor/agents/aws/DescribesInstance.java b/src/main/java/com/rultor/agents/aws/DescribesInstance.java new file mode 100644 index 0000000000..0fad3270eb --- /dev/null +++ b/src/main/java/com/rultor/agents/aws/DescribesInstance.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2024 Yegor Bugayenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: 1) Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. 2) Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. 3) Neither the name of the rultor.com nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.rultor.agents.aws; + +import com.amazonaws.services.ec2.model.DescribeInstanceStatusRequest; +import com.amazonaws.services.ec2.model.DescribeInstanceStatusResult; +import com.amazonaws.services.ec2.model.DescribeInstancesRequest; +import com.amazonaws.services.ec2.model.Instance; +import com.amazonaws.services.ec2.model.InstanceState; +import com.jcabi.aspects.Immutable; +import com.jcabi.log.Logger; +import com.jcabi.xml.XML; +import com.rultor.agents.AbstractAgent; +import java.io.IOException; +import lombok.ToString; +import org.xembly.Directive; +import org.xembly.Directives; + +/** + * Finds IP of a running EC2 instance. + * + * @since 1.77 + */ +@Immutable +@ToString +public final class DescribesInstance extends AbstractAgent { + + /** + * AWS Client. + */ + private final transient AwsEc2 api; + + /** + * Ctor. + * @param aws API + */ + public DescribesInstance(final AwsEc2 aws) { + super("/talk[daemon and ec2 and not(ec2/host)]"); + this.api = aws; + } + + @Override + public Iterable process(final XML xml) throws IOException { + final String instance = xml.xpath("/talk/ec2/@id").get(0); + while (true) { + final DescribeInstanceStatusResult res = this.api.aws().describeInstanceStatus( + new DescribeInstanceStatusRequest() + .withIncludeAllInstances(true) + .withInstanceIds(instance) + ); + final InstanceState state = res.getInstanceStatuses().get(0).getInstanceState(); + Logger.info(this, "AWS instance %s state: %s", instance, state.getName()); + if ("running".equals(state.getName())) { + break; + } + new Sleep(5L).now(); + } + final Instance ready = this.api.aws().describeInstances( + new DescribeInstancesRequest() + .withInstanceIds(instance) + ).getReservations().get(0).getInstances().get(0); + final String host = ready.getPublicIpAddress(); + return new Directives().xpath("/talk/ec2").add("host").set(host); + } + +} diff --git a/src/main/java/com/rultor/agents/shells/PfShell.java b/src/main/java/com/rultor/agents/shells/PfShell.java index a0f80f18bf..ebfdebcdfd 100644 --- a/src/main/java/com/rultor/agents/shells/PfShell.java +++ b/src/main/java/com/rultor/agents/shells/PfShell.java @@ -30,9 +30,11 @@ package com.rultor.agents.shells; import com.jcabi.aspects.Immutable; +import com.jcabi.ssh.Ssh; import com.rultor.spi.Profile; import java.io.IOException; import java.io.InputStream; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -91,6 +93,15 @@ public PfShell(final Profile prof, final String host, this.pvt = key; } + /** + * Make a new shell, with a different address. + * @param host New address + * @return New shell + */ + public PfShell withHost(final String host) { + return new PfShell(this.profile, host, this.prt, this.user, this.pvt); + } + /** * Get host. * @return Host name @@ -158,4 +169,13 @@ public String key() throws IOException { return key; } + /** + * Make SSH shell. + * @return SSH shell + * @throws UnknownHostException If fails + */ + public Ssh toSsh() throws UnknownHostException { + return new Ssh(this.addr, this.prt, this.user, this.pvt); + } + } diff --git a/src/main/java/com/rultor/agents/shells/TalkShells.java b/src/main/java/com/rultor/agents/shells/TalkShells.java index a9aea484e5..206aafea03 100644 --- a/src/main/java/com/rultor/agents/shells/TalkShells.java +++ b/src/main/java/com/rultor/agents/shells/TalkShells.java @@ -31,8 +31,8 @@ import com.jcabi.aspects.Immutable; import com.jcabi.ssh.Shell; -import com.jcabi.ssh.Ssh; import com.jcabi.xml.XML; +import com.rultor.spi.Profile; import java.net.UnknownHostException; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -67,11 +67,12 @@ public TalkShells(final XML talk) { */ public Shell get() throws UnknownHostException { final XML shell = this.xml.nodes("/talk/shell").get(0); - return new Ssh( + return new PfShell( + Profile.EMPTY, shell.xpath("host/text()").get(0), Integer.parseInt(shell.xpath("port/text()").get(0)), shell.xpath("login/text()").get(0), shell.xpath("key/text()").get(0) - ); + ).toSsh(); } }