knife-cloudstack 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES.rdoc +62 -0
- data/LICENSE +202 -0
- data/README.rdoc +211 -0
- data/lib/chef/knife/cs_hosts.rb +83 -0
- data/lib/chef/knife/cs_network_list.rb +84 -0
- data/lib/chef/knife/cs_server_create.rb +315 -0
- data/lib/chef/knife/cs_server_delete.rb +157 -0
- data/lib/chef/knife/cs_server_list.rb +92 -0
- data/lib/chef/knife/cs_server_reboot.rb +103 -0
- data/lib/chef/knife/cs_server_start.rb +103 -0
- data/lib/chef/knife/cs_server_stop.rb +114 -0
- data/lib/chef/knife/cs_service_list.rb +93 -0
- data/lib/chef/knife/cs_stack_create.rb +325 -0
- data/lib/chef/knife/cs_stack_delete.rb +88 -0
- data/lib/chef/knife/cs_template_list.rb +100 -0
- data/lib/chef/knife/cs_zone_list.rb +78 -0
- data/lib/knife-cloudstack/connection.rb +604 -0
- metadata +101 -0
| @@ -0,0 +1,103 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Author:: Ryan Holmes (<rholmes@edmunds.com>)
         | 
| 3 | 
            +
            # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
         | 
| 4 | 
            +
            # Copyright:: Copyright (c) 2011 Edmunds, Inc.
         | 
| 5 | 
            +
            # License:: Apache License, Version 2.0
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Licensed under the Apache License, Version 2.0 (the "License");
         | 
| 8 | 
            +
            # you may not use this file except in compliance with the License.
         | 
| 9 | 
            +
            # You may obtain a copy of the License at
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            #     http://www.apache.org/licenses/LICENSE-2.0
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # Unless required by applicable law or agreed to in writing, software
         | 
| 14 | 
            +
            # distributed under the License is distributed on an "AS IS" BASIS,
         | 
| 15 | 
            +
            # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         | 
| 16 | 
            +
            # See the License for the specific language governing permissions and
         | 
| 17 | 
            +
            # limitations under the License.
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            require 'chef/knife'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            module KnifeCloudstack
         | 
| 23 | 
            +
              class CsServerStart < Chef::Knife
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                deps do
         | 
| 26 | 
            +
                  require 'knife-cloudstack/connection'
         | 
| 27 | 
            +
                  require 'chef/api_client'
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                banner "knife cs server start SERVER_NAME [SERVER_NAME ...] (options)"
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                option :cloudstack_url,
         | 
| 33 | 
            +
                       :short => "-U URL",
         | 
| 34 | 
            +
                       :long => "--cloudstack-url URL",
         | 
| 35 | 
            +
                       :description => "The CloudStack endpoint URL",
         | 
| 36 | 
            +
                       :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                option :cloudstack_api_key,
         | 
| 39 | 
            +
                       :short => "-A KEY",
         | 
| 40 | 
            +
                       :long => "--cloudstack-api-key KEY",
         | 
| 41 | 
            +
                       :description => "Your CloudStack API key",
         | 
| 42 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                option :cloudstack_secret_key,
         | 
| 45 | 
            +
                       :short => "-K SECRET",
         | 
| 46 | 
            +
                       :long => "--cloudstack-secret-key SECRET",
         | 
| 47 | 
            +
                       :description => "Your CloudStack secret key",
         | 
| 48 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def run
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  @name_args.each do |hostname|
         | 
| 53 | 
            +
                    server = connection.get_server(hostname)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    if !server then
         | 
| 56 | 
            +
                      ui.error("Server '#{hostname}' not found")
         | 
| 57 | 
            +
                      next
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    puts "\n"
         | 
| 61 | 
            +
                    msg("Name", server['name'])
         | 
| 62 | 
            +
                    msg("Public IP", connection.get_server_public_ip(server) || '?')
         | 
| 63 | 
            +
                    msg("Service", server['serviceofferingname'])
         | 
| 64 | 
            +
                    msg("Template", server['templatename'])
         | 
| 65 | 
            +
                    msg("Domain", server['domain'])
         | 
| 66 | 
            +
                    msg("Zone", server['zonename'])
         | 
| 67 | 
            +
                    msg("State", server['state'])
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    puts "\n"
         | 
| 70 | 
            +
                    ui.confirm("Do you really want to start this server")
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    print "#{ui.color("Waiting for startup", :magenta)}"
         | 
| 73 | 
            +
                    connection.start_server(hostname)
         | 
| 74 | 
            +
                    puts "\n"
         | 
| 75 | 
            +
                    ui.msg("Started server #{hostname}")
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def connection
         | 
| 81 | 
            +
                  unless @connection
         | 
| 82 | 
            +
                    @connection = CloudstackClient::Connection.new(
         | 
| 83 | 
            +
                        locate_config_value(:cloudstack_url),
         | 
| 84 | 
            +
                        locate_config_value(:cloudstack_api_key),
         | 
| 85 | 
            +
                        locate_config_value(:cloudstack_secret_key)
         | 
| 86 | 
            +
                    )
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                  @connection
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def msg(label, value)
         | 
| 92 | 
            +
                  if value && !value.empty?
         | 
| 93 | 
            +
                    puts "#{ui.color(label, :cyan)}: #{value}"
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def locate_config_value(key)
         | 
| 98 | 
            +
                  key = key.to_sym
         | 
| 99 | 
            +
                  Chef::Config[:knife][key] || config[key]
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              end
         | 
| 103 | 
            +
            end
         | 
| @@ -0,0 +1,114 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Author:: Ryan Holmes (<rholmes@edmunds.com>)
         | 
| 3 | 
            +
            # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
         | 
| 4 | 
            +
            # Copyright:: Copyright (c) 2011 Edmunds, Inc.
         | 
| 5 | 
            +
            # License:: Apache License, Version 2.0
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Licensed under the Apache License, Version 2.0 (the "License");
         | 
| 8 | 
            +
            # you may not use this file except in compliance with the License.
         | 
| 9 | 
            +
            # You may obtain a copy of the License at
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            #     http://www.apache.org/licenses/LICENSE-2.0
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # Unless required by applicable law or agreed to in writing, software
         | 
| 14 | 
            +
            # distributed under the License is distributed on an "AS IS" BASIS,
         | 
| 15 | 
            +
            # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         | 
| 16 | 
            +
            # See the License for the specific language governing permissions and
         | 
| 17 | 
            +
            # limitations under the License.
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            require 'chef/knife'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            module KnifeCloudstack
         | 
| 23 | 
            +
              class CsServerStop < Chef::Knife
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                deps do
         | 
| 26 | 
            +
                  require 'knife-cloudstack/connection'
         | 
| 27 | 
            +
                  require 'chef/api_client'
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                banner "knife cs server stop SERVER_NAME [SERVER_NAME ...] (options)"
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                option :cloudstack_url,
         | 
| 33 | 
            +
                       :short => "-U URL",
         | 
| 34 | 
            +
                       :long => "--cloudstack-url URL",
         | 
| 35 | 
            +
                       :description => "The CloudStack endpoint URL",
         | 
| 36 | 
            +
                       :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                option :cloudstack_api_key,
         | 
| 39 | 
            +
                       :short => "-A KEY",
         | 
| 40 | 
            +
                       :long => "--cloudstack-api-key KEY",
         | 
| 41 | 
            +
                       :description => "Your CloudStack API key",
         | 
| 42 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                option :cloudstack_secret_key,
         | 
| 45 | 
            +
                       :short => "-K SECRET",
         | 
| 46 | 
            +
                       :long => "--cloudstack-secret-key SECRET",
         | 
| 47 | 
            +
                       :description => "Your CloudStack secret key",
         | 
| 48 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                option :cloudstack_force_stop,
         | 
| 51 | 
            +
                       :long => "--force",
         | 
| 52 | 
            +
                       :description => "Force stop the VM. The caller knows the VM is stopped.",
         | 
| 53 | 
            +
                       :boolean => true
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def run
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  @name_args.each do |hostname|
         | 
| 58 | 
            +
                    server = connection.get_server(hostname)
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    if !server then
         | 
| 61 | 
            +
                      ui.error("Server '#{hostname}' not found")
         | 
| 62 | 
            +
                      next
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    puts "\n"
         | 
| 66 | 
            +
                    msg("Name", server['name'])
         | 
| 67 | 
            +
                    msg("Public IP", connection.get_server_public_ip(server) || '?')
         | 
| 68 | 
            +
                    msg("Service", server['serviceofferingname'])
         | 
| 69 | 
            +
                    msg("Template", server['templatename'])
         | 
| 70 | 
            +
                    msg("Domain", server['domain'])
         | 
| 71 | 
            +
                    msg("Zone", server['zonename'])
         | 
| 72 | 
            +
                    msg("State", server['state'])
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    puts "\n"
         | 
| 75 | 
            +
                    if config[:cloudstack_force_stop]
         | 
| 76 | 
            +
                      ui.confirm("Do you really want to force stop this server")
         | 
| 77 | 
            +
                      print "#{ui.color("Forcefully stopping", :magenta)}"
         | 
| 78 | 
            +
                      connection.stop_server(hostname,config[:cloudstack_force_stop])
         | 
| 79 | 
            +
                    else
         | 
| 80 | 
            +
                      ui.confirm("Do you really want to stop this server")
         | 
| 81 | 
            +
                      print "#{ui.color("Stopping", :magenta)}"
         | 
| 82 | 
            +
                      connection.stop_server(hostname)
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    puts "\n"
         | 
| 86 | 
            +
                    ui.msg("Stopped server #{hostname}")
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def connection
         | 
| 92 | 
            +
                  unless @connection
         | 
| 93 | 
            +
                    @connection = CloudstackClient::Connection.new(
         | 
| 94 | 
            +
                        locate_config_value(:cloudstack_url),
         | 
| 95 | 
            +
                        locate_config_value(:cloudstack_api_key),
         | 
| 96 | 
            +
                        locate_config_value(:cloudstack_secret_key)
         | 
| 97 | 
            +
                    )
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                  @connection
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def msg(label, value)
         | 
| 103 | 
            +
                  if value && !value.empty?
         | 
| 104 | 
            +
                    puts "#{ui.color(label, :cyan)}: #{value}"
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def locate_config_value(key)
         | 
| 109 | 
            +
                  key = key.to_sym
         | 
| 110 | 
            +
                  Chef::Config[:knife][key] || config[key]
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              end
         | 
| 114 | 
            +
            end
         | 
| @@ -0,0 +1,93 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Author:: Ryan Holmes (<rholmes@edmunds.com>)
         | 
| 3 | 
            +
            # Copyright:: Copyright (c) 2011 Edmunds, Inc.
         | 
| 4 | 
            +
            # License:: Apache License, Version 2.0
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # Licensed under the Apache License, Version 2.0 (the "License");
         | 
| 7 | 
            +
            # you may not use this file except in compliance with the License.
         | 
| 8 | 
            +
            # You may obtain a copy of the License at
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            #     http://www.apache.org/licenses/LICENSE-2.0
         | 
| 11 | 
            +
            #
         | 
| 12 | 
            +
            # Unless required by applicable law or agreed to in writing, software
         | 
| 13 | 
            +
            # distributed under the License is distributed on an "AS IS" BASIS,
         | 
| 14 | 
            +
            # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         | 
| 15 | 
            +
            # See the License for the specific language governing permissions and
         | 
| 16 | 
            +
            # limitations under the License.
         | 
| 17 | 
            +
            #
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require 'chef/knife'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            module KnifeCloudstack
         | 
