Skip to content

Commit

Permalink
Move raise of UnsupportedTargetOS to TargetHost
Browse files Browse the repository at this point in the history
This continues the move of consolidating all target-related
lookups and translations into TargetHost, which is the only place
that should need to know about them.

This change also adds test coverage for the new TargetHost
methods get_chef_version_manifest, base_os, and installed_chef_version.
  • Loading branch information
marcparadise committed May 10, 2018
1 parent d85af3f commit cc3f341
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 49 deletions.
15 changes: 8 additions & 7 deletions components/chef-workstation/i18n/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,6 @@ errors:
UNKNOWN: An unknown error has occurred.

# Installer action errors
CHEFINS001: |
'%1' is not a supported target operating system at this time.
We plan to support a range of target operating systems,
but during this targeted beta we are constraining our efforts
to Windows and Linux.
CHEFINS002: |
The target does not have chef-client installed.
Expand Down Expand Up @@ -373,6 +366,14 @@ errors:
%1
# Errors relating to target state:
CHEFTARG001: |
'%1' is not a supported target operating system at this time.
We plan to support a range of target operating systems,
but during this targeted beta we are constraining our efforts
to Windows and Linux.
footer:
both: |
If you are not able to resolve this issue, please contact Chef support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,9 @@
module ChefWorkstation::Action::InstallChef
def self.instance_for_target(target_host, opts = { check_only: false })
opts[:target_host] = target_host
p = target_host.platform
if p.family == "windows" # Family is reliable even when mocking; `windows?` is not.
Windows.new(opts)
elsif p.linux?
Linux.new(opts)
else
raise UnsupportedTargetOS.new(p.name)
case target_host.base_os
when :windows then Windows.new(opts)
when :linux then Linux.new(opts)
end
end

class UnsupportedTargetOS < ChefWorkstation::Error
def initialize(os_name); super("CHEFINS001", os_name); end
end

end
51 changes: 30 additions & 21 deletions components/chef-workstation/lib/chef-workstation/target_host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def base_os
elsif platform.linux?
:linux
else
:unknown
raise ChefWorkstation::TargetHost::UnsupportedTargetOS.new(platform.name)
end
end

Expand All @@ -86,36 +86,39 @@ def upload_file(local_path, remote_path)
backend.upload(local_path, remote_path)
end

# Returns the installed chef version as a Gem::Version,
# or raised ChefNotInstalled if chef client version manifest can't
# be found.
def installed_chef_version
return @installed_chef_version if @installed_chef_version

manifest_path = case base_os()
when :windows then windows_version_manifest_path()
when :linux then linux_version_manifest_path()
# else TODO - raise for unsupported OS, move it over from
# installer action
end
manifest_content = backend.file(manifest_path).content
# TODO - handle where it is installed, but at such an old version there is no manifest?
raise ChefNotInstalled.new if manifest_content.nil?
manifest = JSON.parse(manifest_content)
# Note: In the case of a very old version of chef (that has no manifest - pre 12.0?)
# this will report as not installed.
manifest = get_chef_version_manifest()
raise ChefNotInstalled.new if manifest == :not_found
# We'll split the version here because unstable builds (where we currently
# install from) are in the form "Major.Minor.Build+HASH" which is not a valid
# version string.
@installed_chef_version = Gem::Version.new(manifest["build_version"].split("+")[0])
end

def windows_version_manifest_path
"c:\\opscode\\chef\\version-manifest.json'"
end

def linux_version_manifest_path
"/opt/chef/version-manifest.json"
MANIFEST_PATHS = {
# TODO - use a proper method to query the win installation path -
# currently we're assuming the default, but this can be customized
# at install time.
windows: "c:\\opscode\\chef\\version-manifest.json",
linux: "/opt/chef/version-manifest.json"
}

def get_chef_version_manifest
path = MANIFEST_PATHS[base_os()]
content = backend.file(path).content
if content
JSON.parse(content)
else
:not_found
end
end

# For internal capture, so it does not descend from an Workstation error
class ChefNotInstalled < StandardError; end

class RemoteExecutionFailed < ChefWorkstation::ErrorNoLogs
attr_reader :stdout, :stderr
def initialize(host, command, result)
Expand All @@ -126,5 +129,11 @@ def initialize(host, command, result)
result.stderr.empty? ? result.stdout : result.stderr)
end
end

class ChefNotInstalled < StandardError; end

class UnsupportedTargetOS < ChefWorkstation::Error
def initialize(os_name); super("CHEFTARG001", os_name); end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,5 @@
expect(inst).to be_a installer::Linux
end
end

context "unsupported target" do
it "should raise UnsupportedTargetOS" do
expected_error = ChefWorkstation::Action::InstallChef::UnsupportedTargetOS
expect { installer.instance_for_target(target_host) }.to raise_error expected_error
end
end
end
end
110 changes: 108 additions & 2 deletions components/chef-workstation/spec/unit/target_host_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,58 @@
let(:host) { "mock://example.com" }
let(:sudo) { true }
let(:logger) { nil }
subject(:subject) { ChefWorkstation::TargetHost.new(host, sudo: sudo, logger: logger) }
subject { ChefWorkstation::TargetHost.new(host, sudo: sudo, logger: logger) }

context "#base_os" do
before do
platform_mock = double("platform", linux?: is_linux, family: family, name: "an os")
allow(subject).to receive(:platform).and_return platform_mock
end

context "for a windows os" do
let(:family) { "windows" }
let(:is_linux) { false }
it "reports :windows" do
expect(subject.base_os).to eq :windows
end
end

context "for a linux os" do
let(:family) { "debian" }
let(:is_linux) { true }
it "reports :linux" do
expect(subject.base_os).to eq :linux
end
end

context "for an unsupported OS" do
let(:family) { "other" }
let(:is_linux) { false }
it "raises UnsupportedTargetOS" do
expect { subject.base_os }.to raise_error(ChefWorkstation::TargetHost::UnsupportedTargetOS)
end
end
end

context "#installed_chef_version" do
let(:manifest) { :not_found }
before do
allow(subject).to receive(:get_chef_version_manifest).and_return manifest
end

context "when no version manifest is present" do
it "raises ChefNotInstalled" do
expect { subject.installed_chef_version }.to raise_error(ChefWorkstation::TargetHost::ChefNotInstalled)
end
end

context "when version manifest is present" do
let(:manifest) { { "build_version" => "14.0.1" } }
it "reports version based on the build_version field" do
expect(subject.installed_chef_version).to eq Gem::Version.new("14.0.1")
end
end
end

context "#run_command!" do
let(:backend) { double("backend") }
Expand All @@ -27,7 +78,62 @@
context "when an error occurs" do
let(:exit_status) { 1 }
it "raises a RemoteExecutionFailed error" do
expect { subject.run_command!("invalid") }.to raise_error ChefWorkstation::TargetHost::RemoteExecutionFailed
expected_error = ChefWorkstation::TargetHost::RemoteExecutionFailed
expect { subject.run_command!("invalid") }.to raise_error(expected_error)
end
end
end

context "#get_chef_version_manifest" do
let(:manifest_content) { nil }
let(:expected_manifest_path) do
{
windows: "c:\\opscode\\chef\\version-manifest.json",
linux: "/opt/chef/version-manifest.json"
}
end
let(:base_os) { :unknown }
before do
remote_file_mock = double("remote_file", content: manifest_content)
backend_mock = double("backend")
expect(backend_mock).to receive(:file).
with(expected_manifest_path[base_os]).
and_return(remote_file_mock)
allow(subject).to receive(:backend).and_return backend_mock
allow(subject).to receive(:base_os).and_return base_os
end

context "when manifest is missing" do
context "on windows" do
let(:base_os) { :windows }
it "returns :not_found" do
expect(subject.get_chef_version_manifest).to eq :not_found
end

end
context "on linux" do
let(:base_os) { :linux }
it "returns :not_found" do
expect(subject.get_chef_version_manifest).to eq :not_found
end
end
end

context "when manifest is present" do
context "on windows" do
let(:base_os) { :windows }
let(:manifest_content) { '{"build_version" : "1.2.3"}' }
it "should return the parsed manifest" do
expect(subject.get_chef_version_manifest).to eq({ "build_version" => "1.2.3" })
end
end

context "on linux" do
let(:base_os) { :linux }
let(:manifest_content) { '{"build_version" : "1.2.3"}' }
it "should return the parsed manifest" do
expect(subject.get_chef_version_manifest).to eq({ "build_version" => "1.2.3" })
end
end
end
end
Expand Down

0 comments on commit cc3f341

Please sign in to comment.