SshOptions.java

package com.github.dakusui.actionunit.actions.cmd.unix;

import java.util.*;

import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;

/**
 * Models options common to both {@code ssh} and {@code scp} commands. (see below).
 * ----
 * -4               : IPv4
 * -6               : IPv6
 * -C               : compression
 * -c               : cipher spec
 * -F               : config file
 * -i               : identity
 * -o               : ssh option
 * -p (-P for scp)  : port
 * -q               : quiet
 * -v               : verbose
 * ----
 */
public interface SshOptions {
  static SshOptions emptySshOptions() {
    return new Builder().build();
  }

  boolean authAgentConnectionForwardingEnabled();

  boolean ipv4();

  boolean ipv6();

  boolean compression();

  List<String> jumpHosts();

  Optional<String> cipherSpec();

  Optional<String> configFile();

  Optional<String> identity();

  List<String> options();

  OptionalInt port();

  boolean quiet();

  boolean verbose();

  default List<String> formatOptionsWith(Formatter formatter) {
    return requireNonNull(formatter).format(this);
  }

  class Impl implements SshOptions {
    final boolean      ipv4;
    final boolean      ipv6;
    final boolean      compression;
    final List<String> jumpHosts;
    final String       cipherSpec;
    final String       configFile;
    final String       identity;
    final List<String> sshOptions;
    final Integer      port;
    final boolean      quiet;
    final boolean      verbose;
    final boolean      authAgentConnectionForwarding;

    public Impl(boolean authAgentConnectionForwardingEnabled, boolean ipv4, boolean ipv6, boolean compression, List<String> jumpHosts, String cipherSpec, String configFile, String identity, List<String> sshOptions, Integer port, boolean quiet, boolean verbose) {
      this.authAgentConnectionForwarding = authAgentConnectionForwardingEnabled;
      this.ipv4 = ipv4;
      this.ipv6 = ipv6;
      this.compression = compression;
      this.jumpHosts = unmodifiableList(new ArrayList<>(requireNonNull(jumpHosts)));
      this.cipherSpec = cipherSpec;
      this.configFile = configFile;
      this.identity = identity;
      this.sshOptions = unmodifiableList(new ArrayList<>(requireNonNull(sshOptions)));
      this.port = port;
      this.quiet = quiet;
      this.verbose = verbose;
    }


    @Override
    public boolean authAgentConnectionForwardingEnabled() {
      return authAgentConnectionForwarding;
    }

    @Override
    public boolean ipv4() {
      return ipv4;
    }

    @Override
    public boolean ipv6() {
      return ipv6;
    }

    @Override
    public boolean compression() {
      return compression;
    }

    @Override
    public List<String> jumpHosts() {
      return jumpHosts;
    }

    @Override
    public Optional<String> cipherSpec() {
      return Optional.ofNullable(cipherSpec);
    }

    @Override
    public Optional<String> configFile() {
      return Optional.ofNullable(configFile);
    }

    @Override
    public Optional<String> identity() {
      return Optional.ofNullable(identity);
    }

    @Override
    public List<String> options() {
      return sshOptions;
    }

    @Override
    public OptionalInt port() {
      return port != null ? OptionalInt.of(port) : OptionalInt.empty();
    }

    @Override
    public boolean quiet() {
      return quiet;
    }

    @Override
    public boolean verbose() {
      return verbose;
    }
  }

  class Builder {
    private       String       identity;
    private final List<String> sshOptions = new LinkedList<>();
    private final List<String> jumpHosts  = new LinkedList<>();
    private       boolean      ipv4;
    private       boolean      ipv6;
    private       boolean      compression;
    private       String       cipherSpec;
    private       String       configFile;
    private       Integer      port;
    private       boolean      quiet;
    private       boolean      verbose;
    private       boolean      authAgentConnectionForwarding;

    public Builder() {
      this.ipv4(false)
          .ipv6(false)
          .compression(false)
          .cipherSpec(null)
          .configFile(null)
          .identity(null)
          .port(null)
          .quiet(false)
          .verbose(false);
    }

