From 242a424561b893f0feba435c13d52aa0d7246978 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Sat, 4 Aug 2012 02:36:48 -0600 Subject: [PATCH] First attempt at creating a glance_image type --- lib/puppet/provider/glance.rb | 120 +++++++++++++++++++++ lib/puppet/provider/glance_image/glance.rb | 115 ++++++++++++++++++++ lib/puppet/type/glance_image.rb | 73 +++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 lib/puppet/provider/glance.rb create mode 100644 lib/puppet/provider/glance_image/glance.rb create mode 100644 lib/puppet/type/glance_image.rb diff --git a/lib/puppet/provider/glance.rb b/lib/puppet/provider/glance.rb new file mode 100644 index 00000000..47db328a --- /dev/null +++ b/lib/puppet/provider/glance.rb @@ -0,0 +1,120 @@ +# Since there's only one glance type for now, +# this probably could have all gone in the provider file. +# But maybe this is good long-term. +require 'puppet/util/inifile' +class Puppet::Provider::Glance < Puppet::Provider + + def self.glance_credentials + @glance_credentials ||= get_glance_credentials + end + + def self.get_glance_credentials + if glance_file and glance_file['filter:authtoken'] and + glance_file['filter:authtoken']['auth_host'] and + glance_file['filter:authtoken']['auth_port'] and + glance_file['filter:authtoken']['auth_protocol'] and + glance_file['filter:authtoken']['admin_tenant_name'] and + glance_file['filter:authtoken']['admin_user'] and + glance_file['filter:authtoken']['admin_password'] + + g = {} + g['auth_host'] = glance_file['filter:authtoken']['auth_host'].strip + g['auth_port'] = glance_file['filter:authtoken']['auth_port'].strip + g['auth_protocol'] = glance_file['filter:authtoken']['auth_protocol'].strip + g['admin_tenant_name'] = glance_file['filter:authtoken']['admin_tenant_name'].strip + g['admin_user'] = glance_file['filter:authtoken']['admin_user'].strip + g['admin_password'] = glance_file['filter:authtoken']['admin_password'].strip + return g + else + raise(Puppet::Error, 'File: /etc/glance/glance-api-paste.ini does not contain all required sections.') + end + end + + def glance_credentials + self.class.glance_credentials + end + + def self.auth_endpoint + @auth_endpoint ||= get_auth_endpoint + end + + def self.get_auth_endpoint + g = glance_credentials + "#{g['auth_protocol']}://#{g['auth_host']}:#{g['auth_port']}/v2.0/" + end + + def self.glance_file + return @glance_file if @glance_file + @glance_file = Puppet::Util::IniConfig::File.new + @glance_file.read('/etc/glance/glance-api-paste.ini') + @glance_file + end + + def self.glance_hash + @glance_hash ||= build_glance_hash + end + + def glance_hash + self.class.glance_hash + end + + def self.auth_glance(*args) + begin + g = glance_credentials + glance('-T', g['admin_tenant_name'], '-I', g['admin_user'], '-K', g['admin_password'], '-N', auth_endpoint, args) + rescue Exception => e + # Will probably add conditions later + raise(e) + end + end + + def auth_glance(*args) + self.class.auth_glance(args) + end + + def self.auth_glance_stdin(*args) + begin + g = glance_credentials + command = "glance --silent-upload -T #{g['admin_tenant_name']} -I #{g['admin_user']} -K #{g['admin_password']} -N #{auth_endpoint} #{args.join(' ')}" + + # This is a horrible, horrible hack + # Redirect stderr to stdout in order to report errors + # Ignore good output + err = `#{command} 3>&1 1>/dev/null 2>&3` + if $? != 0 + raise(Puppet::Error, err) + end + end + end + + def auth_glance_stdin(*args) + self.class.auth_glance_stdin(args) + end + + + private + def self.list_glance_images + ids = [] + (auth_glance('index').split("\n")[2..-1] || []).collect do |line| + ids << line.split[0] + end + return ids + end + + def self.get_glance_image_attr(id, attr) + (auth_glance('show', id).split("\n") || []).collect do |line| + if line =~ /^#{attr}:/ + return line.split(': ')[1..-1] + end + end + end + + def self.get_glance_image_attrs(id) + attrs = {} + (auth_glance('show', id).split("\n") || []).collect do |line| + attrs[line.split(': ').first.downcase] = line.split(': ')[1..-1].to_s + end + return attrs + end + +end diff --git a/lib/puppet/provider/glance_image/glance.rb b/lib/puppet/provider/glance_image/glance.rb new file mode 100644 index 00000000..54e5cf5e --- /dev/null +++ b/lib/puppet/provider/glance_image/glance.rb @@ -0,0 +1,115 @@ +$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', '..')) + +# Load the Glance provider library to help +require 'puppet/provider/glance' + +Puppet::Type.type(:glance_image).provide( + :glance, + :parent => Puppet::Provider::Glance +) do + desc <<-EOT + Glance provider to manage glance_image type. + + Assumes that the Keystone service is on the same host and is working. + EOT + + commands :glance => 'glance' + + def self.prefetch(resource) + # rebuild the cache for every puppet run + @image_hash = nil + end + + def self.image_hash + @image_hash ||= build_image_hash + end + + def image_hash + self.class.image_hash + end + + def self.instances + image_hash.collect do |k, v| + new(:name => k) + end + end + + def create + stdin = nil + if resource[:source] + # copy_from cannot handle file:// + if resource[:source] =~ /^\// # local file + location = "< #{resource[:source]}" + stdin = true + else + location = "copy_from=#{resource[:source]}" + end + # location cannot handle file:// + # location does not import, so no sense in doing anything more than this + elsif resource[:location] + location = "location=#{resource[:location]}" + else + raise(Puppet::Error, "Must specify either source or location") + end + if stdin + auth_glance_stdin('add', "name='#{resource[:name]}'", "is_public=#{resource[:is_public]}", "container_format=#{resource[:container_format]}", "disk_format=#{resource[:disk_format]}", location) + else + auth_glance('add', "name='#{resource[:name]}'", "is_public=#{resource[:is_public]}", "container_format=#{resource[:container_format]}", "disk_format=#{resource[:disk_format]}", location) + end + end + + def exists? + image_hash[resource[:name]] + end + + def destroy + auth_glance('delete', '-f', image_hash[resource[:name]]['id']) + end + + def location + image_hash[resource[:name]]['location'] + end + + def location=(value) + auth_glance('update', image_hash[resource[:name]]['id'], "location=#{value}") + end + + def is_public + image_hash[resource[:name]]['public'] + end + + def is_public=(value) + auth_glance('update', image_hash[resource[:name]]['id'], "is_public=#{value}") + end + + def disk_format + image_hash[resource[:name]]['disk format'] + end + + def disk_format=(value) + auth_glance('update', image_hash[resource[:name]]['id'], "disk_format=#{value}") + end + + def container_format + image_hash[resource[:name]]['container format'] + end + + def container_format=(value) + auth_glance('update', image_hash[resource[:name]]['id'], "container_format=#{value}") + end + + def id + image_hash[resource[:name]]['id'] + end + + private + def self.build_image_hash + hash = {} + list_glance_images.each do |image| + attrs = get_glance_image_attrs(image) + hash[attrs['name'].to_s] = attrs + end + hash + end +end + diff --git a/lib/puppet/type/glance_image.rb b/lib/puppet/type/glance_image.rb new file mode 100644 index 00000000..d4e7a4b5 --- /dev/null +++ b/lib/puppet/type/glance_image.rb @@ -0,0 +1,73 @@ +Puppet::Type.newtype(:glance_image) do + desc <<-EOT + This allows manifests to declare an image to be + stored in glance. + + glance_image { + ensure => present, + name => "Ubuntu 12.04 cloudimg amd64" + is_public => true, + container_format => ovf, + disk_format => qcow', + source => 'http://uec-images.ubuntu.com/releases/precise/release/ubuntu-12.04-server-cloudimg-amd64-disk1.img' + } + + Known problems / limitations: + * All images are managed by the glance service. + This means that since users are unable to manage their own images via this type, + is_public is really of no use. You can probably hide images this way but that's all. + * I really have no idea what I'm doing. + + EOT + + ensurable + + newparam(:name, :namevar => true) do + desc 'The image name' + newvalues(/.*/) + end + + newproperty(:id) do + desc 'The unique id of the image' + validate do |v| + raise(Puppet:Error, 'This is a read only property') + end + end + + newproperty(:location) do + desc "The permanent location of the image. Optional" + newvalues(/\S+/) + end + + newproperty(:is_public) do + desc "Whether the image is public or not. Default true" + newvalues(/(y|Y)es/, /(n|N)o/) + defaultto('Yes') + munge do |v| + v.to_s.capitalize + end + end + + newproperty(:container_format) do + desc "The format of the container" + newvalues(:ami, :ari, :aki, :bare, :ovf) + end + + newproperty(:disk_format) do + desc "The format of the disk" + newvalues(:ami, :ari, :aki, :vhd, :vmd, :raw, :qcow2, :vdi) + end + + newparam(:source) do + desc "The source of the image to import from" + newvalues(/\S+/) + end + + # Require the Glance service to be running + autorequire(:service) do + ['glance'] + end + +end + +