1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-02-14 01:57:24 +03:00

F #5056: add Dockerfile support for image creation (#721)

This commit is contained in:
Alejandro Huertas Herrero 2021-01-29 20:40:55 +01:00 committed by GitHub
parent 9ea10dd172
commit 28bfcdb3c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 60 deletions

View File

@ -22,7 +22,7 @@ class OneImageHelper < OpenNebulaHelper::OneHelper
# This list contains prefixes that should skip adding user home to the path
# This must have the same content as the case $FROM in downloader.sh
PREFIXES = %w[http https ssh s3 rbd vcenter lxd docker]
PREFIXES = %w[http https ssh s3 rbd vcenter lxd docker dockerfile]
TEMPLATE_OPTIONS=[
{

View File

@ -35,6 +35,7 @@ end
$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << RUBY_LIB_LOCATION + '/cli'
require 'tempfile'
require 'command_parser'
require 'one_helper/oneimage_helper'
require 'one_helper/onedatastore_helper'
@ -73,6 +74,12 @@ CommandParser::CmdParser.new(ARGV) do
:description => 'lock all actions'
}
NO_CONTEXT = {
:name => 'no_context',
:large => '--no-context',
:description => 'Do not add context when building from Dockerfile'
}
########################################################################
# Global Options
########################################################################
@ -83,7 +90,9 @@ CommandParser::CmdParser.new(ARGV) do
list_options << OpenNebulaHelper::NUMERIC
list_options << OpenNebulaHelper::DESCRIBE
CREATE_OPTIONS = [OneDatastoreHelper::DATASTORE, OneImageHelper::IMAGE]
CREATE_OPTIONS = [OneDatastoreHelper::DATASTORE,
OneImageHelper::IMAGE,
NO_CONTEXT]
########################################################################
# Formatters for arguments
@ -168,6 +177,15 @@ CommandParser::CmdParser.new(ARGV) do
next -1
end
# Add context information when building image (just working on Docker)
if (options.key? :no_context) && options[:path]
if options[:path].include?('?')
options[:path] << '&context=no'
else
options[:path] << '?context=no'
end
end
helper.create_resource(options) do |image|
begin
if args[0]
@ -450,4 +468,73 @@ CommandParser::CmdParser.new(ARGV) do
return 0
end
dockerfile_desc = <<-EOT.unindent
Create an image based on a Dockerfile
EOT
command :dockerfile,
dockerfile_desc,
:options => CREATE_OPTIONS +
OneImageHelper::TEMPLATE_OPTIONS do
# Check user options
unless options[:datastore]
STDERR.puts 'Datastore to save the image is mandatory: '
STDERR.puts '\t -d datastore_id'
exit(-1)
end
unless options[:name]
STDERR.puts 'No name provided'
exit(-1)
end
unless options[:size]
STDERR.puts 'No size given'
exit(-1)
end
# Prepare editor
tmp = Tempfile.new('dockerfile')
if ENV['EDITOR']
editor_path = ENV['EDITOR']
else
editor_path = EDITOR_PATH
end
system("#{editor_path} #{tmp.path}")
unless $CHILD_STATUS.exitstatus.zero?
STDERR.puts('Editor not defined')
exit(-1)
end
tmp.close
# Create image
helper.create_resource(options) do |image|
begin
b64 = Base64.strict_encode64(File.read(tmp.path))
options[:path] = "dockerfile:///?fileb64=#{b64}&" \
"size=#{options[:size]}"
options[:path] << '&context=no' if options.key?(:no_context)
res = OneImageHelper.create_image_template(options)
if res.first != 0
STDERR.puts res.last
next -1
end
template = res.last
image.allocate(template, options[:datastore], false)
rescue StandardError => e
STDERR.puts e.message
exit(-1)
end
end
end
end

View File

@ -16,7 +16,7 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
if [ -z "${ONE_LOCATION}" ]; then
if [ -z "$ONE_LOCATION" ]; then
LIB_LOCATION=/usr/lib/one
VAR_LOCATION=/var/lib/one
SHARE_LOCATION=/usr/share/one
@ -31,16 +31,16 @@ fi
#-------------------------------------------------------------------------------
# Downloader configuration attributes
#-------------------------------------------------------------------------------
DRIVER_PATH=$(dirname $0)
DRIVER_PATH=$(dirname "$0")
MARKET_URL=$1
MK_DOCKER=$LIB_LOCATION/sh/create_docker_image.sh
MK_DOCKER="$LIB_LOCATION/sh/create_docker_image.sh"
#Default DNS server to download the packages
DNS_SERVER="1.1.1.1"
#Directory used to download packages
TMP_DIR=/var/tmp
TMP_DIR="/var/tmp"
#Context location
CONTEXT_PATH="$SHARE_LOCATION/context"
@ -54,75 +54,119 @@ DOCKERFILE="$SHARE_LOCATION/dockerhub"
# something fails
#-------------------------------------------------------------------------------
function clean {
docker rm -f "$container_id" > /dev/null 2>&1 || true
docker image rm -f one"$sid" > /dev/null 2>&1
docker rm -f $container_id > /dev/null 2>&1 || true
docker image rm -f one$sid > /dev/null 2>&1
rm -rf $dockerdir
rm -rf "$dockerdir"
}
set -e -o pipefail
id=$(echo "$RANDOM-$RANDOM-$RANDOM-$RANDOM-$RANDOM")
sid=$(echo "$id" | cut -d '-' -f 1)
#-------------------------------------------------------------------------------
# Parse downloader URL
#-------------------------------------------------------------------------------
# URL is in the form
# docker://<conatiner_name>?size=&filesystem=&format=&distro=&tag=
# docker://<container_name>?size=&filesystem=&format=&distro=&tag=
# dockerfile://<path_to_file>?size=&filesystem=&format=&distro=
# dockerfile://?fileb64=&size=&filesystem=&format=&distro=
#
# consinter_name: As listed in docker hub. e.g. alpine
# container_name: As listed in docker hub. e.g. alpine
# size: in MB for the resulting image
# filesystem: filesystem type e.g. ext4
# format: image format e.g. raw or qcow2
# distro: base image distro to install contents
# tag: one of the image supported tags
# fileb64: dockerfile in base 64
# context: yes to generate image with context packages, falso otherwise
#-------------------------------------------------------------------------------
id=`echo "$RANDOM-$RANDOM-$RANDOM-$RANDOM-$RANDOM"`
sid=`echo $id | cut -d '-' -f 1`
url=`echo $MARKET_URL | grep -oP "^"docker://"\K.*"`
arguments=`echo $url | cut -d '?' -f 2`
if [[ $MARKET_URL == dockerfile* ]]; then
url=$(echo "$MARKET_URL" | grep -oP "^"dockerfile://"\K.*")
export_from="dockerfile"
elif [[ $MARKET_URL == docker* ]]; then
url=$(echo "$MARKET_URL" | grep -oP "^"docker://"\K.*")
export_from="dockerhub"
else
echo "Unknown URL format" 1>&2
exit 1
fi
arguments=$(echo "$url" | cut -d '?' -f 2)
#Create a shell variable for every argument (size=5219, format=raw...)
for p in ${arguments//&/ }; do
kvp=( ${p/=/ } );
k=${kvp[0]};v=${kvp[1]};
[ -n "$k" -a -n "$v" ] && eval $k=$v;
[ -n "$k" -a -n "$v" ] && eval "$k"="$v";
done
if [ -z $tag ]; then
if [ -z "$tag" ]; then
tag="latest"
fi
docker_hub="`echo $url | cut -d '?' -f 1`:${tag}"
docker_image=`echo $docker_hub | cut -f1 -d':'``echo $id |cut -f1 -d'-'`
dockerdir=$TMP_DIR/$id
dockerfile=$dockerdir/dockerfile
tarball=$dockerdir/fs.tar
img_raw=$dockerdir/img.raw
img_qcow=$dockerdir/img.qcow
dockerdir="$TMP_DIR/$id"
dockerfile="$dockerdir/dockerfile"
tarball="$dockerdir/fs.tar"
img_raw="$dockerdir/img.raw"
img_qcow="$dockerdir/img.qcow"
# Trap for cleaning temporary directories
trap clean EXIT
mkdir -p $dockerdir
mkdir -p $dockerdir/mnt
mkdir -p "$dockerdir"
mkdir -p "$dockerdir/mnt"
# Check distro
if [ -z $distro ]; then
distro=`docker run --rm --entrypoint cat \
-e "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
$docker_hub /etc/os-release | grep "^ID=.*\n" | cut -d= -f 2 | xargs`
cp -rL "$CONTEXT_PATH" "$dockerdir"
if [ $export_from == "dockerhub" ]; then
docker_hub=$(echo "$url" | cut -d '?' -f 1):"$tag"
extra=''
else
if [ -z "$fileb64" ]; then
# Dockerfile is a path in the server
d_file=$(echo "$url" | cut -d '?' -f 1)
if [ -f "$d_file" ]; then
extra=$(cat "$d_file")
else
echo "$d_file does not exist" 1>&2
exit 1
fi
else
# Dockerfile is encoded in base64
extra=$(echo "$fileb64" | base64 -d)
fi
# Read image that needs to be build
docker_hub=$(echo "$extra" | grep "^FROM" | cut -d ' ' -f2)
if [ -z "$docker_hub" ]; then
echo "Can\'t identify image to build" 1>&2
exit 1
fi
# Remove FROM instruction from dockerfile as it will be added later
extra=$(echo "$extra" | sed '/^FROM.*/d')
fi
if [ -z $distro ]; then
# Check distro
if [ -z "$distro" ]; then
distro=$(docker run --rm --entrypoint cat \
-e "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
"$docker_hub" /etc/os-release | grep "^ID=.*\n" | cut -d= -f 2 | xargs)
fi
if [ -z "$distro" ]; then
echo "Cannot identified $docker_hub distribution" 1>&2
exit 1
fi
#-------------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Create a DockerFile
#-------------------------------------------------------------------------------
#---------------------------------------------------------------------------
case "$distro" in
debian|ubuntu)
@ -141,53 +185,60 @@ esac
#Replace variables _docker_hub and _commands
dockerfile_template=$(cat "$DOCKERFILE/dockerfile")
dockerfile_template=${dockerfile_template/\%IMAGE_ID\%/$docker_hub}
dockerfile_template=${dockerfile_template/\%COMMANDS\%/$commands}
# Only add context if this variable is not set or is set to yes
if [ -z "$context" -o "$context" == "yes" ]; then
dockerfile_template=${dockerfile_template/\%COMMANDS\%/$commands}
else
dockerfile_template=${dockerfile_template/\%COMMANDS\%/''}
fi
# Add extra commands, this is the dockerfile in case there is any
dockerfile_template=${dockerfile_template/\%EXTRA\%/$extra}
echo "$dockerfile_template" > "$dockerfile"
cp -rL $CONTEXT_PATH $dockerdir
docker build -t one"$sid" -f "$dockerfile" "$dockerdir" > /dev/null 2>&1
docker build -t one$sid -f $dockerfile $dockerdir > /dev/null 2>&1
image_id=`docker images -q one$sid`
image_id=$(docker images -q one"$sid")
#-------------------------------------------------------------------------------
# Flatten container image
#-------------------------------------------------------------------------------
container_id=$(docker run -d $image_id /bin/true)
container_id=$(docker run -d "$image_id" /bin/true)
docker export -o $tarball $container_id > /dev/null 2>&1
docker export -o "$tarball" "$container_id" > /dev/null 2>&1
#-------------------------------------------------------------------------------
# Dump container FS and create image
#-------------------------------------------------------------------------------
# Get tarbal size
tar_size=$(stat -c%s $tarball | awk '{ byte =$1 /1024/1024; print byte}' | cut -d '.' -f 1)
tar_size=$(stat -c%s "$tarball" | awk '{ byte =$1 /1024/1024; print byte}' | cut -d '.' -f 1)
# Ensure $size is enough to fit the fs
if [ "$tar_size" -ge "$size" ]; then
echo "Not enough space, image size must be at least ${tar_size}Mb" 1>&2
echo "Not enough space, image size must be at least $tar_size MB" 1>&2
exit 1
fi
qemu-img create -f raw $img_raw ${size}M > /dev/null 2>&1
qemu-img create -f raw "$img_raw" "$size"M > /dev/null 2>&1
case $filesystem in
case "$filesystem" in
"ext4")
mkfs.ext4 -F $img_raw > /dev/null 2>&1
mkfs.ext4 -F "$img_raw" > /dev/null 2>&1
;;
"ext3")
mkfs.ext3 -F $img_raw > /dev/null 2>&1
mkfs.ext3 -F "$img_raw" > /dev/null 2>&1
;;
"ext2")
mkfs.ext2 -F $img_raw > /dev/null 2>&1
mkfs.ext2 -F "$img_raw" > /dev/null 2>&1
;;
"xfs")
mkfs.xfs -f $img_raw > /dev/null 2>&1
mkfs.xfs -f "$img_raw" > /dev/null 2>&1
;;
*)
mkfs.ext4 -F $img_raw > /dev/null 2>&1
mkfs.ext4 -F "$img_raw" > /dev/null 2>&1
;;
esac
@ -195,13 +246,13 @@ esac
# Mount container disk image and untar rootfs contents to it
#-------------------------------------------------------------------------------
sudo -n $MK_DOCKER -d $dockerdir -i $img_raw -t $tarball
sudo -n "$MK_DOCKER" -d "$dockerdir" -i "$img_raw" -t "$tarball"
if [ "$format" == "qcow2" ]; then
qemu-img convert -f raw -O qcow2 $img_raw $img_qcow > /dev/null 2>&1
cat $img_qcow
qemu-img convert -f raw -O qcow2 "$img_raw" "$img_qcow" > /dev/null 2>&1
cat "$img_qcow"
else
cat $img_raw
cat "$img_raw"
fi
exit 0

