Skip to content

Commit

Permalink
Automatically configure environment for CfEnv (#345)
Browse files Browse the repository at this point in the history
* Automatically configure environment for CfEnv

* Fix bug when spring.profiles.active contains cloud in a comma delimited list

* Log error

Co-authored-by: David Turanski <dturanski@pivotal.io>
  • Loading branch information
dturanski and David Turanski authored May 18, 2020
1 parent 1d0e2f1 commit 93f2fca
Show file tree
Hide file tree
Showing 14 changed files with 556 additions and 25 deletions.
11 changes: 2 additions & 9 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,9 @@ This is useful in cases in which the Cloud Foundry foundation is isolated from e
== Set Additional Environment Variables

By default, the deployer adds global and application configuration properties to a single `SPRING_APPLICATION_JSON` environment variable entry in the application manifest.
You can configure additional environment variables in the manifest by setting `spring.cloud.deployer.cloudfoundry.env.<key>=<value>`.
This is useful for setting https://github.com/cloudfoundry/java-buildpack[Java build pack configuration properties] in the application manifest.
You can configure additional top-level environment variables in the manifest by setting `spring.cloud.deployer.cloudfoundry.env.<key>=<value>`.
This is useful for adding https://github.com/cloudfoundry/java-buildpack[Java build pack configuration properties] to the application manifest since the Java build pack does not recognize `SPRING_APPLICATION_JSON`.

=== Disable Spring Auto-reconfiguration
A common use case is to disable auto-reconfiguration if your application handles configuration internally, as is the case if your application uses https://github.com/pivotal-cf/java-cfenv[java-cfenv] to
bind to CF services. In this case, you would set:

----
spring.cloud.deployer.cloudfoundry.env.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION='{enabled:false}',spring.cloud.deployer.cloudfoundry.env.SPRING_PROFILES_ACTIVE=cloud
----



Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<artifactId>spring-cloud-deployer-spi</artifactId>
<version>${spring-cloud-deployer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package org.springframework.cloud.deployer.spi.cloudfoundry;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
Expand All @@ -33,11 +31,18 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.cloudfoundry.AbstractCloudFoundryException;
import org.cloudfoundry.UnknownCloudFoundryException;
import org.cloudfoundry.operations.services.BindServiceInstanceRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;
import reactor.retry.Retry;

import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.cloud.deployer.spi.app.AppScaleRequest;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
Expand All @@ -47,9 +52,6 @@
import org.springframework.http.HttpStatus;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;
import reactor.retry.Retry;

import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY;
import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.JAVA_OPTS_PROPERTY_KEY;
Expand Down Expand Up @@ -315,15 +317,29 @@ private boolean deleteFileOrDirectory(File fileToDelete) {
protected Map<String, String> getEnvironmentVariables(String deploymentId, AppDeploymentRequest request) {
Map<String, String> envVariables = new HashMap<>();
envVariables.putAll(getApplicationProperties(deploymentId, request));

String javaOpts = javaOpts(request);
if (StringUtils.hasText(javaOpts)) {
envVariables.put("JAVA_OPTS", javaOpts(request));
}

if (hasCfEnv(request.getResource())) {
Map<String, String> env =
CfEnvConfigurer.disableJavaBuildPackAutoReconfiguration(deploymentProperties.getEnv());
//Only append to existing spring profiles active
env.putAll(CfEnvConfigurer.activateCloudProfile(env, null));
deploymentProperties.setEnv(env);
}
envVariables.putAll(deploymentProperties.getEnv());
return envVariables;
}

protected boolean hasCfEnv(Resource resource) {
if (resource instanceof CfEnvAwareResource) {
return ((CfEnvAwareResource)resource).hasCfEnv();
}
return CfEnvAwareResource.of(resource).hasCfEnv();
}

private Map<String, String> getApplicationProperties(String deploymentId, AppDeploymentRequest request) {
Map<String, String> applicationProperties = getSanitizedApplicationProperties(deploymentId, request);

Expand All @@ -345,6 +361,11 @@ private Map<String, String> getSanitizedApplicationProperties(String deploymentI
Optional.ofNullable(applicationProperties.remove("server.port"))
.ifPresent(port -> logger.warn("Ignoring 'server.port={}' for app {}, as Cloud Foundry will assign a local dynamic port. Route to the app will use port 80.", port, deploymentId));

// Update active Spring Profiles given in application properties. Create a new entry with the given key if necessary
if (hasCfEnv(request.getResource())) {
applicationProperties = CfEnvConfigurer
.activateCloudProfile(applicationProperties, CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN);
}
return applicationProperties;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;

import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
import org.springframework.cloud.deployer.spi.task.LaunchState;
import org.springframework.cloud.deployer.spi.task.TaskLauncher;
import org.springframework.cloud.deployer.spi.task.TaskStatus;
import reactor.util.function.Tuple2;

/**
* Abstract class to provide base functionality for launching Tasks on Cloud Foundry. This
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.deployer.spi.cloudfoundry;

import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;

/**
* Copies an {@link AppDeploymentRequest} using a {@link CfEnvAwareResource}.
*
* @author David Turanski
* @since 2.4
*/
class CfEnvAwareAppDeploymentRequest extends AppDeploymentRequest {

static CfEnvAwareAppDeploymentRequest of(AppDeploymentRequest appDeploymentRequest) {
return new CfEnvAwareAppDeploymentRequest(appDeploymentRequest);
}

private CfEnvAwareAppDeploymentRequest(AppDeploymentRequest appDeploymentRequest) {
super(appDeploymentRequest.getDefinition(),
CfEnvAwareResource.of(appDeploymentRequest.getResource()),
appDeploymentRequest.getDeploymentProperties(),
appDeploymentRequest.getCommandlineArguments());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.deployer.spi.cloudfoundry;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.loader.archive.JarFileArchive;
import org.springframework.core.io.Resource;

/**
* A {@link Resource} implementation that delegates to a resource and keeps the state of a CfEnv dependency
* as an {@link Optional} which may be empty, true, or false.
*
* @author David Turanski
* @since 2.4
*/
class CfEnvAwareResource implements Resource {
private final Resource resource;

private final boolean hasCfEnv;

static CfEnvAwareResource of(Resource resource) {
return new CfEnvAwareResource(resource);
}
private CfEnvAwareResource(Resource resource) {
this.resource = resource;
this.hasCfEnv = CfEnvResolver.hasCfEnv(this);
}

@Override
public boolean exists() {
return resource.exists();
}

@Override
public URL getURL() throws IOException {
return resource.getURL();
}

@Override
public URI getURI() throws IOException {
return resource.getURI();
}

@Override
public File getFile() throws IOException {
return resource.getFile();
}

@Override
public long contentLength() throws IOException {
return resource.contentLength();
}

@Override
public long lastModified() throws IOException {
return resource.lastModified();
}

@Override
public Resource createRelative(String s) throws IOException {
return resource.createRelative(s);
}

@Override
public String getFilename() {
return resource.getFilename();
}

@Override
public String getDescription() {
return resource.getDescription();
}

@Override
public InputStream getInputStream() throws IOException {
return resource.getInputStream();
}

boolean hasCfEnv() {
return this.hasCfEnv;
}

/**
* Inspect the {@link CfEnvAwareResource} to determine if it contains a dependency on <i>io.pivotal.cfenv.core.CfEnv</i>.
* Cache the result in the resource.
*/
static class CfEnvResolver {

private static Log logger = LogFactory.getLog(CfEnvResolver.class);

private static final String CF_ENV = "io.pivotal.cfenv.core.CfEnv";

static boolean hasCfEnv(CfEnvAwareResource app
) {
try {
String scheme = app.getURI().getScheme().toLowerCase();
if (scheme.equals("docker")) {
return false;
}
}
catch (IOException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}

try {
JarFileArchive archive = new JarFileArchive(app.getFile());
List<URL> urls = new ArrayList<>();
archive.getNestedArchives(entry -> entry.getName().endsWith(".jar")).forEach(a -> {
try {
urls.add(a.getUrl());
}
catch (MalformedURLException e) {
logger.error("Unable to process nested archive " + e.getMessage());
}
});
URLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), null);
try {
Class.forName(CF_ENV, false, classLoader);
return true;
}
catch (ClassNotFoundException e) {
logger.debug(app.getFilename() + " does not contain " + CF_ENV);
return false;
}
}
catch (Exception e) {
logger.warn("Unable to determine dependencies for " + app.getFilename());
}
return false;
}
}
}
Loading

0 comments on commit 93f2fca

Please sign in to comment.