diff options
-rwxr-xr-x | bin/chef-resource-inspector | 26 | ||||
-rw-r--r-- | lib/chef/resource_inspector.rb | 89 | ||||
-rw-r--r-- | spec/unit/resource_inspector_spec.rb | 60 |
3 files changed, 175 insertions, 0 deletions
diff --git a/bin/chef-resource-inspector b/bin/chef-resource-inspector new file mode 100755 index 0000000000..6a7eac0c32 --- /dev/null +++ b/bin/chef-resource-inspector @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby +# +# ./chef-resource-inspector - Find information about a resource +# +# Copyright:: Copyright (c) 2018, Chef Software, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Encoding.default_external = Encoding::UTF_8 + +$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) + +require "chef/resource_inspector" + +ResourceInspector.start diff --git a/lib/chef/resource_inspector.rb b/lib/chef/resource_inspector.rb new file mode 100644 index 0000000000..0f4f718c72 --- /dev/null +++ b/lib/chef/resource_inspector.rb @@ -0,0 +1,89 @@ +# Copyright:: Copyright 2018, Chef Software, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/cookbook_loader" +require "chef/cookbook/file_vendor" +require "chef/cookbook/file_system_file_vendor" +require "chef/resource/lwrp_base" +require "chef/run_context" +require "chef/node" +require "chef/resources" +require "chef/json_compat" + +module ResourceInspector + def self.extract_resource(resource, complete = false) + data = {} + data[:description] = resource.description + # data[:deprecated] = resource.deprecated || false + data[:actions] = resource.allowed_actions + data[:examples] = resource.examples + data[:introduced] = resource.introduced + + properties = unless complete + resource.properties.reject { |_, k| k.options[:declared_in] == Chef::Resource } + else + resource.properties + end + + data[:properties] = properties.each_with_object([]) do |(n, k), acc| + opts = k.options + acc << { name: n, description: opts[:description], introduced: opts[:introduced], is: opts[:is], deprecated: opts[:deprecated] || false } + end + data + end + + def self.extract_cookbook(path, complete) + path = File.expand_path(path) + dir, name = File.split(path) + Chef::Cookbook::FileVendor.fetch_from_disk(path) + loader = Chef::CookbookLoader.new(dir) + cookbooks = loader.load_cookbooks + resources = cookbooks[name].files_for(:resources) + + resources.each_with_object({}) do |r, res| + pth = r["full_path"] + cur = Chef::Resource::LWRPBase.build_from_file(name, pth, Chef::RunContext.new(Chef::Node.new, nil, nil)) + res[cur.resource_name] = extract_resource(cur, complete) + end + end + + # If we're given no resources, dump all of Chef's built ins + # otherwise, if we have a path then extract all the resources from the cookbook + # or else do a list of built in resources + # + # @param complete [TrueClass, FalseClass] Whether to show properties defined in the base Resource class + def self.inspect(arguments = [], complete: false) + output = if arguments.empty? + ObjectSpace.each_object(Class).select { |k| k < Chef::Resource }.each_with_object({}) { |klass, acc| acc[klass.resource_name] = extract_resource(klass) } + else + arguments.each_with_object({}) do |arg, acc| + if File.directory?(arg) + extract_cookbook(arg, complete).each { |k, v| acc[k] = v } + else + r = Chef::ResourceResolver.resolve(arg.to_sym, canonical: nil) + acc[r.resource_name] = extract_resource(r, complete) + end + end + end + + puts Chef::JSONCompat.to_json_pretty(output) + end + + def self.start + inspect(ARGV, complete: true) + end + +end diff --git a/spec/unit/resource_inspector_spec.rb b/spec/unit/resource_inspector_spec.rb new file mode 100644 index 0000000000..2cb9c0bb65 --- /dev/null +++ b/spec/unit/resource_inspector_spec.rb @@ -0,0 +1,60 @@ +# Copyright:: Copyright 2018, Chef Software, Inc +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +require "chef/resource_inspector" + +class DummyResource < Chef::Resource + resource_name :dummy + description "A dummy resource" + examples <<~EOH + dummy "foo" do + first "yes" + end + EOH + introduced "14.0" + property :first, String, description: "My First Property", introduced: "14.0" + + action :dummy do + return true + end +end + +describe ResourceInspector do + describe "inspecting a resource" do + subject { ResourceInspector.extract_resource(DummyResource, false) } + + it "returns a hash with required data" do + expect(subject[:description]).to eq "A dummy resource" + expect(subject[:actions]).to match_array [:nothing, :dummy] + end + + context "excluding built in properties" do + it "returns a single property" do + expect(subject[:properties].count).to eq 1 + expect(subject[:properties].first[:name]).to eq :first + end + end + + context "including built in properties" do + subject { ResourceInspector.extract_resource(DummyResource, true) } + it "returns many properties" do + expect(subject[:properties].count).to be > 1 + expect(subject[:properties].map { |n| n[:name] }).to include(:name, :first, :sensitive) + end + end + end +end |