View File

@ -3,6 +3,8 @@
# IMPORTANT: IMAGE_ID and COMMANDS can **NOT** be changed, they are used
# in the download process and are replaced by some variables.
#
# EXTRA is replaced by the user dockerfile in case it is used
#
# The rest of the template can be modified depending on your needs.
#
# It is installed on /usr/share/one/dockerfiles
@ -11,7 +13,8 @@ FROM %IMAGE_ID%
USER root
COPY context /root/context
%COMMANDS%
%EXTRA%
RUN rm -rf /root/context
RUN echo "#Generated by OpenNebula" > /etc/resolv.conf
RUN rm -f /etc/ssh/ssh_host_* > /dev/null 2>&1
RUN usermod -p '*' root > /dev/null 2>&1
RUN if [ `command -v usermod` ]; then usermod -p '*' root > /dev/null 2>&1; fi

View File

@ -331,7 +331,7 @@ lxd://*)
file_type="application/octet-stream"
command="$VAR_LOCATION/remotes/datastore/lxd_downloader.sh \"$FROM\""
;;
docker://*)
docker://*|dockerfile://*)
file_type="application/octet-stream"
command="$VAR_LOCATION/remotes/datastore/docker_downloader.sh \"$FROM\""
;;

View File

@ -251,8 +251,13 @@ function fs_size {
if [ -d "${SRC}" ]; then
SIZE=`set -o pipefail; du -sb "${SRC}" | cut -f1`
error=$?
elif (echo "${SRC}" | grep -qe '^docker\?://'); then
url=`echo ${SRC} | grep -oP "^"docker://"\K.*"`
elif (echo "${SRC}" | grep -qe '^docker\?://\|^dockerfile\?://'); then
if [[ $SRC == dockerfile* ]]; then
url=`echo ${SRC} | grep -oP "^"dockerfile://"\K.*"`
elif [[ $SRC == docker* ]]; then
url=$(echo $MARKET_URL | grep -oP "^"docker://"\K.*")
fi
arguments=`echo $url | cut -d '?' -f 2`
for p in ${arguments//&/ }; do