Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add podman provisioner #11472

Merged
merged 6 commits into from
Apr 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions plugins/provisioners/container/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
require 'digest/sha1'

module VagrantPlugins
module ContainerProvisioner
class Client
def initialize(machine, container_command)
@machine = machine
@container_command = container_command
end

# Build an image given a path to a Dockerfile
#
# @param [String] - Path to the Dockerfile to pass to
# container build command
def build_images(images)
@machine.communicate.tap do |comm|
images.each do |path, opts|
@machine.ui.info(I18n.t("vagrant.container_building_single", path: path))
comm.sudo("#{@container_command} build #{opts[:args]} #{path}") do |type, data|
handle_comm(type, data)
end
end
end
end

# Pull image given a list of images
#
# @param [String] - Image name
def pull_images(*images)
@machine.communicate.tap do |comm|
images.each do |image|
@machine.ui.info(I18n.t("vagrant.container_pulling_single", name: image))
comm.sudo("#{@container_command} pull #{image}") do |type, data|
handle_comm(type, data)
end
end
end
end

def run(containers)
containers.each do |name, config|
cids_dir = "/var/lib/vagrant/cids"
config[:cidfile] ||= "#{cids_dir}/#{Digest::SHA1.hexdigest name}"

@machine.ui.info(I18n.t("vagrant.container_running", name: name))
@machine.communicate.sudo("mkdir -p #{cids_dir}")
run_container({
name: name,
original_name: name,
}.merge(config))
end
end

# Run a OCI container. If the container does not exist it will be
# created. If the image is stale it will be recreated and restarted
def run_container(config)
raise "Container's cidfile was not provided!" if !config[:cidfile]

id = "$(cat #{config[:cidfile]})"

if container_exists?(id)
if container_args_changed?(config)
@machine.ui.info(I18n.t("vagrant.container_restarting_container_args",
name: config[:name],
))
stop_container(id)
create_container(config)
elsif container_image_changed?(config)
@machine.ui.info(I18n.t("vagrant.container_restarting_container_image",
name: config[:name],
))
stop_container(id)
create_container(config)
else
start_container(id)
end
else
create_container(config)
end
end

def container_exists?(id)
lookup_container(id, true)
end

# Start container
#
# @param String - Image id
def start_container(id)
if !container_running?(id)
@machine.communicate.sudo("#{@container_command} start #{id}")
end
end

# Stop and remove container
#
# @param String - Image id
def stop_container(id)
@machine.communicate.sudo %[
#{@container_command} stop #{id}
#{@container_command} rm #{id}
]
end

def container_running?(id)
lookup_container(id)
end

def container_image_changed?(config)
# Returns true if there is a container running with the given :name,
# and the container is not using the latest :image.

# Here, "<cmd> inspect <container>" returns the id of the image
# that the container is using. We check that the latest image that
# has been built with that name (:image) matches the one that the
# container is running.
cmd = ("#{@container_command} inspect --format='{{.Image}}' #{config[:name]} |" +
" grep $(#{@container_command} images -q #{config[:image]})")
return [email protected](cmd)
end

def container_args_changed?(config)
path = container_data_path(config)
return true if !path.exist?

args = container_run_args(config)
sha = Digest::SHA1.hexdigest(args)
return true if path.read.chomp != sha

return false
end

def create_container(config)
args = container_run_args(config)

