Merge pull request #9380 from chrisroberts/e-checkpoint

Update checkpoint integration
This commit is contained in:
Chris Roberts 2018-01-19 15:06:28 -08:00 committed by GitHub
commit f31badebb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 384 additions and 41 deletions

View File

@ -4,12 +4,14 @@ require 'optparse'
module Vagrant module Vagrant
# Manages the command line interface to Vagrant. # Manages the command line interface to Vagrant.
class CLI < Vagrant.plugin("2", :command) class CLI < Vagrant.plugin("2", :command)
def initialize(argv, env) def initialize(argv, env)
super super
@logger = Log4r::Logger.new("vagrant::cli") @logger = Log4r::Logger.new("vagrant::cli")
@main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv)
Util::CheckpointClient.instance.setup(env).check
@logger.info("CLI: #{@main_args.inspect} #{@sub_command.inspect} #{@sub_args.inspect}") @logger.info("CLI: #{@main_args.inspect} #{@sub_command.inspect} #{@sub_args.inspect}")
end end
@ -36,6 +38,8 @@ module Vagrant
command_class = command_plugin[0].call command_class = command_plugin[0].call
@logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}")
Util::CheckpointClient.instance.display
# Initialize and execute the command class, returning the exit status. # Initialize and execute the command class, returning the exit status.
result = 0 result = 0
begin begin

View File

@ -4,7 +4,6 @@ require 'pathname'
require 'set' require 'set'
require 'thread' require 'thread'
require "checkpoint"
require 'log4r' require 'log4r'
require 'vagrant/util/file_mode' require 'vagrant/util/file_mode'
@ -128,34 +127,6 @@ module Vagrant
# Prepare the directories # Prepare the directories
setup_home_path setup_home_path
# Run checkpoint in a background thread on every environment
# initialization. The cache file will cause this to mostly be a no-op
# most of the time.
@checkpoint_thr = Thread.new do
Thread.current[:result] = nil
# If we disabled checkpoint via env var, don't run this
if ENV["VAGRANT_CHECKPOINT_DISABLE"].to_s != ""
@logger.info("checkpoint: disabled from env var")
next
end
# If we disabled state and knowing what alerts we've seen, then
# disable the signature file.
signature_file = @data_dir.join("checkpoint_signature")
if ENV["VAGRANT_CHECKPOINT_NO_STATE"].to_s != ""
@logger.info("checkpoint: will not store state")
signature_file = nil
end
Thread.current[:result] = Checkpoint.check(
product: "vagrant",
version: VERSION,
signature_file: signature_file,
cache_file: @data_dir.join("checkpoint_cache"),
)
end
# Setup the local data directory. If a configuration path is given, # Setup the local data directory. If a configuration path is given,
# it is expanded relative to the root path. Otherwise, we use the # it is expanded relative to the root path. Otherwise, we use the
# default (which is also expanded relative to the root path). # default (which is also expanded relative to the root path).
@ -289,16 +260,6 @@ module Vagrant
end end
end end
# Checkpoint returns the checkpoint result data. If checkpoint is
# disabled, this will return nil. See the hashicorp-checkpoint gem
# for more documentation on the return value.
#
# @return [Hash]
def checkpoint
@checkpoint_thr.join(THREAD_MAX_JOIN_TIMEOUT)
return @checkpoint_thr[:result]
end
# Makes a call to the CLI with the given arguments as if they # Makes a call to the CLI with the given arguments as if they
# came from the real command line (sometimes they do!). An example: # came from the real command line (sometimes they do!). An example:
# #

View File

@ -1,6 +1,7 @@
module Vagrant module Vagrant
module Util module Util
autoload :Busy, 'vagrant/util/busy' autoload :Busy, 'vagrant/util/busy'
autoload :CheckpointClient, 'vagrant/util/checkpoint_client'
autoload :CommandDeprecation, 'vagrant/util/command_deprecation' autoload :CommandDeprecation, 'vagrant/util/command_deprecation'
autoload :Counter, 'vagrant/util/counter' autoload :Counter, 'vagrant/util/counter'
autoload :CredentialScrubber, 'vagrant/util/credential_scrubber' autoload :CredentialScrubber, 'vagrant/util/credential_scrubber'

