feat: initial commit

This commit is contained in:
amy 2025-04-01 17:40:03 +00:00
commit 38f495e3f4
457 changed files with 40577 additions and 0 deletions

View file

@ -0,0 +1,109 @@
# frozen_string_literal: true
require 'deep_merge'
Puppet::Type.type(:docker_compose).provide(:ruby) do
desc 'Support for Puppet running Docker Compose'
mk_resource_methods
has_command(:docker, 'docker')
def set_tmpdir
return unless resource[:tmpdir]
# Check if the the tmpdir target exists
Puppet.warning("#{resource[:tmpdir]} (defined as docker_compose tmpdir) does not exist") unless Dir.exist?(resource[:tmpdir])
# Set TMPDIR environment variable only if defined among resources and exists
ENV['TMPDIR'] = resource[:tmpdir] if Dir.exist?(resource[:tmpdir])
end
def exists?
Puppet.info("Checking for compose project #{name}")
compose_services = {}
compose_containers = []
set_tmpdir
# get merged config using docker-compose config
args = ['compose', compose_files, '-p', name, 'config'].insert(3, resource[:options]).compact
compose_output = Puppet::Util::Yaml.safe_load(execute([command(:docker)] + args, combine: false), [Symbol])
containers = docker([
'ps',
'--format',
"'{{.Label \"com.docker.compose.service\"}}-{{.Image}}'",
'--filter',
"label=com.docker.compose.project=#{name}",
]).split("\n")
compose_containers.push(*containers)
compose_services = compose_output['services']
return false if compose_services.count != compose_containers.uniq.count
counts = Hash[*compose_services.each.map { |key, array|
image = array['image'] || get_image(key, compose_services)
Puppet.info("Checking for compose service #{key} #{image}")
[key, compose_containers.count("'#{key}-#{image}'")]
}.flatten]
# No containers found for the project
if counts.empty? ||
# Containers described in the compose file are not running
counts.any? { |_k, v| v.zero? } ||
# The scaling factors in the resource do not match the number of running containers
(resource[:scale] && counts.merge(resource[:scale]) != counts)
false
else
true
end
end
def get_image(service_name, compose_services)
image = compose_services[service_name]['image']
unless image
if compose_services[service_name]['extends']
image = get_image(compose_services[service_name]['extends'], compose_services)
elsif compose_services[service_name]['build']
image = "#{name}_#{service_name}"
end
end
image
end
def create
Puppet.info("Running compose project #{name}")
args = ['compose', compose_files, '-p', name, 'up', '-d', '--remove-orphans'].insert(3, resource[:options]).insert(5, resource[:up_args]).compact
docker(args)
return unless resource[:scale]
instructions = resource[:scale].map { |k, v| "#{k}=#{v}" }
Puppet.info("Scaling compose project #{name}: #{instructions.join(' ')}")
args = ['compose', compose_files, '-p', name, 'scale'].insert(3, resource[:options]).compact + instructions
docker(args)
end
def destroy
Puppet.info("Removing all containers for compose project #{name}")
kill_args = ['compose', compose_files, '-p', name, 'kill'].insert(3, resource[:options]).compact
docker(kill_args)
rm_args = ['compose', compose_files, '-p', name, 'rm', '--force', '-v'].insert(3, resource[:options]).compact
docker(rm_args)
end
def restart
return unless exists?
Puppet.info("Rebuilding and Restarting all containers for compose project #{name}")
kill_args = ['compose', compose_files, '-p', name, 'kill'].insert(3, resource[:options]).compact
docker(kill_args)
build_args = ['compose', compose_files, '-p', name, 'build'].insert(3, resource[:options]).compact
docker(build_args)
create
end
def compose_files
resource[:compose_files].map { |x| ['-f', x] }.flatten
end
end

View file

@ -0,0 +1,95 @@
# frozen_string_literal: true
require 'json'
Puppet::Type.type(:docker_network).provide(:ruby) do
desc 'Support for Docker Networking'
mk_resource_methods
has_command(:docker, 'docker')
def network_conf
flags = ['network', 'create']
multi_flags = ->(values, format) {
filtered = [values].flatten.compact
filtered.map { |val| format % val }
}
[
['--driver=%s', :driver],
['--subnet=%s', :subnet],
['--gateway=%s', :gateway],
['--ip-range=%s', :ip_range],
['--ipam-driver=%s', :ipam_driver],
['--aux-address=%s', :aux_address],
['--opt=%s', :options],
].each do |(format, key)|
values = resource[key]
new_flags = multi_flags.call(values, format)
flags.concat(new_flags)
end
if defined?(resource[:additional_flags])
additional_flags = []
if resource[:additional_flags].is_a?(String)
additional_flags = resource[:additional_flags].split
elsif resource[:additional_flags].is_a?(Array)
additional_flags = resource[:additional_flags]
end
additional_flags.each do |additional_flag|
flags << additional_flag
end
end
flags << resource[:name]
end
def self.instances
output = docker(['network', 'ls'])
lines = output.split("\n")
lines.shift # remove header row
lines.map do |line|
_, name, driver = line.split
inspect = docker(['network', 'inspect', name])
obj = JSON.parse(inspect).first
ipam_driver = (obj['IPAM']['Driver'] unless obj['IPAM']['Driver'].nil?)
subnet = (obj['IPAM']['Config'].first['Subnet'] if !(obj['IPAM']['Config'].nil? || obj['IPAM']['Config'].empty?) && (obj['IPAM']['Config'].first.key? 'Subnet'))
new(
name: name,
id: obj['Id'],
ipam_driver: ipam_driver,
subnet: subnet,
ensure: :present,
driver: driver,
)
end
end
def self.prefetch(resources)
instances.each do |prov|
if resource = resources[prov.name] # rubocop:disable Lint/AssignmentInCondition
resource.provider = prov
end
end
end
def flush
raise Puppet::Error, _('Docker network does not support mutating existing networks') if !@property_hash.empty? && @property_hash[:ensure] != :absent
end
def exists?
Puppet.info("Checking if docker network #{name} exists")
@property_hash[:ensure] == :present
end
def create
Puppet.info("Creating docker network #{name}")
docker(network_conf)
end
def destroy
Puppet.info("Removing docker network #{name}")
docker(['network', 'rm', name])
end
end

