All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fabric8.kubernetes.client.dsl.internal.uploadable.PodUpload Maven / Gradle / Ivy

/**
 * 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.internal.uploadable;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.ObjIntConsumer;
import java.util.zip.GZIPOutputStream;

import io.fabric8.kubernetes.client.dsl.base.OperationSupport;
import io.fabric8.kubernetes.client.dsl.internal.PodOperationContext;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.WebSocket;
import io.fabric8.kubernetes.client.utils.Base64;
import io.fabric8.kubernetes.client.utils.URLUtils;
import io.fabric8.kubernetes.client.utils.Utils;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;

import static io.fabric8.kubernetes.client.dsl.internal.core.v1.PodOperationsImpl.shellQuote;

public class PodUpload {

  private static final int DEFAULT_BUFFER_SIZE = 8192;
  private static final String TAR_PATH_DELIMITER = "/";

  private PodUpload() {
  }

  public static boolean upload(HttpClient client, PodOperationContext context,
    OperationSupport operationSupport, Path pathToUpload)
    throws IOException, InterruptedException {

    if (Utils.isNotNullOrEmpty(context.getFile()) && pathToUpload.toFile().isFile()) {
      return uploadFile(client, context, operationSupport, pathToUpload);
    } else if (Utils.isNotNullOrEmpty(context.getDir()) && pathToUpload.toFile().isDirectory()) {
      return uploadDirectory(client, context, operationSupport, pathToUpload);
    }
    throw new IllegalArgumentException("Provided arguments are not valid (file, directory, path)");
  }

  public static boolean uploadFileData(HttpClient client, PodOperationContext context,
    OperationSupport operationSupport, InputStream inputStream)
    throws IOException, InterruptedException {
    final PodUploadWebSocketListener podUploadWebSocketListener = initWebSocket(
      buildCommandUrl(createExecCommandForUpload(context), context, operationSupport), client);
    try (
      final Base64.InputStream b64In = new Base64.InputStream(inputStream, Base64.ENCODE)
    ) {
      podUploadWebSocketListener.waitUntilReady(operationSupport.getConfig().getRequestConfig().getUploadConnectionTimeout());
      copy(b64In, podUploadWebSocketListener::send);
      podUploadWebSocketListener.waitUntilComplete(operationSupport.getConfig().getRequestConfig().getUploadRequestTimeout());
      return true;
    }
  }

  private static boolean uploadFile(HttpClient client, PodOperationContext context,
    OperationSupport operationSupport, Path pathToUpload)
    throws IOException, InterruptedException {
    try (final FileInputStream fis = new FileInputStream(pathToUpload.toFile())) {
      return uploadFileData(client, context, operationSupport, fis);
    }
  }

  private static boolean uploadDirectory(HttpClient client, PodOperationContext context,
    OperationSupport operationSupport, Path pathToUpload)
    throws IOException, InterruptedException {

    final String command = String.format(
      "mkdir -p %1$s && base64 -d - | tar -C %1$s -xzf -", shellQuote(context.getDir()));
    final PodUploadWebSocketListener podUploadWebSocketListener = initWebSocket(
      buildCommandUrl(command, context, operationSupport), client);
    try (
      final PipedOutputStream pos = new PipedOutputStream();
      final PipedInputStream pis = new PipedInputStream(pos);
      final Base64.OutputStream b64Out = new Base64.OutputStream(pos, Base64.ENCODE);
      final GZIPOutputStream gzip = new GZIPOutputStream(b64Out)

    ) {
      podUploadWebSocketListener.waitUntilReady(operationSupport.getConfig().getRequestConfig().getUploadConnectionTimeout());
      final Callable readFiles = () -> {
        try (final TarArchiveOutputStream tar = new TarArchiveOutputStream(gzip)) {
          tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
          for (File file : pathToUpload.toFile().listFiles()) {
            addFileToTar(null, file, tar);
          }
          tar.flush();
        }
        return null;
      };
      final ExecutorService es = Executors.newSingleThreadExecutor();
      Future readFilesFuture = es.submit(readFiles);
      copy(pis, podUploadWebSocketListener::send);
      podUploadWebSocketListener.waitUntilComplete(operationSupport.getConfig().getRequestConfig().getUploadRequestTimeout());
      try {
        readFilesFuture.get(100, TimeUnit.SECONDS);
        return true;
      } catch (ExecutionException ex) {
        if (ex.getCause() instanceof IOException) {
          throw (IOException) ex.getCause();
        }
        throw new IOException(ex.getMessage(), ex.getCause());
      } catch (TimeoutException e) {
        return false;
      } finally {
        es.shutdown();
      }
    }
  }

  static void copy(InputStream inputStream, ObjIntConsumer consumer) throws IOException {
    final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
    int n;
    while (-1 != (n = inputStream.read(buffer))) {
      consumer.accept(buffer, n);
    }
  }

  private static void addFileToTar(String rootTarPath, File file, TarArchiveOutputStream tar)
    throws IOException {

    final String fileName =
      Optional.ofNullable(rootTarPath).orElse("") + TAR_PATH_DELIMITER + file.getName();
    tar.putArchiveEntry(new TarArchiveEntry(file, fileName));
    if (file.isFile()) {
      Files.copy(file.toPath(), tar);
      tar.closeArchiveEntry();
    } else if (file.isDirectory()) {
      tar.closeArchiveEntry();
      for (File fileInDirectory : file.listFiles()) {
        addFileToTar(fileName, fileInDirectory, tar);
      }
    }
  }

  private static PodUploadWebSocketListener initWebSocket(URL url, HttpClient client) {
    final PodUploadWebSocketListener podUploadWebSocketListener = new PodUploadWebSocketListener();
    final HttpClient clone = client.newBuilder().readTimeout(0, TimeUnit.MILLISECONDS).build();
    CompletableFuture startedFuture = clone.newWebSocketBuilder()
      .setHeader("Sec-WebSocket-Protocol", "v4.channel.k8s.io")
      .uri(URI.create(url.toString()))
      .buildAsync(podUploadWebSocketListener);
    startedFuture.whenComplete((w, t) -> {
      if (t != null) {
        podUploadWebSocketListener.onError(w, t);
      }
    });
    return podUploadWebSocketListener;
  }

  private static URL buildCommandUrl(String command, PodOperationContext context, OperationSupport operationSupport)
    throws MalformedURLException {

    final StringBuilder commandBuilder = new StringBuilder();
    commandBuilder.append("exec?");
    commandBuilder.append("command=sh&command=-c");
    commandBuilder.append("&command=");
    commandBuilder.append(URLUtils.encodeToUTF(command));
    if (context.getContainerId() != null && !context.getContainerId().isEmpty()) {
      commandBuilder.append("&container=").append(context.getContainerId());
    }
    commandBuilder.append("&stdin=true");
    commandBuilder.append("&stderr=true");
    return new URL(
      URLUtils.join(operationSupport.getResourceUrl().toString(), commandBuilder.toString()));
  }

  static String createExecCommandForUpload(PodOperationContext context) {
    final String file = context.getFile();
    String directoryTrimmedFromFilePath = file.substring(0, file.lastIndexOf('/'));
    final String directory = directoryTrimmedFromFilePath.isEmpty() ? "/" : directoryTrimmedFromFilePath;
    return String.format(
      "mkdir -p %s && base64 -d - > %s", shellQuote(directory), shellQuote(file));
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy