Skip to content
Permalink
Browse files
Merge branch 'master' into scrR8
  • Loading branch information
tjwatson committed Sep 3, 2021
2 parents b503f5d + b50b6af commit 4f2f15e000eb1adf70984c64f27791da4ee98b51
Show file tree
Hide file tree
Showing 114 changed files with 4,845 additions and 1,514 deletions.
@@ -121,7 +121,7 @@
<dependency>
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon-core</artifactId>
<version>1.2.3</version>
<version>1.2.14</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -1,6 +1,11 @@
# org.apache.felix.configadmin.plugin.interpolation

An OSGi Configuration Admin Plugin that can interpolate values in configuration with values obtained elsewhere. Supported sources:
An OSGi Configuration Admin Plugin that can interpolate values in configuration with values obtained elsewhere.

The [StandaloneInterpolator](./src/main/java/org/apache/felix/configadmin/plugin/interpolation/StandaloneInterpolator.java)
can also be used independently of Configuration Admin.

Supported sources:

* Files on disk, for example to be used with Kubernetes secrets
* Environment variables
@@ -28,7 +28,7 @@

<artifactId>org.apache.felix.configadmin.plugin.interpolation</artifactId>
<packaging>jar</packaging>
<version>1.1.5-SNAPSHOT</version>
<version>1.2.1-SNAPSHOT</version>

<name>Apache Felix Configuration Admin Values Interpolation Plugin</name>
<description>
@@ -51,7 +51,7 @@ public void start(BundleContext context) throws Exception {
}
String encoding = context.getProperty(ENCODING_PROPERTY);

ConfigurationPlugin plugin = new InterpolationConfigurationPlugin(context, directory, encoding);
ConfigurationPlugin plugin = new InterpolationConfigurationPlugin(context::getProperty, directory, encoding);
Dictionary<String, Object> props = new Hashtable<>();
props.put(ConfigurationPlugin.CM_RANKING, PLUGIN_RANKING);
props.put("config.plugin.id", PLUGIN_ID);
@@ -30,10 +30,9 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.function.Function;
import java.util.stream.Stream;

import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationPlugin;
@@ -95,13 +94,13 @@ class InterpolationConfigurationPlugin implements ConfigurationPlugin {
TYPE_MAP.put("char[]", char[].class);
}

private final BundleContext context;
private final Function<String, String> propertiesProvider;
private final List<File> directory;

private final Charset encodingCharset;