View file

@ -0,0 +1,88 @@
# frozen_string_literal: true
require 'deep_merge'
Puppet::Type.type(:docker_stack).provide(:ruby) do
desc 'Support for Puppet running Docker Stacks'
mk_resource_methods
has_command(:docker, 'docker')
def exists?
Puppet.info("Checking for stack #{name}")
stack_services = {}
stack_containers = []
resource[:compose_files].each do |file|
compose_file = YAML.safe_load(File.read(file), [], [], true)
# rubocop:disable Style/StringLiterals
containers = docker([
'ps',
'--format',
"{{.Label \"com.docker.swarm.service.name\"}}-{{.Image}}",
'--filter',
"label=com.docker.stack.namespace=#{name}",
]).split("\n").each do |c|
c.slice!("#{name}_")
end
stack_containers.push(*containers)
stack_containers.uniq!
# rubocop:enable Style/StringLiterals
case compose_file['version']
when %r{^3(\.[0-7])?$}
stack_services.merge!(compose_file['services'])
else
raise(Puppet::Error, "Unsupported docker compose file syntax version \"#{compose_file['version']}\"!")
end
end
return false if stack_services.count != stack_containers.count
counts = Hash[*stack_services.each.map { |key, array|
image = array['image'] || get_image(key, stack_services)
image = "#{image}:latest" unless image.include?(':')
Puppet.info("Checking for compose service #{key} #{image}")
["#{key}-#{image}", stack_containers.count("#{key}-#{image}")]
}.flatten]
# No containers found for the project
if counts.empty? ||
# Containers described in the compose file are not running
counts.any? { |_k, v| v.zero? }
false
else
true
end
end
def get_image(service_name, stack_services)
image = stack_services[service_name]['image']
unless image
if stack_services[service_name]['extends']
image = get_image(stack_services[service_name]['extends'], stack_services)
elsif stack_services[service_name]['build']
image = "#{name}_#{service_name}"
end
end
image
end
def create
Puppet.info("Running stack #{name}")
args = ['stack', 'deploy', compose_files, name].insert(1, bundle_file).insert(4, resource[:up_args]).compact
docker(args)
end
def destroy
Puppet.info("Removing docker stack #{name}")
rm_args = ['stack', 'rm', name]
docker(rm_args)
end
def bundle_file
return resource[:bundle_file].map { |x| ['-c', x] }.flatten unless resource[:bundle_file].nil?
end
def compose_files
resource[:compose_files].map { |x| ['-c', x] }.flatten
end
end

View file

@ -0,0 +1,70 @@
# frozen_string_literal: true
require 'json'
Puppet::Type.type(:docker_volume).provide(:ruby) do
desc 'Support for Docker Volumes'
mk_resource_methods
has_command(:docker, 'docker')
def volume_conf
flags = ['volume', 'create']
multi_flags = ->(values, format) {
filtered = [values].flatten.compact
filtered.map { |val| format % val }
}
[
['--driver=%s', :driver],
['--opt=%s', :options],
].each do |(format, key)|
values = resource[key]
new_flags = multi_flags.call(values, format)
flags.concat(new_flags)
end
flags << resource[:name]
end
def self.instances
output = docker(['volume', 'ls'])
lines = output.split("\n")
lines.shift # remove header row
lines.map do |line|
driver, name = line.split
inspect = docker(['volume', 'inspect', name])
obj = JSON.parse(inspect).first
new(
name: name,
mountpoint: obj['Mountpoint'],
options: obj['Options'],
ensure: :present,
driver: driver,
)
end
end
def self.prefetch(resources)
instances.each do |prov|
if (resource = resources[prov.name])
resource.provider = prov
end
end
end
def exists?
Puppet.info("Checking if docker volume #{name} exists")
@property_hash[:ensure] == :present
end
def create
Puppet.info("Creating docker volume #{name}")
docker(volume_conf)
end
def destroy
Puppet.info("Removing docker volume #{name}")
docker(['volume', 'rm', name])
end
end