diff --git a/lib/puppet/provider/cloudstack_instance/default.rb b/lib/puppet/provider/cloudstack_instance/default.rb index 5ba17b4..2f977ac 100644 --- a/lib/puppet/provider/cloudstack_instance/default.rb +++ b/lib/puppet/provider/cloudstack_instance/default.rb @@ -9,7 +9,9 @@ def self.instances # I may need to fail if the server does not have a name? connection.servers.collect do |server| - if server.state != 'Destroyed' + if (server.state != 'Destroyed') and (server.state != 'Stopping') + Puppet.debug("Found #{server.display_name} in state: #{server.state}") + Puppet.debug("Found #{server.display_name} in state: #{server.state}") if server.nics.size > 1 raise(Puppet::Error, "Does not support dual nics (it is just a prototype") end @@ -26,7 +28,8 @@ def self.instances :host => server.host_name, :state => server.state.downcase, :group => server.group, - #:keypair => server.keypair, + # for some reason the keypair does not seem to be returned from listvminstnaces + :keypair => server.key_pair, :ensure => :present # I may want to print network information here ) @@ -47,22 +50,26 @@ def create :image_id => #{image_id}, :flavor_id => #{flavor_id}, :zone_id => #{zone_id}, - :network_ids => #{network_id} + :network_ids => #{network_id}, + :keypair => #{resource[:keypair]}, ") - connection.servers.bootstrap( + response = connection.servers.bootstrap( :display_name => resource[:name], :image_id => image_id, :flavor_id => flavor_id, :zone_id => zone_id, :network_ids => network_id, + :key_pair => resource[:keypair], :group => resource[:group] - #:keypair => resource[:keypair] ) + @property_hash[:ensure] = :present + @property_hash[:internal_ipaddress] = response.nics.first['ipaddress'] end def destroy # TODO need to block until delete is completed - connection.servers.destroy(@property_hash[:id]) + connection.servers.destroy(@property_hash[:id]) + @property_hash[:ensure] = :absent end def internal_ipaddress diff --git a/lib/puppet/provider/cloudstack_keypair/default.rb b/lib/puppet/provider/cloudstack_keypair/default.rb index 950c1dc..317229a 100644 --- a/lib/puppet/provider/cloudstack_keypair/default.rb +++ b/lib/puppet/provider/cloudstack_keypair/default.rb @@ -8,8 +8,7 @@ def self.instances # I may need to fail if the server does not have a name? - connection.list_ssh_key_pairs['listsshkeypairsresponse']['sshkeypair'].collect do |keypair| - #require 'ruby-debug';debugger + (connection.list_ssh_key_pairs['listsshkeypairsresponse']['sshkeypair'] || []).collect do |keypair| new( :name => keypair['name'], :fingerprint => keypair['fingerprint'], @@ -18,6 +17,10 @@ def self.instances end end + def exists? + @property_hash[:ensure] == :present + end + def create #domain_id = get_domain_id(resource[:domain]) #project_id = get_project_id(resource[:project]) @@ -41,6 +44,7 @@ def create end Puppet.info("Writing your private key to #{key_file}") File.new(key_file, 'w').write(response['privatekey']) + File.chmod(0600, key_file) end @property_hash[:fingerprint] = response['fingerprint'] @property_hash[:privatekey] = response['privatekey'] @@ -53,7 +57,13 @@ def destroy def privatekey if fingerprint - File.read(key_file_path(fingerprint)) + key_file = key_file_path(fingerprint) + if File.exists?(key_file) + File.read(key_file) + else + Puppet.notice("Could not find locally cached private key for #{resource[:name]}") + nil + end else fail('Expected fingerprint to be set') end @@ -67,8 +77,8 @@ def key_file_path(fingerprint_arg=fingerprint) File.join(Puppet[:confdir], 'cloudstack', 'keypair', fingerprint_arg, 'id_rsa') end - def flush - @property_hash = resource.to_hash - end +# def flush +# @property_hash = resource.to_hash +# end -end \ No newline at end of file +end diff --git a/lib/puppet/provider/cloudstack_network/default.rb b/lib/puppet/provider/cloudstack_network/default.rb index e8c8fb5..663b2e4 100644 --- a/lib/puppet/provider/cloudstack_network/default.rb +++ b/lib/puppet/provider/cloudstack_network/default.rb @@ -9,7 +9,7 @@ mk_resource_methods def self.instances - connection.list_networks['listnetworksresponse']['network'].collect do |net| + (connection.list_networks['listnetworksresponse']['network'] || []).collect do |net| new( :name => net['name'], :id => net['id'], diff --git a/lib/puppet/provider/puppet_node/ssh.rb b/lib/puppet/provider/puppet_node/ssh.rb index 0ba5bde..66b88dc 100644 --- a/lib/puppet/provider/puppet_node/ssh.rb +++ b/lib/puppet/provider/puppet_node/ssh.rb @@ -10,16 +10,80 @@ # end def role - # make an ssh call? decide if the role is already installed? - false + # ssh into the instance to see if puppet is already installed + # TODO eventually, this should also check the version + begin + results = run_remote_puppet_script('check_puppet_version') + rescue Puppet::Error => e + return nil + end + version = results['stdout'].split("\n").last.strip + Puppet.info("Puppet version #{version} is currently installed") + version end def role=(role) - machinehostname(resource[:machine]) - getprivatekey(resource[:keypair]) - if role == :master + if role == :pe_master + script_name = 'pe_master' + elsif role == :pe_agent + script_name = 'pe_agent' else + fail("unsupported role #{role.to_s}") end + run_remote_puppet_script(script_name) + end + + def run_remote_puppet_script(script_name) + + hostname = machinehostname(resource[:machine]) + keyfile = getprivatekey(resource[:keypair]) + + if script_name == 'pe_master' + answers = 'https://raw.github.com/gist/4229037/d03453ae789797909745b63d44611adc5090c050/gistfile1.txt' + modulepath = '/etc/puppetlabs/puppet/modules' + elsif script_name == 'pe_agent' + answers = 'https://raw.github.com/gist/3299812/9dea06dba93d218c61e5fa9d9e928a265c137239/answers' + else + modulepath = '/etc/puppet/modules' + Puppet.debug("Script name: #{script_name} does not need an answers file") + end + + Puppet.info("Running script #{script_name} via ssh to '#{hostname}' with key: '#{keyfile}'") + + write_name = "#{hostname}-#{Time.now.to_i}" + unless File.exists?(script_write_dir) + Puppet.info('Creating script dir') + Dir.mkdir(script_write_dir) + end + + compile_options = { + 'installer_payload' => resource[:installer_payload], + 'answers_payload' => answers, + 'certname' => machinehostname(resource[:machine]), + 'puppetmaster' => machinehostname(resource[:puppetmaster]), + 'facts' => {'classes' => resource['classes'].to_pson}, + 'modulepath' => modulepath + } + + Puppet.debug("Compiling script #{script_name} with options:\n#{compile_options.inspect}") + + File.open(File.join(script_write_dir, "#{write_name}.erb"), 'w') do |fh| + fh.write( + compile_erb(File.join(script_dir, "#{script_name}.erb"), compile_options) + ) + end + + require 'puppet/cloudpack' + + Puppet.info("running: ssh root@#{hostname} -i '#{keyfile}'") + Puppet::CloudPack.install(hostname, + { + :install_script => write_name, + :keyfile => keyfile, + :login => 'root' + + } + ) end def modules @@ -27,6 +91,14 @@ def modules end def getprivatekey(key) + fail('Must specify a private key') unless key + key = key.to_s + if key =~ /(Cloudstack_keypair\[(\S+)?\])/ + return model.catalog.resource($1).provider.key_file_path + else + puts 'It is a path' + return key + end key end @@ -41,10 +113,14 @@ def machinehostname(id) raise(Puppet::Error, "Do not support things other than internal_ip at the moment") else # assume we want the internal ip address - res.provider.internal_ipaddress + if res.provider.exists? + res.provider.internal_ipaddress + else + fail("Referenced cloudstack instance #{$1} does not exist") + end end else - raise(Puppet::Error, "Puppet resource #{$1} was set at the machine, but not found in the catalog, try hostname instead") + fail("Puppet resource #{$1} was set at the machine, but not found in the catalog, try hostname instead") end else # otherwise assume id is the hostname of ip address @@ -52,4 +128,20 @@ def machinehostname(id) end end + def compile_erb(filename, options) + ERB.new(File.read(filename)).result(binding) + end + + def script_write_dir + File.join(Puppet[:confdir], 'scripts') + end + + def script_dir + File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'templates')) + end + + def find_template(name) + File.expand_path(File.join(script_dir, "#{name}.erb")) + end + end diff --git a/lib/puppet/type/cloudstack_instance.rb b/lib/puppet/type/cloudstack_instance.rb index 04cd1d3..badea8f 100644 --- a/lib/puppet/type/cloudstack_instance.rb +++ b/lib/puppet/type/cloudstack_instance.rb @@ -29,10 +29,6 @@ desc 'internal ip address. Puppet assumes that instances can only have one' end - newproperty(:keypair) do - desc 'keypair to associate with system' - end - newproperty(:flavor) do desc 'name of flavor' end @@ -78,7 +74,27 @@ def insync?(is) end end + newparam(:userdata) do + desc 'used to set userdata when an instance is created' + end + newproperty(:keypair) do + desc 'keypair to associate with system' + munge do |value| + value = value.to_s + if value =~ /^Cloudstack_keypair\[(\S+)\]/ + $1 + elsif value =~ /^(\S+)\[(\S+)\]/ + fail("#{$1} is not a valid type, expected Cloudstack_keypair") + else + value + end + end + end + + autorequire(:cloudstack_keypair) do + [self[:keypair]] + end # this causes querying the resources to fail ;( #validate do # unless self[:zone_id] and self[:template_id] and self[:service_offering_id] diff --git a/lib/puppet/type/puppet_node.rb b/lib/puppet/type/puppet_node.rb index 9baa383..255161e 100644 --- a/lib/puppet/type/puppet_node.rb +++ b/lib/puppet/type/puppet_node.rb @@ -10,27 +10,66 @@ desc 'classes to apply to the node. Only makes sense for an agent' end - newparam(:machine) do - desc 'machine where puppet actions are performed. Takes a reference or a hostname/ipaddress' - end - newproperty(:modules) do desc 'modules to install from the forge (only makes sense for a master role' end + newproperty(:role) do + desc 'role of this puppet instance' + newvalues(:agent, :master, :pe_agent, :pe_master, :apply) + def insync?(is) + return is =~ /^\d+\.\d+\.\d+/ + end + end + + newparam(:installer_payload) do + # right now this is hardcoded to rhel 5 pe installation payload + defaultto 'https://s3.amazonaws.com/pe-builds/released/2.6.1/puppet-enterprise-2.6.1-el-5-x86_64.tar.gz' + end + + newparam(:answers_payload) do + defaultto 'https://raw.github.com/gist/4229037/d03453ae789797909745b63d44611adc5090c050/gistfile1.txt' + end + + newparam(:puppetmaster) do + desc 'hostname of puppetmaster to connect to. Used by pe_agent and agent roles.' + end + + newparam(:facts) do + desc 'hash of facts that can be used to set instance role' + defaultto {} + end + newparam(:keypair) do desc 'keypair to use to connect' + validate do |value| + unless value.to_s =~ /^Cloudstack_keypair\[(\S+)\]/ + fail("#{value} is not a valid keypair reference, expected Cloudstack_keypair[]") + end + end end - newproperty(:role) do - desc 'role of this puppet instance' - newvalues(:agent, :master) + newparam(:machine) do + desc 'machine where puppet actions are performed. Takes a reference or a hostname/ipaddress' + end + + autorequire(:cloudstack_keypair) do + if self[:keypair].to_s =~ /^Cloudstack_keypair\[(\S+)\]/ + [$1] + else + [] + end end autorequire(:cloudstack_instance) do + instances = [] if self[:machine].to_s =~ /Cloudstack_instance\[(\S+?)\](\[\S+?\])?/ - [$1] + instances.push($1) + end + if self[:puppetmaster].to_s =~ /Cloudstack_instance\[(\S+?)\](\[\S+?\])?/ + instances.push($1) end + instances end end diff --git a/templates/agent.erb b/templates/agent.erb new file mode 100644 index 0000000..792e2c1 --- /dev/null +++ b/templates/agent.erb @@ -0,0 +1,11 @@ +#!/bin/bash +# +# script that performs classification using puppet agent +# + +set -u +set -e + +<%= ERB.new(File.read(find_template('fragments/options')), nil, "<>", '_options').result(binding) %> + +puppet agent --pluginsync --test --certname=<%= options['certname'] %> --server=<%= options['puppetmaster'] %> | tee /tmp/puppet_output.log diff --git a/templates/apply.erb b/templates/apply.erb new file mode 100644 index 0000000..484d358 --- /dev/null +++ b/templates/apply.erb @@ -0,0 +1,13 @@ +#!/bin/bash +# +# script that performs classification using puppet apply +# + +set -u +set -e + +<% if options['install_puppet'] %> +<%= ERB.new(File.read(find_template('fragments/install_poss')), nil, "<>", '_install_poss').result(binding) %> +<% end %> + +<%= ERB.new(File.read(find_template('fragments/install_poss')), nil, "<>", '_install_poss').result(binding) %> diff --git a/templates/check_class_facts.erb b/templates/check_class_facts.erb new file mode 100644 index 0000000..6318a0f --- /dev/null +++ b/templates/check_class_facts.erb @@ -0,0 +1,12 @@ +#!/bin/sh +set -x +set -e +set -u + +echo 'Checking if pe is installed' + +if [ -f /etc/facts.d/classes]; then + cat /etc/facts.d/classes.txt +else + exit 1 +fi diff --git a/templates/check_puppet_version.erb b/templates/check_puppet_version.erb new file mode 100644 index 0000000..bc65e50 --- /dev/null +++ b/templates/check_puppet_version.erb @@ -0,0 +1,8 @@ +#!/bin/sh +set -x +set -e +set -u + +echo 'Checking if pe is installed' + +puppet --version diff --git a/templates/fragments/install_pe.erb b/templates/fragments/install_pe.erb new file mode 100644 index 0000000..b3bfe20 --- /dev/null +++ b/templates/fragments/install_pe.erb @@ -0,0 +1,52 @@ +<%= ERB.new(File.read(find_template('fragments/set_http_get')), nil, "<>", '_ipe_set_http_get').result(binding) %> + +PATH="/opt/puppet/bin:$PATH" +export PATH + +install_dir="puppet-enterprise" +if [ ! -e "${install_dir}" ]; then + mkdir "${install_dir}" +fi + +echo "Downloading from: <%= options['installer_payload'] %>" + +# We assume the payload is a tar.gz file because the option handler +# enforces this. +# To save disk space (I'm concerned about /tmp filling) decompress on the fly. +echo "Uncompressing the payload ..." +$http_get '<%= options['installer_payload'] %>' | \ + gunzip -c | \ + { cd "${install_dir}" && tar -xvf -; } && \ + echo "Uncompressing the payload ... Done." + +echo 'Downloading answers file' +$http_get '<%= options['answers_payload'] %>' > puppet.answers.orig + +# Give me everything _except_ any agent certname specified in the answers +# file, and the agent's server setting. Useless use of cat is for clarity +# Note that this particular ERB syntax requires that the trim_mode be set +# to <> when ERB.new is called (or some other alternative which will +# suppress newlines) or else this shell code will not work. +cat puppet.answers.orig | \ + grep -v '^q_puppetmaster_certname' | \ +<% if options['puppetmaster'] %> + grep -v '^q_puppetagent_server' | \ +<% end %> + cat > puppet.answers + +# Append the user specified option from the command line arguments. +echo 'q_puppetmaster_certname=<%= options['certname'] %>' >> puppet.answers +echo 'q_puppetagent_certname=<%= options['certname'] %>' >> puppet.answers +<% if options['puppetmaster'] %> +echo 'q_puppetagent_server=<%= options['puppetmaster'] %>' >> puppet.answers +<% end %> + +# Install Puppet Enterprise +"${install_dir}"/*puppet*/puppet-enterprise-installer -a puppet.answers 2>&1 | tee install.log + +echo '*' > /etc/puppetlabs/puppet/autosign.conf + +# if redhat turn off the firewall +if [ -f /etc/redhat-release ]; then + /etc/init.d/iptables stop +fi diff --git a/templates/fragments/install_poss.erb b/templates/fragments/install_poss.erb new file mode 100644 index 0000000..67cfd24 --- /dev/null +++ b/templates/fragments/install_poss.erb @@ -0,0 +1,88 @@ +fedora_repo() +{ + cat >/etc/yum.repos.d/puppet.repo <<-EOFYUMREPO + [puppetlabs] + name = Puppetlabs + baseurl = http://yum.puppetlabs.com/fedora/f$releasever/products/$basearch/ + gpgcheck = 1 + enabled = 1 + gpgkey = http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs + EOFYUMREPO +} + +el_repo() +{ + cat >/etc/yum.repos.d/puppet.repo <<-EOFYUMREPO + [puppetlabs] + name = Puppetlabs + baseurl = http://yum.puppetlabs.com/el/$releasever/products/$basearch/ + gpgcheck = 1 + enabled = 1 + gpgkey = http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs + EOFYUMREPO +} + +rpm_install() +{ + # Setup the yum Puppet repository + rpm -q fedora-release && fedora_repo || el_repo + + # Install git + yum install -y git + + # Install Puppet from yum.puppetlabs.com + yum install -y puppet +} + +apt_install() +{ + # Download and install the puppetlabs apt public + apt-key adv --recv-key --keyserver pool.sks-keyservers.net 4BD6EC30 + + # We need to grab the distro and release in order to populate + # the apt repo details. We are assuming that the lsb_release command + # will be available as even puppet evens has it (lsb_base) package as + # dependancy. + + # Since puppet requires lsb-release I believe this is ok to use for + # the purpose of distro and release discovery. + apt-get update + apt-get -y install git-core + apt-get -y install lsb-release + distro=$(lsb_release -i | cut -f 2 | tr "[:upper:]" "[:lower:]") + release=$(lsb_release -c | cut -f 2) + + # Setup the apt Puppet repository + cat > /etc/apt/sources.list.d/puppetlabs.list <<-EOFAPTREPO + deb http://apt.puppetlabs.com/${distro}/ ${release} main + EOFAPTREPO + + # Install Puppet from Debian repositories + apt-get update + apt-get -y install puppet + apt-get install -y git-core + apt-get install -y rubygems +} + +install_puppet() +{ + case "${osfamily}" in + "redhat") + rpm_install ;; + "debian") + apt_install ;; + esac +} + +# install puppet +if [ -f /etc/redhat-release ]; then + export osfamily='redhat' +elif [ -f /etc/debian_version ]; then + export osfamily='debian' +else + echo "This OS is not supported by Puppet Cloud Provisioner" + exit 1 +fi + +install_puppet +echo "Puppet installation finished!" diff --git a/templates/fragments/options.erb b/templates/fragments/options.erb new file mode 100644 index 0000000..9c83a63 --- /dev/null +++ b/templates/fragments/options.erb @@ -0,0 +1,66 @@ +<%= ERB.new(File.read(find_template('fragments/set_http_get')), nil, "<>", '_o_set_http_get').result(binding) %> + +# install git and rubygems +apt-get update || true + +# install epel +if [ -f /etc/redhat-release ]; then + puppet module install stahnma/epel + puppet apply -e 'include epel' +fi + +apt-get install -y git-core || yum install -y git +apt-get install -y rubygems || yum install -y rubygems + +# clone git repos +<% (options['git_repos'] || {}).each do |source, target| %> +mkdir -p <%= target %> || true +git clone <%= source %> <%= target %> +<% end %> + +# switch into specified branches +<% (options['git_checkout'] || {}).each do |dir, branch| %> +pushd <%= dir %> +git checkout <%= branch %> +popd +<% end %> + +# cp files from modules to system locations +<% (options['cp_files'] || {}).each do |source, target| %> +cp <%= source %> <%= target %> +<% end %> + +# if a manifest was specified apply it +<% if options['manifest'] %> +mkdir -p /var/lib/puppet_client +# <%= options.inspect %> +puppet apply --vardir /var/lib/puppet_client --trace --verbose <%= options['manifest'] %> --certname=<%= options['certname'] %> | tee /tmp/puppet_output.log +return=$? +<% end %> + +#This fuction is not called if no custom facts are given +# +#The Puppet Labs' stdlib module is required for this to work +#It can be installed using: +# 'puppet-module install puppetlabs/stdlib' +mkdir -p /etc/facter/facts.d +<% if options['facts'] %> +echo "Installing custom facts" +<% options['facts'].each do |fact,value| %> +echo <%= fact %>=<%= value %> > /etc/facter/facts.d/<%= fact %>.txt +<% end %> +<% end %> + +# upload and run postinstall scripts +<% [options['postinstall']].flatten.compact.each do |script| %> +POSTINSTALL=/tmp/postinstall +<% if script.match(%r{[a-zA-Z0-9]://}) %> +$http_get '<%= script %>' > $POSTINSTALL +<% else %> +cat > "$POSTINSTALL" <<-EOF_STACKBUILDER_POSTINSTALL +<%= heredoc_safe(File.read(script)) %> +EOF_STACKBUILDER_POSTINSTALL +<% end %> +chmod a+x "$POSTINSTALL" +"$POSTINSTALL" +<% end %> diff --git a/templates/fragments/set_http_get.erb b/templates/fragments/set_http_get.erb new file mode 100644 index 0000000..b09bcc4 --- /dev/null +++ b/templates/fragments/set_http_get.erb @@ -0,0 +1,10 @@ +# Figure out if we have wget or curl +if which wget >/dev/null; then + http_get="wget --no-check-certificate --output-document=- --quiet" +elif which curl >/dev/null; then + http_get="curl -o -" +else + echo "Error: could not find wget or curl on the remote system." + echo "To install Puppet Enterprise using HTTP, wget or curl are required" + exit 1 +fi diff --git a/templates/master.erb b/templates/master.erb new file mode 100644 index 0000000..64fb8a0 --- /dev/null +++ b/templates/master.erb @@ -0,0 +1,12 @@ +#!/bin/bash +# +# script that builds a puppet master using puppet apply +# + +set -u +set -e + +mkdir -p /etc/puppet/modules +pushd /etc/puppet/modules + +<%= ERB.new(File.read(find_template('fragments/options')), nil, "<>", '_options').result(binding) %> diff --git a/templates/pe_agent.erb b/templates/pe_agent.erb new file mode 100644 index 0000000..361ad80 --- /dev/null +++ b/templates/pe_agent.erb @@ -0,0 +1,11 @@ +#! /bin/bash +set -x +set -e +set -u + +echo "Installation script has started..." + +<%= ERB.new(File.read(find_template('fragments/install_pe')), nil, "<>", '_install_pe').result(binding) %> +<%= ERB.new(File.read(find_template('fragments/options')), nil, "<>", '_options').result(binding) %> + +puppet agent --pluginsync --test --certname=<%= options['certname'] %> --server=<%= options['puppetmaster'] %> | tee /tmp/puppet_output.log diff --git a/templates/pe_master.erb b/templates/pe_master.erb new file mode 100644 index 0000000..a651339 --- /dev/null +++ b/templates/pe_master.erb @@ -0,0 +1,9 @@ +#!/bin/sh +set -x +set -e +set -u + +echo "Installation script has started..." + +<%= ERB.new(File.read(find_template('fragments/install_pe')), nil, "<>", '_install_pe').result(binding) %> +<%= ERB.new(File.read(find_template('fragments/options')), nil, "<>", '_options').result(binding) %> diff --git a/templates/puppet-community.sh b/templates/puppet-community.sh new file mode 100644 index 0000000..08e8de3 --- /dev/null +++ b/templates/puppet-community.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# this script can be used in combination +# with the gce types to install puppet and +# classify the provisioned instances + +set -u +set -e + +RESULTS_FILE='/tmp/puppet_bootstrap_output' + +function check_exit_status() { + if [ ! -f $RESULTS_FILE ]; then + echo '1' > $RESULTS_FILE + fi +} + +trap check_exit_status INT TERM EXIT + +function fedora_repo() { + cat >/etc/yum.repos.d/puppet.repo <<'EOFYUMREPO' +[puppetlabs] +name = Puppetlabs +baseurl = http://yum.puppetlabs.com/fedora/f$releasever/products/$basearch/ +gpgcheck = 1 +enabled = 1 +gpgkey = http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs +EOFYUMREPO +} + +function el_repo() { + cat >/etc/yum.repos.d/puppet.repo <<'EOFYUMREPO' +[puppetlabs] +name = Puppetlabs +baseurl = http://yum.puppetlabs.com/el/$releasever/products/$basearch/ +gpgcheck = 1 +enabled = 1 +gpgkey = http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs +[puppetlabs-deps] +name = Puppetlabs Dependencies +baseurl = http://yum.puppetlabs.com/el/$releasever/dependencies/$basearch/ +gpgcheck = 1 +enabled = 1 +gpgkey = http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs +EOFYUMREPO +} + +function rpm_install() { + # Setup the yum Puppet repository + rpm -q fedora-release && fedora_repo || el_repo + + # Install Puppet from yum.puppetlabs.com + yum install -y puppet git +} + +function apt_install() { + # Download and install the puppetlabs apt public + apt-key adv --recv-key --keyserver pool.sks-keyservers.net 4BD6EC30 + + # We need to grab the distro and release in order to populate + # the apt repo details. We are assuming that the lsb_release command + # will be available as even puppet evens has it (lsb_base) package as + # dependancy. + + # Since puppet requires lsb-release I believe this is ok to use for + # the purpose of distro and release discovery. + apt-get update + apt-get -y install lsb-release + distro=$(lsb_release -i | cut -f 2 | tr "[:upper:]" "[:lower:]") + release=$(lsb_release -c | cut -f 2) + + # Setup the apt Puppet repository + cat > /etc/apt/sources.list.d/puppetlabs.list </etc/puppet/puppet.conf < /etc/default/puppet < /etc/puppet/manifests/empty.pp + echo '#!/bin/bash + cat /etc/puppet/nodes/$1.yaml' > /etc/puppet/nodes/enc.sh + chmod a+x /etc/puppet/nodes/enc.sh + echo "$1" > /etc/puppet/nodes/"$2".yaml + # yaml terminus does not merge facts, so it failed with puppet + # apply + puppet apply --trace --debug --node_terminus=exec --external_nodes=/etc/puppet/nodes/enc.sh /etc/puppet/manifests/empty.pp + fi +} + + +function provision_puppet() { + if [ -f /etc/redhat-release ]; then + export breed='redhat' + elif [ -f /etc/debian_version ]; then + export breed='debian' + else + echo "This OS is not supported by Puppet Cloud Provisioner" + exit 1 + fi + + PUPPET_CLASSES=$(curl http://metadata.google.internal/0.1/meta-data/attributes/puppet_classes) + # BEGIN HACK + # + # This is a pretty awful hack, but I did not really understand a better way to do it. + # The problem is that applications may need to specify facts or other system specific information + # as a part of the classifaction process. I this case, I need to be able to figure out my own internal + # and external ip addresses. + # I am going to just pass in these specific things as variables in the puppetcode and parse them out here. + # Eventually, I may want to do some kind of a fact lookup + GCE_EXTERNAL_IP=$(curl http://metadata.google.internal/0.1/meta-data/network | tr ":" "\n" | grep -A 1 externalIp | tail -1 | cut -f 2 -d '"') + GCE_INTERNAL_IP=$(curl http://metadata.google.internal/0.1/meta-data/network | tr ":" "\n" | grep -A 1 ip | tail -1 | cut -f 2 -d '"') + PUPPET_CLASSES=$(echo "$PUPPET_CLASSES" | sed -e "s/\$gce_external_ip/$GCE_EXTERNAL_IP/" -e "s/\$gce_internal_ip/$GCE_INTERNAL_IP/") + # END HACK + PUPPET_MODULES=$(curl http://metadata.google.internal/0.1/meta-data/attributes/puppet_modules) + PUPPET_REPOS=$(curl http://metadata.google.internal/0.1/meta-data/attributes/puppet_repos) + PUPPET_HOSTNAME=$(curl http://metadata.google.internal/0.1/meta-data/hostname) + + install_puppet + configure_puppet "$PUPPET_HOSTNAME" + download_modules "$PUPPET_MODULES" + clone_modules "$PUPPET_REPOS" + run_puppet_apply "$PUPPET_CLASSES" "$PUPPET_HOSTNAME" + echo $? > $RESULTS_FILE + echo "Puppet installation finished!" + exit 0 +} + +provision_puppet diff --git a/tests/example.pp b/tests/example.pp index 189c8f2..c93e9b9 100644 --- a/tests/example.pp +++ b/tests/example.pp @@ -1,68 +1,65 @@ -# -# transport object, manages connections to cloudstack API server -# -#cloudstack_transport { 'transport1': } - -# this requires advanced zones -#cloudstack_firewall_rule { -# -#} - -#cloud_portforward_rule { -# -#} - -# this does not - -# I need a basic zone to deploy this into - -#cloudstack_security_group { 'foo': -# ensure => present, -#} - -#volume { -# -#} +if (($::ensure != 'present') and ($::ensure != 'absent')) { + fail('invalid ensure value') +} resources { 'cloudstack_instance': purge => true } -# ssh_transport { -# -# } - -cloudstack_instance { 'foo1': - ensure => present, - flavor => 'Small Instance', - zone => 'FMT-ACS-001', - image => 'CentOS 5.6(64-bit) no GUI (XenServer)', +Cloudstack_instance { + image => 'CentOS 5.6 key + pw', + flavor => 'Large Local', + zone => 'ACS-FMT-1', + #zone => 'FMT-ACS-001', network => 'puppetlabs-network', - # domain - # account - # hostname + keypair => 'dans_keypair4', + require => Cloudstack_keypair['dans_keypair4'], } -cloudstack_instance { 'foo2': - ensure => present, - flavor => 'Small Instance', - zone => 'FMT-ACS-001', - image => 'CentOS 5.6(64-bit) no GUI (XenServer)', - network => 'puppetlabs-network', - group => 'foo2', +cloudstack_keypair { 'dans_keypair4': + ensure => $::ensure, + cache_key => true, } +cloudstack_instance { 'foo3': + ensure => $::ensure, + group => 'role=puppetmaster', } +cloudstack_instance { 'foo4': + ensure => $::ensure, + group => 'role=db', +} -#cloudstack_ssh { -# classes => {} -# modules => [] -# ssh_transport => -# machine => Cloudstack_instnace['foo1'], -# type => 'agent' -#} +cloudstack_instance { 'foo5': + ensure => $::ensure, + userdata => 'foo', +} -# -# should I create a domain? -# +if $::ensure != 'absent' { + puppet_node { 'puppet_master': + role => 'pe_master', + machine => "Cloudstack_instance[foo3]", + keypair => Cloudstack_keypair['dans_keypair4'], + require => Cloudstack_keypair['dans_keypair4'], + #machine => Cloudstack_instance['foo1'], + } + + puppet_node { 'puppet_agent1': + role => 'pe_agent', + machine => "Cloudstack_instance[foo4]", + keypair => Cloudstack_keypair['dans_keypair4'], + require => [Cloudstack_keypair['dans_keypair4'], Puppet_node['puppet_master']], + puppetmaster => "Cloudstack_instance[foo3]", + classes => {'apache' => {}} + } + + puppet_node { 'puppet_agent2': + role => 'pe_agent', + machine => "Cloudstack_instance[foo5]", + keypair => Cloudstack_keypair['dans_keypair4'], + require => [Cloudstack_keypair['dans_keypair4'], Puppet_node['puppet_master']], + puppetmaster => "Cloudstack_instance[foo3]", + classes => {'apache' => {}} + } +}