Merge branch 'f-machine-readable'

This adds the foundation for supporting machine-readable output. This
purposely doesn't add much machine-readable output, because we want to
learn from the community what people want.

This also isn't a full API for controlling Vagrant. Rather, it is a way
to programmatically script Vagrant commands.
This commit is contained in:
Mitchell Hashimoto 2013-11-24 11:45:06 -08:00
commit aa02c64b0a
8 changed files with 271 additions and 55 deletions

View File

@ -55,6 +55,12 @@ if ARGV.include?("--color")
opts[:ui_class] = Vagrant::UI::Colored opts[:ui_class] = Vagrant::UI::Colored
end end
# Highest precedence is if we have enabled machine-readable output
if ARGV.include?("--machine-readable")
ARGV.delete("--machine-readable")
opts[:ui_class] = Vagrant::UI::MachineReadable
end
# Default to colored output # Default to colored output
opts[:ui_class] ||= Vagrant::UI::Colored opts[:ui_class] ||= Vagrant::UI::Colored

View File

@ -31,6 +31,14 @@ module Vagrant
define_method(method) { |*args| } define_method(method) { |*args| }
end end
# For machine-readable output.
#
# @param [String] type The type of the data
# @param [Array] data The data associated with the type
def machine(type, *data)
@logger.info("Machine: #{type} #{data.inspect}")
end
# Returns a new UI class that is scoped to the given resource name. # Returns a new UI class that is scoped to the given resource name.
# Subclasses can then use this scope name to do whatever they please. # Subclasses can then use this scope name to do whatever they please.
# #
@ -51,6 +59,46 @@ module Vagrant
end end
end end
class MachineReadable < Interface
include Util::SafePuts
def initialize
super
@lock = Mutex.new
end
def ask(*args)
super
# Machine-readable can't ask for input
raise Errors::UIExpectsTTY
end
def machine(type, *data)
opts = {}
opts = data.pop if data.last.kind_of?(Hash)
target = opts[:scope] || ""
# Prepare the data by replacing characters that aren't outputted
data.each_index do |i|
data[i] = data[i].to_s
data[i].gsub!(",", "%!(VAGRANT_COMMA)")
data[i].gsub!("\n", "\\n")
data[i].gsub!("\r", "\\r")
end
@lock.synchronize do
safe_puts("#{Time.now.utc.to_i},#{target},#{type},#{data.join(",")}")
end
end
def scope(scope_name)
BasicScope.new(self, scope_name)
end
end
# This is a UI implementation that outputs the text as is. It # This is a UI implementation that outputs the text as is. It
# doesn't add any color. # doesn't add any color.
class Basic < Interface class Basic < Interface
@ -172,6 +220,14 @@ module Vagrant
# By default do nothing, these aren't logged # By default do nothing, these aren't logged
define_method(method) { |*args| @ui.send(method, *args) } define_method(method) { |*args| @ui.send(method, *args) }
end end
def machine(type, *data)
opts = {}
opts = data.pop if data.last.is_a?(Hash)
opts[:scope] = @scope
data << opts
@ui.machine(type, *data)
end
end end
# This is a UI implementation that outputs color for various types # This is a UI implementation that outputs color for various types

View File

@ -30,6 +30,9 @@ module VagrantPlugins
# and which don't, since we plan on doing that transparently. # and which don't, since we plan on doing that transparently.
boxes.each do |name, provider, _v1| boxes.each do |name, provider, _v1|
@env.ui.info("#{name.ljust(longest_box_length)} (#{provider})", :prefix => false) @env.ui.info("#{name.ljust(longest_box_length)} (#{provider})", :prefix => false)
@env.ui.machine("box-name", name)
@env.ui.machine("box-provider", provider)
end end
# Success, exit status 0 # Success, exit status 0

View File

@ -25,7 +25,15 @@ module VagrantPlugins
results = [] results = []
with_target_vms(argv) do |machine| with_target_vms(argv) do |machine|
state = machine.state if !state state = machine.state if !state
results << "#{machine.name.to_s.ljust(max_name_length)} #{machine.state.short_description} (#{machine.provider_name})" current_state = machine.state
results << "#{machine.name.to_s.ljust(max_name_length)} " +
"#{current_state.short_description} (#{machine.provider_name})"
opts = { scope: machine.name.to_s }
@env.ui.machine("provider-name", machine.provider_name, opts)
@env.ui.machine("state", current_state.id, opts)
@env.ui.machine("state-human-short", current_state.short_description, opts)
@env.ui.machine("state-human-long", current_state.long_description, opts)
end end
message = nil message = nil

View File

