Skip to content

Commit

Permalink
feat: support k8s secret/configmaps as smallrye config locations
Browse files Browse the repository at this point in the history
(cherry picked from commit 278500d)
  • Loading branch information
iocanel authored and gsmet committed Feb 8, 2021
1 parent c547f21 commit 8ea58a0
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 0 deletions.
34 changes: 34 additions & 0 deletions docs/src/main/asciidoc/deploying-to-kubernetes.adoc
Expand Up @@ -448,6 +448,34 @@ quarkus.kubernetes.secret-volumes.my-volume.secret-name=my-secret
quarkus.kubernetes.config-map-volumes.my-volume.config-map-name=my-secret
----

==== Passing application configuration

Quarkus supports passing configuration from external locations (via Smallrye Config). This usually requires setting an additional environment variable or system propertiy.
When you need to use a secret or a config map for the purpose of application configuration, you need to:

- define a volume
- mount the volume
- create an environment variable for `SMALLRYE_CONFIG_LOCATIONS`

To simplify things, quarkus provides single step alternative:

[source,properties]
----
quarkus.kubernetes.app-secert=<name of the secret containing the configuration>
----

or

[source,properties]
----
quarkus.kubernetes.app-config-map=<name of the config map containing the configuration>
----

When these properties are used, the generated manifests will contain everything required.
The application config volumes will be created using path: `/mnt/app-secret` and `/mnt/app-config-map` for secrets and configmaps respectively.

Note: Users may use both properties at the same time.

=== Changing the number of replicas:

To change the number of replicas from 1 to 3:
Expand Down Expand Up @@ -606,6 +634,8 @@ The table below describe all the available configuration options.
| quarkus.kubernetes.namespace | String | |
| quarkus.kubernetes.labels | Map | |
| quarkus.kubernetes.annotations | Map | |
| quarkus.kubernetes.app-secret | String | |
| quarkus.kubernetes.app-config-map | String | |
| quarkus.kubernetes.env-vars | Map<String, Env> | |
| quarkus.kubernetes.working-dir | String | |
| quarkus.kubernetes.command | String[] | |
Expand Down Expand Up @@ -844,6 +874,8 @@ The OpenShift resources can be customized in a similar approach with Kubernetes.
| quarkus.openshift.init-containers | Map<String, Container> | |
| quarkus.openshift.labels | Map | |
| quarkus.openshift.annotations | Map | |
| quarkus.openshift.app-secret | String | |
| quarkus.openshift.app-config-map | String | |
| quarkus.openshift.env-vars | Map<String, Env> | |
| quarkus.openshift.working-dir | String | |
| quarkus.openshift.command | String[] | |
Expand Down Expand Up @@ -940,6 +972,8 @@ The generated service can be customized using the following properties:
| quarkus.knative.init-containers | Map<String, Container> | |
| quarkus.knative.labels | Map | |
| quarkus.knative.annotations | Map | |
| quarkus.knative.app-secret | String | |
| quarkus.knative.app-config-map | String | |
| quarkus.knative.env-vars | Map<String, Env> | |
| quarkus.knative.working-dir | String | |
| quarkus.knative.command | String[] | |
Expand Down
Expand Up @@ -420,4 +420,25 @@ public EnvVarsConfig getEnv() {
* Traffic configuration.
*/
Map<String, TrafficConfig> traffic;

/**
* If set, the secret will mounted to the application container and its contents will be used for application configuration.
*/
@ConfigItem
Optional<String> appSecret;

/**
* If set, the config amp will mounted to the application container and its contents will be used for application
* configuration.
*/
@ConfigItem
Optional<String> appConfigMap;

public Optional<String> getAppSecret() {
return this.appSecret;
}

public Optional<String> getAppConfigMap() {
return this.appConfigMap;
}
}
Expand Up @@ -16,15 +16,21 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import io.dekorate.kubernetes.config.Annotation;
import io.dekorate.kubernetes.config.ConfigMapVolumeBuilder;
import io.dekorate.kubernetes.config.EnvBuilder;
import io.dekorate.kubernetes.config.MountBuilder;
import io.dekorate.kubernetes.config.PortBuilder;
import io.dekorate.kubernetes.config.SecretVolumeBuilder;
import io.dekorate.kubernetes.configurator.AddPort;
import io.dekorate.kubernetes.decorator.AddAnnotationDecorator;
import io.dekorate.kubernetes.decorator.AddAwsElasticBlockStoreVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddAzureDiskVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddAzureFileVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddConfigMapVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddEnvVarDecorator;
import io.dekorate.kubernetes.decorator.AddHostAliasesDecorator;
import io.dekorate.kubernetes.decorator.AddImagePullSecretDecorator;
import io.dekorate.kubernetes.decorator.AddInitContainerDecorator;
Expand All @@ -36,6 +42,7 @@
import io.dekorate.kubernetes.decorator.AddRoleBindingResourceDecorator;
import io.dekorate.kubernetes.decorator.AddSecretVolumeDecorator;
import io.dekorate.kubernetes.decorator.AddServiceAccountResourceDecorator;
import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator;
import io.dekorate.kubernetes.decorator.ApplyArgsDecorator;
import io.dekorate.kubernetes.decorator.ApplyCommandDecorator;
import io.dekorate.kubernetes.decorator.ApplyLimitsCpuDecorator;
Expand Down Expand Up @@ -143,6 +150,7 @@ public static List<DecoratorBuildItem> createDecorators(Optional<Project> projec
result.addAll(createPodDecorators(project, target, name, config));
result.addAll(createContainerDecorators(project, target, name, config));
result.addAll(createMountAndVolumeDecorators(project, target, name, config));
result.addAll(createAppConfigVolumeAndEnvDecorators(project, target, name, config));

//Handle Command and arguments
command.ifPresent(c -> {
Expand Down Expand Up @@ -246,10 +254,52 @@ private static List<DecoratorBuildItem> createPodDecorators(Optional<Project> pr
return result;
}

private static List<DecoratorBuildItem> createAppConfigVolumeAndEnvDecorators(Optional<Project> project, String target,
String name,
PlatformConfiguration config) {

List<DecoratorBuildItem> result = new ArrayList<>();
Set<String> paths = new HashSet<>();

config.getAppSecret().ifPresent(s -> {
result.add(new DecoratorBuildItem(target, new AddSecretVolumeDecorator(new SecretVolumeBuilder()
.withSecretName(s)
.withNewVolumeName("app-secret")
.build())));
result.add(new DecoratorBuildItem(target, new AddMountDecorator(new MountBuilder()
.withName("app-secret")
.withPath("/mnt/app-secret")
.build())));
paths.add("/mnt/app-secret");
});

config.getAppConfigMap().ifPresent(s -> {
result.add(new DecoratorBuildItem(target, new AddConfigMapVolumeDecorator(new ConfigMapVolumeBuilder()
.withConfigMapName(s)
.withNewVolumeName("app-config-map")
.build())));
result.add(new DecoratorBuildItem(target, new AddMountDecorator(new MountBuilder()
.withName("app-config-map")
.withPath("/mnt/app-config-map")
.build())));
paths.add("/mnt/app-config-map");
});

if (!paths.isEmpty()) {
result.add(new DecoratorBuildItem(target,
new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, name, new EnvBuilder()
.withName("SMALLRYE_CONFIG_LOCATIONS")
.withValue(paths.stream().collect(Collectors.joining(",")))
.build())));
}
return result;
}

private static List<DecoratorBuildItem> createMountAndVolumeDecorators(Optional<Project> project, String target,
String name,
PlatformConfiguration config) {
List<DecoratorBuildItem> result = new ArrayList<>();

config.getMounts().entrySet().forEach(e -> {
result.add(new DecoratorBuildItem(target, new AddMountDecorator(MountConverter.convert(e))));
});
Expand Down
Expand Up @@ -249,6 +249,19 @@ public class KubernetesConfig implements PlatformConfiguration {
@ConfigItem(defaultValue = "false")
boolean deploy;

/**
* If set, the secret will mounted to the application container and its contents will be used for application configuration.
*/
@ConfigItem
Optional<String> appSecret;

/**
* If set, the config amp will mounted to the application container and its contents will be used for application
* configuration.
*/
@ConfigItem
Optional<String> appConfigMap;

public Optional<String> getPartOf() {
return partOf;
}
Expand Down Expand Up @@ -425,4 +438,13 @@ public ResourcesConfig getResources() {
public boolean isExpose() {
return expose;
}

public Optional<String> getAppSecret() {
return appSecret;
}

public Optional<String> getAppConfigMap() {
return appConfigMap;
}

}
Expand Up @@ -438,4 +438,26 @@ public Map<String, EnvConfig> getEnvVars() {
public EnvVarsConfig getEnv() {
return env;
}

/**
* If set, the secret will mounted to the application container and its contents will be used for application configuration.
*/
@ConfigItem
Optional<String> appSecret;

/**
* If set, the config amp will mounted to the application container and its contents will be used for application
* configuration.
*/
@ConfigItem
Optional<String> appConfigMap;

public Optional<String> getAppSecret() {
return this.appSecret;
}

public Optional<String> getAppConfigMap() {
return this.appConfigMap;
}

}
Expand Up @@ -79,4 +79,9 @@ default boolean isExpose() {
default String getConfigName() {
return getClass().getSimpleName().replaceAll("Config$", "").toLowerCase();
}

public Optional<String> getAppSecret();

public Optional<String> getAppConfigMap();

}
@@ -0,0 +1,81 @@
package io.quarkus.it.kubernetes;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;

import org.assertj.core.api.AbstractObjectAssert;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.quarkus.bootstrap.model.AppArtifact;
import io.quarkus.builder.Version;
import io.quarkus.test.ProdBuildResults;
import io.quarkus.test.ProdModeTestResults;
import io.quarkus.test.QuarkusProdModeTest;

public class OpenshiftWithAppConfigMapTest {

@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class))
.setApplicationName("openshift-with-app-config-map")
.setApplicationVersion("0.1-SNAPSHOT")
.withConfigurationResource("openshift-with-app-config-map.properties")
.setForcedDependencies(Collections.singletonList(
new AppArtifact("io.quarkus", "quarkus-openshift", Version.getVersion())));

@ProdBuildResults
private ProdModeTestResults prodModeTestResults;

@Test
public void assertGeneratedResources() throws IOException {
Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes");

assertThat(kubernetesDir)
.isDirectoryContaining(p -> p.getFileName().endsWith("openshift.json"))
.isDirectoryContaining(p -> p.getFileName().endsWith("openshift.yml"));
List<HasMetadata> openshiftList = DeserializationUtil.deserializeAsList(
kubernetesDir.resolve("openshift.yml"));

assertThat(openshiftList).filteredOn(h -> "DeploymentConfig".equals(h.getKind())).singleElement().satisfies(h -> {
assertThat(h.getMetadata()).satisfies(m -> {
assertThat(m.getName()).isEqualTo("openshift-with-app-config-map");
assertThat(m.getLabels().get("app.openshift.io/runtime")).isEqualTo("quarkus");
});

AbstractObjectAssert<?, ?> specAssert = assertThat(h).extracting("spec");
specAssert.extracting("template").extracting("spec").isInstanceOfSatisfying(PodSpec.class,
podSpec -> {
assertThat(podSpec.getContainers()).singleElement().satisfies(container -> {
List<EnvVar> envVars = container.getEnv();
assertThat(envVars).anySatisfy(envVar -> {
assertThat(envVar.getName()).isEqualTo("SMALLRYE_CONFIG_LOCATIONS");
assertThat(envVar.getValue()).isEqualTo("/mnt/app-config-map");
});

List<VolumeMount> mounts = container.getVolumeMounts();
assertThat(mounts).anySatisfy(mount -> {
assertThat(mount.getName()).isEqualTo("app-config-map");
assertThat(mount.getMountPath()).isEqualTo("/mnt/app-config-map");
});
});
List<Volume> volumes = podSpec.getVolumes();
assertThat(volumes).anySatisfy(volume -> {
assertThat(volume.getName()).isEqualTo("app-config-map");
assertThat(volume.getConfigMap().getName()).isEqualTo("my-config-map");
});
});
});
}
}

0 comments on commit 8ea58a0

Please sign in to comment.