Skip to content

Commit 1fb3f30

Browse files
author
Sophia Castellarin
authored
Merge pull request #11472 from soapy1/podman-provisioner
Add podman provisioner
2 parents 258bbaa + bcce2f7 commit 1fb3f30

File tree

23 files changed

+1005
-395
lines changed

23 files changed

+1005
-395
lines changed

Diff for: plugins/provisioners/container/client.rb

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
require 'digest/sha1'
2+
3+
module VagrantPlugins
4+
module ContainerProvisioner
5+
class Client
6+
def initialize(machine, container_command)
7+
@machine = machine
8+
@container_command = container_command
9+
end
10+
11+
# Build an image given a path to a Dockerfile
12+
#
13+
# @param [String] - Path to the Dockerfile to pass to
14+
# container build command
15+
def build_images(images)
16+
@machine.communicate.tap do |comm|
17+
images.each do |path, opts|
18+
@machine.ui.info(I18n.t("vagrant.container_building_single", path: path))
19+
comm.sudo("#{@container_command} build #{opts[:args]} #{path}") do |type, data|
20+
handle_comm(type, data)
21+
end
22+
end
23+
end
24+
end
25+
26+
# Pull image given a list of images
27+
#
28+
# @param [String] - Image name
29+
def pull_images(*images)
30+
@machine.communicate.tap do |comm|
31+
images.each do |image|
32+
@machine.ui.info(I18n.t("vagrant.container_pulling_single", name: image))
33+
comm.sudo("#{@container_command} pull #{image}") do |type, data|
34+
handle_comm(type, data)
35+
end
36+
end
37+
end
38+
end
39+
40+
def run(containers)
41+
containers.each do |name, config|
42+
cids_dir = "/var/lib/vagrant/cids"
43+
config[:cidfile] ||= "#{cids_dir}/#{Digest::SHA1.hexdigest name}"
44+
45+
@machine.ui.info(I18n.t("vagrant.container_running", name: name))
46+
@machine.communicate.sudo("mkdir -p #{cids_dir}")
47+
run_container({
48+
name: name,
49+
original_name: name,
50+
}.merge(config))
51+
end
52+
end
53+
54+
# Run a OCI container. If the container does not exist it will be
55+
# created. If the image is stale it will be recreated and restarted
56+
def run_container(config)
57+
raise "Container's cidfile was not provided!" if !config[:cidfile]
58+
59+
id = "$(cat #{config[:cidfile]})"
60+
61+
if container_exists?(id)
62+
if container_args_changed?(config)
63+
@machine.ui.info(I18n.t("vagrant.container_restarting_container_args",
64+
name: config[:name],
65+
))
66+
stop_container(id)
67+
create_container(config)
68+
elsif container_image_changed?(config)
69+
@machine.ui.info(I18n.t("vagrant.container_restarting_container_image",
70+
name: config[:name],
71+
))
72+
stop_container(id)
73+
create_container(config)
74+
else
75+
start_container(id)
76+
end
77+
else
78+
create_container(config)
79+
end
80+
end
81+
82+
def container_exists?(id)
83+
lookup_container(id, true)
84+
end
85+
86+
# Start container
87+
#
88+
# @param String - Image id
89+
def start_container(id)
90+
if !container_running?(id)
91+
@machine.communicate.sudo("#{@container_command} start #{id}")
92+
end
93+
end
94+
95+
# Stop and remove container
96+
#
97+
# @param String - Image id
98+
def stop_container(id)
99+
@machine.communicate.sudo %[
100+
#{@container_command} stop #{id}
101+
#{@container_command} rm #{id}
102+
]
103+
end
104+
105+
def container_running?(id)
106+
lookup_container(id)
107+
end
108+
109+
def container_image_changed?(config)
110+
# Returns true if there is a container running with the given :name,
111+
# and the container is not using the latest :image.
112+
113+
# Here, "<cmd> inspect <container>" returns the id of the image
114+
# that the container is using. We check that the latest image that
115+
# has been built with that name (:image) matches the one that the
116+
# container is running.
117+
cmd = ("#{@container_command} inspect --format='{{.Image}}' #{config[:name]} |" +
118+
" grep $(#{@container_command} images -q #{config[:image]})")
119+
return !@machine.communicate.test(cmd)
120+
end
121+
122+
def container_args_changed?(config)
123+
path = container_data_path(config)
124+
return true if !path.exist?
125+
126+
args = container_run_args(config)
127+
sha = Digest::SHA1.hexdigest(args)
128+
return true if path.read.chomp != sha
129+
130+
return false
131+
end
132+
133+
def create_container(config)
134+
args = container_run_args(config)
135+
136+
@machine.communicate.sudo %[rm -f "#{config[:cidfile]}"]
137+
@machine.communicate.sudo %[#{@container_command} run #{args}]
138+
139+
sha = Digest::SHA1.hexdigest(args)
140+
container_data_path(config).open("w+") do |f|
141+
f.write(sha)
142+
end
143+
end
144+
145+
# Looks up if a container with a given id exists using the
146+
# `ps` command. Returns Boolean
147+
#
148+
# @param String - Image id
149+
def lookup_container(id, list_all = false)
150+
container_ps = "sudo #{@container_command} ps -q"
151+
container_ps << " -a" if list_all
152+
@machine.communicate.tap do |comm|
153+
return comm.test("#{container_ps} --no-trunc | grep -wFq #{id}")
154+
end
155+
end
156+
157+
def container_name(config)
158+
name = config[:name]
159+
160+
# If the name is the automatically assigned name, then
161+
# replace the "/" with "-" because "/" is not a valid
162+
# character for a container name.
163+
name = name.gsub("/", "-") if name == config[:original_name]
164+
name
165+
end
166+
167+
# Compiles run arguments to be appended to command string.
168+
# Returns String
169+
def container_run_args(config)
170+
name = container_name(config)
171+
172+
args = "--cidfile=#{config[:cidfile]} "
173+
args << "-d " if config[:daemonize]
174+
args << "--name #{name} " if name && config[:auto_assign_name]
175+
args << "--restart=#{config[:restart]}" if config[:restart]
176+
args << " #{config[:args]}" if config[:args]
177+
178+
"#{args} #{config[:image]} #{config[:cmd]}".strip
179+
end
180+
181+
def container_data_path(config)
182+
name = container_name(config)
183+
@machine.data_dir.join("#{@container_command}-#{name}")
184+
end
185+
186+
protected
187+
188+
# This handles outputting the communication data back to the UI
189+
def handle_comm(type, data)
190+
if [:stderr, :stdout].include?(type)
191+
# Clear out the newline since we add one
192+
data = data.chomp
193+
return if data.empty?
194+
195+
options = {}
196+
#options[:color] = color if !config.keep_color
197+
198+
@machine.ui.info(data.chomp, options)
199+
end
200+
end
201+
end
202+
end
203+
end

Diff for: plugins/provisioners/container/config.rb

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'set'
2+
3+
module VagrantPlugins
4+
module ContainerProvisioner
5+
class Config < Vagrant.plugin("2", :config)
6+
attr_reader :images
7+
attr_accessor :post_install_provisioner
8+
9+
def initialize
10+
@images = Set.new
11+
@post_install_provisioner = nil
12+
13+
@__build_images = []
14+
@__containers = Hash.new { |h, k| h[k] = {} }
15+
end
16+
17+
# Accessor for internal state.
18+
def build_images
19+
@__build_images
20+
end
21+
22+
# Accessor for the internal state.
23+
def containers
24+
@__containers
25+
end
26+
27+
# Defines an image to build using `<cmd> build` within the machine.
28+
#
29+
# @param [String] path Path to the Dockerfile to pass to
30+
# container build command
31+
def build_image(path, **opts)
32+
@__build_images << [path, opts]
33+
end
34+
35+
def images=(images)
36+
@images = Set.new(images)
37+
end
38+
39+
def pull_images(*images)
40+
@images += images.map(&:to_s)
41+
end
42+
43+
def post_install_provision(name, **options, &block)
44+
proxy = VagrantPlugins::Kernel_V2::VMConfig.new
45+
proxy.provision(name, **options, &block)
46+
@post_install_provisioner = proxy.provisioners.first
47+
end
48+
49+
def run(name, **options)
50+
@__containers[name.to_s] = options.dup
51+
end
52+
53+
def merge(other)
54+
super.tap do |result|
55+
result.pull_images(*(other.images + self.images))
56+
57+
build_images = @__build_images.dup
58+
build_images += other.build_images
59+
result.instance_variable_set(:@__build_images, build_images)
60+
61+
containers = {}
62+
@__containers.each do |name, params|
63+
containers[name] = params.dup
64+
end
65+
other.containers.each do |name, params|
66+
containers[name] = @__containers[name].merge(params)
67+
end
68+
69+
result.instance_variable_set(:@__containers, containers)
70+
end
71+
end
72+
73+
def finalize!
74+
@__containers.each do |name, params|
75+
params[:image] ||= name
76+
params[:auto_assign_name] = true if !params.key?(:auto_assign_name)
77+
params[:daemonize] = true if !params.key?(:daemonize)
78+
params[:restart] = "always" if !params.key?(:restart)
79+
end
80+
end
81+
end
82+
end
83+
end

Diff for: plugins/provisioners/container/installer.rb

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module VagrantPlugins
2+
module ContainerProvisioner
3+
class Installer
4+
def initialize(machine)
5+
@machine = machine
6+
end
7+
8+
def ensure_installed
9+
# nothing to do
10+
end
11+
end
12+
end
13+
end

Diff for: plugins/provisioners/container/plugin.rb

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
require "vagrant"
2+
3+
module VagrantPlugins
4+
module ContainerProvisioner
5+
class Plugin < Vagrant.plugin("2")
6+
name "container"
7+
description <<-DESC
8+
Provides support for provisioning your virtual machines with
9+
OCI images and containers.
10+
DESC
11+
12+
config(:container, :provisioner) do
13+
require_relative "config"
14+
Config
15+
end
16+
17+
provisioner(:container) do
18+
require_relative "provisioner"
19+
Provisioner
20+
end
21+
end
22+
end
23+
end

Diff for: plugins/provisioners/container/provisioner.rb

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require_relative "client"
2+
require_relative "installer"
3+
4+
module VagrantPlugins
5+
module ContainerProvisioner
6+
class Provisioner < Vagrant.plugin("2", :provisioner)
7+
def initialize(machine, config, installer = nil, client = nil)
8+
super(machine, config)
9+
10+
@installer = installer || Installer.new(@machine)
11+
@client = client || Client.new(@machine, "")
12+
@logger = Log4r::Logger.new("vagrant::provisioners::container")
13+
end
14+
15+
def provision
16+
# nothing to do
17+
end
18+
19+
def run_provisioner(env)
20+
klass = Vagrant.plugin("2").manager.provisioners[env[:provisioner].type]
21+
result = klass.new(env[:machine], env[:provisioner].config)
22+
result.config.finalize!
23+
24+
result.provision
25+
end
26+
end
27+
end
28+
end

0 commit comments

Comments
 (0)