diff --git a/pom.xml b/pom.xml index a31f4ce..c887f70 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,11 @@ 4.0.0 - io.bosh.client + de.evoila bosh-java-client - 0.9.1-SNAPSHOT + 1.0.2-SNAPSHOT BOSH Java Client A Java client for the BOSH Director API. - https://github.com/davidehringer/bosh-java-client + https://github.com/evoila/bosh-java-client Cloud Foundry Community @@ -27,10 +27,10 @@ - scm:git:https://github.com/davidehringer/bosh-java-client.git - scm:git:https://github.com/davidehringer/bosh-java-client.git - https://github.com/davidehringer/bosh-java-client.git - HEAD + scm:git:https://github.com/evoila/bosh-java-client + scm:git:https://github.com/evoila/bosh-java-client + https://github.com/evoila/bosh-java-client + bosh-java-client-1.0.1 @@ -70,6 +70,11 @@ org.apache.httpcomponents httpclient + + org.springframework.security.oauth + spring-security-oauth2 + 2.0.7.RELEASE + org.slf4j slf4j-api @@ -107,6 +112,19 @@ logback-classic test + + + com.hierynomus + sshj + 0.23.0 + + + + com.jcraft + jsch + 0.1.54 + + diff --git a/src/main/java/io/bosh/client/OAuthCredentialsProvider.java b/src/main/java/io/bosh/client/OAuthCredentialsProvider.java new file mode 100644 index 0000000..92a4649 --- /dev/null +++ b/src/main/java/io/bosh/client/OAuthCredentialsProvider.java @@ -0,0 +1,92 @@ +package io.bosh.client; + +import io.bosh.client.authentication.Authentication; +import io.bosh.client.authentication.OAuth; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.ParseException; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.token.AccessTokenProvider; +import org.springframework.security.oauth2.client.token.AccessTokenProviderChain; +import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; +import org.springframework.security.oauth2.common.AuthenticationScheme; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.util.Assert; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; + +/** + * Created by jannikheyl on 09.02.18. + */ +public class OAuthCredentialsProvider implements Header { + private OAuth2AccessToken token; + private AccessTokenProviderChain CHAIN; + private ClientCredentialsResourceDetails credentials = new ClientCredentialsResourceDetails(); + + + public OAuthCredentialsProvider(String host, String username, + String password, String scheme, OAuth auth) throws URISyntaxException { + if (!auth.isStrictHostKeyChecking()) { + CHAIN = new AccessTokenProviderChain( + Arrays.asList(new UnsecureClientCredentialsAccessTokenProvider())); + } else { + CHAIN = new AccessTokenProviderChain( + Arrays.asList(new ClientCredentialsAccessTokenProvider())); + } + URI uri = new URI(scheme + host + ":8443/oauth/token"); + + getCredentials(uri.toString(), username, password); + requestToken(); + } + + private void requestToken() { + + if (token == null) { + token = CHAIN.obtainAccessToken(credentials, new DefaultAccessTokenRequest()); + } else if (token.isExpired()) { + refreshAccessToken(); + } + + } + + private void refreshAccessToken() { + Assert.notNull(token); + + token = CHAIN.refreshAccessToken(credentials, token.getRefreshToken(), new DefaultAccessTokenRequest()); + } + + private ClientCredentialsResourceDetails getCredentials(String host, String username, + String password) { + credentials.setAccessTokenUri(host); + credentials.setClientAuthenticationScheme(AuthenticationScheme.form); + credentials.setClientId(username); + credentials.setClientSecret(password); + credentials.setGrantType("client_credentials"); + return credentials; + } + + @Override + public String getName() { + return "Authorization"; + } + + @Override + public String getValue() { + return token.getTokenType() + " " + token.getValue(); + } + + @Override + public HeaderElement[] getElements() throws ParseException { + return new HeaderElement[0]; + } +} diff --git a/src/main/java/io/bosh/client/RequestLoggingInterceptor.java b/src/main/java/io/bosh/client/RequestLoggingInterceptor.java new file mode 100644 index 0000000..633b385 --- /dev/null +++ b/src/main/java/io/bosh/client/RequestLoggingInterceptor.java @@ -0,0 +1,33 @@ +package io.bosh.client; + +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Created by jannikheyl on 12.02.18. + */ +public class RequestLoggingInterceptor implements ClientHttpRequestInterceptor { + private final static org.slf4j.Logger log = LoggerFactory.getLogger(RequestLoggingInterceptor.class); + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + ClientHttpResponse response = execution.execute(request, body); + + log.debug("request method: {}, request URI: {}, request headers: {}, request body: {}, response status code: {}, response headers: {}, response body: {}", + request.getMethod(), + request.getURI(), + request.getHeaders(), + new String(body, Charset.forName("UTF-8")), + response.getStatusCode(), + response.getHeaders(), + response.getBody()); + + return response; + } +} diff --git a/src/main/java/io/bosh/client/Scheme.java b/src/main/java/io/bosh/client/Scheme.java new file mode 100644 index 0000000..305a5f4 --- /dev/null +++ b/src/main/java/io/bosh/client/Scheme.java @@ -0,0 +1,5 @@ +package io.bosh.client; + +public enum Scheme { + https, http +} diff --git a/src/main/java/io/bosh/client/SpringDirectorClient.java b/src/main/java/io/bosh/client/SpringDirectorClient.java index 77b6822..a24958d 100644 --- a/src/main/java/io/bosh/client/SpringDirectorClient.java +++ b/src/main/java/io/bosh/client/SpringDirectorClient.java @@ -40,7 +40,7 @@ * @author David Ehringer */ public class SpringDirectorClient implements DirectorClient { - + private final RestTemplate restTemplate; private final Info info; @@ -63,8 +63,8 @@ public class SpringDirectorClient implements DirectorClient { this.jobs = new SpringJobs(restTemplate, root, tasks, deployments); this.vms = new SpringVms(restTemplate, root, tasks); } - - public RestTemplate restTemplate(){ + + public RestTemplate restTemplate() { return restTemplate; } diff --git a/src/main/java/io/bosh/client/SpringDirectorClientBuilder.java b/src/main/java/io/bosh/client/SpringDirectorClientBuilder.java index 4a227b3..461c0d2 100644 --- a/src/main/java/io/bosh/client/SpringDirectorClientBuilder.java +++ b/src/main/java/io/bosh/client/SpringDirectorClientBuilder.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -28,6 +29,10 @@ import javax.net.ssl.SSLContext; +import io.bosh.client.authentication.Authentication; +import io.bosh.client.authentication.BasicAuth; +import io.bosh.client.authentication.OAuth; +import org.apache.http.auth.AUTH; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; @@ -40,17 +45,14 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.*; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; + /** * @author David Ehringer */ @@ -59,33 +61,45 @@ public class SpringDirectorClientBuilder { private String host; private String username; private String password; - - public SpringDirectorClientBuilder withCredentials(String username, String password){ + private Authentication auth; + private Scheme scheme; + private Integer boshPort; + + public SpringDirectorClientBuilder withCredentials(String username, String password, Authentication auth, + Scheme scheme, Integer boshPort) { this.username = username; this.password = password; + this.auth = auth; + this.scheme = scheme; + this.boshPort = boshPort; return this; } - - public SpringDirectorClientBuilder withHost(String host){ + + + + public SpringDirectorClientBuilder withHost(String host) { this.host = host; return this; } - - public SpringDirectorClient build(){ + + public SpringDirectorClient build() { // TODO validate - URI root = UriComponentsBuilder.newInstance().scheme("https").host(host).port(25555) + URI root = UriComponentsBuilder.newInstance().scheme(scheme.name()).host(host).port(boshPort) .build().toUri(); - RestTemplate restTemplate = new RestTemplate(createRequestFactory(host, username, password)); + RestTemplate restTemplate = null; + try { + restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(createRequestFactory(host, username, password, auth, scheme, boshPort))); + } catch (URISyntaxException e) { + e.printStackTrace(); + } restTemplate.getInterceptors().add(new ContentTypeClientHttpRequestInterceptor()); + restTemplate.getInterceptors().add(new RequestLoggingInterceptor()); handleTextHtmlResponses(restTemplate); return new SpringDirectorClient(root, restTemplate); } - + private ClientHttpRequestFactory createRequestFactory(String host, String username, - String password) { - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(new AuthScope(host, 25555), - new UsernamePasswordCredentials(username, password)); + String password, Authentication auth, Scheme scheme, Integer boshPort) throws URISyntaxException { SSLContext sslContext = null; try { @@ -98,10 +112,26 @@ private ClientHttpRequestFactory createRequestFactory(String host, String userna SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier()); - // disabling redirect handling is critical for the way BOSH uses 302's - HttpClient httpClient = HttpClientBuilder.create().disableRedirectHandling() - .setDefaultCredentialsProvider(credentialsProvider) - .setSSLSocketFactory(connectionFactory).build(); + HttpClient httpClient; + + if (auth.getClass().equals(BasicAuth.class)) { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(new AuthScope(host, boshPort), + new UsernamePasswordCredentials(username, password)); + + // disabling redirect handling is critical for the way BOSH uses 302's + httpClient = HttpClientBuilder.create().disableRedirectHandling() + .setDefaultCredentialsProvider(credentialsProvider) + .setSSLSocketFactory(connectionFactory).build(); + } else { + + // disabling redirect handling is critical for the way BOSH uses 302's + httpClient = HttpClientBuilder.create().disableRedirectHandling() + .setDefaultHeaders(Arrays.asList(new OAuthCredentialsProvider(host, username, password, scheme.name(), (OAuth) auth))) + .setSSLSocketFactory(connectionFactory).build(); + + } + return new HttpComponentsClientHttpRequestFactory(httpClient); } @@ -111,17 +141,17 @@ private void handleTextHtmlResponses(RestTemplate restTemplate) { messageConverters.add(new StringHttpMessageConverter()); MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); messageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", - DEFAULT_CHARSET), new MediaType("application", "*+json", DEFAULT_CHARSET), + DEFAULT_CHARSET), new MediaType("application", "*+json", DEFAULT_CHARSET), new MediaType("text", "html", DEFAULT_CHARSET))); messageConverters.add(messageConverter); restTemplate.setMessageConverters(messageConverters); } - + private static class ContentTypeClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, - ClientHttpRequestExecution execution) throws IOException { + ClientHttpRequestExecution execution) throws IOException { ClientHttpResponse response = execution.execute(request, body); // some BOSH resources return text/plain and this modifies this response // so we can use Jackson diff --git a/src/main/java/io/bosh/client/UnsecureClientCredentialsAccessTokenProvider.java b/src/main/java/io/bosh/client/UnsecureClientCredentialsAccessTokenProvider.java new file mode 100644 index 0000000..ab51bed --- /dev/null +++ b/src/main/java/io/bosh/client/UnsecureClientCredentialsAccessTokenProvider.java @@ -0,0 +1,56 @@ +package io.bosh.client; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; + +/** + * Created by jannikheyl on 12.02.18. + */ +public class UnsecureClientCredentialsAccessTokenProvider extends ClientCredentialsAccessTokenProvider { + @Override + protected RestOperations getRestTemplate() { + RestTemplate restOperations = (RestTemplate) super.getRestTemplate(); + TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true; + + SSLContext sslContext = null; + try { + sslContext = org.apache.http.conn.ssl.SSLContexts.custom() + .loadTrustMaterial(null, acceptingTrustStrategy) + .build(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } catch (KeyStoreException e) { + e.printStackTrace(); + } + + SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext); + + CloseableHttpClient httpClient = HttpClients.custom() + .setSSLSocketFactory(csf) + .build(); + + HttpComponentsClientHttpRequestFactory requestFactory = + new HttpComponentsClientHttpRequestFactory(); + + requestFactory.setHttpClient(httpClient); + + restOperations.setRequestFactory(requestFactory); + return restOperations; + } +} diff --git a/src/main/java/io/bosh/client/authentication/Authentication.java b/src/main/java/io/bosh/client/authentication/Authentication.java new file mode 100644 index 0000000..c3bad2d --- /dev/null +++ b/src/main/java/io/bosh/client/authentication/Authentication.java @@ -0,0 +1,8 @@ +package io.bosh.client.authentication; + +/** + * Created by jannikheyl on 09.02.18. + */ +public interface Authentication { + +} diff --git a/src/main/java/io/bosh/client/authentication/BasicAuth.java b/src/main/java/io/bosh/client/authentication/BasicAuth.java new file mode 100644 index 0000000..2b2fc9c --- /dev/null +++ b/src/main/java/io/bosh/client/authentication/BasicAuth.java @@ -0,0 +1,5 @@ +package io.bosh.client.authentication; + +public class BasicAuth implements Authentication { + +} diff --git a/src/main/java/io/bosh/client/authentication/OAuth.java b/src/main/java/io/bosh/client/authentication/OAuth.java new file mode 100644 index 0000000..d768d30 --- /dev/null +++ b/src/main/java/io/bosh/client/authentication/OAuth.java @@ -0,0 +1,18 @@ +package io.bosh.client.authentication; + +public class OAuth implements Authentication { + + private boolean strictHostKeyChecking; + + public OAuth(boolean strictHostKeyChecking) { + this.strictHostKeyChecking = strictHostKeyChecking; + } + + public boolean isStrictHostKeyChecking() { + return strictHostKeyChecking; + } + + public void setStrictHostKeyChecking(boolean strictHostKeyChecking) { + this.strictHostKeyChecking = strictHostKeyChecking; + } +} diff --git a/src/main/java/io/bosh/client/deployments/Deployment.java b/src/main/java/io/bosh/client/deployments/Deployment.java index d66df6f..905711a 100644 --- a/src/main/java/io/bosh/client/deployments/Deployment.java +++ b/src/main/java/io/bosh/client/deployments/Deployment.java @@ -20,6 +20,7 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; /** * @author David Ehringer @@ -43,6 +44,8 @@ public String getRawManifest() { return manifest; } + public void setRawManifest(String manifest) { this.manifest = manifest; } + public Map getManifest() { return Collections.unmodifiableMap(manifestMap); } diff --git a/src/main/java/io/bosh/client/deployments/Deployments.java b/src/main/java/io/bosh/client/deployments/Deployments.java index 6248605..7134624 100644 --- a/src/main/java/io/bosh/client/deployments/Deployments.java +++ b/src/main/java/io/bosh/client/deployments/Deployments.java @@ -15,10 +15,12 @@ */ package io.bosh.client.deployments; -import java.util.List; - +import io.bosh.client.tasks.Task; +import org.springframework.http.HttpHeaders; import rx.Observable; +import java.util.List; + /** * @author David Ehringer */ @@ -27,6 +29,14 @@ public interface Deployments { Observable> list(); Observable get(String deploymentName); + + Observable create(Deployment deployment, HttpHeaders headers); + + Observable create(Deployment deployment); + + Observable update(Deployment deployment); + + Observable delete(Deployment deployment); Observable> cloudcheck(String deploymentName); } diff --git a/src/main/java/io/bosh/client/deployments/SSHConfig.java b/src/main/java/io/bosh/client/deployments/SSHConfig.java new file mode 100644 index 0000000..2ce9926 --- /dev/null +++ b/src/main/java/io/bosh/client/deployments/SSHConfig.java @@ -0,0 +1,85 @@ +package io.bosh.client.deployments; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class SSHConfig { + + public SSHConfig (String deploymentName, String username, String publicKey, String jobTarget, int indexes){ + this.command = "setup"; + this.deploymentName = deploymentName; + this.target = new SSHTarget(jobTarget, indexes); + this.params = new SSHParams(username, publicKey); + } + + + private String command; + @JsonProperty("deployment_name") + String deploymentName; + private SSHParams params; + private SSHTarget target; + + public SSHConfig (SSHConfig config, String pubKey) { + this.command = config.getCommand(); + this.deploymentName = config.getDeploymentName(); + this.target = new SSHTarget(config.getTarget()); + this.params = new SSHParams(config.getParams().getUser(), pubKey); + } + + public String getCommand () { + return command; + } + + public String getDeploymentName () { + return deploymentName; + } + + public SSHParams getParams () { + return params; + } + + public SSHTarget getTarget () { + return target; + } + + public class SSHParams { + private String user; + @JsonProperty("public_key") + private String publicKey; + + public SSHParams (String username, String publicKey) { + this.user = username; + this.publicKey = publicKey; + } + + public String getUser () { + return user; + } + + public String getPublicKey () { + return publicKey; + } + } + + public class SSHTarget { + String job; + int indexes; + + public SSHTarget (String jobTarget, int indexes) { + this.job = jobTarget; + this.indexes =indexes; + } + + public SSHTarget (SSHTarget target) { + this.job = target.getJob(); + this.indexes = target.getIndexes(); + } + + public String getJob () { + return job; + } + + public int getIndexes () { + return indexes; + } + } +} diff --git a/src/main/java/io/bosh/client/deployments/SpringDeployments.java b/src/main/java/io/bosh/client/deployments/SpringDeployments.java index 5aa28f5..0241909 100644 --- a/src/main/java/io/bosh/client/deployments/SpringDeployments.java +++ b/src/main/java/io/bosh/client/deployments/SpringDeployments.java @@ -15,22 +15,30 @@ */ package io.bosh.client.deployments; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; import io.bosh.client.DirectorException; import io.bosh.client.internal.AbstractSpringOperations; +import io.bosh.client.tasks.Task; import io.bosh.client.tasks.Tasks; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import rx.Observable; + import java.io.IOException; import java.net.URI; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.Arrays; import java.util.List; import java.util.Map; - -import org.springframework.web.client.RestTemplate; - -import rx.Observable; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.util.function.Consumer; /** * @author David Ehringer @@ -41,6 +49,7 @@ public class SpringDeployments extends AbstractSpringOperations implements Deplo public SpringDeployments(RestTemplate restTemplate, URI root, Tasks tasks) { super(restTemplate, root); + restTemplate.getRequestFactory(); this.tasks = tasks; } @@ -58,19 +67,48 @@ public Observable get(String deploymentName) { builder -> builder.pathSegment("deployments", deploymentName)) .map(response -> { response.setName(deploymentName); - - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - Map manifestMap = null; - try { - manifestMap = mapper.readValue(response.getRawManifest(), Map.class); - } catch (IOException e) { - throw new DirectorException("Unable to parse deployment manifest", e); + if(response.getManifest() != null) { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + Map manifestMap = null; + try { + manifestMap = mapper.readValue(response.getRawManifest(), Map.class); + } catch (IOException e) { + throw new DirectorException("Unable to parse deployment manifest", e); + } + response.setManifestMap(manifestMap); } - response.setManifestMap(manifestMap); return response; }); } + @Override + public Observable create(Deployment deployment) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "text/yaml"); + return create(deployment, headers); + } + + public Observable update(Deployment deployment) { + return create(deployment); + } + + @Override + public Observable create(Deployment deployment, HttpHeaders headers) { + return exchangeWithTaskRedirect(deployment.getRawManifest(), + Task.class, + headers, + HttpMethod.POST, + builder -> builder.path("deployments")) + .map(exchange -> exchange.getBody()); + } + + @Override + public Observable delete(Deployment deployment) { + return exchangeWithTaskRedirect("", Task.class, null, HttpMethod.DELETE, + builder -> builder.pathSegment("deployments", deployment.getName())) + .map(exchange -> exchange.getBody()); + } + @Override public Observable> cloudcheck(String deploymentName) { return postForEntity(Void.class, null, builder -> builder.pathSegment("deployments", deploymentName, "scans")) @@ -79,4 +117,6 @@ public Observable> cloudcheck(String deploymentName) { .map(problems -> Arrays.asList(problems)); } + + } diff --git a/src/main/java/io/bosh/client/errands/Errands.java b/src/main/java/io/bosh/client/errands/Errands.java index c70ae15..febbe5f 100644 --- a/src/main/java/io/bosh/client/errands/Errands.java +++ b/src/main/java/io/bosh/client/errands/Errands.java @@ -17,6 +17,7 @@ import java.util.List; +import io.bosh.client.tasks.Task; import rx.Observable; /** @@ -25,4 +26,10 @@ public interface Errands { Observable> list(String deploymentName); + + default Observable runErrand(String deploymentName, String errandName){ + return runErrand(deploymentName,errandName,false,false); + } + + Observable runErrand(String deploymentName, String errandName, boolean keep_alive, boolean when_changed); } diff --git a/src/main/java/io/bosh/client/errands/SpringErrands.java b/src/main/java/io/bosh/client/errands/SpringErrands.java index ea0ede9..14f38e4 100644 --- a/src/main/java/io/bosh/client/errands/SpringErrands.java +++ b/src/main/java/io/bosh/client/errands/SpringErrands.java @@ -19,8 +19,13 @@ import java.net.URI; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import io.bosh.client.tasks.Task; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.web.client.RestOperations; import rx.Observable; @@ -39,4 +44,17 @@ public Observable> list(String deploymentName) { return get( ErrandSummary[].class, builder -> builder.pathSegment("deployments", deploymentName, "errands")) .map(response -> Arrays.asList(response)); } + + public Observable runErrand(String deploymentName, String errandName, boolean keep_alive, boolean when_changed) { + HashMap body = new HashMap(); + body.put("keep_alive", keep_alive); + body.put("when_changed", when_changed); + return exchangeWithTaskRedirect(body, + Task.class, + new HttpHeaders(), + HttpMethod.POST, + builder -> builder.pathSegment("deployments", deploymentName, "errands", errandName, "runs") + ) + .map(HttpEntity::getBody); + } } diff --git a/src/main/java/io/bosh/client/internal/AbstractSpringOperations.java b/src/main/java/io/bosh/client/internal/AbstractSpringOperations.java index 6ed1208..e3e262a 100644 --- a/src/main/java/io/bosh/client/internal/AbstractSpringOperations.java +++ b/src/main/java/io/bosh/client/internal/AbstractSpringOperations.java @@ -16,13 +16,6 @@ package io.bosh.client.internal; import io.bosh.client.DirectorException; - -import java.net.URI; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; @@ -32,9 +25,14 @@ import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestOperations; import org.springframework.web.util.UriComponentsBuilder; - import rx.Observable; +import java.net.URI; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * @author David Ehringer */ @@ -81,7 +79,7 @@ protected final Observable post(Class responseType, Object request, builderCallback.accept(builder); URI uri = builder.build().toUri(); - this.logger.debug("GET {}", uri); + this.logger.debug("POST {}", uri); return this.restOperations.postForObject(uri, request, responseType); }); } @@ -105,7 +103,7 @@ protected final Observable> exchangeForEntity(T request builderCallback.accept(builder); URI uri = builder.build().toUri(); - RequestEntity requestEntity = new RequestEntity(request, headers, HttpMethod.PUT, uri); + RequestEntity requestEntity = new RequestEntity(request, headers, method, uri); this.logger.debug("{} {}", method, uri); return this.restOperations.exchange( requestEntity, responseType); }); @@ -132,4 +130,12 @@ protected String getTaskId(ResponseEntity response) { } throw new IllegalArgumentException("Response does not have a redirect header for a task"); } + + protected final Observable> exchangeWithTaskRedirect (T request, + Class responseType, HttpHeaders headers, HttpMethod method, Consumer builderCallback) { + return exchangeForEntity(request,responseType,headers,method,builderCallback).map(r -> { + String taskId = getTaskId(r); + return getEntity(responseType, builder -> builder.pathSegment("tasks", taskId)).toBlocking().first(); + }); + } } diff --git a/src/main/java/io/bosh/client/vms/SpringVms.java b/src/main/java/io/bosh/client/vms/SpringVms.java index f5b08ed..316341f 100644 --- a/src/main/java/io/bosh/client/vms/SpringVms.java +++ b/src/main/java/io/bosh/client/vms/SpringVms.java @@ -15,16 +15,25 @@ */ package io.bosh.client.vms; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; import io.bosh.client.DirectorException; +import io.bosh.client.deployments.SSHConfig; import io.bosh.client.internal.AbstractSpringOperations; +import io.bosh.client.tasks.Task; import io.bosh.client.tasks.Tasks; import java.io.IOException; import java.net.URI; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.springframework.http.HttpMethod; import org.springframework.web.client.RestOperations; import rx.Observable; @@ -73,4 +82,41 @@ public Observable> listDetails(String deploymentName) { })); } + + public Observable ssh(SSHConfig config) { + KeyPairGenerator keyGen = null; + try { + keyGen = KeyPairGenerator.getInstance("RSA"); + } catch (NoSuchAlgorithmException e) { + throw new DirectorException("Unable to generate SSH-Keypair" , e); + } + keyGen.initialize(1024); + KeyPair keyPair =keyGen.generateKeyPair(); + config = new SSHConfig(config, keyPair.getPublic().getEncoded().toString()); + return this.ssh(config, keyPair.getPrivate().getEncoded().toString()); + } + + public Observable ssh(SSHConfig config, String privateKey){ + return exchangeWithTaskRedirect(config, + Task.class, + null, + HttpMethod.POST, + builder -> builder.pathSegment("deployments", config.getDeploymentName(), "ssh")) + .map(exchange -> exchange.getBody()) + .map(body -> { + List vms = listDetails(config.getDeploymentName()).toBlocking().first(); + Vm vm = vms.get(config.getTarget().getIndexes()); + + JSch jsch=new JSch(); + Session session = null; + try { + jsch.addIdentity(privateKey); + session = jsch.getSession(config.getParams().getUser(), vm.getIps().get(0), 22); + } catch (JSchException e) { + throw new DirectorException("Unable to create ssh connection to " + vm.getJobName() + vm.getIndex(), e); + } + return session; + }); + } + } \ No newline at end of file diff --git a/src/main/java/io/bosh/client/vms/Vms.java b/src/main/java/io/bosh/client/vms/Vms.java index 8471696..b7183d7 100644 --- a/src/main/java/io/bosh/client/vms/Vms.java +++ b/src/main/java/io/bosh/client/vms/Vms.java @@ -17,6 +17,8 @@ import java.util.List; +import com.jcraft.jsch.Session; +import io.bosh.client.deployments.SSHConfig; import rx.Observable; /** @@ -26,5 +28,9 @@ public interface Vms { Observable> list(String deploymentName); - Observable> listDetails(String deploymentName); + Observable> listDetails(String deploymentName); + + Observable ssh(SSHConfig config, String privateKey); + + Observable ssh(SSHConfig config); } diff --git a/src/test/java/io/bosh/client/AbstractDirectorTest.java b/src/test/java/io/bosh/client/AbstractDirectorTest.java index d42ad84..7d0081a 100644 --- a/src/test/java/io/bosh/client/AbstractDirectorTest.java +++ b/src/test/java/io/bosh/client/AbstractDirectorTest.java @@ -18,10 +18,8 @@ import java.io.IOException; import java.io.InputStreamReader; -import io.bosh.client.DirectorClient; -import io.bosh.client.SpringDirectorClient; -import io.bosh.client.SpringDirectorClientBuilder; - +import io.bosh.client.authentication.Authentication; +import io.bosh.client.authentication.BasicAuth; import org.springframework.core.io.ClassPathResource; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.util.FileCopyUtils; @@ -36,11 +34,11 @@ public abstract class AbstractDirectorTest { { SpringDirectorClient springClient = new SpringDirectorClientBuilder() - .withHost("192.168.50.4").withCredentials("admin", "admin").build(); + .withHost("192.168.50.4").withCredentials("admin", "admin", new BasicAuth(), Scheme.https, 25555).build(); mockServer = MockRestServiceServer.createServer(springClient.restTemplate()); client = springClient; } - + protected String url(String url) { return "https://192.168.50.4:25555" + url;