| 22 | 
            +
              class CsServiceList < Chef::Knife
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                MEGABYTES = 1024 * 1024
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                deps do
         | 
| 27 | 
            +
                  require 'knife-cloudstack/connection'
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                banner "knife cs service list (options)"
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                option :cloudstack_url,
         | 
| 33 | 
            +
                       :short => "-U URL",
         | 
| 34 | 
            +
                       :long => "--cloudstack-url URL",
         | 
| 35 | 
            +
                       :description => "The CloudStack endpoint URL",
         | 
| 36 | 
            +
                       :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                option :cloudstack_api_key,
         | 
| 39 | 
            +
                       :short => "-A KEY",
         | 
| 40 | 
            +
                       :long => "--cloudstack-api-key KEY",
         | 
| 41 | 
            +
                       :description => "Your CloudStack API key",
         | 
| 42 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                option :cloudstack_secret_key,
         | 
| 45 | 
            +
                       :short => "-K SECRET",
         | 
| 46 | 
            +
                       :long => "--cloudstack-secret-key SECRET",
         | 
| 47 | 
            +
                       :description => "Your CloudStack secret key",
         | 
| 48 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def run
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  connection = CloudstackClient::Connection.new(
         | 
| 53 | 
            +
                      locate_config_value(:cloudstack_url),
         | 
| 54 | 
            +
                      locate_config_value(:cloudstack_api_key),
         | 
| 55 | 
            +
                      locate_config_value(:cloudstack_secret_key)
         | 
| 56 | 
            +
                  )
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  service_list = [
         | 
| 59 | 
            +
                      ui.color('Name', :bold),
         | 
| 60 | 
            +
                      ui.color('Memory', :bold),
         | 
| 61 | 
            +
                      ui.color('CPUs', :bold),
         | 
| 62 | 
            +
                      ui.color('CPU Speed', :bold),
         | 
| 63 | 
            +
                      ui.color('Created', :bold)
         | 
| 64 | 
            +
                  ]
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  services = connection.list_service_offerings
         | 
| 67 | 
            +
                  services.each do |s|
         | 
| 68 | 
            +
                    service_list << s['name']
         | 
| 69 | 
            +
                    service_list << (human_memory(s['memory']) || 'Unknown')
         | 
| 70 | 
            +
                    service_list << s['cpunumber'].to_s
         | 
| 71 | 
            +
                    service_list << s['cpuspeed'].to_s + ' Mhz'
         | 
| 72 | 
            +
                    service_list << s['created']
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                  puts ui.list(service_list, :columns_across, 5)
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                def human_memory n
         | 
| 79 | 
            +
                  count = 0
         | 
| 80 | 
            +
                  while  n >= 1024 and count < 2
         | 
| 81 | 
            +
                    n /= 1024.0
         | 
| 82 | 
            +
                    count += 1
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                  format("%.2f", n) + %w(MB GB TB)[count]
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def locate_config_value(key)
         | 
| 88 | 
            +
                  key = key.to_sym
         | 
| 89 | 
            +
                  Chef::Config[:knife][key] || config[key]
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
            end
         | 
| @@ -0,0 +1,325 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Author:: Ryan Holmes (<rholmes@edmunds.com>)
         | 
| 3 | 
            +
            # Copyright:: Copyright (c) 2011 Edmunds, Inc.
         | 
| 4 | 
            +
            # License:: Apache License, Version 2.0
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # Licensed under the Apache License, Version 2.0 (the "License");
         | 
| 7 | 
            +
            # you may not use this file except in compliance with the License.
         | 
| 8 | 
            +
            # You may obtain a copy of the License at
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            #     http://www.apache.org/licenses/LICENSE-2.0
         | 
| 11 | 
            +
            #
         | 
| 12 | 
            +
            # Unless required by applicable law or agreed to in writing, software
         | 
| 13 | 
            +
            # distributed under the License is distributed on an "AS IS" BASIS,
         | 
| 14 | 
            +
            # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         | 
