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)

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia committed Dec 23, 2022
1 parent 1bcf0ed commit 3cff070
Show file tree
Hide file tree
Showing 13 changed files with 273 additions and 13 deletions.
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,7 @@
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,11 @@
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 ConfigMap
*/
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 @@ -43,6 +54,7 @@
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class KubernetesResourceUtil {
private KubernetesResourceUtil() {
Expand Down Expand Up @@ -457,6 +469,97 @@ 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();
Map.Entry<String, String> configMapEntry = createConfigMapEntry(key, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.fabric8.kubernetes.client.dsl.internal.core.v1;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapList;
import io.fabric8.kubernetes.client.Client;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.ConfigMapResource;
import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperation;
import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl;
import io.fabric8.kubernetes.client.dsl.internal.OperationContext;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;

import static io.fabric8.kubernetes.client.utils.KubernetesResourceUtil.createNewConfigMapFromDirOrFile;

public class ConfigMapOperationsImpl extends HasMetadataOperation<ConfigMap, ConfigMapList, ConfigMapResource>
implements ConfigMapResource {

public ConfigMapOperationsImpl(Client client) {
this(HasMetadataOperationsImpl.defaultContext(client));
}

public ConfigMapOperationsImpl(OperationContext context) {
super(context.withPlural("configmaps"), ConfigMap.class, ConfigMapList.class);
}

@Override
public ConfigMapOperationsImpl newInstance(OperationContext context) {
return new ConfigMapOperationsImpl(context);
}

@Override
public ConfigMapResource fromFile(String dirOrFilePath) {
if (dirOrFilePath == null || dirOrFilePath.isEmpty()) {
throw new IllegalArgumentException("invalid file path provided");
}
File file = Paths.get(dirOrFilePath).toFile();
if (!file.exists()) {
throw new IllegalArgumentException(String.format("File %s doesn't exist", dirOrFilePath));
}

return resource(createNewConfigMap(file.getName(), dirOrFilePath));
}

private ConfigMap createNewConfigMap(String fileName, String dirOrFilePath) {
try {
return createNewConfigMapFromDirOrFile(name, fileName, dirOrFilePath);
} catch (IOException ioException) {
throw new KubernetesClientException("Unable to create ConfigMap " + name, ioException);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,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 @@ -141,6 +142,7 @@
import io.fabric8.kubernetes.client.dsl.internal.apps.v1.StatefulSetOperationsImpl;
import io.fabric8.kubernetes.client.dsl.internal.batch.v1.JobOperationsImpl;
import io.fabric8.kubernetes.client.dsl.internal.certificates.v1.CertificateSigningRequestOperationsImpl;
import io.fabric8.kubernetes.client.dsl.internal.core.v1.ConfigMapOperationsImpl;
import io.fabric8.kubernetes.client.dsl.internal.core.v1.PodOperationsImpl;
import io.fabric8.kubernetes.client.dsl.internal.core.v1.ReplicationControllerOperationsImpl;
import io.fabric8.kubernetes.client.dsl.internal.core.v1.ServiceOperationsImpl;
Expand Down Expand Up @@ -239,6 +241,7 @@ public KubernetesClientImpl(HttpClient httpClient, Config config, ExecutorSuppli
this.getHandlers().register(Pod.class, PodOperationsImpl::new);
this.getHandlers().register(Job.class, JobOperationsImpl::new);
this.getHandlers().register(Service.class, ServiceOperationsImpl::new);
this.getHandlers().register(ConfigMap.class, ConfigMapOperationsImpl::new);
this.getHandlers().register(Deployment.class, DeploymentOperationsImpl::new);
this.getHandlers().register(io.fabric8.kubernetes.api.model.extensions.Deployment.class,
io.fabric8.kubernetes.client.dsl.internal.extensions.v1beta1.DeploymentOperationsImpl::new);
Expand Down Expand Up @@ -484,8 +487,8 @@ public NonNamespaceOperation<APIService, APIServiceList, Resource<APIService>> a
* {@inheritDoc}
*/
@Override
public MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> configMaps() {
return resources(ConfigMap.class, ConfigMapList.class);
public MixedOperation<ConfigMap, ConfigMapList, ConfigMapResource> configMaps() {
return new ConfigMapOperationsImpl(this);
}

/**
Expand Down
Loading

0 comments on commit 3cff070

Please sign in to comment.