@machine.communicate.sudo %[rm -f "#{config[:cidfile]}"]
@machine.communicate.sudo %[#{@container_command} run #{args}]

sha = Digest::SHA1.hexdigest(args)
container_data_path(config).open("w+") do |f|
f.write(sha)
end
end

# Looks up if a container with a given id exists using the
# `ps` command. Returns Boolean
#
# @param String - Image id
def lookup_container(id, list_all = false)
container_ps = "sudo #{@container_command} ps -q"
container_ps << " -a" if list_all
@machine.communicate.tap do |comm|
return comm.test("#{container_ps} --no-trunc | grep -wFq #{id}")
end
end

def container_name(config)
name = config[:name]

# If the name is the automatically assigned name, then
# replace the "/" with "-" because "/" is not a valid
# character for a container name.
name = name.gsub("/", "-") if name == config[:original_name]
name
end

# Compiles run arguments to be appended to command string.
# Returns String
def container_run_args(config)
name = container_name(config)

args = "--cidfile=#{config[:cidfile]} "
args << "-d " if config[:daemonize]
args << "--name #{name} " if name && config[:auto_assign_name]
args << "--restart=#{config[:restart]}" if config[:restart]
args << " #{config[:args]}" if config[:args]

"#{args} #{config[:image]} #{config[:cmd]}".strip
end

def container_data_path(config)
name = container_name(config)
@machine.data_dir.join("#{@container_command}-#{name}")
end

protected

# This handles outputting the communication data back to the UI
def handle_comm(type, data)
if [:stderr, :stdout].include?(type)
# Clear out the newline since we add one
data = data.chomp
return if data.empty?

options = {}
#options[:color] = color if !config.keep_color

@machine.ui.info(data.chomp, options)
end
end
end
end
end
83 changes: 83 additions & 0 deletions plugins/provisioners/container/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require 'set'

module VagrantPlugins
module ContainerProvisioner
class Config < Vagrant.plugin("2", :config)
attr_reader :images
attr_accessor :post_install_provisioner

def initialize
@images = Set.new
@post_install_provisioner = nil

@__build_images = []
@__containers = Hash.new { |h, k| h[k] = {} }
end

# Accessor for internal state.
def build_images
@__build_images
end

# Accessor for the internal state.
def containers
@__containers
end

# Defines an image to build using `<cmd> build` within the machine.
#
# @param [String] path Path to the Dockerfile to pass to
# container build command
def build_image(path, **opts)
@__build_images << [path, opts]
end

def images=(images)
@images = Set.new(images)
end

def pull_images(*images)
@images += images.map(&:to_s)
end

def post_install_provision(name, **options, &block)
proxy = VagrantPlugins::Kernel_V2::VMConfig.new
proxy.provision(name, **options, &block)
@post_install_provisioner = proxy.provisioners.first
end

def run(name, **options)
@__containers[name.to_s] = options.dup
end

def merge(other)
super.tap do |result|
result.pull_images(*(other.images + self.images))

build_images = @__build_images.dup
build_images += other.build_images
result.instance_variable_set(:@__build_images, build_images)

containers = {}
@__containers.each do |name, params|
containers[name] = params.dup
end
other.containers.each do |name, params|
containers[name] = @__containers[name].merge(params)
end

result.instance_variable_set(:@__containers, containers)
end
end

def finalize!
@__containers.each do |name, params|
params[:image] ||= name
params[:auto_assign_name] = true if !params.key?(:auto_assign_name)
params[:daemonize] = true if !params.key?(:daemonize)
params[:restart] = "always" if !params.key?(:restart)
end
end
end
end
end
13 changes: 13 additions & 0 deletions plugins/provisioners/container/installer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module VagrantPlugins
module ContainerProvisioner
class Installer
def initialize(machine)
@machine = machine
end

def ensure_installed
# nothing to do
end
end
end
end
23 changes: 23 additions & 0 deletions plugins/provisioners/container/plugin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "vagrant"

module VagrantPlugins
module ContainerProvisioner
class Plugin < Vagrant.plugin("2")
name "container"
description <<-DESC
Provides support for provisioning your virtual machines with
OCI images and containers.
DESC

config(:container, :provisioner) do
require_relative "config"
Config
end

provisioner(:container) do
require_relative "provisioner"
Provisioner
end
end
end
end
28 changes: 28 additions & 0 deletions plugins/provisioners/container/provisioner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative "client"
require_relative "installer"

module VagrantPlugins
module ContainerProvisioner
class Provisioner < Vagrant.plugin("2", :provisioner)
def initialize(machine, config, installer = nil, client = nil)
super(machine, config)

@installer = installer || Installer.new(@machine)
@client = client || Client.new(@machine, "")
@logger = Log4r::Logger.new("vagrant::provisioners::container")
end

def provision
# nothing to do
end

def run_provisioner(env)
klass = Vagrant.plugin("2").manager.provisioners[env[:provisioner].type]
result = klass.new(env[:machine], env[:provisioner].config)
result.config.finalize!

result.provision
end
end
end
end
Loading