Skip to content

Commit

Permalink
feat (kubernetes-client) : Add DSL support for creating ConfigMap fro…
Browse files Browse the repository at this point in the history
…m file (fabric8io#4184)

+ Added ConfigMapResource for modifying default ConfigMap operations
+ Added FromFileCreatable DSL method to provide file
+ Move KubernetesResourceUtilTest to
  `io.fabric8.kubernetes.client.utils`

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia committed Dec 26, 2022
1 parent d5a0e4c commit 3947e31
Show file tree
Hide file tree
Showing 19 changed files with 587 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#### Dependency Upgrade

#### New Features
* Fix #4184: Add DSL support for creating ConfigMap from file

#### _**Note**_: Breaking changes
* Fix #3972: deprecated Parameterizable and methods on Serialization accepting parameters - that was only needed as a workaround for non-string parameters. You should instead include those parameter values in the map passed to processLocally.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import io.fabric8.kubernetes.client.dsl.AutoscalingAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.BatchAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.CertificatesAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.ConfigMapResource;
import io.fabric8.kubernetes.client.dsl.DiscoveryAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.EventingAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.ExtensionsAPIGroupDSL;
Expand Down Expand Up @@ -450,7 +451,7 @@ NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata> resourc
*
* @return MixedOperation object for ConfigMap related operations.
*/
MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> configMaps();
MixedOperation<ConfigMap, ConfigMapList, ConfigMapResource> configMaps();

/**
* API entrypoint for LimitRange related operations. LimitRange (core/v1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import io.fabric8.kubernetes.client.dsl.AutoscalingAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.BatchAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.CertificatesAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.ConfigMapResource;
import io.fabric8.kubernetes.client.dsl.DiscoveryAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.EventingAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.ExtensionsAPIGroupDSL;
Expand Down Expand Up @@ -354,7 +355,7 @@ public NonNamespaceOperation<APIService, APIServiceList, Resource<APIService>> a
}

@Override
public MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> configMaps() {
public MixedOperation<ConfigMap, ConfigMapList, ConfigMapResource> configMaps() {
return getClient().configMaps();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (C) 2015 Red Hat, Inc.
*
* 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
*
* 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 io.fabric8.kubernetes.client.dsl;

import io.fabric8.kubernetes.api.model.ConfigMap;

public interface ConfigMapResource extends Resource<ConfigMap>,
FromFileCreatable<Resource<ConfigMap>> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (C) 2015 Red Hat, Inc.
*
* 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
*
* 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 io.fabric8.kubernetes.client.dsl;

public interface FromFileCreatable<T> {
/**
* Create new ConfigMap from a directory or file contents.
*
* @param dirOrFilePath a file or directory path
* @return {@link Resource<T> for operations to do with this resource
*/
T fromFile(String dirOrFilePath);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import io.fabric8.kubernetes.api.builder.VisitableBuilder;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.DefaultKubernetesResourceList;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
Expand All @@ -31,9 +33,18 @@
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.readiness.Readiness;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
Expand All @@ -42,7 +53,9 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class KubernetesResourceUtil {
private KubernetesResourceUtil() {
Expand Down Expand Up @@ -457,6 +470,101 @@ public static Secret createDockerRegistrySecret(String dockerServer, String user
return createDockerSecret(secretName, dockerConfigAsStr);
}

/**
* Create New ConfigMap from a file or a directory
*
* @param name name of the ConfigMap
* @param key (optional) if it's a file key for ConfigMap entry
* @param dirOrFilePath file or directory path
* @return a ConfigMap object
* @throws IOException in case of error while reading file
*/
public static ConfigMap createNewConfigMapFromDirOrFile(final String name, final String key,
final String dirOrFilePath) throws IOException {
final Path path = Paths.get(dirOrFilePath);

if (Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
ConfigMapBuilder configMapBuilder = new ConfigMapBuilder()
.withNewMetadata().withName(name).endMetadata();
addConfigMapEntriesFromDirectoryToExistingConfigMap(configMapBuilder, path);
return configMapBuilder.build();
} else {
return createNewConfigMapFromFile(name, key, path);
}
}

/**
* Create new ConfigMap from file contents
*
* @param name name of ConfigMap
* @param key key
* @param file file whose content would be used in ConfigMap entry
* @return a ConfigMap with data containing file contents
* @throws IOException in case of error while reading file
*/
public static ConfigMap createNewConfigMapFromFile(final String name, final String key, final Path file)
throws IOException {
ConfigMapBuilder configMapBuilder = new ConfigMapBuilder();
configMapBuilder.withNewMetadata().withName(name).endMetadata();
String entryKey = Optional.ofNullable(key).orElse(file.toFile().getName());
Map.Entry<String, String> configMapEntry = createConfigMapEntry(entryKey, file);
addConfigMapEntry(configMapBuilder, configMapEntry, file);
return configMapBuilder.build();
}

/**
* Create a ConfigMap entry based on file contents
*
* @param key key for entry
* @param file file path whose contents would be used in value of entry
* @return an entry containing key and value
* @throws IOException in case of error while reading file
*/
public static Map.Entry<String, String> createConfigMapEntry(final String key, final Path file) throws IOException {
final byte[] bytes = Files.readAllBytes(file);
if (isFileWithBinaryContent(file)) {
final String value = Base64.getEncoder().encodeToString(bytes);
return new AbstractMap.SimpleEntry<>(key, value);
} else {
return new AbstractMap.SimpleEntry<>(key, new String(bytes));
}
}

public static boolean isFileWithBinaryContent(final Path file) throws IOException {
final byte[] bytes = Files.readAllBytes(file);
try {
StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
.decode(ByteBuffer.wrap(bytes));
return false;
} catch (CharacterCodingException e) {
return true;
}
}

public static void addConfigMapEntriesFromDirectoryToExistingConfigMap(ConfigMapBuilder configMapBuilder, final Path path)
throws IOException {
try (Stream<Path> files = Files.list(path)) {
files.filter(p -> !Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)).forEach(file -> {
try {
addConfigMapEntry(configMapBuilder, createConfigMapEntry(file.getFileName().toString(), file), file);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
});
}
}

public static void addConfigMapEntry(ConfigMapBuilder configMapBuilder, Map.Entry<String, String> entry, final Path file)
throws IOException {
if (isFileWithBinaryContent(file)) {
configMapBuilder.addToBinaryData(entry.getKey(), entry.getValue());
} else {
configMapBuilder.addToData(entry.getKey(), entry.getValue());
}
}

private static Map<String, Object> createDockerRegistryConfigMap(String dockerServer, String username, String password) {
Map<String, Object> dockerConfigMap = new HashMap<>();
Map<String, Object> auths = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@
import io.fabric8.kubernetes.api.model.ConfigMapList;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.ConfigMapResource;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.ReplaceDeletable;
import io.fabric8.kubernetes.client.dsl.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Answers;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;

import java.time.Duration;
Expand All @@ -52,16 +51,19 @@
class ConfigMapLockTest {

private KubernetesClient kc;
private MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> configMaps;
private MixedOperation<ConfigMap, ConfigMapList, ConfigMapResource> configMaps;
private ConfigMapResource configMapResource;
private ConfigMapBuilder configMapBuilder;
private ConfigMapBuilder.MetadataNested<ConfigMapBuilder> metadata;

@BeforeEach
void setUp() {
kc = mock(KubernetesClient.class, RETURNS_DEEP_STUBS);
configMaps = mock(MixedOperation.class, RETURNS_DEEP_STUBS);
configMaps = mock(MixedOperation.class);
configMapResource = mock(ConfigMapResource.class);
configMapBuilder = Mockito.mock(ConfigMapBuilder.class, RETURNS_DEEP_STUBS);
metadata = mock(ConfigMapBuilder.MetadataNested.class, RETURNS_DEEP_STUBS);
when(configMaps.withName("name")).thenReturn(configMapResource);
when(kc.configMaps().inNamespace(anyString())).thenReturn(configMaps);
when(configMapBuilder.editOrNewMetadata()).thenReturn(metadata);
}
Expand Down Expand Up @@ -102,7 +104,7 @@ void missingIdentityShouldThrowException() {
void getWithExistingConfigMapShouldReturnLeaderElectionRecord() {
// Given
final ConfigMap cm = new ConfigMap();
when(configMaps.withName(ArgumentMatchers.eq("name")).get()).thenReturn(cm);
when(configMapResource.get()).thenReturn(cm);
cm.setMetadata(new ObjectMetaBuilder()
.withAnnotations(
Collections.singletonMap("control-plane.alpha.kubernetes.io/leader",
Expand All @@ -125,6 +127,7 @@ void createWithValidLeaderElectionRecordShouldSendPostRequest() throws Exception
final LeaderElectionRecord record = new LeaderElectionRecord(
"1", Duration.ofSeconds(1), ZonedDateTime.now(), ZonedDateTime.now(), 0);
final ConfigMapLock lock = new ConfigMapLock("namespace", "name", "1337");
when(configMapResource.get()).thenReturn(new ConfigMap());
// When
lock.create(kc, record);
// Then
Expand All @@ -134,7 +137,6 @@ void createWithValidLeaderElectionRecordShouldSendPostRequest() throws Exception
@Test
void updateWithValidLeaderElectionRecordShouldSendPutRequest() throws Exception {
// Given
final Resource<ConfigMap> configMapResource = configMaps.withName("name");
final ReplaceDeletable<ConfigMap> replaceable = mock(ReplaceDeletable.class, Answers.RETURNS_DEEP_STUBS);
when(configMapResource.lockResourceVersion(any())).thenReturn(replaceable);
final ConfigMap configMapInTheCluster = new ConfigMap();
Expand Down
Loading

0 comments on commit 3947e31

Please sign in to comment.