Module: PuppetDockerTools::Utilities

Defined in:
lib/puppet_docker_tools/utilities.rb

Class Method Summary collapse

Class Method Details

.current_git_sha(directory = '.') ⇒ Object

Get the current git sha for the specified directory

Parameters:

  • directory (defaults to: '.')


124
125
126
127
128
# File 'lib/puppet_docker_tools/utilities.rb', line 124

def current_git_sha(directory = '.')
  Dir.chdir directory do
    `git rev-parse HEAD`.strip
  end
end

.filter_build_args(build_args:, dockerfile:) ⇒ Object

Filter build args to only include ARGs listed in the dockerfile. This is meant for compatibility with old versions of docker.

Parameters:

  • build_args

    hash of build args to filter

  • dockerfile

    the dockerfile to look for ARGs in



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/puppet_docker_tools/utilities.rb', line 55

def filter_build_args(build_args: , dockerfile: )
  fail "File #{dockerfile} doesn't exist!" unless File.exist? dockerfile
  text = File.read(dockerfile)

  # Docker only supports passing a single ARG on each line, so
  # look for arg, and ignore any default settings since we only care
  # whether or not the key is available
  implemented_args = text.scan(/arg +([^\n=]+)/i).flatten

  # reject any entries for args that are not in the dockerfile
  build_args.reject { |k,v|
    if implemented_args.include?(k)
      false
    else
      puts "Rejecting `--build-arg #{k}` since that ARG isn't in the Dockerfile"
      true
    end
  }
end

.format_timestamp(timestamp) ⇒ Object

Convert timestamps from second since epoch to ISO 8601 timestamps. If the given timestamp is entirely numeric it will be converted to an ISO 8601 timestamp, if not the parameter will be returned as passed.

Parameters:

  • timestamp

    The timestamp to convert



135
136
137
138
139
140
# File 'lib/puppet_docker_tools/utilities.rb', line 135

def format_timestamp(timestamp)
  if "#{timestamp}" =~ /^\d+$/
    timestamp = Time.at(Integer(timestamp)).utc.iso8601
  end
  timestamp
end

.get_hadolint_command(file = '-') ⇒ Object

Generate the hadolint command that should be run. Hadolint is a linter for dockerfiles that also validates inline bash with shellcheck. For more info, see the github repo (github.com/hadolint/hadolint)

Parameters:

  • file (defaults to: '-')

    Dockerfile to lint, defaults to stdin



193
194
195
196
197
198
199
200
201
202
203
# File 'lib/puppet_docker_tools/utilities.rb', line 193

def get_hadolint_command(file = '-')
  ignore_rules = [
    'DL3008',
    'DL3018',
    'DL4000',
    'DL4001',
  ]
  ignore_string = ignore_rules.map { |x| "--ignore #{x}" }.join(' ')

  "hadolint #{ignore_string} #{file}"
end

.get_value_from_base_image(value, namespace:, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '') ⇒ Object

Get a value from a container’s base image

Parameters:

  • value

    The value we want to get from this image’s base image, e.g. ‘version’

  • namespace

    The namespace for the value, e.g. ‘org.label-schema’

  • directory (defaults to: '.')

    The directory containing the Dockerfile, defaults to $PWD

  • dockerfile (defaults to: 'Dockerfile')

    The file name for your dockerfile, defaults to ‘Dockerfile’

  • dockerfile_contents (defaults to: '')

    A string containing the contents of the Dockerfile [optional]



228
229
230
231
# File 'lib/puppet_docker_tools/utilities.rb', line 228

def get_value_from_base_image(value, namespace:, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '')
  base_image = get_value_from_dockerfile('from', directory: directory, dockerfile: dockerfile, dockerfile_contents: dockerfile_contents).split(':').first.split('/').last
  get_value_from_env(value, namespace: namespace, directory: "#{directory}/../#{base_image}", dockerfile: dockerfile)
end

.get_value_from_dockerfile(key, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '') ⇒ Object

Get a value from a Dockerfile