| 15 | 
            +
            # See the License for the specific language governing permissions and
         | 
| 16 | 
            +
            # limitations under the License.
         | 
| 17 | 
            +
            #
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require 'chef/knife'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            module KnifeCloudstack
         | 
| 22 | 
            +
              class CsStackCreate < Chef::Knife
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                attr_accessor :current_stack
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                deps do
         | 
| 27 | 
            +
                  require 'chef/json_compat'
         | 
| 28 | 
            +
                  require 'chef/mash'
         | 
| 29 | 
            +
                  require 'chef/search/query'
         | 
| 30 | 
            +
                  require 'chef/knife/node_run_list_remove'
         | 
| 31 | 
            +
                  require 'net/ssh'
         | 
| 32 | 
            +
                  require 'net/ssh/multi'
         | 
| 33 | 
            +
                  require 'knife-cloudstack/connection'
         | 
| 34 | 
            +
                  Chef::Knife::Ssh.load_deps
         | 
| 35 | 
            +
                  Chef::Knife::NodeRunListRemove.load_deps
         | 
| 36 | 
            +
                  KnifeCloudstack::CsServerCreate.load_deps
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                banner "knife cs stack create JSON_FILE (options)"
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                option :cloudstack_url,
         | 
| 42 | 
            +
                       :short => "-U URL",
         | 
| 43 | 
            +
                       :long => "--cloudstack-url URL",
         | 
| 44 | 
            +
                       :description => "The CloudStack endpoint URL",
         | 
| 45 | 
            +
                       :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                option :cloudstack_api_key,
         | 
| 48 | 
            +
                       :short => "-A KEY",
         | 
| 49 | 
            +
                       :long => "--cloudstack-api-key KEY",
         | 
| 50 | 
            +
                       :description => "Your CloudStack API key",
         | 
| 51 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                option :cloudstack_secret_key,
         | 
| 54 | 
            +
                       :short => "-K SECRET",
         | 
| 55 | 
            +
                       :long => "--cloudstack-secret-key SECRET",
         | 
| 56 | 
            +
                       :description => "Your CloudStack secret key",
         | 
| 57 | 
            +
                       :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                option :ssh_user,
         | 
| 60 | 
            +
                       :short => "-x USERNAME",
         | 
| 61 | 
            +
                       :long => "--ssh-user USERNAME",
         | 
| 62 | 
            +
                       :description => "The ssh username"
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                option :ssh_password,
         | 
| 65 | 
            +
                       :short => "-P PASSWORD",
         | 
| 66 | 
            +
                       :long => "--ssh-password PASSWORD",
         | 
| 67 | 
            +
                       :description => "The ssh password"
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                option :identity_file,
         | 
| 70 | 
            +
                       :short => "-i IDENTITY_FILE",
         | 
| 71 | 
            +
                       :long => "--identity-file IDENTITY_FILE",
         | 
| 72 | 
            +
                       :description => "The SSH identity file used for authentication"
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def run
         | 
| 75 | 
            +
                  file_path = File.expand_path(@name_args.first)
         | 
| 76 | 
            +
                  unless File.exist?(file_path) then
         | 
| 77 | 
            +
                    ui.error "Stack file '#{file_path}' not found. Please check the path."
         | 
| 78 | 
            +
                    exit 1
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  data = File.read file_path
         | 
| 82 | 
            +
                  stack = Chef::JSONCompat.from_json data
         | 
| 83 | 
            +
                  create_stack stack
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  #puts "Stack: #{stack.inspect}"
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def connection
         | 
| 89 | 
            +
                  if (!@connection) then
         | 
| 90 | 
            +
                    url = locate_config_value(:cloudstack_url)
         | 
| 91 | 
            +
                    api_key = locate_config_value(:cloudstack_api_key)
         | 
| 92 | 
            +
                    secret_key = locate_config_value(:cloudstack_secret_key)
         | 
