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

The newest version!
/*
 * 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 io.fabric8.kubernetes.client.dsl.ExecWatch;
import io.fabric8.kubernetes.client.dsl.internal.core.v1.PodOperationsImpl;
import io.fabric8.kubernetes.client.utils.InputStreamPumper;
import io.fabric8.kubernetes.client.utils.Utils;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.utils.CountingOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

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

public class PodUpload {

  private static final Logger LOG = LoggerFactory.getLogger(PodUpload.class);

  private static final String TAR_PATH_DELIMITER = "/";

  private PodUpload() {
  }

  public static boolean upload(PodOperationsImpl operation, Path pathToUpload)
      throws IOException {

    final File toUpload = pathToUpload.toFile();
    if (Utils.isNotNullOrEmpty(operation.getContext().getFile()) && toUpload.isFile()) {
      return uploadTar(operation, getDirectoryFromFile(operation.getContext().getFile()),
          tar -> addFileToTar(new File(operation.getContext().getFile()).getName(), toUpload, tar));
    } else if (Utils.isNotNullOrEmpty(operation.getContext().getDir()) && toUpload.isDirectory()) {
      return uploadTar(operation, ensureEndsWithSlash(operation.getContext().getDir()), tar -> {
        for (File file : Objects.requireNonNull(toUpload.listFiles())) {
          addFileToTar(file.getName(), file, tar);
        }
      });
    }
    throw new IllegalArgumentException("Provided arguments are not valid (file, directory, path)");
  }

  private static String getDirectoryFromFile(String file) {
    String directoryTrimmedFromFilePath = file.substring(0, file.lastIndexOf('/'));
    return ensureEndsWithSlash(directoryTrimmedFromFilePath.isEmpty() ? "/" : directoryTrimmedFromFilePath);
  }

  private static boolean upload(PodOperationsImpl operation, String file, UploadProcessor processor)
      throws IOException {

    String command = createExecCommandForUpload(file);

    CompletableFuture exitFuture;

    int uploadRequestTimeout = operation.getRequestConfig().getUploadRequestTimeout();
    long uploadRequestTimeoutEnd = uploadRequestTimeout < 0 ? Long.MAX_VALUE
        : uploadRequestTimeout + System.currentTimeMillis();
    long expected = 0;
    try (ExecWatch execWatch = operation.redirectingInput().terminateOnError().exec("sh", "-c", command)) {
      OutputStream out = execWatch.getInput();
      CountingOutputStream countingStream = new CountingOutputStream(out);
      processor.process(countingStream);
      out.close(); // also flushes
      expected = countingStream.getBytesWritten();
      exitFuture = execWatch.exitCode();
    }

    // enforce the timeout after we've written everything - generally this won't fail, but
    // we may have already exceeded the timeout because of how long it took to write
    if (!Utils.waitUntilReady(exitFuture, Math.max(0, uploadRequestTimeoutEnd - System.currentTimeMillis()),
        TimeUnit.MILLISECONDS)) {
      LOG.debug("failed to complete upload before timeout expired");
      return false;
    }
    final Integer exitCode = exitFuture.getNow(null);
    if (exitCode != null && exitCode != 0) {
      LOG.debug("upload process failed with exit code {}", exitCode);
      return false;
    }

    ByteArrayOutputStream byteCount = new ByteArrayOutputStream();
    try (ExecWatch countWatch = operation.writingOutput(byteCount).exec("sh", "-c",
        String.format("wc -c < %s", shellQuote(file)))) {
      CompletableFuture countExitFuture = countWatch.exitCode();
      if (!Utils.waitUntilReady(countExitFuture, Math.max(0, uploadRequestTimeoutEnd - System.currentTimeMillis()),
          TimeUnit.MILLISECONDS) || !Integer.valueOf(0).equals(countExitFuture.getNow(null))) {
        LOG.debug("failed to validate the upload size, exit code {}", countExitFuture.getNow(null));
        return false;
      }
      String remoteSize = new String(byteCount.toByteArray(), StandardCharsets.UTF_8);
      if (!String.valueOf(expected).equals(remoteSize.trim())) {
        LOG.debug("upload file size validation failed, expected {}, but was {}", expected, remoteSize);
        return false;
      }
    }
    return true;
  }

  public static boolean uploadFileData(PodOperationsImpl operation, InputStream inputStream)
      throws IOException {
    return upload(operation, operation.getContext().getFile(), os -> InputStreamPumper.transferTo(inputStream, os::write));
  }

  private static boolean uploadTar(PodOperationsImpl operation, String directory,
      UploadProcessor processor)
      throws IOException {

    String fileName = String.format("%sfabric8-%s.tar", directory, generateId());

    boolean uploaded = upload(operation, fileName, os -> {
      try (final TarArchiveOutputStream tar = new TarArchiveOutputStream(os)) {
        tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
        tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
        processor.process(tar);
      }
    });

    if (!uploaded) {
      // best effort delete of the failed upload
      try (ExecWatch rm = operation.writingOutput(new ByteArrayOutputStream()).exec("sh", "-c",
          String.format("rm %s", fileName))) {
        if (!Utils.waitUntilReady(rm.exitCode(), operation.getRequestConfig().getUploadRequestTimeout(), TimeUnit.MILLISECONDS)
            || !Integer.valueOf(0).equals(rm.exitCode().getNow(null))) {
          LOG.warn("delete of temporary tar file {} may not have completed", fileName);
        }
      }
      return false;
    }

    final String command = extractTarCommand(directory, fileName);

    try (ExecWatch execWatch = operation.redirectingInput().exec("sh", "-c", command)) {
      CompletableFuture countExitFuture = execWatch.exitCode();
      // TODO: this enforcement duplicates the timeout
      return Utils.waitUntilReady(countExitFuture, operation.getRequestConfig().getUploadRequestTimeout(),
          TimeUnit.MILLISECONDS) && Integer.valueOf(0).equals(countExitFuture.getNow(null));
    }

  }

  static String extractTarCommand(String directory, String tar) {
    return String.format("mkdir -p %1$s; tar -C %1$s -xmf %2$s; e=$?; rm %2$s; exit $e", shellQuote(directory), tar);
  }

  private static void addFileToTar(String fileName, File file, TarArchiveOutputStream tar)
      throws IOException {
    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 : Objects.requireNonNull(file.listFiles())) {
        addFileToTar(fileName + TAR_PATH_DELIMITER + fileInDirectory.getName(), fileInDirectory, tar);
      }
    }
  }

  static String createExecCommandForUpload(String file) {
    return String.format(
        "mkdir -p %s && cat - > %s && echo $?", shellQuote(getDirectoryFromFile(file)), shellQuote(file));
  }

  private static String ensureEndsWithSlash(String path) {
    return path.endsWith("/") ? path : (path + "/");
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy