Add `container_spec` for containerized plugin commands

The `tap-mssql` plugin is a good example of a non-standard, non-python tap which would nevertheless be desirable for mainstream support. One way to support this is by adding a docker_image option in discovery.yml and work through whatever issues come up in that process.

Originally started as a discussion here: https://meltano.slack.com/archives/C013Z450LCD/p1596043957041600?thread_ts=1595992122.035100&cid=C013Z450LCD

--------------

## Update (2021-08-24)

-----------------

The original use case for this issue (java-based `singer/tap-mssql`) is no longer a priority since we now have a python-based `tap-mssql` implementation and there just haven't been many (any?) other taps or targets requested which were not pip installable.

A newer use case is Lightdash, which has a complex set of requirements and is recommended to be run via docker - or specifically via docker compose.

## Update ~~(2021-10-05)~~ ~~(2021-11-29)~~ (2021-12-07)

In discussions around this topic, it appears clear we need to provide docker and non-docker invocation methods for plugins so that a `--container=True` flag (or similar) could toggle the behavior to dockerized vs native invocation methods. The docker config is then additive to existing `executable` and `pip_url` inputs.

### New `container_spec` option for plugin commands

This feature would add a structured `container_spec` config option to each plugin command:

```yml
plugins:
    utilities:
        name: lightdash
        commands:
        - name: serve
          executable: "lightdash serve"  # ignored when running via docker
          container_spec:
            image: lightdash
            command: lightdash serve
            entrypoint: 
            env:
                ENVVAR1: foo
                ENVVAR2: bar
            ports:
            - 5000
            - 8080
            volumes:
            - ".:/project/myproject"
        - name: build
          executable: "lightdash build"  # ignored when running via docker
          container_spec:
            image: lightdash
            command: lightdash build
            entrypoint: 
            env:
                ENVVAR1: foo
                ENVVAR2: bar
            ports: [] # None needed
            volumes:
            - ".:/project/myproject"
```

### Supported `container_spec` options in V1:

* `image`
* `vars`
* `ports`
* `command`
* `entrypoint`
* `volumes` - or *at least* a container path in which to mount the root project dir

Only `image` is required. All other options are optional.

### New `--containers=true|false` flag for CLI commands `elt`, `invoke`, and `run`

The CLI commands `elt`, `invoke`, and `run` would all have a new `--containers` CLI arg. This arg has the following behaviors:

- `true`: This uses `container_spec` whenever it is available.
    - Important: `true` setting does not guarantee that all invocations will be run containerized. Any plugin or command that does not have a `container_spec` will be attempted to be invoked natively. If native invocation fails, the command will fail - as is the existing behavior today.
- `false`: This users local invocation (non-containerized) for all plugins. (Assumption being that there is no container runtime available on the machine.)
    - Note: If a plugin _only_ has a `container_spec` config and does not have any other means of being invoked, then the command will fail.

Note that if multiple plugins need to be invoked, they will share the same preference setting across the entire meltano CLI command.

#### Sample invocation

For instance, taking the following sample invocation: `meltano --containers=true run tap-gitlab target-snowflake dbt:run dbt:test lightdash:build` and assuming that only `tap-gitlab` and `lightdash:build` have a defined `container_spec`, the behavior will be as follows:

- `tap-gitlab` will be invoked within a container and its data will be passed to the STDIN of `target-snowflake`, which will be running natively.
- `dbt:run` and `dbt:test` will both be run natively, since no `container_spec` exists for those commands.
- `lightdash:build` will be run within a container.

Assuming the same command is run with `--containers=false`, and assuming `lightdash:build` does not also have an non-containerized `executable` set, then meltano will fail to build an execution plan and will abort immediately with something like:

> Execution of `lightdash:build` is not defined when container runtimes are disabled. To proceed, add a native `executable` definition to `lightdash:build` command or run with `--containers=true` to enable containerized execution.

Lastly, if `executable` and `container_spec` are both provided for the `lightdash:build` command, but the command is invoked natively (`--containers=false`) without the proper prerequisites installed, then the command will fail at the very end of the job when `meltano run` reaches the final `lightdash:build` step.

#### Optional CLI conventions we could support:

- `--containers` is short for `--containers=true`
- `--no-containers` is short for `--containers=false`


## Out of scope

### Tap and Target containerization

We will probably build support for general plugins (`transformers`, `orchestrators`, and `utilities`) first - or more specifically for plugin `commands` first.

Due to the additional complexity, containerized extractors and loaders would likely be a fast-follow on top of the first MVC release.

Unlike for general utilities, the `container_spec` config for extractors and loaders needs to be at the plugin level, not the command level, and any `command` value will likely be ignored by Meltano at runtime, in favor of the ways that Meltano already knows to invoke a tap, especially: `tap-mytap --discover --config=/path/to/config.json` (discovery) and `tap-mytap --config=/path/to/config.json` (sync).

### Compose Syntax and docker-compose.yml files

Out of scope for now, but we _might_ also support docker-compose in future:

```yml
plugins:
    utilities:
        name: lighdash
        commands:
        - name: serve
          container_spec:
            compose: lightdash.docker-compose.yml
```

Story points: 12.0