| 93 | 
            +
                    @connection = CloudstackClient::Connection.new(url, api_key, secret_key)
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                  @connection
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def create_stack(stack)
         | 
| 99 | 
            +
                  @current_stack = Mash.new(stack)
         | 
| 100 | 
            +
                  current_stack[:servers].each do |server|
         | 
| 101 | 
            +
                    if server[:name]
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                      # create server(s)
         | 
| 104 | 
            +
                      names = server[:name].split(/[\s,]+/)
         | 
| 105 | 
            +
                      names.each do |n|
         | 
| 106 | 
            +
                        s = Mash.new(server)
         | 
| 107 | 
            +
                        s[:name] = n
         | 
| 108 | 
            +
                        create_server(s)
         | 
| 109 | 
            +
                      end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    # execute actions
         | 
| 114 | 
            +
                    run_actions server[:actions]
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  print_local_hosts
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def create_server(server)
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  cmd = KnifeCloudstack::CsServerCreate.new([server[:name]])
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  # configure and run command
         | 
| 125 | 
            +
                  # TODO: validate parameters
         | 
| 126 | 
            +
                  cmd.config[:ssh_user] = config[:ssh_user]
         | 
| 127 | 
            +
                  cmd.config[:ssh_password] = config[:ssh_password]
         | 
| 128 | 
            +
                  cmd.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
         | 
| 129 | 
            +
                  cmd.config[:identity_file] = config[:identity_file]
         | 
| 130 | 
            +
                  cmd.config[:cloudstack_template] = server[:template] if server[:template]
         | 
| 131 | 
            +
                  cmd.config[:cloudstack_service] = server[:service] if server[:service]
         | 
| 132 | 
            +
                  cmd.config[:cloudstack_zone] = server[:service] if server[:zone]
         | 
| 133 | 
            +
                  cmd.config[:cloudstack_networks] = server[:networks].split(/[\s,]+/) if server[:networks]
         | 
| 134 | 
            +
                  cmd.config[:run_list] = server[:run_list].split(/[\s,]+/) if server[:run_list]
         | 
| 135 | 
            +
                  cmd.config[:port_rules] = server[:port_rules].split(/[\s,]+/) if server[:port_rules]
         | 
| 136 | 
            +
                  if current_stack[:environment]
         | 
| 137 | 
            +
                    cmd.config[:environment] = current_stack[:environment]
         | 
| 138 | 
            +
                    Chef::Config[:environment] = current_stack[:environment]
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  cmd.run_with_pretty_exceptions
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                def run_actions(actions)
         | 
| 146 | 
            +
                  puts "\n"
         | 
| 147 | 
            +
                  ui.msg("Processing actions...")
         | 
| 148 | 
            +
                  sleep 1 # pause for e.g. chef solr indexing
         | 
| 149 | 
            +
                  actions ||= []
         | 
| 150 | 
            +
                  actions.each do |cmd|
         | 
| 151 | 
            +
                    cmd ||= {}
         | 
| 152 | 
            +
                    cmd.each do |name, args|
         | 
| 153 | 
            +
                      case name
         | 
| 154 | 
            +
                        when 'knife_ssh'
         | 
| 155 | 
            +
                          knife_ssh_action(*args)
         | 
| 156 | 
            +
                        when 'http_request'
         | 
| 157 | 
            +
                          http_request(args)
         | 
| 158 | 
            +
                        when 'run_list_remove'
         | 
| 159 | 
            +
                          run_list_remove(*args)
         | 
| 160 | 
            +
                        when 'sleep'
         | 
| 161 | 
            +
                          dur = args || 5
         | 
| 162 | 
            +
                          sleep dur
         | 
| 163 | 
            +
                      end
         | 
| 164 | 
            +
                    end
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                def search_nodes(query, attribute=nil)
         | 
| 170 | 
            +
                  if get_environment
         | 
| 171 | 
            +
                    query = "(#{query})" + " AND chef_environment:#{get_environment}"
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  Chef::Log.debug("Searching for nodes: #{query}")
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                  q = Chef::Search::Query.new
         | 