    public Builder authAgentConnectionForwarding(boolean enable) {
      this.authAgentConnectionForwarding = enable;
      return this;
    }

    public Builder ipv4(boolean enable) {
      this.ipv4 = enable;
      return this;
    }

    public Builder ipv6(boolean enable) {
      this.ipv6 = enable;
      return this;
    }

    public Builder compression(boolean enable) {
      this.compression = enable;
      return this;
    }

    public Builder cipherSpec(String cipherSpec) {
      this.cipherSpec = cipherSpec;
      return this;
    }

    public Builder configFile(String configFile) {
      this.configFile = configFile;
      return this;
    }

    public Builder port(Integer port) {
      this.port = port;
      return this;
    }

    public Builder quiet(boolean quiet) {
      this.quiet = quiet;
      return this;
    }

    public Builder verbose(boolean verbose) {
      this.verbose = verbose;
      return this;
    }


    public Builder identity(String identity) {
      this.identity = identity;
      return this;
    }

    public Builder addJumpHost(String jumpHost) {
      this.jumpHosts.add(requireNonNull(jumpHost));
      return this;
    }

    public Builder addSshOption(String option) {
      sshOptions.add(option);
      return this;
    }

    public Builder addSshOption(String option, String value) {
      return this.addSshOption(String.format("%s=%s", option, value));
    }

    public Builder disablePasswordAuthentication() {
      return this.addSshOption("PasswordAuthentication", "no");
    }

    public Builder disableStrictHostkeyChecking() {
      return this.addSshOption("StrictHostkeyChecking", "no");
    }

    public SshOptions build() {
      return new SshOptions.Impl(
          authAgentConnectionForwarding,
          ipv4,
          ipv6,
          compression,
          jumpHosts,
          cipherSpec,
          configFile,
          identity,
          sshOptions,
          port,
          quiet,
          verbose);
    }
  }

  @FunctionalInterface
  interface Formatter {
    List<String> format(SshOptions sshOptions);

    static Formatter forSsh() {
      return sshOptions -> new LinkedList<String>() {{
        if (sshOptions.authAgentConnectionForwardingEnabled())
          add("-A");
        if (sshOptions.ipv4())
          add("-4");
        if (sshOptions.ipv6())
          add("-6");
        if (sshOptions.compression())
          add("-C");
        sshOptions.configFile().ifPresent(v -> {
          add("-F");
          add(v);
        });
        if (!sshOptions.jumpHosts().isEmpty())
          add("-J " + String.join(",", sshOptions.jumpHosts()));
        sshOptions.cipherSpec().ifPresent(v -> {
          add("-c");
          add(v);
        });
        sshOptions.identity().ifPresent(v -> {
          add("-i");
          add(v);
        });
        sshOptions.options().forEach(v -> {
          add("-o");
          add(v);
        });
        sshOptions.port().ifPresent(v -> {
          add("-p");
          add(Objects.toString(v));
        });
        if (sshOptions.quiet())
          add("-q");
        if (sshOptions.verbose())
          add("-v");
      }};
    }

    static Formatter forScp() {
      return sshOptions -> new LinkedList<String>() {{
        if (sshOptions.authAgentConnectionForwardingEnabled())
          add("-A");
        if (sshOptions.ipv4())
          add("-4");
        if (sshOptions.ipv6())
          add("-6");
        if (sshOptions.compression())
          add("-C");
        sshOptions.configFile().ifPresent(v -> {
          add("-F");
          add(v);
        });
        if (!sshOptions.jumpHosts().isEmpty())
          add("-J " + String.join(",", sshOptions.jumpHosts()));
        sshOptions.cipherSpec().ifPresent(v -> {
          add("-c");
          add(v);
        });
        sshOptions.identity().ifPresent(v -> {
          add("-i");
          add(v);
        });
        sshOptions.options().forEach(v -> {
          add("-o");
          add(v);
        });
        sshOptions.port().ifPresent(v -> {
          add("-P");
          add(Objects.toString(v));
        });
        if (sshOptions.quiet())
          add("-q");
        if (sshOptions.verbose())
          add("-v");
      }};
    }
  }
}