InterpolationConfigurationPlugin(BundleContext bc, String dir, String fileEncoding) {
context = bc;
InterpolationConfigurationPlugin(Function<String, String> pp, String dir, String fileEncoding) {
propertiesProvider = pp;
if (dir != null) {
directory = Stream.of(dir.split("\\s*,\\s*")).map(File::new).collect(toList());
getLog().info("Configured directory for secrets: {}", dir);
@@ -189,7 +188,7 @@ String getVariableFromEnvironment(final String name) {
}

String getVariableFromProperty(final String name) {
return context.getProperty(name);
return propertiesProvider.apply(name);
}

String getVariableFromFile(final String key, final String name, final Object pid) {
@@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://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.apache.felix.configadmin.plugin.interpolation;

import java.io.File;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Map;
import java.util.stream.Collectors;

import org.osgi.framework.Constants;

/**
* Entrypoint into the interpolator independent of the OSGi API, so it can be used from outside of an
* OSGi Configuration Admin environment.
*/
public class StandaloneInterpolator {
final InterpolationConfigurationPlugin plugin;

/**
* Constructor.
*
* @param frameworkProperties Properties to use for framework property substitutions.
* @param secretsLocations The directories where secrets files can be found. The platform default
* encoding will be used for these files.
*/
public StandaloneInterpolator(Map<String,String> frameworkProperties, File ... secretsLocations) {
this(frameworkProperties, null, secretsLocations);
}

/**
* Constructor.
*
* @param frameworkProperties Properties to use for framework property substitutions.
* @param encoding The file encoding to be used for the files in the secrets locations.
* @param secretsLocations The directories where secrets files can be found.
*/
public StandaloneInterpolator(Map<String,String> frameworkProperties, String encoding, File ... secretsLocations) {
if (secretsLocations == null)
secretsLocations = new File[] {};

String locations = Arrays.asList(secretsLocations).stream()
.map(File::toString)
.collect(Collectors.joining(","));
plugin = new InterpolationConfigurationPlugin(frameworkProperties::get, locations, encoding);
}

/**
* Perform configuration interpolations.
*
* @param pid The PID of the configuration.
* @param dict The dictionary containing the configuration properties. The dictionary will be updated
* by the interpolation substitutions.
*/
public void interpolate(String pid, Dictionary<String, Object> dict) {
boolean pidAdded = false;
try {
if (dict.get(Constants.SERVICE_PID) == null) {
dict.put(Constants.SERVICE_PID, pid);
pidAdded = true;
}

plugin.modifyConfiguration(null, dict);
} finally {
if (pidAdded)
dict.remove(Constants.SERVICE_PID);
}
}
}
@@ -69,7 +69,7 @@ public void testModifyConfigurationNoDirConfig() throws Exception {
BundleContext bc = Mockito.mock(BundleContext.class);
Mockito.when(bc.getProperty("foo.bar")).thenReturn("hello there");

InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc, null, null);
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty, null, null);

String envUser = System.getenv("USER");
String userVar;
@@ -171,7 +171,7 @@ public void testTypeConversion() throws IOException {
public void testReplacementInStringArray() throws IOException {
BundleContext bc = Mockito.mock(BundleContext.class);
Mockito.when(bc.getProperty("foo.bar")).thenReturn("hello there");
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc, null, null);
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty, null, null);

Dictionary<String, Object> dict = new Hashtable<>();
dict.put("array", new String[] { "1", "$[prop:foo.bar]", "3" });
@@ -191,7 +191,7 @@ public void testMultiplePlaceholders() throws Exception {
Mockito.when(bc.getProperty("foo.bar")).thenReturn("hello there");
String rf = getClass().getResource("/other/testfile.txt").getFile();
File file = new File(rf);
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc,
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty,
file.getParent() + "," + file.getParentFile().getParent(), null);

assertEquals("xxhello thereyyhello therezz",
@@ -206,7 +206,7 @@ public void testMultipleDirectories() throws Exception {
BundleContext bc = Mockito.mock(BundleContext.class);
String rf = getClass().getResource("/other/testfile.txt").getFile();
File file = new File(rf);
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc,
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty,
file.getParent() + "," + file.getParentFile().getParent(), null);

assertEquals("xxhello thereyyhello therezz",
@@ -221,7 +221,7 @@ public void testNestedPlaceholders() throws Exception {
BundleContext bc = Mockito.mock(BundleContext.class);
Mockito.when(bc.getProperty("foo.bar")).thenReturn("hello there");
Mockito.when(bc.getProperty("key")).thenReturn("foo.bar");
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc, null, null);
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty, null, null);

assertEquals("hello there", plugin.replace("akey", "$[prop:$[prop:key]]", "apid"));
}
@@ -240,7 +240,7 @@ public void testArraySplit() throws Exception {
public void testArrayTypeConversion() throws Exception {
BundleContext bc = Mockito.mock(BundleContext.class);
Mockito.when(bc.getProperty("foo")).thenReturn("2000,3000");
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc, null, null);
InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty, null, null);

assertArrayEquals(new Integer[] { 2000, 3000 },
(Integer[]) plugin.replace("key", "$[prop:foo;type=Integer[];delimiter=,]", "somepid"));
@@ -0,0 +1,124 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://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.apache.felix.configadmin.plugin.interpolation;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

import java.io.File;
import java.net.URL;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import org.junit.Test;
import org.osgi.framework.Constants;

public class StandaloneInterpolatorTest {
@Test
public void testFrameworkPropertyInterpolation() {
Map<String, String> fprops = new HashMap<>();
fprops.put("my.prop", "12345");
fprops.put("my.other.prop", "ABCDE");

StandaloneInterpolator interpolator = new StandaloneInterpolator(fprops);

Dictionary<String, Object> dict = new Hashtable<>();
dict.put("foo", "bar");
dict.put("nonsubst", "$[yeah:yeah]");
dict.put("prop", "$[prop:my.prop]");
interpolator.interpolate("org.foo.bar", dict);

assertEquals(3, Collections.list(dict.keys()).size());
assertEquals("bar", dict.get("foo"));
assertEquals("$[yeah:yeah]", dict.get("nonsubst"));
assertEquals("12345", dict.get("prop"));
}

@Test
public void testEnvVarInterpolation() {
String envUser = System.getenv("USER");
String userVar;
if (envUser == null) {
envUser = System.getenv("USERNAME"); // maybe we're on Windows
userVar = "USERNAME";
} else {
userVar = "USER";
}

StandaloneInterpolator interpolator = new StandaloneInterpolator(Collections.emptyMap(), null);

Dictionary<String, Object> dict = new Hashtable<>();
dict.put("someuser", "$[env:" + userVar + "]");
dict.put(Constants.SERVICE_PID, "org.foo.bar");
interpolator.interpolate("org.foo.bar", dict);

assertEquals(envUser, dict.get("someuser"));
}

@Test
public void testSecretInterpolation() {
URL resUrl = getClass().getResource("/res1");
File res1Dir = new File(resUrl.getFile());
File res0Dir = new File(res1Dir.getParentFile(), "res0");

StandaloneInterpolator interpolator = new StandaloneInterpolator(Collections.emptyMap(), res0Dir, res1Dir);

Dictionary<String, Object> dict = new Hashtable<>();
dict.put("name", "$[secret:my.db]");
dict.put("pass", "$[secret:my.pwd]");
interpolator.interpolate("my.pid", dict);

assertEquals(2, dict.size());
assertEquals("tiger", dict.get("name"));
assertEquals("$[secret:my.pwd]", dict.get("pass"));
}

@Test
public void testSecretInterpolationEncoding() {
URL resUrl = getClass().getResource("/res1");
File res1Dir = new File(resUrl.getFile());
File res0Dir = new File(res1Dir.getParentFile(), "res0");

StandaloneInterpolator interpolator = new StandaloneInterpolator(Collections.emptyMap(), "UTF-8", res1Dir, res0Dir);

Dictionary<String, Object> dict = new Hashtable<>();
dict.put("name", "$[secret:my.db]");
dict.put("pass", "$[secret:my.pwd]");
interpolator.interpolate("my.pid", dict);

assertEquals("tiger", dict.get("name"));
}

@Test
public void testSecretInterpolationEncoding2() {
URL resUrl = getClass().getResource("/res1");
File res1Dir = new File(resUrl.getFile());

StandaloneInterpolator interpolator = new StandaloneInterpolator(Collections.emptyMap(), "UTF-16", res1Dir);

Dictionary<String, Object> dict = new Hashtable<>();
dict.put("name", "$[secret:my.db]");
dict.put("pass", "$[secret:my.pwd]");
interpolator.interpolate("my.pid", dict);

assertNotEquals("tiger", dict.get("name"));
assertNotEquals("$[secret:my.db]", dict.get("name"));
}
}
@@ -0,0 +1 @@
tiger
@@ -28,7 +28,7 @@

<name>Apache Felix Converter</name>
<artifactId>org.apache.felix.converter</artifactId>
<version>1.0.15-SNAPSHOT</version>
<version>1.0.19-SNAPSHOT</version>
<packaging>bundle</packaging>

<scm>

0 comments on commit 4f2f15e

Please sign in to comment.