| 177 | 
            +
                  nodes = Array(q.search(:node, query))
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                  # the array of nodes is the first item in the array returned by the search
         | 
| 180 | 
            +
                  if nodes.length > 1
         | 
| 181 | 
            +
                    nodes = nodes.first || []
         | 
| 182 | 
            +
                  end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  # return attribute values instead of nodes
         | 
| 185 | 
            +
                  if attribute
         | 
| 186 | 
            +
                    nodes.map do |node|
         | 
| 187 | 
            +
                      node[attribute.to_s]
         | 
| 188 | 
            +
                    end
         | 
| 189 | 
            +
                  else
         | 
| 190 | 
            +
                    nodes
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                def knife_ssh(host_list, command)
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                  ssh = Chef::Knife::Ssh.new
         | 
| 197 | 
            +
                  ssh.name_args = [host_list, command]
         | 
| 198 | 
            +
                  ssh.config[:ssh_user] = config[:ssh_user]
         | 
| 199 | 
            +
                  ssh.config[:ssh_password] = config[:ssh_password]
         | 
| 200 | 
            +
                  ssh.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
         | 
| 201 | 
            +
                  ssh.config[:identity_file] = config[:identity_file]
         | 
| 202 | 
            +
                  ssh.config[:manual] = true
         | 
| 203 | 
            +
                  ssh.config[:no_host_key_verify] = config[:no_host_key_verify]
         | 
| 204 | 
            +
                  ssh
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                def knife_ssh_with_password_auth(host_list, command)
         | 
| 208 | 
            +
                  ssh = knife_ssh(host_list, command)
         | 
| 209 | 
            +
                  ssh.config[:identity_file] = nil
         | 
| 210 | 
            +
                  ssh.config[:ssh_password] = ssh.get_password
         | 
| 211 | 
            +
                  ssh
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                def knife_ssh_action(query, command)
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                  public_ips = find_public_ips(query)
         | 
| 217 | 
            +
                  return if public_ips.nil? || public_ips.empty?
         | 
| 218 | 
            +
                  host_list = public_ips.join(' ')
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  ssh = knife_ssh(host_list, command)
         | 
| 221 | 
            +
                  begin
         | 
| 222 | 
            +
                    ssh.run
         | 
| 223 | 
            +
                  rescue Net::SSH::AuthenticationFailed
         | 
| 224 | 
            +
                    unless config[:ssh_password]
         | 
| 225 | 
            +
                      puts "Failed to authenticate #{config[:ssh_user]} - trying password auth"
         | 
| 226 | 
            +
                      ssh = knife_ssh_with_password_auth(host_list, command)
         | 
| 227 | 
            +
                      ssh.run
         | 
| 228 | 
            +
                    end
         | 
| 229 | 
            +
                  end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                def http_request(url)
         | 
| 234 | 
            +
                  match_data = /\$\{([a-zA-Z0-9-]+)\}/.match url
         | 
| 235 | 
            +
                  if match_data
         | 
| 236 | 
            +
                    server_name = match_data[1]
         | 
| 237 | 
            +
                    ip = public_ip_for_host(server_name)
         | 
