Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: Add VM-based container configuration section #949

Merged
merged 1 commit into from
Mar 31, 2018

Conversation

sameo
Copy link

@sameo sameo commented Feb 12, 2018

This adds a section to describe VM based container configurations to be
used by OCI runtimes using hardware virtualization to provide another
layer of isolation.

As part of this section we define 3 entries:

  • A virtual machine root image opbject. This is the guest image that
    contains the virtual machine root filesystem. The container image will
    be mounted on top of that filesystem.

  • A virtual machine kernel object. This is the kernel that will boot
    inside the virtual machine. The object describes the host kernel image
    path, additional parameters and an optional guest initrd for the
    kernel to use.

  • A virtual machine hypervisor object. This is the hypervisor that will
    manage the container virtual machine from the host. The object
    describe a hypervisor binary path and some additional parameters.

Signed-off-by: James O. D. Hunt james.o.hunt@intel.com
Signed-off-by: Samuel Ortiz sameo@linux.intel.com

Important note: This is a refresh of the very long standing PR #405.

@sameo
Copy link
Author

sameo commented Feb 12, 2018

@sameo
Copy link
Author

sameo commented Feb 12, 2018

cc @gnawux @bergwolf @laijs

Could you please provide some feedback here?

config-vm.md Outdated
Used by virtual-machine-based runtimes only.

* **`path`** (string, required) specifies the host path to the hypervisor used to manage the container virtual machine.
* **`parameters`** (string, optional) specifies a space-separated list of parameters to pass to the hypervisor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is going to be an array, can it be a JSON array (“array of strings”) instead of a single JSON string with internal space delimiters?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is supposed to be a parameter list that could be passed as is to the command, and would obviously be quite specific to that binary, what would be the advantage of using a JSON array?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is supposed to be a parameter list that could be passed as is to the command, and would obviously be quite specific to that binary...

So is execv's argv, and we use an array there.

... what would be the advantage of using a JSON array?

No need to quote/escape spaces within arguments and a simpler structure for removing/replacing arguments while composing the array.

config-vm.md Outdated
@@ -0,0 +1,49 @@
# <a name="VirtualMachineSpecificContainerConfiguration" /> Virtual-machine-specific Container Configuration

Virtual-machine-based containers require additional configuration to that specified in the [default spec configuration](config.md).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config.md isn't the “default” configuration, it's the (mostly) platform-agnostic configuration. It's not entirely platform-agnostic (e.g. hooks is POSIX-only), but I think we can do better than “default”. Maybe “base”? “main”?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

config-vm.md Outdated

This **optional** configuration is specified in a "VM" object:

* **`imagePath`** (string, required) host path to file that represents the root filesystem for the container virtual machine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“required” → “REQUIRED” (and similarly below, also “optional” → “OPTIONAL”. The casing needs to match the declarations here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

config-vm.md Outdated

* **`imagePath`** (string, REQUIRED) host path to file that represents the root filesystem for the container virtual machine.
* **`kernel`** (object, REQUIRED) specifies details of the kernel to boot the container virtual machine with.
* **`hypervisor`** (object, REQUIRED) specifies details of the hypervisor that manages the container virtual machine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do all VM-based runtimes support multiple hypervisors? I'd expect this to be OPTIONAL, with an implementation-defined default.

config-vm.md Outdated

This **optional** configuration is specified in a "VM" object:

* **`imagePath`** (string, REQUIRED) host path to file that represents the root filesystem for the container virtual machine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It (imagePath) should be optional.

imagePath and initrd are both booting media. We need to mark them both optional and document that at least one of them should be specified.

Moreover, since imagePath and initrd are both booting media, they are recommended to be put together. So we can move imagePath to the kernel section around the initrd field. After we do it, we may also need to rename kernel to boot and rename kernel.path to boot.kernel. Another reason why boot is better than kernel here is that bios or cbfs can be added to the boot section if any team strongly require them.

Finally, I like the name imagePath, but I didn't expect that it has ambiguous with the path field in the Root Configuration section. So I suggest to rename it to initImage, since it and initrd are almost the same functionality.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure you're familiar with that already, but an initrd is typically linked to a specific kernel build whereas the imagePath here is pointing at a kernel agnostic artifact. So although I agree it makes sense to make imagePath optional, I don't think it should be under the kernel section. As a matter of fact, I think the imagePath should be part of an image section where further down the road we could add an image type field.
I like the boot suggestion, especially because it could include a firmware path (this could very well be needed, I agree). But I'd prefer to have an optional firmware section instead.
I like the boot suggestion and

@sameo sameo force-pushed the vm-section branch 3 times, most recently from 0741390 to 5e63443 Compare February 14, 2018 12:42
@sameo
Copy link
Author

sameo commented Feb 14, 2018

@wking @laijs Updated with changes for your suggestions/comments.

@laijs
Copy link
Contributor

laijs commented Feb 15, 2018

LGTM

config-vm.md Outdated

Virtual-machine-based containers require additional configuration to that specified in the [base spec configuration](config.md).

This **optional** configuration is specified in a "VM" object:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line doesn't seem to have a precedent in the other platform-specific sections. Can we drop it? If we follow the Linux pattern, we'd have something like:

This section describes the schema for the [virtual-machine-specific section](config.md#platform-specific-configuration) of the [container configuration](config.md).
The virtual-machine container specification provides additional configuration for the hypervisor, kernel, and image.

config-vm.md Outdated

* **`hypervisor`** (object, OPTIONAL) specifies details of the hypervisor that manages the container virtual machine.
* **`kernel`** (object, REQUIRED) specifies details of the kernel to boot the container virtual machine with.
* **`image`** (object, OPTIONAL) specifies details of the image that contains the root filesystem for the container virtual machine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other platform-specific sections don't take this table-of-contents approach, and instead define the properties (hypervisor, etc.) in the section that describes their structure. For example, devices (linux.devices) is defined here, immediately after the Devices section header.

config-vm.md Outdated

## <a name="HypervisorObject" /> Hypervisor Object

Used by virtual-machine-based runtimes only.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a precedent for this line (which you also use in the following sections). I think we can drop the lines, because the semantics are implied by the presence of this section within the Virtual-machine-specific Container Configuration section.

config-vm.md Outdated

Used by virtual-machine-based runtimes only.

* **`path`** (string, REQUIRED) specifies the host path to the hypervisor used to manage the container virtual machine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MUST this path be absolute? If so, precedent suggests wording like:

This value MUST be an absolute path in the [runtime mount namespace](glossary.md#runtime-namespace).

or:

This value MUST be an absolute path.

If, on the other hand, relative paths are allowed, precedent suggests:

This value is either absolute or relative to the bundle.

I prefer “runtime mount namespace”, for which we have a glossary entry, over your current “host path”. The glossary entry is currently Linux-specific, but the idea is generic enough that we can generalize the entry. For this particular property, it's hard to imagine someone expecting a container mount namespace path, but since we've already defined terminology for this concept, I think we should use it.

config-vm.md Outdated

Used by virtual-machine-based runtimes only.

* **`path`** (string, REQUIRED) specifies the host path to the kernel used to boot the container virtual machine. This is an absolute path on the host.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To restrict to absolute paths, I think you want a MUST in there somewhere (otherwise validation tooling can't complain). See my earlier comment for examples of existing spec wording for this restriction.

config-vm.md Outdated

* **`path`** (string, REQUIRED) specifies the host path to the kernel used to boot the container virtual machine. This is an absolute path on the host.
* **`parameters`** (array of strings, OPTIONAL) specifies an array of parameters to pass to the kernel.
* **`initrd`** (string, OPTIONAL) specifies the host path to an initial ramdisk to be used by the container virtual machine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same path-wording issues as above. Are relative paths allowed? Do you want to use existing terminology to bind to the runtime mount namespace?

config-vm.md Outdated

Used by virtual-machine-based runtimes only.

* **`path`** (string, REQUIRED) specifies the absolute host path to the container virtual machine root image. This image contains the root filesystem that the virtual machine **`kernel`** will boot into, not to be confused with the container root filesystem itself. The latter, as specified by **`path`** from the [Root Configuration](config.md#Root-Configuration) section, will be mounted inside the virtual machine at a location chosen by the virtual-machine-based runtime.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same path-wording issues as above.

config-vm.md Outdated
* **`path`** (string, REQUIRED) specifies the absolute host path to the container virtual machine root image. This image contains the root filesystem that the virtual machine **`kernel`** will boot into, not to be confused with the container root filesystem itself. The latter, as specified by **`path`** from the [Root Configuration](config.md#Root-Configuration) section, will be mounted inside the virtual machine at a location chosen by the virtual-machine-based runtime.


## <a name="FullyPopulatedVMExample" /> Example of a fully-populated `VM` object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no precedent for “fully-populated”. I'd rather have per-vm-child-property examples like the other platform-specific sections. I have no preference between rooted examples (like the Windows section uses) or relative example (like the Solaris and Linux sections use). A fully-populated example could go in here. And if you decide to keep a full vm example here, I'd just title it “Example” and rely on section nesting to define the example scope.

config.md Outdated
@@ -462,6 +462,12 @@ Instead they MUST ignore unknown properties.
Runtimes that are reading or processing this configuration file MUST generate an error when invalid or unsupported values are encountered.
Unless support for a valid value is explicitly required, runtimes MAY choose which subset of the valid values it will support.

## <a name="VirtualMachine" />VM
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VM-specific section isn't quite platform-specific, but I think this needs to go into the platform-specific section because not all runtimes will be able to support these properties, and platforms are linked to the compliance language. This is one of the reasons I prefer “protocol” to “platform”.

You should also define the vm property wherever the Virtual Machine entry ends up in config.md.

@sameo sameo force-pushed the vm-section branch 3 times, most recently from 9ae489f to fb96e51 Compare February 16, 2018 11:08
@sameo
Copy link
Author

sameo commented Feb 16, 2018

@wking I think I addressed all your comments, thanks a lot for the thorough review.

@@ -199,6 +199,9 @@
},
"windows": {
"$ref": "config-windows.json#/windows"
},
"vm": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a non-standard indent. You can fix it with:

schema $ make fmt

@@ -349,6 +349,8 @@ For Windows based systems the user structure has the following fields:
This MUST be set if the target platform of this spec is `windows`.
* **`solaris`** (object, OPTIONAL) [Solaris-specific configuration](config-solaris.md).
This MAY be set if the target platform of this spec is `solaris`.
* **`vm`** (object, OPTIONAL) [Virtual-machine-specific configuration](config-vm.md).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll also want to add virtual-machine entries here and here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done now

// VM contains information for virtual-machine-based containers.
type VM struct {
// Hypervisor specifies hypervisor-related configuration for virtual-machine-based containers.
Hypervisor VMHypervisor `json:"hypervisor"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hypervisor is optional, so you'll want:

Hypervisor *VMHypervisor `json:"hypervisor,omitempty"`

And similarly for Image below.

{
"vm": {
"description": "configuration for virtual-machine-based containers",
"id": "https://opencontainers.org/schema/bundle/vm",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should drop these id entries (#945).

"id": "https://opencontainers.org/schema/bundle/vm",
"type": "object",
"required" : [
"kernel",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trailing comma is invalid JSON.

@sameo
Copy link
Author

sameo commented Feb 18, 2018

@wking All comments addressed, thanks.

@wking
Copy link
Contributor

wking commented Feb 19, 2018

3d8cce4 looks good to me. It doesn't add any new runtime requirements though, so there would be no way to check for runtime compliance. That's true for other portions of the spec as well though (e.g. most of the Linux cgroups config, #746), so it may not be an issue for maintainers. If it is an issue, we'll want additional language like:

Runtimes MUST execute the `hypervisor` with [IEEE Std 1003.1-2008 `execvp`'s *argv*][ieee-1003.1-2008-functions-exec] set to the union of `path` and `parameters`.

for hypervisor, and similarly for the other properties.

Actually, for hypervisor, I'd encourage reuse of the existing process or hooks schema, which would make life easier for implementations (see here where I called for those to be unified). If you use the hooks schema, you get very similar semantics to your existing hypervisor schema, except that you have args instead of parameters, and you gain env and timeout. You also get to punt on runtime requirements as “not a problem for this PR, because we're leaning on current spec language”. That seems like a win to me.

Copy link
Member

@vbatts vbatts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how simple this this

// VMImage contains information about the virtual machine root image.
type VMImage struct {
// Path is the host path to the root image that the VM kernel would boot into.
Path string `json:"path"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this leaving the type of that path completely up to the runtime? (qcow, raw, directory, etc)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, this image is a small disk and is used for booting only. Making it raw is enough.

By the way, I hope rootfs and volume support media types. But it is not the purpose of this PR.

@@ -349,6 +349,8 @@ For Windows based systems the user structure has the following fields:
This MUST be set if the target platform of this spec is `windows`.
* **`solaris`** (object, OPTIONAL) [Solaris-specific configuration](config-solaris.md).
This MAY be set if the target platform of this spec is `solaris`.
* **`vm`** (object, OPTIONAL) [Virtual-machine-specific configuration](config-vm.md).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done now

@mrunalp
Copy link
Contributor

mrunalp commented Mar 7, 2018

LGTM

Approved with PullApprove

@mrunalp
Copy link
Contributor

mrunalp commented Mar 7, 2018

@crosbymichael PTAL

@sameo sameo force-pushed the vm-section branch 2 times, most recently from 8eb8773 to 3e939f0 Compare March 7, 2018 23:55
config-vm.md Outdated
**`image`** (object, OPTIONAL) specifies details of the image that contains the root filesystem for the container virtual machine.
* **`path`** (string, REQUIRED) path to the container virtual machine root image.
This value MUST be an absolute path in the [runtime mount namespace](glossary.md#runtime-namespace).
* **`format`** (string, REQUIRED) format of the container virtual machine root image. The following root image formats are supported:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supported by what? Who? Support for formats seem like an implementation detail to me for various runtimes, not something that should be dictated by the spec.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crosbymichael Supported by the current major hypervisors. So you'd prefer to see a free form string that would only be a hint to the hypervisor?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the only worry I have is that people would read the spec and the certification committee will say that these are MUSTs that all runtimes have to support if we have it in the spec.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we change the wording to something like "commonly supported formats"?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be better indeed, thanks. Let me change that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, LGTM after this change

This adds a section to describe VM based container configurations to be
used by OCI runtimes using hardware virtualization to provide another
layer of isolation.

As part of this section we define 3 entries:

- A virtual machine root image opbject. This is the guest image that
  contains the virtual machine root filesystem. The container image will
  be mounted on top of that filesystem.

- A virtual machine kernel object. This is the kernel that will boot
  inside the virtual machine. The object describes the host kernel image
  path, additional parameters and an optional guest initrd for the
  kernel to use.

- A virtual machine hypervisor object. This is the hypervisor that will
  manage the container virtual machine from the host. The object
  describe a hypervisor binary path and some additional parameters.

Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
@crosbymichael
Copy link
Member

crosbymichael commented Mar 9, 2018

LGTM

Approved with PullApprove

@crosbymichael
Copy link
Member

Thanks!

@wking
Copy link
Contributor

wking commented Mar 9, 2018

Now that two separate maintainers have (at various points) LGTMed this PR, I'm getting jumpy about it landing without sameo#1 getting a look over. I've just rebased that on top of is PR's current 74b670e, but I'd really like to avoid a third process-launching schema in hypervisor (wking/opencontainer-runtime-spec@edd501a6 shrinks the repo by 19 lines), so if there's anything I can do to make the sub-PR more appealing, let me know.

@sameo
Copy link
Author

sameo commented Mar 12, 2018

@wking If maintainers want to re-use existing process schema, I'd much prefer to go with wking/opencontainer-runtime-spec@edd501a . The Process description is fairly container specific but we've contemplated containerizing the hypervisor in the past anyway. The only concern I have about it is User being a mandatory field. In most cases the hypervisor/VMM will be called from a runtime implementation (kata-runtime binary or a CRI implementation) and will likely want to run under the same user as the caller, under the same user namespace. Forcing caller to specify this is a little redundant and cumbersome, or is there a way to specify that implicitly?

@wking
Copy link
Contributor

wking commented Mar 12, 2018

If maintainers want to re-use existing process schema, I'd much prefer to go with wking/opencontainer-runtime-spec@edd501a .

I agree. The commit after that was because @crosbymichael specifically requested a copy/paste implementation and not edd501a's reference. I'm hoping I misunderstood, or that he changes his mind after seeing both approaches.

The only concern I have about it is User being a mandatory field.

I'm in favor of making it optional, with unset user properties meaning "don't change this". Previous discussion in #417, #489, #618. It's been required since 8af3eb6. A possible compromise would be removing it from hypervisor, as I'm already doing for the terminal properties in edd501a.

@sameo
Copy link
Author

sameo commented Mar 12, 2018

A possible compromise would be removing it from hypervisor

You mean by specifying that it MUST NOT be configured ?

@wking
Copy link
Contributor

wking commented Mar 12, 2018

A possible compromise would be removing it from hypervisor

You mean by specifying that it MUST NOT be configured?

Yeah, at least as long as it's required for process.

@crosbymichael
Copy link
Member

ping @mrunalp can you review again?

@sameo
Copy link
Author

sameo commented Mar 16, 2018

@crosbymichael @mrunalp @stevvooe Could you also please let us know if you want to use the process schema @wking is proposing or not?

@crosbymichael
Copy link
Member

@sameo I think it's fine as is. The majority of the fields on Process do not apply here.

@wking
Copy link
Contributor

wking commented Mar 21, 2018

I think it's fine as is. The majority of the fields on Process do not apply here.

So MUST more of them to be unset? Or recycle the hook structure instead? I'd still like to have this process-launching structure avoid being a third, incompatible way to set argv.

wking added a commit to wking/opencontainer-runtime-spec that referenced this pull request Mar 21, 2018
We already have two ways to specify a process to launch (for the
container process and for hooks).  This commit recycles the
hook-process schema for launcing the hypervisor.  I prefer using the
more-fundamental container process schema, but Michael felt it had too
many properties that wouldn't apply to the hypervisor-launching case
[1].

[1]: opencontainers#949 (comment)

Signed-off-by: W. Trevor King <wking@tremily.us>
wking added a commit to wking/opencontainer-runtime-spec that referenced this pull request Mar 21, 2018
We already have two ways to specify a process to launch (for the
container process and for hooks).  This commit recycles the
hook-process schema for launcing the hypervisor.  I prefer using the
more-fundamental container process schema, but Michael felt it had too
many properties that wouldn't apply to the hypervisor-launching case
[1].

[1]: opencontainers#949 (comment)

Signed-off-by: W. Trevor King <wking@tremily.us>
@wking
Copy link
Contributor

wking commented Mar 21, 2018

Or recycle the hook structure instead?

I've filed sameo#2 in case folks want to see what this looks like. With this approach, runtimes can use their existing hook-launching code (although they won't be writing the container state to stdin for hypervisor). They also gain access to env and timeout, and we gain the POSIX references for the path and args definitions while dropping the repository size by 18 lines.

@crosbymichael
Copy link
Member

ping @opencontainers/runtime-spec-maintainers

// VM contains information for virtual-machine-based containers.
type VM struct {
// Hypervisor specifies hypervisor-related configuration for virtual-machine-based containers.
Hypervisor VMHypervisor `json:"hypervisor,omitempty"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Hypervisor is ever set in a container spec, it can only be run by the same hypervisor type and the same deployment. Is it acceptable from OCI point of view?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, good point. Shouldn't the hypervisor be exec'd with the correct path and args anyways from a runtime?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been spun off into #964.

@hqhq
Copy link
Contributor

hqhq commented Mar 31, 2018

LGTM

Approved with PullApprove

@hqhq hqhq merged commit d362ed3 into opencontainers:master Mar 31, 2018
wking added a commit to wking/opencontainer-runtime-spec that referenced this pull request Apr 2, 2018
We already have two ways to specify a process to launch (for the
container process and for hooks).  This commit recycles the
hook-process schema for launching the hypervisor.  I prefer using the
more-fundamental container process schema, but Michael felt it had too
many properties that wouldn't apply to the hypervisor-launching case
[1].

[1]: opencontainers#949 (comment)

Signed-off-by: W. Trevor King <wking@tremily.us>
@vbatts vbatts mentioned this pull request Jan 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants