mirror of
https://github.com/inspec/inspec
synced 2024-11-10 07:04:15 +00:00
Merge pull request #5945 from inspec/ss/enhance-docker-image
CFINSPEC-86: Enhance docker_image resource
This commit is contained in:
commit
b1835bf9f9
5 changed files with 243 additions and 32 deletions
|
@ -11,84 +11,104 @@ platform = "linux"
|
||||||
parent = "inspec/resources/os"
|
parent = "inspec/resources/os"
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Use the `docker_image` Chef InSpec audit resource to verify a Docker image.
|
Use the `docker_image` Chef InSpec audit resource to verify a Docker image. A Docker Image is a template that contains the application and all the dependencies required to run an application on Docker.
|
||||||
|
|
||||||
## Availability
|
## Availability
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
This resource is distributed along with Chef InSpec itself. You can use it automatically.
|
This resource is distributed with Chef InSpec.
|
||||||
|
|
||||||
### Version
|
### Version
|
||||||
|
|
||||||
This resource first became available in v1.21.0 of InSpec.
|
This resource is available from the InSpec version, 1.21.0.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
A `docker_image` resource block declares the image:
|
A `docker_image` resource block declares the image.
|
||||||
|
|
||||||
describe docker_image('alpine:latest') do
|
describe docker_image('ALPINE:LATEST') do
|
||||||
it { should exist }
|
it { should exist }
|
||||||
its('id') { should eq 'sha256:4a415e...a526' }
|
its('id') { should eq 'sha256:4a415e...a526' }
|
||||||
its('repo') { should eq 'alpine' }
|
its('repo') { should eq 'ALPINE' }
|
||||||
its('tag') { should eq 'latest' }
|
its('tag') { should eq 'LATEST' }
|
||||||
end
|
end
|
||||||
|
|
||||||
## Resource Parameter Examples
|
### Resource Parameter Examples
|
||||||
|
|
||||||
The resource allows you to pass in an image id:
|
The resource allows you to pass with an image ID.
|
||||||
|
|
||||||
describe docker_image(id: alpine_id) do
|
describe docker_image(id: ID) do
|
||||||
...
|
...
|
||||||
end
|
end
|
||||||
|
|
||||||
If the tag is missing for an image, `latest` is assumed as default:
|
If the tag is missing for an image, `LATEST` is assumed as default.
|
||||||
|
|
||||||
describe docker_image('alpine') do
|
describe docker_image('ALPINE') do
|
||||||
...
|
...
|
||||||
end
|
end
|
||||||
|
|
||||||
You can also pass in repository and tag as separate values
|
You can also pass the repository and tag values as separate values.
|
||||||
|
|
||||||
describe docker_image(repo: 'alpine', tag: 'latest') do
|
describe docker_image(repo: 'ALPINE', tag: 'LATEST') do
|
||||||
...
|
...
|
||||||
end
|
end
|
||||||
|
|
||||||
## Property Examples
|
## Properties
|
||||||
|
|
||||||
### id
|
### id
|
||||||
|
|
||||||
The `id` property returns the full image id:
|
The `id` property returns the full image ID.
|
||||||
|
|
||||||
its('id') { should eq 'sha256:4a415e3663882fbc554ee830889c68a33b3585503892cc718a4698e91ef2a526' }
|
its('id') { should eq 'sha256:4a415e3663882fbc554ee830889c68a33b3585503892cc718a4698e91ef2a526' }
|
||||||
|
|
||||||
### image
|
### image
|
||||||
|
|
||||||
The `image` property tests the value of the image. It is a combination of `repository/tag`:
|
The `image` property tests the value of the image. It is a combination of `repository/tag`.
|
||||||
|
|
||||||
its('image') { should eq 'alpine:latest' }
|
its('image') { should eq 'ALPINE:LATEST' }
|
||||||
|
|
||||||
### repo
|
### repo
|
||||||
|
|
||||||
The `repo` property tests the value of the repository name:
|
The `repo` property tests the value of the repository name.
|
||||||
|
|
||||||
its('repo') { should eq 'alpine' }
|
its('repo') { should eq 'ALPINE' }
|
||||||
|
|
||||||
### tag
|
### tag
|
||||||
|
|
||||||
The `tag` property tests the value of image tag:
|
The `tag` property tests the value of the image tag.
|
||||||
|
|
||||||
its('tag') { should eq 'latest' }
|
its('tag') { should eq 'LATEST' }
|
||||||
|
|
||||||
### Test a Docker Image
|
### Low-level information of docker image as docker_image's property
|
||||||
|
|
||||||
describe docker_image('alpine:latest') do
|
#### inspection
|
||||||
it { should exist }
|
|
||||||
its('id') { should eq 'sha256:4a415e...a526' }
|
The property allows testing the low-level information of docker image returned by `docker inspect [docker_image]`. Use hash format `'key' => 'value` for testing the information.
|
||||||
its('image') { should eq 'alpine:latest' }
|
|
||||||
its('repo') { should eq 'alpine' }
|
its(:inspection) { should include "Key" => "Value" }
|
||||||
its('tag') { should eq 'latest' }
|
its(:inspection) { should include "Key" =>
|
||||||
end
|
{
|
||||||
|
"SubKey" => "Value1",
|
||||||
|
"SubKey" => "Value2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Additionally, all keys of the low-level information are valid properties and can be passed in three ways when writing the test.
|
||||||
|
|
||||||
|
- Serverspec's syntax
|
||||||
|
|
||||||
|
its(['key']) { should eq some_value }
|
||||||
|
its(['key1.key2.key3']) { should include some_value }
|
||||||
|
|
||||||
|
- InSpec's syntax
|
||||||
|
|
||||||
|
its(['key']) { should eq some_value }
|
||||||
|
its(['key1', 'key2', 'key3']) { should include some_value }
|
||||||
|
|
||||||
|
- Combination of Serverspec and InSpec
|
||||||
|
|
||||||
|
its(['key1.key2', 'key3']) { should include some_value }
|
||||||
|
|
||||||
## Matchers
|
## Matchers
|
||||||
|
|
||||||
|
@ -96,6 +116,39 @@ For a full list of available matchers, please visit our [matchers page](/inspec/
|
||||||
|
|
||||||
### exist
|
### exist
|
||||||
|
|
||||||
The `exist` matcher tests if the image is available on the node:
|
The `exist` matcher tests if the image is available on the node.
|
||||||
|
|
||||||
it { should exist }
|
it { should exist }
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Test if a docker image exists and verifies the image properties: ID, image, repo, and tag
|
||||||
|
|
||||||
|
describe docker_image('ALPINE:LATEST') do
|
||||||
|
it { should exist }
|
||||||
|
its('id') { should eq 'sha256:4a415e...a526' }
|
||||||
|
its('image') { should eq 'ALPINE:LATEST' }
|
||||||
|
its('repo') { should eq 'ALPINE' }
|
||||||
|
its('tag') { should eq 'LATEST' }
|
||||||
|
end
|
||||||
|
|
||||||
|
### Test if a docker image exists and verifies the low-level information: Architecture, Config.Cmd, and GraphDriver
|
||||||
|
|
||||||
|
describe docker_image('ubuntu:latest') do
|
||||||
|
it { should exist }
|
||||||
|
its(['Architecture']) { should eq 'ARM64' }
|
||||||
|
its(['Config.Cmd']) { should include 'BASH' }
|
||||||
|
its(['GraphDriver.Data.MergedDir']) { should include "/var/lib/docker/overlay2/4336ba2a87c8d82abaa9ee5afd3ac20ea275bf05502d74d8d8396f8f51a4736c/merged" }
|
||||||
|
its(:inspection) { should include 'Architecture' => 'ARM64' }
|
||||||
|
its(:inspection) { should_not include 'Architecture' => 'i386' }
|
||||||
|
its(:inspection) { should include "GraphDriver" =>
|
||||||
|
{
|
||||||
|
"Data" => {
|
||||||
|
"MergedDir" => "/var/lib/docker/overlay2/4336ba2a87c8d82abaa9ee5afd3ac20ea275bf05502d74d8d8396f8f51a4736c/merged",
|
||||||
|
"UpperDir" => "/var/lib/docker/overlay2/4336ba2a87c8d82abaa9ee5afd3ac20ea275bf05502d74d8d8396f8f51a4736c/diff",
|
||||||
|
"WorkDir"=> "/var/lib/docker/overlay2/4336ba2a87c8d82abaa9ee5afd3ac20ea275bf05502d74d8d8396f8f51a4736c/work"
|
||||||
|
},
|
||||||
|
"Name" => "overlay2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
|
@ -48,6 +48,25 @@ module Inspec::Resources
|
||||||
object_info.tags[0] if object_info.entries.size == 1
|
object_info.tags[0] if object_info.entries.size == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# method_missing handles when hash_keys are invoked to check information obtained on docker inspect [image_name]
|
||||||
|
def method_missing(*hash_keys)
|
||||||
|
# User can test the low-level inspect information in three ways:
|
||||||
|
# Way 1: Serverspec style: its(['Config.Cmd']) { should include some_value }
|
||||||
|
# here, the value for hash_keys recieved is [:[], "Config.Cmd"]
|
||||||
|
# Way 2: InSpec style: its(['Config','Cmd']) { should include some_value }
|
||||||
|
# here, the value for hash_keys recieved is [:[], "Config", "Cmd"]
|
||||||
|
# Way 3: Mix of both: its(['GraphDriver.Data','MergedDir']) { should include some_value }
|
||||||
|
# here, the value for hash_keys recieved is [:[], "GraphDriver.Data", "MergedDir"]
|
||||||
|
|
||||||
|
# hash_keys are passed to this method to evaluate the value
|
||||||
|
image_hash_inspection(hash_keys)
|
||||||
|
end
|
||||||
|
|
||||||
|
# inspection property allows to test any of the hash key-value pairs as part of the image_inspect_info
|
||||||
|
def inspection
|
||||||
|
image_inspect_info
|
||||||
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
img = @opts[:image] || @opts[:id]
|
img = @opts[:image] || @opts[:id]
|
||||||
"Docker Image #{img}"
|
"Docker Image #{img}"
|
||||||
|
@ -80,5 +99,39 @@ module Inspec::Resources
|
||||||
(repository == opts[:repo] && tag == opts[:tag]) || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id])))
|
(repository == opts[:repo] && tag == opts[:tag]) || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id])))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# image_inspect_info returns the complete inspect hash_values of the image
|
||||||
|
def image_inspect_info
|
||||||
|
return @inspect_info if defined?(@inspect_info)
|
||||||
|
|
||||||
|
@inspect_info = inspec.docker.object(@opts[:image] || (!@opts[:id].nil? && @opts[:id]))
|
||||||
|
end
|
||||||
|
|
||||||
|
# image_hash_inspection formats the input hash_keys and checks if any value exists for such keys in @inspect_info(image_inspect_info)
|
||||||
|
def image_hash_inspection(hash_keys)
|
||||||
|
# The hash_keys recieved are in three formats as mentioned in method_missing
|
||||||
|
# The hash_keys recieved must be in array format [] and the zeroth index must be :[]
|
||||||
|
# Check for the conditions and remove the zeroth element from the hash_keys
|
||||||
|
|
||||||
|
hash_keys.shift if hash_keys.is_a?(Array) && hash_keys[0] == :[]
|
||||||
|
|
||||||
|
# When received hash_keys in Serverspec style or mix of both
|
||||||
|
# The hash_keys are to be splitted at '.' (dot) and flatten it so that it doesn't become array of arrays
|
||||||
|
# After splitting and flattening is done, hash_keys is now an array with individual keys
|
||||||
|
hash_keys = hash_keys.map { |key| key.split(".") }.flatten
|
||||||
|
|
||||||
|
# image_inspect_info returns the complete inspect hash_values of the image
|
||||||
|
# dig() finds the nested value specified by the sequence of the key object by calling dig at each step.
|
||||||
|
# hash_keys is the key object. If one of the key is bad, value will be nil.
|
||||||
|
hash_value = image_inspect_info.dig(*hash_keys)
|
||||||
|
|
||||||
|
# If one of the key is bad, hash_value will be nil, so raise exception which throws it in rescue block
|
||||||
|
# else return hash_value
|
||||||
|
raise Inspec::Exceptions::ResourceFailed if hash_value.nil?
|
||||||
|
|
||||||
|
hash_value
|
||||||
|
rescue
|
||||||
|
raise Inspec::Exceptions::ResourceFailed, "#{hash_keys.join(".")} is not a valid key for your image or has nil value."
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
88
test/fixtures/cmd/docker-inspect-image
vendored
Normal file
88
test/fixtures/cmd/docker-inspect-image
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Id": "sha256:a457a74c9aaabc62ddc119d2fb03ba6f58fa299bf766bd2411c159142b972c1d",
|
||||||
|
"RepoTags": [
|
||||||
|
"ubuntu:latest"
|
||||||
|
],
|
||||||
|
"RepoDigests": [
|
||||||
|
"ubuntu@sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be"
|
||||||
|
],
|
||||||
|
"Parent": "",
|
||||||
|
"Comment": "",
|
||||||
|
"Created": "2022-02-02T03:19:27.692029463Z",
|
||||||
|
"Container": "396e646862a702db784450345079c41dc9da7103da54ca3d777394b06aba775e",
|
||||||
|
"ContainerConfig": {
|
||||||
|
"Hostname": "396e646862a7",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": false,
|
||||||
|
"AttachStderr": false,
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
],
|
||||||
|
"Cmd": [
|
||||||
|
"/bin/sh",
|
||||||
|
"-c",
|
||||||
|
"#(nop) ",
|
||||||
|
"CMD [\"bash\"]"
|
||||||
|
],
|
||||||
|
"Image": "sha256:90d6079446c1908361700f819e620a87b923908fe4a1c5bfb12ae45b36358547",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": null,
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": {}
|
||||||
|
},
|
||||||
|
"DockerVersion": "20.10.7",
|
||||||
|
"Author": "",
|
||||||
|
"Config": {
|
||||||
|
"Hostname": "",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": false,
|
||||||
|
"AttachStderr": false,
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
],
|
||||||
|
"Cmd": [
|
||||||
|
"bash"
|
||||||
|
],
|
||||||
|
"Image": "sha256:90d6079446c1908361700f819e620a87b923908fe4a1c5bfb12ae45b36358547",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": null,
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": null
|
||||||
|
},
|
||||||
|
"Architecture": "arm64",
|
||||||
|
"Variant": "v8",
|
||||||
|
"Os": "linux",
|
||||||
|
"Size": 65592278,
|
||||||
|
"VirtualSize": 65592278,
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": {
|
||||||
|
"MergedDir": "/var/lib/docker/overlay2/4336ba2a87c8d82abaa9ee5afd3ac20ea275bf05502d74d8d8396f8f51a4736c/merged",
|
||||||
|
"UpperDir": "/var/lib/docker/overlay2/4336ba2a87c8d82abaa9ee5afd3ac20ea275bf05502d74d8d8396f8f51a4736c/diff",
|
||||||
|
"WorkDir": "/var/lib/docker/overlay2/4336ba2a87c8d82abaa9ee5afd3ac20ea275bf05502d74d8d8396f8f51a4736c/work"
|
||||||
|
},
|
||||||
|
"Name": "overlay2"
|
||||||
|
},
|
||||||
|
"RootFS": {
|
||||||
|
"Type": "layers",
|
||||||
|
"Layers": [
|
||||||
|
"sha256:0c20a4bc193b305ce66d3bde10d177631646a8844804953c320f1f5b68655213"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Metadata": {
|
||||||
|
"LastTagTime": "0001-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -475,6 +475,7 @@ class MockLoader
|
||||||
"docker inspect fried_water" => cmd.call("docker-inspect-e"), # inspect container to check for mounted volumes
|
"docker inspect fried_water" => cmd.call("docker-inspect-e"), # inspect container to check for mounted volumes
|
||||||
# docker images
|
# docker images
|
||||||
"83c36bfade9375ae1feb91023cd1f7409b786fd992ad4013bf0f2259d33d6406" => cmd.call("docker-images"),
|
"83c36bfade9375ae1feb91023cd1f7409b786fd992ad4013bf0f2259d33d6406" => cmd.call("docker-images"),
|
||||||
|
"docker inspect ubuntu:latest" => cmd.call("docker-inspect-image"),
|
||||||
# docker services
|
# 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 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 plugins
|
||||||
|
|
|
@ -12,6 +12,22 @@ describe "Inspec::Resources::DockerImage" do
|
||||||
_(resource.repo).must_equal "alpine"
|
_(resource.repo).must_equal "alpine"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test case for inspect image information handled by inspection and method_missing
|
||||||
|
it "check attributes returned by docker inspect [docker_image]" do
|
||||||
|
resource = load_resource("docker_image", "ubuntu:latest")
|
||||||
|
_(resource["Architecture"]).must_equal "arm64"
|
||||||
|
_(resource["Config.Cmd"]).must_include "bash"
|
||||||
|
_(resource.inspection).must_include "Architecture"
|
||||||
|
_(resource.inspection.Architecture).must_equal "arm64"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test case for inspect image information with invalid keys
|
||||||
|
it "checks exception when key is invalid or doesn't exist as part of the inspect information" do
|
||||||
|
resource = load_resource("docker_image", "ubuntu:latest")
|
||||||
|
ex = _ { resource["Garbage.Key"] }.must_raise(Inspec::Exceptions::ResourceFailed)
|
||||||
|
_(ex.message).must_include "Garbage.Key is not a valid key for your image or has nil value."
|
||||||
|
end
|
||||||
|
|
||||||
it "prints as a docker_image resource" do
|
it "prints as a docker_image resource" do
|
||||||
resource = load_resource("docker_image", "alpine")
|
resource = load_resource("docker_image", "alpine")
|
||||||
_(resource.to_s).must_equal "Docker Image alpine:latest"
|
_(resource.to_s).must_equal "Docker Image alpine:latest"
|
||||||
|
|
Loading…
Reference in a new issue