Parameters:

  • key

    The key to read from the Dockerfile, e.g. ‘from’

  • directory (defaults to: '.')

    The directory containing the Dockerfile, defaults to $PWD

  • dockerfile (defaults to: 'Dockerfile')

    The file name for your dockerfile, defaults to ‘Dockerfile’

  • dockerfile_contents (defaults to: '')

    A string containing the contents of the Dockerfile [optional]



211
212
213
214
215
216
217
218
# File 'lib/puppet_docker_tools/utilities.rb', line 211

def get_value_from_dockerfile(key, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '')
  if dockerfile_contents.empty?
    file = "#{directory}/#{dockerfile}"
    fail "File #{file} doesn't exist!" unless File.exist? file
    dockerfile_contents = File.read("#{file}")
  end
  dockerfile_contents[/^#{key.upcase} (.*$)/, 1]
end

.get_value_from_env(label, namespace: '', directory: '.', dockerfile: 'Dockerfile') ⇒ Object

Get a value from a Dockerfile. Extrapolates variables and variables set in the base docker image

Parameters:

  • label

    The label containing the value you want to retrieve, e.g. ‘version’

  • namespace (defaults to: '')

    The namespace for the label, e.g. ‘org.label-schema’

  • directory (defaults to: '.')

    The directory containing the Dockerfile, defaults to $PWD

  • dockerfile (defaults to: 'Dockerfile')

    The file name for your dockerfile, defaults to ‘Dockerfile’



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/puppet_docker_tools/utilities.rb', line 95

def get_value_from_env(label, namespace: '', directory: '.', dockerfile: 'Dockerfile')
  file = "#{directory}/#{dockerfile}"
  fail "File #{file} doesn't exist!" unless File.exist? file
  text = File.read(file)

  value = text.scan(/#{Regexp.escape(namespace)}\.(.+)=(.+) \\?/).to_h[label]
  # tracking to make sure we aren't in an infinite variable loop
  checked_variables = []

  # expand out environment variables
  # This supports either label=$variable or label="$variable"
  while ! value.nil? && (value.start_with?('$') || value.start_with?('"$'))
    # if variable is quoted, get rid of leading and trailing quotes
    value.gsub!(/\A"|"\Z/, '')

    fail "Looks like there's an infinite loop with '#{value}'" if checked_variables.include?(value)

    checked_variables << value
    value = get_value_from_variable(value, directory: directory, dockerfile: dockerfile, dockerfile_contents: text)
  end
  # check in higher-level image if we didn't find it defined in this docker file
  value = get_value_from_base_image(label, namespace: namespace, directory: directory, dockerfile: dockerfile) if value.nil?
  # This gets rid of leading or trailing quotes
  value.gsub(/\A"|"\Z/, '')
end

.get_value_from_label(image, value:, namespace:) ⇒ Object

Get a value from the labels on a docker image

Parameters:

  • image

    The docker image you want to get a value from, e.g. ‘puppet/puppetserver’

  • value

    The value you want to get from the labels, e.g. ‘version’

  • namespace

    The namespace for the value, e.g. ‘org.label-schema’



81
82
83
84
85
86
# File 'lib/puppet_docker_tools/utilities.rb', line 81

def get_value_from_label(image, value: , namespace: )
  labels = Docker::Image.get(image).json["Config"]["Labels"]
  labels["#{namespace}.#{value.tr('_', '-')}"]
rescue
  nil
end

.get_value_from_variable(variable, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '') ⇒ Object

Get a value from a variable in a Dockerfile

Parameters:

  • variable

    The variable we want to look for in the Dockerfile, e.g. $PUPPET_SERVER_VERSION

  • directory (defaults to: '.')

    The directory containing the Dockerfile, defaults to $PWD

  • dockerfile (defaults to: 'Dockerfile')

    The file name for your dockerfile, defaults to ‘Dockerfile’

  • dockerfile_contents (defaults to: '')

    A string containing the contents of the Dockerfile [optional]



240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/puppet_docker_tools/utilities.rb', line 240

def get_value_from_variable(variable, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '')
  if dockerfile_contents.empty?
    file = "#{directory}/#{dockerfile}"
    fail "File #{file} doesn't exist!" unless File.exist? file
    dockerfile_contents = File.read("#{file}")
  end
  variable_clone = String.new(variable)
  # get rid of the leading $ for the variable
  variable_clone[0] = ''

  dockerfile_contents[/#{variable_clone}=([^\s]+)/, 1]
end

.parse_build_args(build_args) ⇒ Object

parse build args into a hash for easier manipulation

Parameters:

  • build_args

    array of build_args with each entry in the format ‘arg=value’



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/puppet_docker_tools/utilities.rb', line 34

def parse_build_args(build_args)
  args_hash = {}

  build_args.each do |arg|
    fields = arg.split('=')
    key = fields.first
    # get rid of the key from the fields so we can get the value
    fields.shift
    # join the remaining fields with '=' in case the value had '=' in it
    value = fields.join('=')
    args_hash[key] = value
  end

  args_hash
end

.pull(image) ⇒ Object

Pull a docker image

Parameters:

  • image

    The image to pull. If the image does not include the tag to pull, it will pull all tags for that image



146
147
148
149
150
151
152
153
154
# File 'lib/puppet_docker_tools/utilities.rb', line 146

def pull(image)
  if image.include?(':')
    puts "Pulling #{image}"
    PuppetDockerTools::Utilities.pull_single_tag(image)
  else
    puts "Pulling all tags for #{image}"
    PuppetDockerTools::Utilities.pull_all_tags(image)
  end
end

.pull_all_tags(image) ⇒ Object

Pull all tags for a docker image

Parameters:

  • image

    The image to pull, e.g. puppet/puppetserver



159
160
161
162
163
164
165
166
167
168
# File 'lib/puppet_docker_tools/utilities.rb', line 159

def pull_all_tags(image)
  Docker::Image.create('fromImage' => image)

  # Filter through existing tags of that image so we can output what we pulled
  images = Docker::Image.all('filter' => image)
  images.each do |img|
    timestamp = PuppetDockerTools::Utilities.format_timestamp(img.info["Created"])
    puts "Pulled #{img.info["RepoTags"].join(', ')}, last updated #{timestamp}"
  end
end

.pull_single_tag(tag) ⇒ Object

Pull a single tag of a docker image

Parameters:

  • tag

    The image/tag to pull, e.g. puppet/puppetserver:latest



173
174
175
176
177
# File 'lib/puppet_docker_tools/utilities.rb', line 173

def pull_single_tag(tag)
  image = Docker::Image.create('fromImage' => tag)
  timestamp = PuppetDockerTools::Utilities.format_timestamp(image.info["Created"])
  puts "Pulled #{image.info["RepoTags"].first}, last updated #{timestamp}"
end

.push_to_docker_repo(image_name, stream_output = true) ⇒ Object

Push an image to a docker repository

Parameters:

  • image_name

    The image to push, including the tag e.g., puppet/puppetserver:latest If pushing to a private repo, the image name should include the repo endpoint, like my-docker-repo.internal.net/puppet/puppetserver:latest

  • stream_output (defaults to: true)

    Whether or not to stream output as it comes in, defaults to true

Returns:

  • Returns an array containing the integer exitstatus of the push command and a string containing the combined stdout and stderr from the push



17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/puppet_docker_tools/utilities.rb', line 17

def push_to_docker_repo(image_name, stream_output=true)
  Open3.popen2e("docker push #{image_name}") do |stdin, output_stream, wait_thread|
    output=''
    while line = output_stream.gets
      if stream_output
        puts line
      end
      output += line
    end
    exit_status = wait_thread.value.exitstatus
    return exit_status, output
  end
end

.update_base_images(tags) ⇒ Object

Pull the specified tags

Parameters:

  • tags (Array)

    A list of tags to pull, e.g. [‘centos:7’, ‘ubuntu:16.04’]



182
183
184
185
186
# File 'lib/puppet_docker_tools/utilities.rb', line 182

def update_base_images(tags)
  tags.each do |tag|
    PuppetDockerTools::Utilities.pull(tag)
  end
end