package com.ibm.streamsx.rest.build; import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.jstring; import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.object; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import org.apache.http.client.fluent.Executor; import org.apache.http.client.fluent.Request; import org.apache.http.entity.ContentType; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import com.google.gson.annotations.Expose; import com.ibm.streamsx.rest.RESTException; import com.ibm.streamsx.rest.internal.BuildType; import com.ibm.streamsx.rest.internal.RestUtils; import com.ibm.streamsx.rest.internal.StandaloneAuthenticator; import com.ibm.streamsx.topology.internal.context.streamsrest.BuildServiceSetters; import com.ibm.streamsx.topology.internal.streams.Util; class StreamsBuildService extends AbstractConnection implements BuildService, BuildServiceSetters { static final String STREAMS_REST_RESOURCES = "/streams/rest/resources"; static final String STREAMS_BUILD_PATH = "/streams/rest/builds"; static BuildService of(Function<Executor,String> authenticator, JsonObject serviceDefinition, boolean verify) throws IOException { String buildServiceEndpoint = jstring(object(serviceDefinition, "connection_info"), "serviceBuildEndpoint"); String buildServicePoolsEndpoint = jstring(object(serviceDefinition, "connection_info"), "serviceBuildPoolsEndpoint"); // buildServicePoolsEndpoint is null when "connection_info" JSON element has no "serviceBuildPoolsEndpoint" if (authenticator instanceof StandaloneAuthenticator) { if (buildServiceEndpoint == null) { buildServiceEndpoint = Util.getenv(Util.STREAMS_BUILD_URL); } if (!buildServiceEndpoint.endsWith(STREAMS_BUILD_PATH)) { // URL was user-provided root of service, add the path URL url = new URL(buildServiceEndpoint); URL buildsUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), STREAMS_BUILD_PATH); buildServiceEndpoint = buildsUrl.toExternalForm(); } return StreamsBuildService.of(authenticator, buildServiceEndpoint, verify); } return new StreamsBuildService(buildServiceEndpoint, buildServicePoolsEndpoint, authenticator, verify); } static BuildService of(Function<Executor,String> authenticator, String buildServiceEndpoint, boolean verify) throws IOException { if (buildServiceEndpoint == null) { buildServiceEndpoint = Util.getenv(Util.STREAMS_BUILD_URL); if (!buildServiceEndpoint.endsWith(STREAMS_BUILD_PATH)) { // URL was user-provided root of service, add the path URL url = new URL(buildServiceEndpoint); URL buildsUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), STREAMS_BUILD_PATH); buildServiceEndpoint = buildsUrl.toExternalForm(); } } return new StreamsBuildService(buildServiceEndpoint, null, authenticator, verify); } private static final String TOOLKITS_RESOURCE_NAME = "toolkits"; private String endpoint; private String poolsEndpoint; private BuildType buildType = BuildType.APPLICATION; private String toolkitsUrl; private Function<Executor, String> authenticator; private StreamsBuildService(String endpoint, String poolsEndpoint, Function<Executor, String> authenticator, boolean verify) throws MalformedURLException { super(!verify); this.endpoint = endpoint; this.poolsEndpoint = poolsEndpoint; this.authenticator = authenticator; } @Override public void setBuildType(BuildType buildType) { this.buildType = buildType; } @Override String getAuthorization() { return authenticator.apply(getExecutor()); } @Override public void allowInsecureHosts() { this.executor = RestUtils.createExecutor(true); } @Override public Build createBuild(String name, JsonObject buildConfig) throws IOException { JsonObject buildParams = new JsonObject(); buildParams.addProperty("type", buildType.getJsonValue()); buildParams.addProperty("incremental", false); if (name != null) buildParams.addProperty("name", name); String bodyStr = buildParams.toString(); // System.out.println("StreamsBuildService: =======> POST body = " + bodyStr); Request post = Request.Post(endpoint) .addHeader("Authorization", getAuthorization()) .bodyString(bodyStr, ContentType.APPLICATION_JSON); Build build = Build.create(this, this, StreamsRestUtils.requestGsonResponse(executor, post)); return build; } String getToolkitsURL() throws IOException { if (toolkitsUrl == null) { String resourcesUrl = endpoint; if (resourcesUrl.endsWith("/builds")) { resourcesUrl = resourcesUrl.replaceFirst("builds$", "resources"); } // Query the resourcesUrl to find the resources URL String response = getResponseString(resourcesUrl); ResourcesArray resources = new GsonBuilder() .excludeFieldsWithoutExposeAnnotation() .create().fromJson(response, ResourcesArray.class); for (Resource resource : resources.resources) { if (TOOLKITS_RESOURCE_NAME.equals(resource.name)) { toolkitsUrl = resource.resource; break; } } if (toolkitsUrl == null) { // If we couldn't find toolkits something is wrong throw new RESTException("Unable to find toolkits resource from resources URL: " + resourcesUrl); } } return toolkitsUrl; } @Override public List<Toolkit> getToolkits() throws IOException { return Toolkit.createToolkitList(this, getToolkitsURL()); } @Override public Toolkit getToolkit(String toolkitId) throws IOException { if (toolkitId.isEmpty()) { throw new IllegalArgumentException("Empty toolkit id"); } else { String query = getToolkitsURL() + "/" + URLEncoder.encode(toolkitId, StandardCharsets.UTF_8.name()); Toolkit toolkit = Toolkit.create(this, query); return toolkit; } } public Toolkit uploadToolkit(File path) throws IOException { return StreamsRestActions.uploadToolkit(this, path); } boolean deleteToolkit(Toolkit toolkit) throws IOException { return StreamsRestActions.deleteToolkit(toolkit); } /** * @see com.ibm.streamsx.rest.build.BuildService#getBaseImages() */ @Override public List<BaseImage> getBaseImages() throws IOException { if (this.poolsEndpoint == null) { // exposed endpoint for the build pools is optional, but required for getting base images throw new IOException("No REST build pool endpoint available."); } // find out the right build pool; we use the first build pool with type 'image' final String poolType = "image"; List<BuildPool> imageBuildPools = BuildPool.createPoolList(this, this.poolsEndpoint, poolType); if (imageBuildPools.size() == 0) { throw new IOException("No build pool of '" + poolType + "' type found."); } final String poolId = imageBuildPools.get(0).getRestid(); final String baseImagesUri = this.poolsEndpoint + (this.poolsEndpoint.endsWith("/")? "": "/") + poolId + "/baseimages"; return BaseImage.createImageList(this, baseImagesUri); } private static class Resource { @Expose public String name; @Expose public String resource; } private static class ResourcesArray { @Expose public ArrayList<Resource> resources; } }