core/storage_service.js

const { Context } = require('./context');
const { PersistentObject } = require('./persistent_object');
const { Util } = require('./util');

/**
 * StorageService describes any remote service (s3, ftp, etc.) to which
 * we can upload data. This object contains the information required to
 * connect to the remote service (hostname, login name, password, etc.).
 */
class StorageService extends PersistentObject {
    /**
     * Creates a new StorageService.
     *
     * @param {object} opts - Object containing properties to set.
     *
     * @param {string} opts.id - A UUID in hex-string format. This is
     * the object's unique identifier.
     *
     * @param {boolean} opts.userCanDelete - Indicates whether user is
     * allowed to delete this record.
     *
     * @param {string} opts.name - The name of the remote storage service. This
     * can be anything that's meaningful to the user (e.g. 'My S3 Bucket',
     * 'Library SFTP Server', etc.). Names should be unique to prevent confusion.
     *
     * @param {string} opts.description - A user-friendly description of
     * the storage service.
     *
     * @param {string} opts.protocol - The protocol to use when connecting
     * to the remote repo ('s3', 'sftp', etc.).
     *
     * @param {string} opts.host - The name or IP address of the remote host.
     *
     * @param {string} opts.port - The port to connect to on the remote host.
     * A value of zero means use the default port.
     *
     * @param {string} opts.bucket - The bucket or root folder into which
     * to upload material.
     *
     * @param {string} opts.login - The user name or AWS Access Key ID to use
     * when authenticating with the remote service. To avoid storing sensitive
     * info in DART's data files, you can specify an environment variable
     * here by using "env:VAR_NAME". When performing upload operations, DART
     * will substitute the value of the environment variable "VAR_NAME".
     *
     * @param {string} opts.password - The password or AWS Secret Access Key
     * to use when authenticating with the remote service. You can specify
     * an environment variable here by using "env:VAR_NAME". When performing
     * upload operations, DART will substitute the value of the environment
     * variable "VAR_NAME".
     *
     * @param {string} opts.loginExtra - Optional additional information to
     * pass to the remote service during the authentication process. You can
     * specify an environment variable here by using "env:VAR_NAME". When
     * performing upload operations, DART will substitute the value of the
     * environment variable "VAR_NAME".
     *
     */
    constructor(opts = {}) {
        opts.required = ["name", "protocol", "host"];
        super(opts);
        /**
          * name is the name of this storage service. It should be meaningful
          * to the user.
          *
          * @type {string}
          */
        this.name = opts.name || "";
        /**
          * A description of this storage service. It should be meaningful
          * to the user.
          *
          * @type {string}
          */
        this.description = opts.description || "";
        /**
          * The protocol to use when connecting to the remote service.
          * For example, 's3', 'sftp', etc. There should be a valid plugin
          * capable of communicating via that protocol.
          *
          * @type {string}
          */
        this.protocol = opts.protocol || "";
        /**
          * The hostname or IP address of the remote server.
          *
          * @type {string}
          */
        this.host = opts.host || "";
        /**
          * The port number to connect to on the remote server. This should
          * be a whole number. You can leave this blank if you're connecting
          * to the service's default port.
          *
          * @type {number}
          */
        this.port = opts.port || 0;
        /**
          * Bucket is the name of the s3 bucket to connect to, or the directory
          * to cd into on the remote server.
          *
          * @type {string}
          */
        this.bucket = opts.bucket || "";
        /**
          * login is the name to use when logging in to the remote server.
          * For s3 connections, it's the Access Key Id. You can use an
          * environment variable in this field by specifying "env:VAR_NAME".
          * DART will look up the environment variable at runtime, and its
          * value will not be stored with DART's data.
          *
          * @type {string}
          */
        this.login = opts.login || "";
        /**
          * password is the password required to connect to the remote server.
          * For S3, it's the secret key (aka AWS Secret Access Key). You can use
          * an environment variable in this field by specifying "env:VAR_NAME".
          * DART will look up the environment variable at runtime, and its
          * value will not be stored with DART's data.
          *
          * @type {string}
          */
        this.password = opts.password || "";
        /**
          * loginExtra is any additional information required by plugins to
          * connect to remote services. What the plugin does with this bit of info
          * is its own business. For example, a plugin that requires the path to
          * a local private key file can ask the user to enter the path to that
          * file here. You can use an environment variable in this field by
          * specifying a value like "env:VAR_NAME". DART will look up the environment
          * variable at runtime, and its value will not be stored with DART's data.
          *
          * @type {string}
          */
        this.loginExtra = opts.loginExtra || "";
        /**
         * This describes whether the storage service allows users to
         * write data/files into it.
         *
         * @type {boolean}
         * @default true
         */
        this.allowsUpload = opts.allowsUpload || true;
        /**
         * This describes whether the storage service allows users to
         * read data/files from it.
         *
         * @type {boolean}
         * @default true
         */
        this.allowsDownload = opts.allowsDownload || true;
    }

    /**
     * Returns the url to which files will be uploaded.
     *
     * @returns {string}
     */
    url(filename = '') {
        // TODO: Branch for protocols that construct URLs differently.
        let portString = this.port ? `:${this.port}` : '';
        return `${this.protocol}://${this.host}${portString}/${this.bucket}/${filename}`
    }

    /**
     * validate returns true or false, indicating whether this object
     * contains complete and valid data. If it returns false, check
     * the errors property for specific errors.
     *
     * @returns {boolean}
     */
    validate() {
        super.validate();
        if (Util.isEmpty(this.port)) {
            this.port = 0
        }
        if (!Util.isEmpty(this.port) && this.port != 0 && parseInt(this.port, 10) != this.port) {
            this.errors["port"] = "Port must be a whole number, or leave at zero to use the default port.";
        }
        return Object.keys(this.errors).length == 0;
    }

    /**
     * Returns true if this storage service setting contains a
     * plaintext password. When exporting workflows, storage service
     * should use "env:VAR_NAME" to read passwords from the environment.
     *
     * @returns {boolean}
     *
     */
    hasPlaintextPassword() {
        return this.password.trim() != "" && !this.password.startsWith('env:')
    }

    /**
     * Returns true if this storage service setting contains a
     * plaintext login. When exporting workflows, storage service
     * should use "env:VAR_NAME" to read passwords from the environment.
     *
     * @returns {boolean}
     *
     */
    hasPlaintextLogin() {
        return this.login.trim() != "" && !this.login.startsWith('env:')
    }

    /**
     * This converts a generic object into an StorageService
     * object. this is useful when loading objects from JSON.
     *
     * @param {object} data - An object you want to convert to
     * a StorageService.
     *
     * @returns {StorageService}
     *
     */
    static inflateFrom(data) {
        let setting = new StorageService();
        Object.assign(setting, data);
        return setting;
    }

}

// Get static methods from base class.
Object.assign(StorageService, PersistentObject);

module.exports.StorageService = StorageService;