diff --git a/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalActuatorTemplate.java b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalActuatorTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..599073e0758656dcfdee705f7c63b8f70557ea0f --- /dev/null +++ b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalActuatorTemplate.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 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.local; + +import org.springframework.cloud.deployer.spi.app.AbstractActuatorTemplate; +import org.springframework.cloud.deployer.spi.app.AppAdmin; +import org.springframework.cloud.deployer.spi.app.AppDeployer; +import org.springframework.cloud.deployer.spi.app.AppInstanceStatus; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * @author David Turanski + */ +public class LocalActuatorTemplate extends AbstractActuatorTemplate { + + public LocalActuatorTemplate(RestTemplate restTemplate, AppDeployer appDeployer, + AppAdmin appAdmin) { + super(restTemplate, appDeployer, appAdmin); + } + + @Override + protected String actuatorUrlForInstance(AppInstanceStatus appInstanceStatus) { + return UriComponentsBuilder.fromHttpUrl(appInstanceStatus.getAttributes().get("url")) + .path("/actuator").toUriString(); + } +} diff --git a/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployer.java b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployer.java index 69636361251b06f7bc3c3542a3c296e1d3cf4959..cc6e7733637dca8b9eec876d6290428b59aa9f7e 100644 --- a/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployer.java +++ b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -67,6 +67,7 @@ import org.springframework.util.StringUtils; * @author Michael Minella * @author Glenn Renfro * @author Christian Tzolov + * @author David Turanski */ public class LocalAppDeployer extends AbstractLocalDeployerSupport implements AppDeployer { @@ -302,6 +303,8 @@ public class LocalAppDeployer extends AbstractLocalDeployerSupport implements Ap appInstanceEnv.put("SPRING_CLOUD_APPLICATION_GUID", guid); } + this.getLocalDeployerProperties().getAppAdmin().addCredentialsToAppEnvironmentAsProperties(appInstanceEnv); + boolean useDynamicPort = !request.getDefinition().getProperties().containsKey(SERVER_PORT_KEY); // WATCH OUT: The calcServerPort sets the computed port in the appInstanceEnv#SERVER_PORT_KEY. // Later is implicitly passed to and used inside the command builder. Therefore the calcServerPort() method diff --git a/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerAutoConfiguration.java b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerAutoConfiguration.java index d70ff8c9564bd9ff74aa289ec50f392a49d8f7ac..de8abbe4aa45e806658bc40bf94f105266d179bc 100644 --- a/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerAutoConfiguration.java +++ b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2022 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. @@ -19,16 +19,19 @@ package org.springframework.cloud.deployer.spi.local; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.deployer.spi.app.ActuatorOperations; import org.springframework.cloud.deployer.spi.app.AppDeployer; import org.springframework.cloud.deployer.spi.task.TaskLauncher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.web.client.RestTemplate; /** * Creates a {@link LocalAppDeployer} and {@link LocalTaskLauncher} * * @author Mark Fisher + * @author David Turanski */ @Configuration @EnableConfigurationProperties(LocalDeployerProperties.class) @@ -46,4 +49,17 @@ public class LocalDeployerAutoConfiguration { public TaskLauncher taskLauncher(LocalDeployerProperties properties) { return new LocalTaskLauncher(properties); } + + @Bean + @ConditionalOnMissingBean + RestTemplate actuatorRestTemplate() { + return new RestTemplate(); + } + + @Bean + @ConditionalOnMissingBean(ActuatorOperations.class) + ActuatorOperations actuatorOperations(RestTemplate actuatorRestTemplate, AppDeployer appDeployer, + LocalDeployerProperties properties) { + return new LocalActuatorTemplate(actuatorRestTemplate, appDeployer, properties.getAppAdmin()); + } } diff --git a/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerProperties.java b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerProperties.java index 4d6ebf543191c5a9697fb0934e9a81bee18c667a..ec8d18788800bddfa5a2867171010968e00041ce 100644 --- a/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerProperties.java +++ b/spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.deployer.spi.app.AppAdmin; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.validation.annotation.Validated; @@ -177,6 +178,8 @@ public class LocalDeployerProperties { */ private String hostname; + private AppAdmin appAdmin = new AppAdmin(); + public LocalDeployerProperties() { } @@ -200,6 +203,7 @@ public class LocalDeployerProperties { this.useSpringApplicationJson = from.isUseSpringApplicationJson(); this.workingDirectoriesRoot = Paths.get(from.getWorkingDirectoriesRoot().toUri()); this.hostname =from.getHostname(); + this.appAdmin = from.appAdmin; } public static class PortRange { @@ -493,6 +497,14 @@ public class LocalDeployerProperties { this.healthProbe = healthProbe; } + public AppAdmin getAppAdmin() { + return appAdmin; + } + + public void setAppAdmin(AppAdmin appAdmin) { + this.appAdmin = appAdmin; + } + public static class HttpProbe { /** Path to check as a probe */ diff --git a/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerEnvironmentIntegrationTests.java b/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerEnvironmentIntegrationTests.java index 9e71ae6ac3117b685037cd20f057930bd36fef6f..45702bdfae7b63552c747d7ac8e697952d66646e 100644 --- a/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerEnvironmentIntegrationTests.java +++ b/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerEnvironmentIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 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. @@ -34,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.deployer.resource.docker.DockerResource; +import org.springframework.cloud.deployer.spi.app.ActuatorOperations; import org.springframework.cloud.deployer.spi.app.AppDeployer; import org.springframework.cloud.deployer.spi.app.AppInstanceStatus; import org.springframework.cloud.deployer.spi.app.AppStatus; @@ -67,7 +68,8 @@ import static org.awaitility.Awaitility.await; */ @SpringBootTest(classes = { Config.class, AbstractIntegrationTests.Config.class }, value = { "maven.remoteRepositories.springRepo.url=https://repo.spring.io/libs-snapshot", - "spring.cloud.deployer.local.use-spring-application-json=false" }) + "spring.cloud.deployer.local.use-spring-application-json=false" +}) public class LocalAppDeployerEnvironmentIntegrationTests extends AbstractAppDeployerIntegrationJUnit5Tests { private static final String TESTAPP_DOCKER_IMAGE_NAME = "springcloud/spring-cloud-deployer-spi-test-app:latest"; @@ -75,6 +77,9 @@ public class LocalAppDeployerEnvironmentIntegrationTests extends AbstractAppDepl @Autowired private AppDeployer appDeployer; + @Autowired + private ActuatorOperations actuatorOperations; + @Value("${spring-cloud-deployer-spi-test-use-docker:false}") private boolean useDocker; @@ -242,6 +247,46 @@ public class LocalAppDeployerEnvironmentIntegrationTests extends AbstractAppDepl deployer.undeploy(deploymentId); } + @Test + public void testActuatorOperations() { + if (useDocker) { + // would not expect to be able to check anything on docker + return; + } + Map<String, String> properties = new HashMap<>(); + AppDefinition definition = new AppDefinition(randomName(), properties); + Resource resource = testApplication(); + AppDeploymentRequest request = new AppDeploymentRequest(definition, resource); + + log.info("Deploying {}...", request.getDefinition().getName()); + + String deploymentId = appDeployer().deploy(request); + Timeout timeout = deploymentTimeout(); + await().pollInterval(Duration.ofMillis(timeout.pause)) + .atMost(Duration.ofMillis(timeout.maxAttempts * timeout.pause)) + .untilAsserted(() -> { + assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed); + }); + String id = deploymentId + "-0"; + Map<String, Object> env = actuatorOperations + .getFromActuator(deploymentId, id, "/env", Map.class); + assertThat(env).containsKeys("activeProfiles", "propertySources"); + Map<String, Object> status = actuatorOperations + .getFromActuator(deploymentId, id, "/health", Map.class); + assertThat(status.get("status")).isEqualTo("UP"); + + Map<String, Object> loggers = actuatorOperations + .getFromActuator(deploymentId, id, "/loggers/org.springframework", Map.class); + assertThat(loggers).isNotNull(); + assertThat(loggers.get("configuredLevel")).isNull(); + actuatorOperations.postToActuator(deploymentId, id,"/loggers/org.springframework", + Collections.singletonMap("configuredLevel", "debug"), Object.class); + loggers = actuatorOperations + .getFromActuator(deploymentId, id, "/loggers/org.springframework", Map.class); + assertThat(((String)loggers.get("configuredLevel")).toLowerCase()).isEqualTo("debug"); + + } + private String getCommandOutput(String cmd) throws IOException { Process process = Runtime.getRuntime().exec(cmd); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); @@ -256,6 +301,11 @@ public class LocalAppDeployerEnvironmentIntegrationTests extends AbstractAppDepl public AppDeployer appDeployer(LocalDeployerProperties properties) { return new LocalAppDeployer(properties); } + + @Bean + ActuatorOperations actuatorOperations(AppDeployer appDeployer, LocalDeployerProperties properties) { + return new LocalActuatorTemplate(new RestTemplate(), appDeployer, properties.getAppAdmin()); + } } } diff --git a/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerPropertiesTests.java b/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerPropertiesTests.java index 8ff6dda7290ccc15e24f25787a1fbc524b61cb93..4d4237284c14a9c599a4b5cdbeec9c06d91d4938 100644 --- a/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerPropertiesTests.java +++ b/spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerPropertiesTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.condition.OS; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.deployer.spi.app.AppAdmin; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.SystemEnvironmentPropertySource; @@ -85,6 +86,8 @@ public class LocalDeployerPropertiesTests { map.put("spring.cloud.deployer.local.docker.volume-mounts", "/tmp:/opt"); map.put("spring.cloud.deployer.local.startup-probe.path", "/path1"); map.put("spring.cloud.deployer.local.health-probe.path", "/path2"); + map.put("spring.cloud.deployer.local.app-admin.user","user"); + map.put("spring.cloud.deployer.local.app-admin.password","password"); context.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map)); @@ -110,6 +113,8 @@ public class LocalDeployerPropertiesTests { assertThat(properties.getDocker().getVolumeMounts()).isEqualTo("/tmp:/opt"); assertThat(properties.getStartupProbe().getPath()).isEqualTo("/path1"); assertThat(properties.getHealthProbe().getPath()).isEqualTo("/path2"); + assertThat(properties.getAppAdmin().getUser()).isEqualTo("user"); + assertThat(properties.getAppAdmin().getPassword()).isEqualTo("password"); }); } @@ -118,6 +123,10 @@ public class LocalDeployerPropertiesTests { public void setAllPropertiesCamelCase() { this.contextRunner .withInitializer(context -> { + AppAdmin appAdmin = new AppAdmin(); + appAdmin.setUser("user"); + appAdmin.setPassword("password"); + Map<String, Object> map = new HashMap<>(); map.put("spring.cloud.deployer.local.debugPort", "8888"); map.put("spring.cloud.deployer.local.debugSuspend", "n"); @@ -134,6 +143,8 @@ public class LocalDeployerPropertiesTests { map.put("spring.cloud.deployer.local.docker.network", "spring-cloud-dataflow-server_default"); map.put("spring.cloud.deployer.local.docker.portMappings", "9091:5678"); map.put("spring.cloud.deployer.local.docker.volumeMounts", "/tmp:/opt"); + map.put("spring.cloud.deployer.local.appAdmin.user","user"); + map.put("spring.cloud.deployer.local.appAdmin.password","password"); context.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map)); @@ -157,6 +168,8 @@ public class LocalDeployerPropertiesTests { assertThat(properties.getDocker().getNetwork()).isEqualTo("spring-cloud-dataflow-server_default"); assertThat(properties.getDocker().getPortMappings()).isEqualTo("9091:5678"); assertThat(properties.getDocker().getVolumeMounts()).isEqualTo("/tmp:/opt"); + assertThat(properties.getAppAdmin().getUser()).isEqualTo("user"); + assertThat(properties.getAppAdmin().getPassword()).isEqualTo("password"); }); }