Initial support for HclPropertySourceLoader

This commit is contained in:
spencergibb
2020-07-22 19:31:01 -04:00
parent 23e6c1f9ae
commit 025229690f
7 changed files with 259 additions and 0 deletions

View File

@@ -37,6 +37,11 @@
<artifactId>consul-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.bertramlabs.plugins</groupId>
<artifactId>hcl4j</artifactId>
<version>0.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-consul-core</artifactId>

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2013-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.consul.hcl;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.bertramlabs.plugins.hcl4j.HCLParser;
import com.bertramlabs.plugins.hcl4j.HCLParserException;
import org.springframework.beans.factory.config.YamlProcessor;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
public class HclPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[] {"hcl"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Map<String, ?> properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
return Collections
.singletonList(new OriginTrackedMapPropertySource(name, Collections
.unmodifiableMap(properties), true));
}
private Map<String, ?> loadProperties(Resource resource) throws IOException {
try {
Map<String, Object> map = new HCLParser().parse(resource.getInputStream());
map = getFlattenedMap(map);
return map;
}
catch (HCLParserException e) {
throw new IOException("Error parsing " + resource.getFilename(), e);
}
}
/**
* Return a flattened version of the given map, recursively following any nested Map
* or Collection values. Entries from the resulting map retain the same order as the
* source. When called with the Map from a {@link YamlProcessor.MatchCallback} the result will
* contain the same values as the {@link YamlProcessor.MatchCallback} Properties.
* @param source the source map
* @return a flattened map
* @since 4.1.3
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
Map<String, Object> result = new LinkedHashMap<>();
buildFlattenedMap(result, source, null);
return result;
}
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path) {
source.forEach((key, value) -> {
if (StringUtils.hasText(path)) {
if (key.startsWith("[")) {
key = path + key;
}
else {
key = path + '.' + key;
}
}
if (value instanceof String) {
result.put(key, value);
}
else if (value instanceof Map) {
// Need a compound key
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
buildFlattenedMap(result, map, key);
}
else if (value instanceof Collection) {
// Need a compound key
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) value;
if (collection.isEmpty()) {
result.put(key, "");
}
else {
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result, Collections.singletonMap(
"[" + (count++) + "]", object), key);
}
}
}
else {
result.put(key, (value != null ? value : ""));
}
});
}
}

View File

@@ -4,3 +4,7 @@ org.springframework.cloud.consul.config.ConsulConfigAutoConfiguration
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.cloud.consul.hcl.HclPropertySourceLoader

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2013-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.consul.hcl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(properties = { "spring.cloud.consul.config.enabled=false",
"spring.profiles.active=hcltest" })
public class HclPropertySourceLoaderIntegrationTests {
@Autowired
Environment env;
@Test
void loadProperties() throws Exception {
assertThat(env.getProperty("variable.region.description"))
.isEqualTo("This is the location where the Linode instance is deployed.");
}
@SpringBootConfiguration
protected static class TestConfig {
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2013-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.consul.hcl;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
public class HclPropertySourceLoaderTests {
private HclPropertySourceLoader loader = new HclPropertySourceLoader();
@Test
void getFileExtensions() {
assertThat(this.loader.getFileExtensions()).isEqualTo(new String[] { "hcl" });
}
@Test
void loadProperties() throws Exception {
List<PropertySource<?>> loaded = this.loader.load("test.hcl",
new ClassPathResource("test.hcl", getClass()));
PropertySource<?> source = loaded.get(0);
assertThat(source.getProperty("variable.region.description"))
.isEqualTo("This is the location where the Linode instance is deployed.");
}
}

View File

@@ -0,0 +1,19 @@
# Linode provider block. Installs Linode plugin.
provider "linode" {
token = "${var.token}"
}
variable "region" {
description = "This is the location where the Linode instance is deployed."
}
/* A multi
line comment. */
resource "linode_instance" "example_linode" {
image = "linode/ubuntu18.04"
label = "example-linode"
region = "${var.region}"
type = "g6-standard-1"
authorized_keys = [ "my-key" ]
root_pass = "example-password"
}

View File

@@ -0,0 +1,19 @@
# Linode provider block. Installs Linode plugin.
provider "linode" {
token = "${var.token}"
}
variable "region" {
description = "This is the location where the Linode instance is deployed."
}
/* A multi
line comment. */
resource "linode_instance" "example_linode" {
image = "linode/ubuntu18.04"
label = "example-linode"
region = "${var.region}"
type = "g6-standard-1"
authorized_keys = [ "my-key" ]
root_pass = "example-password"
}