@ -119,6 +119,7 @@
<li<%= sidebar_current("cli-status") %>><a href="/v2/cli/status.html">status</a></li> <li<%= sidebar_current("cli-status") %>><a href="/v2/cli/status.html">status</a></li>
<li<%= sidebar_current("cli-suspend") %>><a href="/v2/cli/suspend.html">suspend</a></li> <li<%= sidebar_current("cli-suspend") %>><a href="/v2/cli/suspend.html">suspend</a></li>
<li<%= sidebar_current("cli-up") %>><a href="/v2/cli/up.html">up</a></li> <li<%= sidebar_current("cli-up") %>><a href="/v2/cli/up.html">up</a></li>
<li<%= sidebar_current("cli-machinereadable") %>><a href="/v2/cli/machine-readable.html">Machine Readable Output</a></li>
</ul> </ul>
<% end %> <% end %>

View File

@ -86,7 +86,7 @@ h6 {
line-height: @font-size; line-height: @font-size;
} }
p { p, td {
letter-spacing: 1px; letter-spacing: 1px;
line-height: 32px; line-height: 32px;

View File

@ -1,5 +1,11 @@
/* compontents */ /* compontents */
table.mr-types {
th.mr-type {
width: 200px;
}
}
//search //search
.search-bar { .search-bar {
border-bottom: 1px solid fade(@black, 10%); border-bottom: 1px solid fade(@black, 10%);

View File

@ -0,0 +1,136 @@
---
page_title: "Machine Readable Output - Command-Line Interface"
sidebar_current: "cli-machinereadable"
---
# Machine Readable Output
Every Vagrant commands accepts a `--machine-readable` flag which enables
machine readable output mode. In this mode, the output to the terminal
is replaced with machine-friendly output.
This mode makes it easy to programmatically execute Vagrant and read data
out of it. This output format is protected by our
[backwards compatibility](/v2/installation/backwards-compatibility.html)
policy. Until Vagrant 2.0 is released, however, the machine readable output
may change as we determine more use cases for it. But the backwards
compatibility promise should make it safe to write client libraries to
parse the output format.
<div class="alert alert-block alert-warn">
<p>
<strong>Advanced topic!</strong> This is an advanced topic for use only if
you want to programmatically execute Vagrant. If you're just getting started
with Vagrant, you may safely skip this section.
</p>
</div>
## Work-In-Progress
The machine-readable output is very new (released as part of Vagrant 1.4).
We're still gathering use cases for it and building up the output for each
of the commands. It is likely that what you may want to achieve with
the machine-readable output is not possible due to missing information.
In this case, we ask that you please
[open an issue](https://github.com/mitchellh/vagrant/issues)
requesting that certain information become available. We'll most likely add
it!
## Format
The machine readable format is a line-oriented, comma-delimeted text format.
This makes it extremely easy to parse using standard Unix tools such as awk or
grep in addition to full programming languages like Ruby or Python.
The format is:
```
timestamp,target,type,data...
```
Each component is explained below:
* **timestamp** is a Unix timestamp in UTC of when the message was printed.
* **target** is the target of the following output. This is empty if the
message is related to Vagrant globally. Otherwise, this is generally a machine
name so you can relate output to a specific machine when multi-VM is in use.
* **type** is the type of machine-readable message being outputted. There are
a set of standard types which are covered later.
* **data** is zero or more comma-seperated values associated with the prior
type. The exact amount and meaning of this data is type-dependent, so you
must read the documentation associated with the type to understand fully.
Within the format, if data contains a comma, it is replaced with
`%!(VAGRANT_COMMA)`. This was preferred over an escape character such as \'
because it is more friendly to tools like awk.
Newlines within the format are replaced with their respective standard escape
sequence. Newlines become a literal `\n` within the output. Carriage returns
become a literal `\r`.
## Types
This section documents all the available types that may be outputted
with the machine-readable output.
<table class="table table-hover table-bordered mr-types">
<thead>
<tr>
<th class="mr-type">Type</th>
<th>Description</th>
</tr>
</thead>
<tr>
<td>box-name</td>
<td>
Name of a box installed into Vagrant.
</td>
</tr>
<tr>
<td>box-provider</td>
<td>
Provider for an installed box.
</td>
</tr>
<tr>
<td>provider-name</td>
<td>
The provider name of the target machine.
<span class="label">targetted</span>
</td>
</tr>
<tr>
<td>state</td>
<td>
The state ID of the target machine.
<span class="label">targetted</span>
</td>
</tr>
<tr>
<td>state-human-long</td>
<td>
Human-readable description of the state of the machine. This is the
long version, and may be a paragraph or longer.
<span class="label">targetted</span>
</td>
</tr>
<tr>
<td>state-human-short</td>
<td>
Human-readable description of the state of the machine. This is the
short version, limited to at most a sentence.
<span class="label">targetted</span>
</td>
</tr>
</table>