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

io.honnix.rkt.launcher.service.api.RktCommandResource Maven / Gradle / Ivy

/*-
 * -\-\-
 * rkt-launcher
 * --
 * 
 * --
 * 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.honnix.rkt.launcher.service.api;

import static java.util.stream.Collectors.toList;

import com.google.common.collect.ImmutableList;
import com.spotify.apollo.Request;
import com.spotify.apollo.RequestContext;
import com.spotify.apollo.Response;
import com.spotify.apollo.Status;
import com.spotify.apollo.entity.EntityMiddleware;
import com.spotify.apollo.entity.JacksonEntityCodec;
import com.spotify.apollo.route.AsyncHandler;
import com.spotify.apollo.route.Middleware;
import com.spotify.apollo.route.Route;
import io.honnix.rkt.launcher.RktLauncher;
import io.honnix.rkt.launcher.RktLauncherConfig;
import io.honnix.rkt.launcher.command.CatManifest;
import io.honnix.rkt.launcher.command.Command;
import io.honnix.rkt.launcher.command.Config;
import io.honnix.rkt.launcher.command.Fetch;
import io.honnix.rkt.launcher.command.Gc;
import io.honnix.rkt.launcher.command.Prepare;
import io.honnix.rkt.launcher.command.Rm;
import io.honnix.rkt.launcher.command.Run;
import io.honnix.rkt.launcher.command.RunPrepared;
import io.honnix.rkt.launcher.command.Stop;
import io.honnix.rkt.launcher.command.Version;
import io.honnix.rkt.launcher.exception.RktException;
import io.honnix.rkt.launcher.exception.RktLauncherException;
import io.honnix.rkt.launcher.exception.RktUnexpectedOutputException;
import io.honnix.rkt.launcher.options.FetchOptions;
import io.honnix.rkt.launcher.options.GcOptions;
import io.honnix.rkt.launcher.options.ListOptions;
import io.honnix.rkt.launcher.options.Options;
import io.honnix.rkt.launcher.options.PrepareOptions;
import io.honnix.rkt.launcher.options.RunOptions;
import io.honnix.rkt.launcher.options.RunPreparedOptions;
import io.honnix.rkt.launcher.options.StatusOptions;
import io.honnix.rkt.launcher.options.StopOptions;
import io.honnix.rkt.launcher.output.CatManifestOutput;
import io.honnix.rkt.launcher.output.ConfigOutput;
import io.honnix.rkt.launcher.output.FetchOutput;
import io.honnix.rkt.launcher.output.GcOutput;
import io.honnix.rkt.launcher.output.ListOutput;
import io.honnix.rkt.launcher.output.Output;
import io.honnix.rkt.launcher.output.PrepareOutput;
import io.honnix.rkt.launcher.output.RmOutput;
import io.honnix.rkt.launcher.output.RunOutput;
import io.honnix.rkt.launcher.output.StatusOutput;
import io.honnix.rkt.launcher.output.StopOutput;
import io.honnix.rkt.launcher.output.VersionOutput;
import io.honnix.rkt.launcher.service.exception.RktLauncherServiceException;
import io.honnix.rkt.launcher.util.Json;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import java.util.stream.Stream;
import okio.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class RktCommandResource {

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

  private static final Function
      DEFAULT_RKT_LAUNCHER_FACTORY = RktLauncher::new;

  private static final String DEFAULT_HTTP_METHOD = "POST";

  private final RktLauncherConfig rktLauncherConfig;

  private final ExecutorService asyncCommandExecutorService;

  private Function rktLauncherFactory;

  RktCommandResource(final RktLauncherConfig rktLauncherConfig,
                     final ExecutorService asyncCommandExecutorService) {
    this(rktLauncherConfig, DEFAULT_RKT_LAUNCHER_FACTORY, asyncCommandExecutorService);
  }

  RktCommandResource(final RktLauncherConfig rktLauncherConfig,
                     final Function rktLauncherFactory,
                     final ExecutorService asyncCommandExecutorService) {
    this.rktLauncherConfig = Objects.requireNonNull(rktLauncherConfig);
    this.rktLauncherFactory = Objects.requireNonNull(rktLauncherFactory);
    this.asyncCommandExecutorService = asyncCommandExecutorService;
  }

  Stream>>> routes() {
    final String base = "/rkt";

    final EntityMiddleware em =
        EntityMiddleware.forCodec(JacksonEntityCodec.forMapper(Json.OBJECT_MAPPER));

    final List>>> entityRoutes = Stream.of(
        Route.with(
            em.serializerResponse(CatManifestOutput.class),
            DEFAULT_HTTP_METHOD, base + "/cat-manifest/",
            rc -> catManifest(getId(rc))),
        Route.with(
            em.serializerResponse(ConfigOutput.class),
            DEFAULT_HTTP_METHOD, base + "/config",
            rc -> config()),
        Route.with(
            em.serializerResponse(FetchOutput.class),
            DEFAULT_HTTP_METHOD, base + "/fetch",
            rc -> fetch(rc.request())),
        Route.with(
            em.serializerResponse(GcOutput.class),
            DEFAULT_HTTP_METHOD, base + "/gc",
            rc -> gc(rc.request())),
        Route.with(
            em.serializerResponse(ListOutput.class),
            DEFAULT_HTTP_METHOD, base + "/list",
            rc -> list()),
        Route.with(
            em.response(PrepareOptions.class, PrepareOutput.class),
            DEFAULT_HTTP_METHOD, base + "/prepare",
            rc -> payload -> prepare(payload, rc.request())),
        Route.with(
            em.serializerResponse(RmOutput.class),
            DEFAULT_HTTP_METHOD, base + "/rm",
            rc -> rm(rc.request())),
        Route.with(
            em.serializerResponse(RmOutput.class),
            DEFAULT_HTTP_METHOD, base + "/rm/",
            rc -> rm(getId(rc))),
        Route.with(
            em.response(RunOptions.class, RunOutput.class),
            DEFAULT_HTTP_METHOD, base + "/run",
            rc -> payload -> run(rc.request(), payload)),
        Route.with(
            em.serializerResponse(RunOutput.class),
            DEFAULT_HTTP_METHOD, base + "/run-prepared/",
            rc -> runPrepared(getId(rc), rc.request())),
        Route.with(
            em.serializerResponse(StatusOutput.class),
            DEFAULT_HTTP_METHOD, base + "/status/",
            rc -> status(getId(rc), rc.request())),
        Route.with(
            em.serializerResponse(StopOutput.class),
            DEFAULT_HTTP_METHOD, base + "/stop",
            rc -> stop(rc.request())),
        Route.with(
            em.serializerResponse(StopOutput.class),
            DEFAULT_HTTP_METHOD, base + "/stop/",
            rc -> stop(getId(rc), rc.request())),
        Route.with(
            em.serializerResponse(VersionOutput.class),
            DEFAULT_HTTP_METHOD, base + "/version",
            rc -> version())
    )
        .map(r -> r.withMiddleware(Middleware::syncToAsync))
        .collect(toList());

    return Api.prefixRoutes(entityRoutes, Api.Version.V0);
  }

  private static String getId(final RequestContext rc) {
    return rc.pathArgs().get("id");
  }

  private static  T readPayloadIfExists(final Request request, final Class optionsClass)
      throws IOException {
    final Optional payload = request.payload();
    if (payload.isPresent() && payload.get().size() != 0) {
      return Json.deserialize(payload.get().toByteArray(), optionsClass);
    } else {
      return null;
    }
  }

  private  Response runCommand(
      final Command command) {
    try {
      return Response.forPayload(rktLauncherFactory.apply(rktLauncherConfig).run(command));
    } catch (RktLauncherException e) {
      LOG.error("unable to execute command [{}]]", command, e);
      throw new RktLauncherServiceException(e);
    } catch (RktException e) {
      LOG.debug("non zero exit code [{}] received from rkt when executing command [{}]]",
                e.getExitCode(), command, e);
      return Response.forStatus(Status.UNPROCESSABLE_ENTITY.withReasonPhrase(e.toString()));
    } catch (RktUnexpectedOutputException e) {
      LOG.error("unexpected output received from rkt when executing command [{}]", command, e);
      throw e;
    }
  }

  private  void submitCommand(
      final Command command) {
    asyncCommandExecutorService.submit(() -> {
      try {
        rktLauncherFactory.apply(rktLauncherConfig).run(command);
      } catch (RktLauncherException e) {
        LOG.error("unable to execute command [{}]]", command, e);
      } catch (RktException e) {
        LOG.debug("non zero exit code [{}] received from rkt when executing command [{}]]",
                  e.getExitCode(), command, e);
      } catch (RktUnexpectedOutputException e) {
        LOG.error("unexpected output received from rkt when executing command [{}]", command, e);
      }
    });
  }

  private Response catManifest(final String id) {
    final CatManifest catManifest = CatManifest.builder()
        .args(ImmutableList.of(id))
        .build();
    return runCommand(catManifest);
  }

  private Response config() {
    return runCommand(Config.COMMAND);
  }

  private Response fetch(final Request request) {
    final List images = request.parameters().get("image");
    if (images == null) {
      return Response
          .forStatus(Status.BAD_REQUEST.withReasonPhrase("Missing 'image' query parameter"));
    }

    final FetchOptions options;
    try {
      options = readPayloadIfExists(request, FetchOptions.class);
    } catch (IOException e) {
      return Response.forStatus(Status.BAD_REQUEST.withReasonPhrase(e.getMessage()));
    }

    final Fetch fetch = Fetch.builder()
        .options(options)
        .args(images)
        .build();
    if (request.parameter("async").orElse("false").equals("true")) {
      submitCommand(fetch);
      return Response.forStatus(Status.ACCEPTED);
    } else {
      return runCommand(fetch);
    }
  }

  private Response gc(final Request request) {
    final GcOptions options;
    try {
      options = readPayloadIfExists(request, GcOptions.class);
    } catch (IOException e) {
      return Response.forStatus(Status.BAD_REQUEST.withReasonPhrase(e.getMessage()));
    }

    final Gc gc = Gc.builder()
        .options(options)
        .build();
    return runCommand(gc);
  }

  private Response list() {
    final io.honnix.rkt.launcher.command.List list =
        io.honnix.rkt.launcher.command.List.builder()
            .options(ListOptions.builder().build())
            .build();
    return runCommand(list);
  }

  private Response prepare(final PrepareOptions options, final Request request) {
    final Prepare prepare = Prepare.builder()
        .options(options)
        .build();
    if (request.parameter("async").orElse("false").equals("true")) {
      submitCommand(prepare);
      return Response.forStatus(Status.ACCEPTED);
    } else {
      return runCommand(prepare);
    }
  }

  private Response rm(final Request request) {
    final List ids = request.parameters().get("id");
    if (ids == null) {
      return Response
          .forStatus(Status.BAD_REQUEST.withReasonPhrase("Missing 'id' query parameter"));
    }
    final Rm rm = Rm.builder()
        .args(ids)
        .build();
    return runCommand(rm);
  }

  private Response rm(final String id) {
    final Rm rm = Rm.builder()
        .args(ImmutableList.of(id))
        .build();
    return runCommand(rm);
  }

  private Response run(final Request request, final RunOptions options) {
    final boolean daemonize = request.parameter("daemonize").orElse("true").equals("true");

    if (options.uuidFileSave().isPresent()) {
      return Response.forStatus(Status.BAD_REQUEST.withReasonPhrase("UUID file not supported"));
    }

    final Run run = Run.builder()
        .daemonize(daemonize)
        .options(options)
        .build();
    return runCommand(run);
  }

  private Response runPrepared(final String id, final Request request) {
    final RunPreparedOptions options;
    try {
      options = readPayloadIfExists(request, RunPreparedOptions.class);
    } catch (IOException e) {
      return Response.forStatus(Status.BAD_REQUEST.withReasonPhrase(e.getMessage()));
    }

    final boolean daemonize = request.parameter("daemonize").orElse("true").equals("true");
    final RunPrepared runPrepared = RunPrepared.builder()
        .daemonize(daemonize)
        .options(options)
        .addArg(id)
        .build();
    return runCommand(runPrepared);
  }

  private Response status(final String id, final Request request) {
    final StatusOptions userOptions;
    try {
      userOptions = readPayloadIfExists(request, StatusOptions.class);
    } catch (IOException e) {
      return Response.forStatus(Status.BAD_REQUEST.withReasonPhrase(e.getMessage()));
    }

    final StatusOptions options =
        userOptions == null ? StatusOptions.builder().build() : userOptions;

    final io.honnix.rkt.launcher.command.Status status =
        io.honnix.rkt.launcher.command.Status.builder()
            .options(options)
            .addArg(id)
            .build();
    return runCommand(status);
  }

  private Response stop(final List ids, final Request request) {
    final StopOptions options;
    try {
      options = readPayloadIfExists(request, StopOptions.class);
    } catch (IOException e) {
      return Response.forStatus(Status.BAD_REQUEST.withReasonPhrase(e.getMessage()));
    }

    if (options != null && options.uuidFile().isPresent()) {
      return Response.forStatus(Status.BAD_REQUEST.withReasonPhrase("UUID file not supported"));
    }

    final Stop stop = Stop.builder()
        .options(options)
        .args(ids)
        .build();
    return runCommand(stop);
  }

  private Response stop(final Request request) {
    final List ids = request.parameters().get("id");
    if (ids == null) {
      return Response
          .forStatus(Status.BAD_REQUEST.withReasonPhrase("Missing 'id' query parameter"));
    }

    return stop(ids, request);
  }

  private Response stop(final String id, final Request request) {
    return stop(ImmutableList.of(id), request);
  }

  private Response version() {
    return runCommand(Version.COMMAND);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy