diff --git a/docs/resources/docker.md.erb b/docs/resources/docker.md.erb
index 81dc8a8dd..197f7f9b7 100644
--- a/docs/resources/docker.md.erb
+++ b/docs/resources/docker.md.erb
@@ -148,6 +148,17 @@ Or execute the profile directly via URL:
its('sizes') { should_not include "1.41 GB" }
end
+### plugins
+
+`plugins` returns information about Docker plugins as returned by [docker plugin ls](https://docs.docker.com/engine/reference/commandline/plugin/).
+
+ describe docker.plugins do
+ its('names') { should include ["store/weaveworks/net-plugin", "docker4x/cloudstor"] }
+ its('ids') { should cmp ["6ea8176de74b", "771d3ee7c7ea"] }
+ its('versions') { should cmp ["2.3.0", "18.03.1-ce-aws1"] }
+ its('enabled') { should cmp [true, false] }
+ end
+
### info
`info` returns the parsed result of [docker info](https://docs.docker.com/engine/reference/commandline/info/)
diff --git a/docs/resources/docker_plugin.md.erb b/docs/resources/docker_plugin.md.erb
new file mode 100644
index 000000000..61ca42f12
--- /dev/null
+++ b/docs/resources/docker_plugin.md.erb
@@ -0,0 +1,80 @@
+---
+title: About the docker_plugin Resource
+platform: linux
+---
+
+# docker_plugin
+
+Use the `docker_plugin` InSpec audit resource to verify a Docker plugin.
+
+
+
+## Syntax
+
+A `docker_plugin` resource block declares the plugin:
+
+ describe docker_plugin('rexray/ebs') do
+ it { should exist }
+ its('id') { should_not eq '0ac30b93ad40' }
+ its('version') { should eq '0.11.1' }
+ it { should be_enabled }
+ end
+
+
+
+## Resource Parameter Examples
+
+The resource allows you to pass in an plugin id:
+
+ describe docker_plugin(id: plugin_id) do
+ it { should be_enabled }
+ end
+
+
+
+## Properties
+
+### id
+
+The `id` property returns the full plugin id:
+
+ describe docker_plugin('cloudstor/aws') do
+ its('id') { should eq '0ac30b93ad40' }
+ end
+
+### version
+
+The `version` property tests the value of plugin version:
+
+ describe docker_plugin('cloudstor/aws') do
+ its('version') { should eq '0.11.0' }
+ end
+
+## Examples
+
+### Test a Docker plugin
+
+ describe docker_plugin('rexray/ebs') do
+ it { should exist }
+ its('id') { should_not eq '0ac30b93ad40' }
+ its('version') { should eq '0.11.1' }
+ it { should be_enabled }
+ end
+
+
+
+## Matchers
+
+For a full list of available matchers, please visit our [Universal Matchers](https://www.inspec.io/docs/reference/matchers/).
+
+### exist
+
+The `exist` matcher tests if the plugin is available on the node:
+
+ describe docker_plugin('rexray/ebs') do
+ it { should exist }
+ end
+
+### enabled
+
+The `be_enabled` matches tests if the plugin is enabled
diff --git a/lib/inspec/resource.rb b/lib/inspec/resource.rb
index 1c09603d3..1a255eeba 100644
--- a/lib/inspec/resource.rb
+++ b/lib/inspec/resource.rb
@@ -128,6 +128,7 @@ require 'resources/directory'
require 'resources/docker'
require 'resources/docker_container'
require 'resources/docker_image'
+require 'resources/docker_plugin'
require 'resources/docker_service'
require 'resources/elasticsearch'
require 'resources/etc_fstab'
diff --git a/lib/resources/docker.rb b/lib/resources/docker.rb
index 07e03025d..fa048215b 100644
--- a/lib/resources/docker.rb
+++ b/lib/resources/docker.rb
@@ -52,6 +52,20 @@ module Inspec::Resources
end
end
+ class DockerPluginFilter
+ filter = FilterTable.create
+ filter.add(:ids, field: 'id')
+ .add(:names, field: 'name')
+ .add(:versions, field: 'version')
+ .add(:enabled, field: 'enabled')
+ filter.connect(self, :plugins)
+
+ attr_reader :plugins
+ def initialize(plugins)
+ @plugins = plugins
+ end
+ end
+
class DockerServiceFilter
filter = FilterTable.create
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
@@ -89,6 +103,10 @@ module Inspec::Resources
its('repositories') { should_not include 'inssecure_image' }
end
+ describe docker.plugins.where { name == 'rexray/ebs' } do
+ it { should exist }
+ end
+
describe docker.services do
its('images') { should_not include 'inssecure_image' }
end
@@ -119,6 +137,10 @@ module Inspec::Resources
DockerImageFilter.new(parse_images)
end
+ def plugins
+ DockerPluginFilter.new(parse_plugins)
+ end
+
def services
DockerServiceFilter.new(parse_services)
end
@@ -226,5 +248,17 @@ module Inspec::Resources
warn 'Could not parse `docker images` output'
[]
end
+
+ def parse_plugins
+ plugins = inspec.command('docker plugin ls --format \'{"id": {{json .ID}}, "name": "{{ with split .Name ":"}}{{index . 0}}{{end}}", "version": "{{ with split .Name ":"}}{{index . 1}}{{end}}", "enabled": {{json .Enabled}} }\'').stdout
+ c_plugins = []
+ plugins.each_line { |entry|
+ c_plugins.push(JSON.parse(entry))
+ }
+ c_plugins
+ rescue JSON::ParserError => _e
+ warn 'Could not parse `docker plugin ls` output'
+ []
+ end
end
end
diff --git a/lib/resources/docker_plugin.rb b/lib/resources/docker_plugin.rb
new file mode 100644
index 000000000..5b3bc3811
--- /dev/null
+++ b/lib/resources/docker_plugin.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+
+module Inspec::Resources
+ class DockerPlugin < Inspec.resource(1)
+ name 'docker_plugin'
+ supports platform: 'unix'
+ desc 'Retrieves info about docker plugins'
+ example "
+ describe docker_plugin('rexray/ebs') do
+ it { should exist }
+ its('id') { should_not eq '0ac30b93ad40' }
+ its('version') { should eq '0.11.1' }
+ it { should be_enabled }
+ end
+
+ describe docker_plugin('alpine:latest') do
+ it { should exist }
+ end
+
+ describe docker_plugin(id: '4a415e366388') do
+ it { should exist }
+ end
+ "
+
+ def initialize(opts = {})
+ # do sanitizion of input values
+ o = opts.dup
+ o = { name: opts } if opts.is_a?(String)
+ @opts = o
+ end
+
+ def exist?
+ object_info.entries.size == 1
+ end
+
+ def enabled?
+ object_info.enabled[0]
+ end
+
+ def id
+ object_info.ids[0] if object_info.entries.size == 1
+ end
+
+ def version
+ object_info.versions[0] if object_info.entries.size == 1
+ end
+
+ def to_s
+ plugin = @opts[:name] || @opts[:id]
+ "Docker plugin #{plugin}"
+ end
+
+ private
+
+ def object_info
+ return @info if defined?(@info)
+ opts = @opts
+ @info = inspec.docker.plugins.where {
+ (name == opts[:name]) || (!id.nil? && !opts[:id].nil? && (id == opts[:id]))
+ }
+ end
+ end
+end
diff --git a/test/helper.rb b/test/helper.rb
index e502ec7d4..7b0278831 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -425,6 +425,8 @@ class MockLoader
"83c36bfade9375ae1feb91023cd1f7409b786fd992ad4013bf0f2259d33d6406" => cmd.call('docker-images'),
# docker services
%{docker service ls --format '{"ID": {{json .ID}}, "Name": {{json .Name}}, "Mode": {{json .Mode}}, "Replicas": {{json .Replicas}}, "Image": {{json .Image}}, "Ports": {{json .Ports}}}'} => cmd.call('docker-service-ls'),
+ # docker plugins
+ %{docker plugin ls --format '{"id": {{json .ID}}, "name": "{{ with split .Name ":"}}{{index . 0}}{{end}}", "version": "{{ with split .Name ":"}}{{index . 1}}{{end}}", "enabled": {{json .Enabled}} }'} => cmd.call('docker-plugin-ls'),
# modprobe for kernel_module
"modprobe --showconfig" => cmd.call('modprobe-config'),
# get-process cmdlet for processes resource
diff --git a/test/unit/mock/cmd/docker-plugin-ls b/test/unit/mock/cmd/docker-plugin-ls
new file mode 100644
index 000000000..3393f84b8
--- /dev/null
+++ b/test/unit/mock/cmd/docker-plugin-ls
@@ -0,0 +1,2 @@
+{"id": "6ea8176de74b", "name": "store/weaveworks/net-plugin", "version": "2.3.0", "enabled": true }
+{"id": "771d3ee7c7ea", "name": "docker4x/cloudstor", "version": "18.03.1-ce-aws1", "enabled": false }
diff --git a/test/unit/resources/docker_plugin_test.rb b/test/unit/resources/docker_plugin_test.rb
new file mode 100644
index 000000000..7cd0ca825
--- /dev/null
+++ b/test/unit/resources/docker_plugin_test.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+# author: Noel Georgi
+
+require 'helper'
+require 'inspec/resource'
+
+describe 'Inspec::Resources::DockerContainer' do
+ describe 'docker_plugin' do
+ it 'check plugin parsing for docker4x/cloudstor' do
+ resource = load_resource('docker_plugin', 'docker4x/cloudstor')
+ _(resource.id).must_equal '771d3ee7c7ea'
+ _(resource.version).must_equal '18.03.1-ce-aws1'
+ _(resource.enabled?).must_equal false
+ _(resource.exist?).must_equal true
+ end
+
+ it 'check plugin parsing for store/weaveworks/net-plugin' do
+ resource = load_resource('docker_plugin', 'store/weaveworks/net-plugin')
+ _(resource.id).must_equal '6ea8176de74b'
+ _(resource.version).must_equal '2.3.0'
+ _(resource.enabled?).must_equal true
+ _(resource.exist?).must_equal true
+ end
+
+ it 'check plugin parsing when there are no plugins' do
+ resource = load_resource('docker_plugin')
+ assert_nil resource.id
+ assert_nil resource.version
+ assert_nil resource.id
+ assert_nil resource.enabled?
+ _(resource.exist?).must_equal false
+ end
+
+ it 'prints as a docker resource' do
+ resource = load_resource('docker_plugin', 'store/weaveworks/net-plugin')
+ resource.to_s.must_equal 'Docker plugin store/weaveworks/net-plugin'
+ end
+ end
+end
diff --git a/test/unit/resources/docker_test.rb b/test/unit/resources/docker_test.rb
index 8ef789251..5ffd30586 100644
--- a/test/unit/resources/docker_test.rb
+++ b/test/unit/resources/docker_test.rb
@@ -24,6 +24,13 @@ describe 'Inspec::Resources::Docker' do
_(resource.services.images).must_equal ["foo/image:1.0", "foo/image:1.1", "bar:latest", "bar:latest"]
end
+ it 'check docker plugins parsing' do
+ _(resource.plugins.ids).must_equal ["6ea8176de74b", "771d3ee7c7ea"]
+ _(resource.plugins.names).must_equal ["store/weaveworks/net-plugin", "docker4x/cloudstor"]
+ _(resource.plugins.versions).must_equal ["2.3.0", "18.03.1-ce-aws1"]
+ _(resource.plugins.enabled).must_equal [true, false]
+ end
+
it 'check docker version parsing' do
_(resource.version.Server.Version).must_equal '17.03.0-ce'
_(resource.version.Client.Version).must_equal '17.03.0-ce'