View File

@ -0,0 +1,175 @@
require "log4r"
require "singleton"
module Vagrant
module Util
class CheckpointClient
include Singleton
# Maximum number of seconds to wait for check to complete
CHECKPOINT_TIMEOUT = 10
# @return [Log4r::Logger]
attr_reader :logger
# @return [Boolean]
attr_reader :enabled
# @return [Hash]
attr_reader :files
# @return [Vagrant::Environment]
attr_reader :env
def initialize
@logger = Log4r::Logger.new("vagrant::checkpoint_client")
@enabled = false
end
# Setup will attempt to load the checkpoint library and define
# required paths
#
# @param [Vagrant::Environment] env
# @return [self]
def setup(env)
begin
require "checkpoint"
@enabled = true
rescue LoadError
@logger.warn("checkpoint library not found. disabling.")
end
@files = {
signature: env.data_dir.join("checkpoint_signature"),
cache: env.data_dir.join("checkpoint_cache")
}
@checkpoint_thread = nil
@env = env
self
end
# Check has completed
def complete?
!@checkpoint_thread.nil? && !@checkpoint_thread.alive?
end
# Result of check
#
# @return [Hash, nil]
def result
if !enabled || @checkpoint_thread.nil?
nil
elsif !defined?(@result)
@checkpoint_thread.join(CHECKPOINT_TIMEOUT)
@result = @checkpoint_thread[:result]
else
@result
end
end
# Run check
#
# @return [self]
def check
if enabled && @checkpoint_thread.nil?
logger.debug("starting plugin check")
@checkpoint_thread = Thread.new do
begin
Thread.current[:result] = Checkpoint.check(
product: "vagrant",
version: VERSION,
signature_file: files[:signature],
cache_file: files[:cache]
)
if !Thread.current[:result].is_a?(Hash)
Thread.current[:result] = nil
end
logger.debug("plugin check complete")
rescue => e
logger.debug("plugin check failure - #{e}")
end
end
end
self
end
# Display any alerts or version update information
#
# @return [boolean] true if displayed, false if not
def display
if !defined?(@displayed)
if !complete?
@logger.debug("waiting for checkpoint to complete...")
end
# Don't display if information is cached
if result && !result["cached"]
version_check
alerts_check
else
@logger.debug("no information received from checkpoint")
end
@displayed = true
else
false
end
end
def alerts_check
if result["alerts"] && !result["alerts"].empty?
result["alerts"].group_by{|a| a["level"]}.each_pair do |_, alerts|
alerts.each do |alert|
date = nil
begin
date = Time.at(alert["date"])
rescue
date = Time.now
end
output = I18n.t("vagrant.alert",
message: alert["message"],
date: date,
url: alert["url"]
)
case alert["level"]
when "info"
alert_ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant")
alert_ui.info(output)
when "warn"
alert_ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant-warning")
alert_ui.warn(output)
when "critical"
alert_ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant-alert")
alert_ui.error(output)
end
end
env.ui.info("")
end
else
@logger.debug("no alert notifications to display")
end
end
def version_check
latest_version = Gem::Version.new(result["current_version"])
installed_version = Gem::Version.new(VERSION)
ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant")
if latest_version > installed_version
@logger.info("new version of Vagrant available - #{latest_version}")
ui.info(I18n.t("vagrant.version_upgrade_available", latest_version: latest_version))
env.ui.info("")
else
@logger.debug("vagrant is currently up to date")
end
end
# @private
# Reset the cached values for platform. This is not considered a public
# API and should only be used for testing.
def reset!
logger = @logger
instance_variables.each(&method(:remove_instance_variable))
@logger = logger
@enabled = false
end
end
end
end

View File

@ -22,7 +22,7 @@ module VagrantPlugins
@env.ui.machine("version-installed", Vagrant::VERSION) @env.ui.machine("version-installed", Vagrant::VERSION)
# Load the latest information # Load the latest information
cp = @env.checkpoint cp = Vagrant::Util::CheckpointClient.instance.result
if !cp if !cp
@env.ui.output("\n"+I18n.t( @env.ui.output("\n"+I18n.t(
"vagrant.version_no_checkpoint")) "vagrant.version_no_checkpoint"))

View File

@ -1,5 +1,10 @@
en: en:
vagrant: vagrant:
alert: |-
[%{date}]:
%{message}
- %{url}
boot_completed: |- boot_completed: |-
Machine booted and ready! Machine booted and ready!
boot_waiting: |- boot_waiting: |-
@ -283,6 +288,10 @@ en:
version_no_checkpoint: |- version_no_checkpoint: |-
Vagrant was unable to check for the latest version of Vagrant. Vagrant was unable to check for the latest version of Vagrant.
Please check manually at https://www.vagrantup.com Please check manually at https://www.vagrantup.com
version_upgrade_available: |-
A new version of Vagrant is available: %{latest_version}!
To upgrade visit: https://www.vagrantup.com/downloads.html
version_upgrade_howto: |- version_upgrade_howto: |-
To upgrade to the latest version, visit the downloads page and To upgrade to the latest version, visit the downloads page and
download and install the latest version of Vagrant from the URL download and install the latest version of Vagrant from the URL

View File

@ -1,6 +1,7 @@
require_relative "../base" require_relative "../base"
require "vagrant/cli" require "vagrant/cli"
require "vagrant/util"
describe Vagrant::CLI do describe Vagrant::CLI do
include_context "unit" include_context "unit"
@ -9,9 +10,21 @@ describe Vagrant::CLI do
let(:commands) { {} } let(:commands) { {} }
let(:iso_env) { isolated_environment } let(:iso_env) { isolated_environment }
let(:env) { iso_env.create_vagrant_env } let(:env) { iso_env.create_vagrant_env }
let(:checkpoint) { double("checkpoint") }
before do before do
allow(Vagrant.plugin("2").manager).to receive(:commands).and_return(commands) allow(Vagrant.plugin("2").manager).to receive(:commands).and_return(commands)
allow(Vagrant::Util::CheckpointClient).to receive(:instance).and_return(checkpoint)
allow(checkpoint).to receive(:setup).and_return(checkpoint)
allow(checkpoint).to receive(:check)
allow(checkpoint).to receive(:display)
end
describe "#initialize" do
it "should setup checkpoint" do
expect(checkpoint).to receive(:check)
described_class.new(["destroy"], env)
end
end end
describe "#execute" do describe "#execute" do
@ -35,6 +48,12 @@ describe Vagrant::CLI do
subject = described_class.new(["destroy"], env) subject = described_class.new(["destroy"], env)
expect(subject.execute).to eql(1) expect(subject.execute).to eql(1)
end end
it "displays any checkpoint information" do
commands[:destroy] = [command_lambda("destroy", 42), {}]
expect(checkpoint).to receive(:display)
described_class.new(["destroy"], env).execute
end
end end
describe "#help" do describe "#help" do

View File

@ -0,0 +1,174 @@
require File.expand_path("../../../base", __FILE__)
require "vagrant/util/checkpoint_client"
describe Vagrant::Util::CheckpointClient do
include_context "unit"
let(:iso_env) { isolated_environment }
let(:env) { iso_env.create_vagrant_env }
let(:result) { {} }
let(:prefixed_ui) { double("prefixed_ui") }
subject{ Vagrant::Util::CheckpointClient.instance }
after{ subject.reset! }
before do
allow(subject).to receive(:result).and_return(result)
allow(Vagrant::UI::Prefixed).to receive(:new).and_return(prefixed_ui)
end
it "should not be enabled by default" do
expect(subject.enabled).to be(false)
end
describe "#setup" do
before{ subject.setup(env) }
it "should enable after setup" do
expect(subject.enabled).to be(true)
end
it "should generate required paths" do
expect(subject.files).not_to be_empty
end
end
describe "#check" do
context "without #setup" do
it "should not start the check" do
expect(Thread).not_to receive(:new)
subject.check
end
end
context "with setup" do
before{ subject.setup(env) }
it "should start the check" do
expect(Thread).to receive(:new)
subject.check
end
it "should call checkpoint" do
expect(Thread).to receive(:new).and_yield
expect(Checkpoint).to receive(:check)
subject.check
end
end
end
describe "#display" do
it "should only dislay once" do
expect(subject).to receive(:version_check).once
expect(subject).to receive(:alerts_check).once
2.times{ subject.display }
end
it "should not display cached information" do
expect(subject).to receive(:result).and_return("cached" => true).at_least(:once)
expect(subject).not_to receive(:version_check)
expect(subject).not_to receive(:alerts_check)
subject.display
end
end
describe "#alerts_check" do
let(:critical){
[{"level" => "critical", "message" => "critical message",
"url" => "http://example.com", "date" => Time.now.to_i}]
}
let(:warn){
[{"level" => "warn", "message" => "warn message",
"url" => "http://example.com", "date" => Time.now.to_i}]
}
let(:info){
[{"level" => "info", "message" => "info message",
"url" => "http://example.com", "date" => Time.now.to_i}]
}
before{ subject.setup(env) }
context "with no alerts" do
it "should not display alerts" do
expect(prefixed_ui).not_to receive(:info)
subject.alerts_check
end
end
context "with critical alerts" do
let(:result) { {"alerts" => critical} }
it "should display critical alert" do
expect(prefixed_ui).to receive(:error)
subject.alerts_check
end
end
context "with warn alerts" do
let(:result) { {"alerts" => warn} }
it "should display warn alerts" do
expect(prefixed_ui).to receive(:warn)
subject.alerts_check
end
end
context "with info alerts" do
let(:result) { {"alerts" => info} }
it "should display info alerts" do
expect(prefixed_ui).to receive(:info)
subject.alerts_check
end
end
context "with mixed alerts" do
let(:result) { {"alerts" => info + warn + critical} }
it "should display all alert types" do
expect(prefixed_ui).to receive(:info)
expect(prefixed_ui).to receive(:warn)
expect(prefixed_ui).to receive(:error)
subject.alerts_check
end
end
end
describe "#version_check" do
before{ subject.setup(env) }
let(:new_version){ Gem::Version.new(Vagrant::VERSION).bump.to_s }
let(:old_version){ Gem::Version.new("1.0.0") }
context "latest version is same as current version" do
let(:result) { {"current_version" => Vagrant::VERSION } }
it "should not display upgrade information" do
expect(prefixed_ui).not_to receive(:info)
subject.version_check
end
end
context "latest version is older than current version" do
let(:result) { {"current_version" => old_version} }
it "should not display upgrade information" do
expect(prefixed_ui).not_to receive(:info)
subject.version_check
end
end
context "latest version is newer than current version" do
let(:result) { {"current_version" => new_version} }
it "should not display upgrade information" do
expect(prefixed_ui).not_to receive(:info).at_least(:once)
subject.version_check
end
end
end
end

View File

@ -20,7 +20,7 @@ Gem::Specification.new do |s|
s.add_dependency "erubis", "~> 2.7.0" s.add_dependency "erubis", "~> 2.7.0"
s.add_dependency "i18n", ">= 0.6.0", "<= 0.8.0" s.add_dependency "i18n", ">= 0.6.0", "<= 0.8.0"
s.add_dependency "listen", "~> 3.1.5" s.add_dependency "listen", "~> 3.1.5"
s.add_dependency "hashicorp-checkpoint", "~> 0.1.1" s.add_dependency "hashicorp-checkpoint", "~> 0.1.5"
s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11" s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11"
s.add_dependency "net-ssh", "~> 4.2.0" s.add_dependency "net-ssh", "~> 4.2.0"
s.add_dependency "net-sftp", "~> 2.1" s.add_dependency "net-sftp", "~> 2.1"