From 4f5b99cea77b1f9e78a3dd9e741ab6bb9cea1b4f Mon Sep 17 00:00:00 2001 From: "S.Cavallo" Date: Sat, 8 Dec 2018 03:23:55 -0500 Subject: [PATCH 1/2] docker image prune code and documentation Signed-off-by: S.Cavallo --- README.md | 35 ++++++++++++++++++++++++++++++++ libraries/docker_image_prune.rb | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 libraries/docker_image_prune.rb diff --git a/README.md b/README.md index 9f68fed4a6..f0ca5f5e0f 100644 --- a/README.md +++ b/README.md @@ -577,6 +577,41 @@ docker_image 'alpine' do end ``` +## docker_image_prune + +The `docker_image_prune` is responsible for pruning Docker images from the system. It speaks directly to the [Docker Engine API](https://docs.docker.com/engine/api/v1.35/#operation/ImagePrune). + +### Actions + +- `:prune` - Delete unused images + +### Properties + +The `docker_image_prune` resource properties map to filters + +- `dangling` - When set to true (or 1), prune only unused and untagged images. When set to false (or 0), all unused images are pruned +- `prune_until` - Prune images created before this timestamp. The can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the daemon machine’s time. +- `label` - (label=, label==, label!=, or label!==) Prune images with (or without, in case label!=... is used) the specified labels. + +### Examples + +- default action, default properties + +```ruby +docker_image_prune 'prune-old-images' +``` + +- All filters + +```ruby +docker_image_prune "prune-old-images" do + dangling true + prune_until '1h30m' + label ['"com.example.vendor"="ACME Incorporated"'] + action :prune +end +``` + ## docker_tag Docker tags work very much like hard links in a Unix filesystem. They are just references to an existing image. Therefore, the docker_tag resource has taken inspiration from the Chef `link` resource. diff --git a/libraries/docker_image_prune.rb b/libraries/docker_image_prune.rb new file mode 100644 index 0000000000..f20c3e1a65 --- /dev/null +++ b/libraries/docker_image_prune.rb @@ -0,0 +1,36 @@ +module DockerCookbook + class DockerImagePrune < DockerBase + resource_name :docker_image_prune + + # Modify the default of read_timeout from 60 to 120 + property :read_timeout, default: 120, desired_state: false + + # https://docs.docker.com/engine/api/v1.35/#operation/ImagePrune + property :dangling, [TrueClass, FalseClass], default: false + property :prune_until, String + # https://docs.docker.com/engine/reference/builder/#label + property :label, Array, default: [] + + ######### + # Actions + ######### + + default_action :prune + + action :prune do + # Have to call this method ourselves due to + # https://github.com/swipely/docker-api/pull/507 + # {filters: {dangling: ['false']}.to_json} + # + opts = {filters: { + dangling: new_resource.dangling + }} + opts.filters.merge(until: new_resource.prune_until) if new_resource.property_is_set?(:prune_until) + opts.filters.merge(label: new_resource.label) if new_resource.property_is_set?(:label) + + # Post + res = conn.post("/images/prune", opts.to_json) + Chef::Log.info res + end + end +end From ba9cc670569c6f4a273e7b541c75802889e1ec9c Mon Sep 17 00:00:00 2001 From: "S.Cavallo" Date: Sat, 8 Dec 2018 22:03:37 -0500 Subject: [PATCH 2/2] fix implementation of prune and add all tests Signed-off-by: S.Cavallo --- README.md | 7 +++-- libraries/docker_image_prune.rb | 27 ++++++++++--------- spec/docker_test/image_prune_spec.rb | 24 +++++++++++++++++ spec/libraries/image_prune_spec.rb | 27 +++++++++++++++++++ .../docker_test/recipes/image_prune.rb | 15 +++++++++++ 5 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 spec/docker_test/image_prune_spec.rb create mode 100644 spec/libraries/image_prune_spec.rb create mode 100644 test/cookbooks/docker_test/recipes/image_prune.rb diff --git a/README.md b/README.md index f0ca5f5e0f..7f084ed56b 100644 --- a/README.md +++ b/README.md @@ -580,6 +580,7 @@ end ## docker_image_prune The `docker_image_prune` is responsible for pruning Docker images from the system. It speaks directly to the [Docker Engine API](https://docs.docker.com/engine/api/v1.35/#operation/ImagePrune). +Note - this is best implemented by subscribing to `docker_image` changes. There is no need to to clean up old images upon each converge. It is best done at the end of a chef run (delayed) only if a new image was pulled. ### Actions @@ -591,7 +592,8 @@ The `docker_image_prune` resource properties map to filters - `dangling` - When set to true (or 1), prune only unused and untagged images. When set to false (or 0), all unused images are pruned - `prune_until` - Prune images created before this timestamp. The can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the daemon machine’s time. -- `label` - (label=, label==, label!=, or label!==) Prune images with (or without, in case label!=... is used) the specified labels. +- `with_label/without_label` - (label=, label==, label!=, or label!==) Prune images with (or without, in case label!=... is used) the specified labels. +- `host` - A string containing the host the API should communicate with. Defaults to `ENV['DOCKER_HOST']` if set. ### Examples @@ -607,7 +609,8 @@ docker_image_prune 'prune-old-images' docker_image_prune "prune-old-images" do dangling true prune_until '1h30m' - label ['"com.example.vendor"="ACME Incorporated"'] + with_label 'com.example.vendor=ACME' + without_label 'no_prune' action :prune end ``` diff --git a/libraries/docker_image_prune.rb b/libraries/docker_image_prune.rb index f20c3e1a65..2cc6f8cdd1 100644 --- a/libraries/docker_image_prune.rb +++ b/libraries/docker_image_prune.rb @@ -1,15 +1,17 @@ module DockerCookbook class DockerImagePrune < DockerBase resource_name :docker_image_prune - + # Requires docker API v1.25 # Modify the default of read_timeout from 60 to 120 property :read_timeout, default: 120, desired_state: false + property :host, [String, nil], default: lazy { ENV['DOCKER_HOST'] }, desired_state: false # https://docs.docker.com/engine/api/v1.35/#operation/ImagePrune - property :dangling, [TrueClass, FalseClass], default: false + property :dangling, [TrueClass, FalseClass], default: true property :prune_until, String # https://docs.docker.com/engine/reference/builder/#label - property :label, Array, default: [] + property :with_label, String + property :without_label, String ######### # Actions @@ -20,17 +22,18 @@ class DockerImagePrune < DockerBase action :prune do # Have to call this method ourselves due to # https://github.com/swipely/docker-api/pull/507 - # {filters: {dangling: ['false']}.to_json} - # - opts = {filters: { - dangling: new_resource.dangling - }} - opts.filters.merge(until: new_resource.prune_until) if new_resource.property_is_set?(:prune_until) - opts.filters.merge(label: new_resource.label) if new_resource.property_is_set?(:label) - + json = generate_json(new_resource) # Post - res = conn.post("/images/prune", opts.to_json) + res = connection.post('/images/prune', json) Chef::Log.info res end + + def generate_json(new_resource) + opts = { filters: ["dangling=#{new_resource.dangling}"] } + opts[:filters].push("until=#{new_resource.prune_until}") if new_resource.property_is_set?(:prune_until) + opts[:filters].push("label=#{new_resource.with_label}") if new_resource.property_is_set?(:with_label) + opts[:filters].push("label!=#{new_resource.without_label}") if new_resource.property_is_set?(:without_label) + opts.to_json + end end end diff --git a/spec/docker_test/image_prune_spec.rb b/spec/docker_test/image_prune_spec.rb new file mode 100644 index 0000000000..e33bdb209c --- /dev/null +++ b/spec/docker_test/image_prune_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'docker_test::image_prune' do + context 'it steps over the provider' do + cached(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04').converge(described_recipe) } + + context 'testing default action, default properties' do + it 'prunes docker_image[hello-world]' do + expect(chef_run).to prune_docker_image_prune('hello-world').with( + dangling: true + ) + end + + it 'prunes docker_image[hello-world]' do + expect(chef_run).to prune_docker_image_prune('prune-old-images').with( + dangling: true, + prune_until: '1h30m', + with_label: 'com.example.vendor=ACME', + without_label: 'no_prune' + ) + end + end + end +end diff --git a/spec/libraries/image_prune_spec.rb b/spec/libraries/image_prune_spec.rb new file mode 100644 index 0000000000..4890f7a512 --- /dev/null +++ b/spec/libraries/image_prune_spec.rb @@ -0,0 +1,27 @@ +# Load all the libraries +require 'chef' +Dir['libraries/*.rb'].each { |f| require File.expand_path(f) } + +describe DockerCookbook::DockerImagePrune do + let(:resource) { DockerCookbook::DockerImagePrune.new('rspec') } + + it 'has a default action of [:prune]' do + expect(resource.action).to eql([:prune]) + end + + it 'generates filter json' do + # Arrange + expected = '{"filters":["dangling=true","until=1h30m","label=com.example.vendor=ACME","label!=no_prune"]}' + resource.dangling = true + resource.prune_until = '1h30m' + resource.with_label = 'com.example.vendor=ACME' + resource.without_label = 'no_prune' + resource.action :prune + + # Act + actual = resource.generate_json(resource) + + # Assert + expect(actual).to eq(expected) + end +end diff --git a/test/cookbooks/docker_test/recipes/image_prune.rb b/test/cookbooks/docker_test/recipes/image_prune.rb new file mode 100644 index 0000000000..9130c2f372 --- /dev/null +++ b/test/cookbooks/docker_test/recipes/image_prune.rb @@ -0,0 +1,15 @@ +######################### +# :prune +######################### + +docker_image_prune 'hello-world' do + dangling true +end + +docker_image_prune "prune-old-images" do + dangling true + prune_until '1h30m' + with_label 'com.example.vendor=ACME' + without_label 'no_prune' + action :prune +end