Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit 93f2fca

Browse files
dturanskiDavid Turanski
andauthored
Automatically configure environment for CfEnv (#345)
* 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>
1 parent 1d0e2f1 commit 93f2fca

14 files changed

+556
-25
lines changed

README.adoc

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,9 @@ This is useful in cases in which the Cloud Foundry foundation is isolated from e
9595
== Set Additional Environment Variables
9696

9797
By default, the deployer adds global and application configuration properties to a single `SPRING_APPLICATION_JSON` environment variable entry in the application manifest.
98-
You can configure additional environment variables in the manifest by setting `spring.cloud.deployer.cloudfoundry.env.<key>=<value>`.
99-
This is useful for setting https://github.com/cloudfoundry/java-buildpack[Java build pack configuration properties] in the application manifest.
98+
You can configure additional top-level environment variables in the manifest by setting `spring.cloud.deployer.cloudfoundry.env.<key>=<value>`.
99+
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`.
100100

101-
=== Disable Spring Auto-reconfiguration
102-
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
103-
bind to CF services. In this case, you would set:
104-
105-
----
106-
spring.cloud.deployer.cloudfoundry.env.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION='{enabled:false}',spring.cloud.deployer.cloudfoundry.env.SPRING_PROFILES_ACTIVE=cloud
107-
----
108101

109102

110103

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
<artifactId>spring-cloud-deployer-spi</artifactId>
3030
<version>${spring-cloud-deployer.version}</version>
3131
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-loader</artifactId>
35+
</dependency>
3236
<dependency>
3337
<groupId>org.springframework.boot</groupId>
3438
<artifactId>spring-boot-starter-validation</artifactId>

src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/AbstractCloudFoundryDeployer.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

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

19-
import com.fasterxml.jackson.core.JsonProcessingException;
20-
import com.fasterxml.jackson.databind.ObjectMapper;
2119
import java.io.File;
2220
import java.io.IOException;
2321
import java.nio.file.Path;
@@ -33,11 +31,18 @@
3331
import java.util.function.Predicate;
3432
import java.util.stream.Collectors;
3533
import java.util.stream.Stream;
34+
35+
import com.fasterxml.jackson.core.JsonProcessingException;
36+
import com.fasterxml.jackson.databind.ObjectMapper;
3637
import org.cloudfoundry.AbstractCloudFoundryException;
3738
import org.cloudfoundry.UnknownCloudFoundryException;
3839
import org.cloudfoundry.operations.services.BindServiceInstanceRequest;
3940
import org.slf4j.Logger;
4041
import org.slf4j.LoggerFactory;
42+
import reactor.core.Exceptions;
43+
import reactor.core.publisher.Mono;
44+
import reactor.retry.Retry;
45+
4146
import org.springframework.cloud.deployer.spi.app.AppDeployer;
4247
import org.springframework.cloud.deployer.spi.app.AppScaleRequest;
4348
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
@@ -47,9 +52,6 @@
4752
import org.springframework.http.HttpStatus;
4853
import org.springframework.util.FileSystemUtils;
4954
import org.springframework.util.StringUtils;
50-
import reactor.core.Exceptions;
51-
import reactor.core.publisher.Mono;
52-
import reactor.retry.Retry;
5355

5456
import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY;
5557
import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.JAVA_OPTS_PROPERTY_KEY;
@@ -315,15 +317,29 @@ private boolean deleteFileOrDirectory(File fileToDelete) {
315317
protected Map<String, String> getEnvironmentVariables(String deploymentId, AppDeploymentRequest request) {
316318
Map<String, String> envVariables = new HashMap<>();
317319
envVariables.putAll(getApplicationProperties(deploymentId, request));
318-
319320
String javaOpts = javaOpts(request);
320321
if (StringUtils.hasText(javaOpts)) {
321322
envVariables.put("JAVA_OPTS", javaOpts(request));
322323
}
324+
325+
if (hasCfEnv(request.getResource())) {
326+
Map<String, String> env =
327+
CfEnvConfigurer.disableJavaBuildPackAutoReconfiguration(deploymentProperties.getEnv());
328+
//Only append to existing spring profiles active
329+
env.putAll(CfEnvConfigurer.activateCloudProfile(env, null));
330+
deploymentProperties.setEnv(env);
331+
}
323332
envVariables.putAll(deploymentProperties.getEnv());
324333
return envVariables;
325334
}
326335

336+
protected boolean hasCfEnv(Resource resource) {
337+
if (resource instanceof CfEnvAwareResource) {
338+
return ((CfEnvAwareResource)resource).hasCfEnv();
339+
}
340+
return CfEnvAwareResource.of(resource).hasCfEnv();
341+
}
342+
327343
private Map<String, String> getApplicationProperties(String deploymentId, AppDeploymentRequest request) {
328344
Map<String, String> applicationProperties = getSanitizedApplicationProperties(deploymentId, request);
329345

@@ -345,6 +361,11 @@ private Map<String, String> getSanitizedApplicationProperties(String deploymentI
345361
Optional.ofNullable(applicationProperties.remove("server.port"))
346362
.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));
347363

364+
// Update active Spring Profiles given in application properties. Create a new entry with the given key if necessary
365+
if (hasCfEnv(request.getResource())) {
366+
applicationProperties = CfEnvConfigurer
367+
.activateCloudProfile(applicationProperties, CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN);
368+
}
348369
return applicationProperties;
349370
}
350371

src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/AbstractCloudFoundryTaskLauncher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
import org.slf4j.Logger;
3232
import org.slf4j.LoggerFactory;
3333
import reactor.core.publisher.Mono;
34+
import reactor.util.function.Tuple2;
3435

3536
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
3637
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
3738
import org.springframework.cloud.deployer.spi.task.LaunchState;
3839
import org.springframework.cloud.deployer.spi.task.TaskLauncher;
3940
import org.springframework.cloud.deployer.spi.task.TaskStatus;
40-
import reactor.util.function.Tuple2;
4141

4242
/**
4343
* Abstract class to provide base functionality for launching Tasks on Cloud Foundry. This
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.deployer.spi.cloudfoundry;
18+
19+
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
20+
21+
/**
22+
* Copies an {@link AppDeploymentRequest} using a {@link CfEnvAwareResource}.
23+
*
24+
* @author David Turanski
25+
* @since 2.4
26+
*/
27+
class CfEnvAwareAppDeploymentRequest extends AppDeploymentRequest {
28+
29+
static CfEnvAwareAppDeploymentRequest of(AppDeploymentRequest appDeploymentRequest) {
30+
return new CfEnvAwareAppDeploymentRequest(appDeploymentRequest);
31+
}
32+
33+
private CfEnvAwareAppDeploymentRequest(AppDeploymentRequest appDeploymentRequest) {
34+
super(appDeploymentRequest.getDefinition(),
35+
CfEnvAwareResource.of(appDeploymentRequest.getResource()),
36+
appDeploymentRequest.getDeploymentProperties(),
37+
appDeploymentRequest.getCommandlineArguments());
38+
}
39+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.deployer.spi.cloudfoundry;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.net.MalformedURLException;
23+
import java.net.URI;
24+
import java.net.URL;
25+
import java.net.URLClassLoader;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.Optional;
29+
30+
import org.apache.commons.logging.Log;
31+
import org.apache.commons.logging.LogFactory;
32+
33+
import org.springframework.boot.loader.archive.JarFileArchive;
34+
import org.springframework.core.io.Resource;
35+
36+
/**
37+
* A {@link Resource} implementation that delegates to a resource and keeps the state of a CfEnv dependency
38+
* as an {@link Optional} which may be empty, true, or false.
39+
*
40+
* @author David Turanski
41+
* @since 2.4
42+
*/
43+
class CfEnvAwareResource implements Resource {
44+
private final Resource resource;
45+
46+
private final boolean hasCfEnv;
47+
48+
static CfEnvAwareResource of(Resource resource) {
49+
return new CfEnvAwareResource(resource);
50+
}
51+
private CfEnvAwareResource(Resource resource) {
52+
this.resource = resource;
53+
this.hasCfEnv = CfEnvResolver.hasCfEnv(this);
54+
}
55+
56+
@Override
57+
public boolean exists() {
58+
return resource.exists();
59+
}
60+
61+
@Override
62+
public URL getURL() throws IOException {
63+
return resource.getURL();
64+
}
65+
66+
@Override
67+
public URI getURI() throws IOException {
68+
return resource.getURI();
69+
}
70+
71+
@Override
72+
public File getFile() throws IOException {
73+
return resource.getFile();
74+
}
75+
76+
@Override
77+
public long contentLength() throws IOException {
78+
return resource.contentLength();
79+
}
80+
81+
@Override
82+
public long lastModified() throws IOException {
83+
return resource.lastModified();
84+
}
85+
86+
@Override
87+
public Resource createRelative(String s) throws IOException {
88+
return resource.createRelative(s);
89+
}
90+
91+
@Override
92+
public String getFilename() {
93+
return resource.getFilename();
94+
}
95+
96+
@Override
97+
public String getDescription() {
98+
return resource.getDescription();
99+
}
100+
101+
@Override
102+
public InputStream getInputStream() throws IOException {
103+
return resource.getInputStream();
104+
}
105+
106+
boolean hasCfEnv() {
107+
return this.hasCfEnv;
108+
}
109+
110+
/**
111+
* Inspect the {@link CfEnvAwareResource} to determine if it contains a dependency on <i>io.pivotal.cfenv.core.CfEnv</i>.
112+
* Cache the result in the resource.
113+
*/
114+
static class CfEnvResolver {
115+
116+
private static Log logger = LogFactory.getLog(CfEnvResolver.class);
117+
118+
private static final String CF_ENV = "io.pivotal.cfenv.core.CfEnv";
119+
120+
static boolean hasCfEnv(CfEnvAwareResource app
121+
) {
122+
try {
123+
String scheme = app.getURI().getScheme().toLowerCase();
124+
if (scheme.equals("docker")) {
125+
return false;
126+
}
127+
}
128+
catch (IOException e) {
129+
throw new IllegalArgumentException(e.getMessage(), e);
130+
}
131+
132+
try {
133+
JarFileArchive archive = new JarFileArchive(app.getFile());
134+
List<URL> urls = new ArrayList<>();
135+
archive.getNestedArchives(entry -> entry.getName().endsWith(".jar")).forEach(a -> {
136+
try {
137+
urls.add(a.getUrl());
138+
}
139+
catch (MalformedURLException e) {
140+
logger.error("Unable to process nested archive " + e.getMessage());
141+
}
142+
});
143+
URLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), null);
144+
try {
145+
Class.forName(CF_ENV, false, classLoader);
146+
return true;
147+
}
148+
catch (ClassNotFoundException e) {
149+
logger.debug(app.getFilename() + " does not contain " + CF_ENV);
150+
return false;
151+
}
152+
}
153+
catch (Exception e) {
154+
logger.warn("Unable to determine dependencies for " + app.getFilename());
155+
}
156+
return false;
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)