| 238 | 
            +
                    url = url.sub(/\$\{#{server_name}\}/, ip)
         | 
| 239 | 
            +
                  end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
             | 
| 242 | 
            +
                  puts "HTTP Request: #{url}"
         | 
| 243 | 
            +
                  puts `curl -s -m 5 #{url}`
         | 
| 244 | 
            +
                end
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                def run_list_remove(query, entry)
         | 
| 247 | 
            +
                  nodes = search_nodes(query)
         | 
| 248 | 
            +
                  return unless nodes
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                  nodes.each do |n|
         | 
| 251 | 
            +
                    cmd = Chef::Knife::NodeRunListRemove.new([n.name, entry])
         | 
| 252 | 
            +
                    cmd.run_with_pretty_exceptions
         | 
| 253 | 
            +
                  end
         | 
| 254 | 
            +
                end
         | 
| 255 | 
            +
             | 
| 256 | 
            +
                def find_public_ips(query)
         | 
| 257 | 
            +
                  hostnames = search_nodes(query, 'hostname')
         | 
| 258 | 
            +
                  puts "Found hostnames: #{hostnames.inspect}"
         | 
| 259 | 
            +
                  ips = hostnames.map { |h|
         | 
| 260 | 
            +
                    public_ip_for_host h
         | 
| 261 | 
            +
                  }
         | 
| 262 | 
            +
                  ips.compact.uniq
         | 
| 263 | 
            +
                end
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                def public_ip_for_host(name)
         | 
| 266 | 
            +
                  return nil unless name
         | 
| 267 | 
            +
                  @public_ip_cache ||= {}
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                  if !@public_ip_cache[name] then
         | 
| 270 | 
            +
                    server = connection.get_server(name)
         | 
| 271 | 
            +
                    return nil unless server
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                    ip = connection.get_server_public_ip(server)
         | 
| 274 | 
            +
                    @public_ip_cache[name] = ip if ip
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                  @public_ip_cache[name]
         | 
| 278 | 
            +
                end
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                def get_environment
         | 
| 281 | 
            +
                  current_stack[:environment]
         | 
| 282 | 
            +
                end
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                def destroy_all(domain, excludes=[])
         | 
| 285 | 
            +
                  servers = connection.list_servers || []
         | 
| 286 | 
            +
                  servers.each do |s|
         | 
| 287 | 
            +
                    excluded = false
         | 
| 288 | 
            +
                    excludes.each { |val|
         | 
| 289 | 
            +
                      if s['name'] =~ /#{val}/ then
         | 
| 290 | 
            +
                        excluded = true
         | 
| 291 | 
            +
                        next
         | 
| 292 | 
            +
                      end
         | 
| 293 | 
            +
                    }
         | 
| 294 | 
            +
                    next if excluded
         | 
| 295 | 
            +
                    nodename = "#{s['name']}.#{domain}"
         | 
| 296 | 
            +
                    system "knife cs server delete #{s['name']} -y"
         | 
| 297 | 
            +
                    system "knife client delete #{nodename} -y"
         | 
| 298 | 
            +
                    system "knife node delete #{nodename} -y"
         | 
| 299 | 
            +
                  end
         | 
| 300 | 
            +
                end
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                def print_local_hosts
         | 
| 303 | 
            +
                  hosts = []
         | 
| 304 | 
            +
                  current_stack[:servers].each do |server|
         | 
| 305 | 
            +
                    next unless server[:local_hosts]
         | 
| 306 | 
            +
                    name = server[:name].split(' ').first
         | 
| 307 | 
            +
                    ip = public_ip_for_host(name)
         | 
| 308 | 
            +
                    server[:local_hosts].each { |host|
         | 
| 309 | 
            +
                      hostname = host.sub(/\$\{environment\}/, get_environment)
         | 
| 310 | 
            +
                      hosts << "#{ip}    #{hostname}"
         | 
| 311 | 
            +
                    }
         | 
| 312 | 
            +
                  end
         | 
| 313 | 
            +
                  unless hosts.empty?
         | 
| 314 | 
            +
                    puts "\nAdd this to your /etc/hosts file:"
         | 
| 315 | 
            +
                    puts hosts.join("\n")
         | 
| 316 | 
            +
                  end
         | 
| 317 | 
            +
                end
         | 
| 318 | 
            +
             | 
| 319 | 
            +
                def locate_config_value(key)
         | 
| 320 | 
            +
                  key = key.to_sym
         | 
| 321 | 
            +
                  Chef::Config[:knife][key] || config[key]
         | 
| 322 | 
            +
                end
         | 
| 323 | 
            +
             | 
| 324 | 
            +
              end
         | 
| 325 | 
            +
            end
         |