ironfan 3.1.0.rc1
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/.gitignore +51 -0
 - data/.rspec +3 -0
 - data/CHANGELOG.md +130 -0
 - data/Gemfile +26 -0
 - data/LICENSE.md +201 -0
 - data/README.md +328 -0
 - data/Rakefile +104 -0
 - data/TODO.md +16 -0
 - data/VERSION +1 -0
 - data/chefignore +41 -0
 - data/cluster_chef-knife.gemspec +123 -0
 - data/cluster_chef.gemspec +111 -0
 - data/config/client.rb +59 -0
 - data/config/proxy.pac +12 -0
 - data/config/ubuntu10.04-ironfan.erb +157 -0
 - data/config/ubuntu11.10-ironfan.erb +145 -0
 - data/ironfan.gemspec +121 -0
 - data/lib/chef/knife/bootstrap/ubuntu10.04-ironfan.erb +157 -0
 - data/lib/chef/knife/bootstrap/ubuntu11.10-ironfan.erb +145 -0
 - data/lib/chef/knife/cluster_bootstrap.rb +74 -0
 - data/lib/chef/knife/cluster_kick.rb +94 -0
 - data/lib/chef/knife/cluster_kill.rb +73 -0
 - data/lib/chef/knife/cluster_launch.rb +164 -0
 - data/lib/chef/knife/cluster_list.rb +50 -0
 - data/lib/chef/knife/cluster_proxy.rb +126 -0
 - data/lib/chef/knife/cluster_show.rb +61 -0
 - data/lib/chef/knife/cluster_ssh.rb +141 -0
 - data/lib/chef/knife/cluster_start.rb +40 -0
 - data/lib/chef/knife/cluster_stop.rb +43 -0
 - data/lib/chef/knife/cluster_sync.rb +77 -0
 - data/lib/chef/knife/generic_command.rb +66 -0
 - data/lib/chef/knife/knife_common.rb +195 -0
 - data/lib/ironfan.rb +143 -0
 - data/lib/ironfan/chef_layer.rb +299 -0
 - data/lib/ironfan/cloud.rb +412 -0
 - data/lib/ironfan/cluster.rb +118 -0
 - data/lib/ironfan/compute.rb +153 -0
 - data/lib/ironfan/deprecated.rb +33 -0
 - data/lib/ironfan/discovery.rb +177 -0
 - data/lib/ironfan/dsl_object.rb +124 -0
 - data/lib/ironfan/facet.rb +144 -0
 - data/lib/ironfan/fog_layer.rb +150 -0
 - data/lib/ironfan/private_key.rb +130 -0
 - data/lib/ironfan/role_implications.rb +58 -0
 - data/lib/ironfan/security_group.rb +119 -0
 - data/lib/ironfan/server.rb +281 -0
 - data/lib/ironfan/server_slice.rb +260 -0
 - data/lib/ironfan/volume.rb +157 -0
 - data/spec/ironfan/cluster_spec.rb +13 -0
 - data/spec/ironfan/facet_spec.rb +69 -0
 - data/spec/ironfan/server_slice_spec.rb +19 -0
 - data/spec/ironfan/server_spec.rb +112 -0
 - data/spec/ironfan_spec.rb +193 -0
 - data/spec/spec_helper.rb +50 -0
 - data/spec/spec_helper/dummy_chef.rb +25 -0
 - data/spec/test_config.rb +20 -0
 - data/tasks/chef_config.rake +38 -0
 - data/tasks/jeweler_use_alt_branch.rake +53 -0
 - metadata +217 -0
 
| 
         @@ -0,0 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Author:: Philip (flip) Kromer (<flip@infochimps.com>)
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Copyright:: Copyright (c) 2011 Infochimps, 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 File.expand_path(File.dirname(__FILE__)+"/generic_command.rb")
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            class Chef
         
     | 
| 
      
 22 
     | 
    
         
            +
              class Knife
         
     | 
| 
      
 23 
     | 
    
         
            +
                class ClusterSync < Ironfan::Script
         
     | 
| 
      
 24 
     | 
    
         
            +
                  import_banner_and_options(Ironfan::Script)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  option :cloud,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    :long        => "--[no-]cloud",
         
     | 
| 
      
 28 
     | 
    
         
            +
                    :description => "Sync to the cloud (default is yes, sync cloud; use --no-cloud to skip)",
         
     | 
| 
      
 29 
     | 
    
         
            +
                    :default     => true,
         
     | 
| 
      
 30 
     | 
    
         
            +
                    :boolean     => true
         
     | 
| 
      
 31 
     | 
    
         
            +
                  option :chef,
         
     | 
| 
      
 32 
     | 
    
         
            +
                    :long        => "--[no-]chef",
         
     | 
| 
      
 33 
     | 
    
         
            +
                    :description => "Sync to the chef server (default is yes, sync chef; use --no-chef to skip)",
         
     | 
| 
      
 34 
     | 
    
         
            +
                    :default     => true,
         
     | 
| 
      
 35 
     | 
    
         
            +
                    :boolean     => true
         
     | 
| 
      
 36 
     | 
    
         
            +
                  option :sync_all,
         
     | 
| 
      
 37 
     | 
    
         
            +
                    :long        => "--[no-]sync-all",
         
     | 
| 
      
 38 
     | 
    
         
            +
                    :description => "Sync, as best as possible, any defined node (even if it is missing from cloud or chef)",
         
     | 
| 
      
 39 
     | 
    
         
            +
                    :default     => false,
         
     | 
| 
      
 40 
     | 
    
         
            +
                    :boolean     => true
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def relevant?(server)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    if config[:sync_all]
         
     | 
| 
      
 45 
     | 
    
         
            +
                      not server.bogus?
         
     | 
| 
      
 46 
     | 
    
         
            +
                    else
         
     | 
| 
      
 47 
     | 
    
         
            +
                      server.created? && server.in_chef?
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def perform_execution(target)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    if config[:chef]
         
     | 
| 
      
 53 
     | 
    
         
            +
                      sync_to_chef target
         
     | 
| 
      
 54 
     | 
    
         
            +
                    else Chef::Log.debug("Skipping sync to chef") ; end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    if config[:cloud] && target.any?(&:in_cloud?)
         
     | 
| 
      
 57 
     | 
    
         
            +
                      sync_to_cloud target
         
     | 
| 
      
 58 
     | 
    
         
            +
                    else Chef::Log.debug("Skipping sync to cloud") ; end
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def sync_to_chef(target)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    if config[:dry_run]
         
     | 
| 
      
 63 
     | 
    
         
            +
                      ui.info "(can't do a dry-run when syncing to chef -- skipping)"
         
     | 
| 
      
 64 
     | 
    
         
            +
                      return
         
     | 
| 
      
 65 
     | 
    
         
            +
                    end
         
     | 
| 
      
 66 
     | 
    
         
            +
                    ui.info "Syncing to Chef:"
         
     | 
| 
      
 67 
     | 
    
         
            +
                    target.sync_to_chef
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  def sync_to_cloud(target)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    ui.info "Syncing to cloud:"
         
     | 
| 
      
 72 
     | 
    
         
            +
                    target.sync_to_cloud
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,66 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Author:: Philip (flip) Kromer (<flip@infochimps.com>)
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Copyright:: Copyright (c) 2011 Infochimps, 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 File.expand_path(File.dirname(__FILE__)+"/knife_common.rb")
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            module Ironfan
         
     | 
| 
      
 22 
     | 
    
         
            +
              class Script < Chef::Knife
         
     | 
| 
      
 23 
     | 
    
         
            +
                include Ironfan::KnifeCommon
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                deps do
         
     | 
| 
      
 26 
     | 
    
         
            +
                  Ironfan::KnifeCommon.load_deps
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                option :dry_run,
         
     | 
| 
      
 30 
     | 
    
         
            +
                  :long        => "--dry-run",
         
     | 
| 
      
 31 
     | 
    
         
            +
                  :description => "Don't really run, just use mock calls",
         
     | 
| 
      
 32 
     | 
    
         
            +
                  :boolean     => true,
         
     | 
| 
      
 33 
     | 
    
         
            +
                  :default     => false
         
     | 
| 
      
 34 
     | 
    
         
            +
                option :yes,
         
     | 
| 
      
 35 
     | 
    
         
            +
                  :long        => "--yes",
         
     | 
| 
      
 36 
     | 
    
         
            +
                  :description => "Skip confirmation prompts on risky actions.",
         
     | 
| 
      
 37 
     | 
    
         
            +
                  :boolean     => true
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def run
         
     | 
| 
      
 40 
     | 
    
         
            +
                  load_ironfan
         
     | 
| 
      
 41 
     | 
    
         
            +
                  die(banner) if @name_args.empty?
         
     | 
| 
      
 42 
     | 
    
         
            +
                  configure_dry_run
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  target = get_relevant_slice(* @name_args)
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  die("No nodes to #{sub_command}, exiting", 1) if target.empty?
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  ui.info(["\n",
         
     | 
| 
      
 49 
     | 
    
         
            +
                      ui.color("Running #{sub_command}", :cyan),
         
     | 
| 
      
 50 
     | 
    
         
            +
                      " on #{target.joined_names}..."].join())
         
     | 
| 
      
 51 
     | 
    
         
            +
                  unless config[:yes]
         
     | 
| 
      
 52 
     | 
    
         
            +
                    ui.info("")
         
     | 
| 
      
 53 
     | 
    
         
            +
                    confirm_execution(target)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  #
         
     | 
| 
      
 56 
     | 
    
         
            +
                  perform_execution(target)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  ui.info("")
         
     | 
| 
      
 58 
     | 
    
         
            +
                  ui.info "Finished! Current state:"
         
     | 
| 
      
 59 
     | 
    
         
            +
                  display(target)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def perform_execution(target)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  target.send(sub_command)
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,195 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'chef/knife'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ironfan
         
     | 
| 
      
 4 
     | 
    
         
            +
              module KnifeCommon
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                def self.load_deps
         
     | 
| 
      
 7 
     | 
    
         
            +
                  require 'formatador'
         
     | 
| 
      
 8 
     | 
    
         
            +
                  require 'chef/node'
         
     | 
| 
      
 9 
     | 
    
         
            +
                  require 'chef/api_client'
         
     | 
| 
      
 10 
     | 
    
         
            +
                  require 'fog'
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def load_ironfan
         
     | 
| 
      
 14 
     | 
    
         
            +
                  $LOAD_PATH << File.join(Chef::Config[:ironfan_path], '/lib') if Chef::Config[:ironfan_path]
         
     | 
| 
      
 15 
     | 
    
         
            +
                  require 'ironfan'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  $stdout.sync = true
         
     | 
| 
      
 17 
     | 
    
         
            +
                  Ironfan.ui          = self.ui
         
     | 
| 
      
 18 
     | 
    
         
            +
                  Ironfan.chef_config = self.config
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                # A slice of a cluster:
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @param [String] cluster_name  -- cluster to slice
         
     | 
| 
      
 25 
     | 
    
         
            +
                # @param [String] facet_name    -- facet to slice (or nil for all in cluster)
         
     | 
| 
      
 26 
     | 
    
         
            +
                # @param [Array, String] slice_indexes -- servers in that facet (or nil for all in facet).
         
     | 
| 
      
 27 
     | 
    
         
            +
                #   You must specify a facet if you use slice_indexes.
         
     | 
| 
      
 28 
     | 
    
         
            +
                #
         
     | 
| 
      
 29 
     | 
    
         
            +
                # @return [Ironfan::ServerSlice] the requested slice
         
     | 
| 
      
 30 
     | 
    
         
            +
                def get_slice(cluster_name, facet_name=nil, slice_indexes=nil)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  if facet_name.nil? && slice_indexes.nil?
         
     | 
| 
      
 32 
     | 
    
         
            +
                    cluster_name, facet_name, slice_indexes = cluster_name.split(/[\s\-]/, 3)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  ui.info("Inventorying servers in #{predicate_str(cluster_name, facet_name, slice_indexes)}")
         
     | 
| 
      
 35 
     | 
    
         
            +
                  cluster = Ironfan.load_cluster(cluster_name)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  cluster.resolve!
         
     | 
| 
      
 37 
     | 
    
         
            +
                  cluster.discover!
         
     | 
| 
      
 38 
     | 
    
         
            +
                  cluster.slice(facet_name, slice_indexes)
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def predicate_str(cluster_name, facet_name, slice_indexes)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  [ "#{ui.color(cluster_name, :bold)} cluster",
         
     | 
| 
      
 43 
     | 
    
         
            +
                    (facet_name    ? "#{ui.color(facet_name, :bold)} facet"      : "#{ui.color("all", :bold)} facets"),
         
     | 
| 
      
 44 
     | 
    
         
            +
                    (slice_indexes ? "servers #{ui.color(slice_indexes, :bold)}" : "#{ui.color("all", :bold)} servers")
         
     | 
| 
      
 45 
     | 
    
         
            +
                  ].join(', ')
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                # method to nodes should be filtered on
         
     | 
| 
      
 49 
     | 
    
         
            +
                def relevant?(server)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  server.exists?
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                # override in subclass to confirm risky actions
         
     | 
| 
      
 54 
     | 
    
         
            +
                def confirm_execution(*args)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # pass
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                #
         
     | 
| 
      
 59 
     | 
    
         
            +
                # Get a slice of nodes matching the given filter
         
     | 
| 
      
 60 
     | 
    
         
            +
                #
         
     | 
| 
      
 61 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 62 
     | 
    
         
            +
                #    target = get_relevant_slice(* @name_args)
         
     | 
| 
      
 63 
     | 
    
         
            +
                #
         
     | 
| 
      
 64 
     | 
    
         
            +
                def get_relevant_slice( *predicate )
         
     | 
| 
      
 65 
     | 
    
         
            +
                  full_target = get_slice( *predicate )
         
     | 
| 
      
 66 
     | 
    
         
            +
                  display(full_target) do |svr|
         
     | 
| 
      
 67 
     | 
    
         
            +
                    rel = relevant?(svr)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    { :relevant? => (rel ? "[blue]#{rel}[reset]" : '-' ) }
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  full_target.select{|svr| relevant?(svr) }
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                # passes target to ClusterSlice#display, will show headings in server slice
         
     | 
| 
      
 74 
     | 
    
         
            +
                # tables based on the --verbose flag
         
     | 
| 
      
 75 
     | 
    
         
            +
                def display(target, display_style=nil, &block)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  display_style ||= (config[:verbosity] == 0 ? :default : :expanded)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  target.display(display_style, &block)
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                #
         
     | 
| 
      
 81 
     | 
    
         
            +
                # Put Fog into mock mode if --dry_run
         
     | 
| 
      
 82 
     | 
    
         
            +
                #
         
     | 
| 
      
 83 
     | 
    
         
            +
                def configure_dry_run
         
     | 
| 
      
 84 
     | 
    
         
            +
                  if config[:dry_run]
         
     | 
| 
      
 85 
     | 
    
         
            +
                    Fog.mock!
         
     | 
| 
      
 86 
     | 
    
         
            +
                    Fog::Mock.delay = 0
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                # Show a pretty progress bar while we wait for a set of threads to finish.
         
     | 
| 
      
 91 
     | 
    
         
            +
                def progressbar_for_threads(threads)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  section "Waiting for servers:"
         
     | 
| 
      
 93 
     | 
    
         
            +
                  total      = threads.length
         
     | 
| 
      
 94 
     | 
    
         
            +
                  remaining  = threads.select(&:alive?)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  start_time = Time.now
         
     | 
| 
      
 96 
     | 
    
         
            +
                  until remaining.empty?
         
     | 
| 
      
 97 
     | 
    
         
            +
                    remaining = remaining.select(&:alive?)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    if config[:verbose]
         
     | 
| 
      
 99 
     | 
    
         
            +
                      ui.info "waiting: #{total - remaining.length} / #{total}, #{(Time.now - start_time).to_i}s"
         
     | 
| 
      
 100 
     | 
    
         
            +
                      sleep 5
         
     | 
| 
      
 101 
     | 
    
         
            +
                    else
         
     | 
| 
      
 102 
     | 
    
         
            +
                      Formatador.redisplay_progressbar(total - remaining.length, total, {:started_at => start_time })
         
     | 
| 
      
 103 
     | 
    
         
            +
                      sleep 1
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
                  # Collapse the threads
         
     | 
| 
      
 107 
     | 
    
         
            +
                  threads.each(&:join)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  ui.info ''
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                def bootstrapper(server, hostname)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  bootstrap = Chef::Knife::Bootstrap.new
         
     | 
| 
      
 113 
     | 
    
         
            +
                  bootstrap.config.merge!(config)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  bootstrap.name_args               = [ hostname ]
         
     | 
| 
      
 116 
     | 
    
         
            +
                  bootstrap.config[:node]           = server
         
     | 
| 
      
 117 
     | 
    
         
            +
                  bootstrap.config[:run_list]       = server.combined_run_list
         
     | 
| 
      
 118 
     | 
    
         
            +
                  bootstrap.config[:ssh_user]       = config[:ssh_user]       || server.cloud.ssh_user
         
     | 
| 
      
 119 
     | 
    
         
            +
                  bootstrap.config[:attribute]      = config[:attribute]
         
     | 
| 
      
 120 
     | 
    
         
            +
                  bootstrap.config[:identity_file]  = config[:identity_file]  || server.cloud.ssh_identity_file
         
     | 
| 
      
 121 
     | 
    
         
            +
                  bootstrap.config[:distro]         = config[:distro]         || server.cloud.bootstrap_distro
         
     | 
| 
      
 122 
     | 
    
         
            +
                  bootstrap.config[:use_sudo]       = true unless config[:use_sudo] == false
         
     | 
| 
      
 123 
     | 
    
         
            +
                  bootstrap.config[:chef_node_name] = server.fullname
         
     | 
| 
      
 124 
     | 
    
         
            +
                  bootstrap.config[:client_key]     = server.client_key.body  if server.client_key.body
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                  bootstrap
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                def run_bootstrap(node, hostname)
         
     | 
| 
      
 130 
     | 
    
         
            +
                  bs = bootstrapper(node, hostname)
         
     | 
| 
      
 131 
     | 
    
         
            +
                  if config[:skip].to_s == 'true'
         
     | 
| 
      
 132 
     | 
    
         
            +
                    ui.info "Skipping: bootstrapp #{hostname} with #{JSON.pretty_generate(bs.config)}"
         
     | 
| 
      
 133 
     | 
    
         
            +
                    return
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 136 
     | 
    
         
            +
                    bs.run
         
     | 
| 
      
 137 
     | 
    
         
            +
                  rescue StandardError => e
         
     | 
| 
      
 138 
     | 
    
         
            +
                    ui.warn e
         
     | 
| 
      
 139 
     | 
    
         
            +
                    ui.warn e.backtrace
         
     | 
| 
      
 140 
     | 
    
         
            +
                    ui.warn ""
         
     | 
| 
      
 141 
     | 
    
         
            +
                    ui.warn node.inspect
         
     | 
| 
      
 142 
     | 
    
         
            +
                    ui.warn ""
         
     | 
| 
      
 143 
     | 
    
         
            +
                  end
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                #
         
     | 
| 
      
 147 
     | 
    
         
            +
                # Utilities
         
     | 
| 
      
 148 
     | 
    
         
            +
                #
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                def sub_command
         
     | 
| 
      
 151 
     | 
    
         
            +
                  self.class.sub_command
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                def confirm_or_exit question, correct_answer
         
     | 
| 
      
 155 
     | 
    
         
            +
                  response = ui.ask_question(question)
         
     | 
| 
      
 156 
     | 
    
         
            +
                  unless response.chomp == correct_answer
         
     | 
| 
      
 157 
     | 
    
         
            +
                    die "I didn't think so.", "Aborting!", 1
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  ui.info("")
         
     | 
| 
      
 160 
     | 
    
         
            +
                end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                #
         
     | 
| 
      
 163 
     | 
    
         
            +
                # Announce a new section of tasks
         
     | 
| 
      
 164 
     | 
    
         
            +
                #
         
     | 
| 
      
 165 
     | 
    
         
            +
                def section(desc, *style)
         
     | 
| 
      
 166 
     | 
    
         
            +
                  style = [:blue] if style.empty?
         
     | 
| 
      
 167 
     | 
    
         
            +
                  ui.info(ui.color(desc, *style))
         
     | 
| 
      
 168 
     | 
    
         
            +
                end
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                def die *args
         
     | 
| 
      
 171 
     | 
    
         
            +
                  Ironfan.die(*args)
         
     | 
| 
      
 172 
     | 
    
         
            +
                end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 175 
     | 
    
         
            +
                  def sub_command
         
     | 
| 
      
 176 
     | 
    
         
            +
                    self.to_s.gsub(/^.*::/, '').gsub(/^Cluster/, '').downcase
         
     | 
| 
      
 177 
     | 
    
         
            +
                  end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                  def import_banner_and_options(klass, options={})
         
     | 
| 
      
 180 
     | 
    
         
            +
                    options[:except] ||= []
         
     | 
| 
      
 181 
     | 
    
         
            +
                    deps{ klass.load_deps }
         
     | 
| 
      
 182 
     | 
    
         
            +
                    klass.options.sort.each do |name, info|
         
     | 
| 
      
 183 
     | 
    
         
            +
                      next if options.include?(name) || options[:except].include?(name)
         
     | 
| 
      
 184 
     | 
    
         
            +
                      option name, info
         
     | 
| 
      
 185 
     | 
    
         
            +
                    end
         
     | 
| 
      
 186 
     | 
    
         
            +
                    banner "knife cluster #{sub_command} CLUSTER_NAME [FACET_NAME [INDEXES]] (options)"
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
      
 188 
     | 
    
         
            +
                end
         
     | 
| 
      
 189 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 190 
     | 
    
         
            +
                  base.class_eval do
         
     | 
| 
      
 191 
     | 
    
         
            +
                    extend ClassMethods
         
     | 
| 
      
 192 
     | 
    
         
            +
                  end
         
     | 
| 
      
 193 
     | 
    
         
            +
                end
         
     | 
| 
      
 194 
     | 
    
         
            +
              end
         
     | 
| 
      
 195 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ironfan.rb
    ADDED
    
    | 
         @@ -0,0 +1,143 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'chef/mash'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'chef/config'
         
     | 
| 
      
 3 
     | 
    
         
            +
            #
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'gorillib/metaprogramming/class_attribute'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'gorillib/hash/reverse_merge'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'gorillib/object/blank'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'gorillib/hash/compact'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            require 'ironfan/dsl_object'
         
     | 
| 
      
 11 
     | 
    
         
            +
            require 'ironfan/cloud'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require 'ironfan/security_group'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require 'ironfan/compute'           # base class for machine attributes
         
     | 
| 
      
 14 
     | 
    
         
            +
            require 'ironfan/facet'             # similar machines within a cluster
         
     | 
| 
      
 15 
     | 
    
         
            +
            require 'ironfan/cluster'           # group of machines with a common mission
         
     | 
| 
      
 16 
     | 
    
         
            +
            require 'ironfan/server'            # realization of a specific facet
         
     | 
| 
      
 17 
     | 
    
         
            +
            require 'ironfan/discovery'         # pair servers with Fog and Chef objects
         
     | 
| 
      
 18 
     | 
    
         
            +
            require 'ironfan/server_slice'      # collection of server objects
         
     | 
| 
      
 19 
     | 
    
         
            +
            require 'ironfan/volume'            # configure external and internal volumes
         
     | 
| 
      
 20 
     | 
    
         
            +
            require 'ironfan/private_key'       # coordinate chef keys, cloud keypairs, etc
         
     | 
| 
      
 21 
     | 
    
         
            +
            require 'ironfan/role_implications' # make roles trigger other actions (security groups, etc)
         
     | 
| 
      
 22 
     | 
    
         
            +
            #
         
     | 
| 
      
 23 
     | 
    
         
            +
            require 'ironfan/chef_layer'        # interface to chef for server actions
         
     | 
| 
      
 24 
     | 
    
         
            +
            require 'ironfan/fog_layer'         # interface to fog  for server actions
         
     | 
| 
      
 25 
     | 
    
         
            +
            #
         
     | 
| 
      
 26 
     | 
    
         
            +
            require 'ironfan/deprecated'        # stuff slated to go away
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            module Ironfan
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              # path to search for cluster definition files
         
     | 
| 
      
 31 
     | 
    
         
            +
              def self.cluster_path
         
     | 
| 
      
 32 
     | 
    
         
            +
                return Chef::Config[:cluster_path] if Chef::Config[:cluster_path]
         
     | 
| 
      
 33 
     | 
    
         
            +
                raise "Holy smokes, you have no cookbook_path or cluster_path set up. Follow chef's directions for creating a knife.rb." if Chef::Config[:cookbook_path].blank?
         
     | 
| 
      
 34 
     | 
    
         
            +
                cl_path = Chef::Config[:cookbook_path].map{|dir| File.expand_path('../clusters', dir) }.uniq
         
     | 
| 
      
 35 
     | 
    
         
            +
                ui.warn "No cluster path set. Taking a wild guess that #{cl_path.inspect} is \nreasonable based on your cookbook_path -- but please set cluster_path in your knife.rb"
         
     | 
| 
      
 36 
     | 
    
         
            +
                Chef::Config[:cluster_path] = cl_path
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              #
         
     | 
| 
      
 40 
     | 
    
         
            +
              # Delegates
         
     | 
| 
      
 41 
     | 
    
         
            +
              def self.clusters
         
     | 
| 
      
 42 
     | 
    
         
            +
                Chef::Config[:clusters] ||= Mash.new
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
              def self.ui=(ui) @ui = ui ; end
         
     | 
| 
      
 46 
     | 
    
         
            +
              def self.ui()    @ui      ; end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              def self.chef_config=(cc) @chef_config = cc ; end
         
     | 
| 
      
 49 
     | 
    
         
            +
              def self.chef_config()    @chef_config      ; end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              #
         
     | 
| 
      
 52 
     | 
    
         
            +
              # Defines a cluster with the given name.
         
     | 
| 
      
 53 
     | 
    
         
            +
              #
         
     | 
| 
      
 54 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 55 
     | 
    
         
            +
              #   Ironfan.cluster 'demosimple' do
         
     | 
| 
      
 56 
     | 
    
         
            +
              #     cloud :ec2 do
         
     | 
| 
      
 57 
     | 
    
         
            +
              #       availability_zones  ['us-east-1d']
         
     | 
| 
      
 58 
     | 
    
         
            +
              #       flavor              "t1.micro"
         
     | 
| 
      
 59 
     | 
    
         
            +
              #       image_name          "ubuntu-natty"
         
     | 
| 
      
 60 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 61 
     | 
    
         
            +
              #     role                  :base_role
         
     | 
| 
      
 62 
     | 
    
         
            +
              #     role                  :chef_client
         
     | 
| 
      
 63 
     | 
    
         
            +
              #
         
     | 
| 
      
 64 
     | 
    
         
            +
              #     facet :sandbox do
         
     | 
| 
      
 65 
     | 
    
         
            +
              #       instances 2
         
     | 
| 
      
 66 
     | 
    
         
            +
              #       role                :nfs_client
         
     | 
| 
      
 67 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 68 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 69 
     | 
    
         
            +
              #
         
     | 
| 
      
 70 
     | 
    
         
            +
              #
         
     | 
| 
      
 71 
     | 
    
         
            +
              def self.cluster(name, attrs={}, &block)
         
     | 
| 
      
 72 
     | 
    
         
            +
                name = name.to_sym
         
     | 
| 
      
 73 
     | 
    
         
            +
                cl = ( self.clusters[name] ||= Ironfan::Cluster.new(name, attrs) )
         
     | 
| 
      
 74 
     | 
    
         
            +
                cl.configure(&block)
         
     | 
| 
      
 75 
     | 
    
         
            +
                cl
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              #
         
     | 
| 
      
 79 
     | 
    
         
            +
              # Return cluster if it's defined. Otherwise, search Ironfan.cluster_path
         
     | 
| 
      
 80 
     | 
    
         
            +
              # for an eponymous file, load it, and return the cluster it defines.
         
     | 
| 
      
 81 
     | 
    
         
            +
              #
         
     | 
| 
      
 82 
     | 
    
         
            +
              # Raises an error if a matching file isn't found, or if loading that file
         
     | 
| 
      
 83 
     | 
    
         
            +
              # doesn't define the requested cluster.
         
     | 
| 
      
 84 
     | 
    
         
            +
              #
         
     | 
| 
      
 85 
     | 
    
         
            +
              # @return [Ironfan::Cluster] the requested cluster
         
     | 
| 
      
 86 
     | 
    
         
            +
              def self.load_cluster(cluster_name)
         
     | 
| 
      
 87 
     | 
    
         
            +
                raise ArgumentError, "Please supply a cluster name" if cluster_name.to_s.empty?
         
     | 
| 
      
 88 
     | 
    
         
            +
                return clusters[cluster_name] if clusters[cluster_name]
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                cluster_file = cluster_filenames[cluster_name] or die("Couldn't find a definition for #{cluster_name} in cluster_path: #{cluster_path.inspect}")
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                Chef::Log.info("Loading cluster #{cluster_file}")
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                require cluster_file
         
     | 
| 
      
 95 
     | 
    
         
            +
                unless clusters[cluster_name] then  die("#{cluster_file} was supposed to have the definition for the #{cluster_name} cluster, but didn't") end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                clusters[cluster_name]
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
              #
         
     | 
| 
      
 101 
     | 
    
         
            +
              # Map from cluster name to file name
         
     | 
| 
      
 102 
     | 
    
         
            +
              #
         
     | 
| 
      
 103 
     | 
    
         
            +
              # @return [Hash] map from cluster name to file name
         
     | 
| 
      
 104 
     | 
    
         
            +
              def self.cluster_filenames
         
     | 
| 
      
 105 
     | 
    
         
            +
                return @cluster_filenames if @cluster_filenames
         
     | 
| 
      
 106 
     | 
    
         
            +
                @cluster_filenames = {}
         
     | 
| 
      
 107 
     | 
    
         
            +
                cluster_path.each do |cp_dir|
         
     | 
| 
      
 108 
     | 
    
         
            +
                  Dir[ File.join(cp_dir, '*.rb') ].each do |filename|
         
     | 
| 
      
 109 
     | 
    
         
            +
                    cluster_name = File.basename(filename).gsub(/\.rb$/, '')
         
     | 
| 
      
 110 
     | 
    
         
            +
                    @cluster_filenames[cluster_name] ||= filename
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
                @cluster_filenames
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
              #
         
     | 
| 
      
 117 
     | 
    
         
            +
              # Utility to die with an error message.
         
     | 
| 
      
 118 
     | 
    
         
            +
              # If the last arg is an integer, use it as the exit code.
         
     | 
| 
      
 119 
     | 
    
         
            +
              #
         
     | 
| 
      
 120 
     | 
    
         
            +
              def self.die *strings
         
     | 
| 
      
 121 
     | 
    
         
            +
                exit_code = strings.last.is_a?(Integer) ? strings.pop : -1
         
     | 
| 
      
 122 
     | 
    
         
            +
                strings.each{|str| ui.warn str }
         
     | 
| 
      
 123 
     | 
    
         
            +
                exit exit_code
         
     | 
| 
      
 124 
     | 
    
         
            +
              end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
              #
         
     | 
| 
      
 127 
     | 
    
         
            +
              # Utility to turn an error into a warning
         
     | 
| 
      
 128 
     | 
    
         
            +
              #
         
     | 
| 
      
 129 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 130 
     | 
    
         
            +
              #   Ironfan.safely do
         
     | 
| 
      
 131 
     | 
    
         
            +
              #     Ironfan.fog_connection.associate_address(self.fog_server.id, address)
         
     | 
| 
      
 132 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 133 
     | 
    
         
            +
              #
         
     | 
| 
      
 134 
     | 
    
         
            +
              def self.safely
         
     | 
| 
      
 135 
     | 
    
         
            +
                begin
         
     | 
| 
      
 136 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 137 
     | 
    
         
            +
                rescue StandardError => boom
         
     | 
| 
      
 138 
     | 
    
         
            +
                  ui.info( boom )
         
     | 
| 
      
 139 
     | 
    
         
            +
                  Chef::Log.error( boom )
         
     | 
| 
      
 140 
     | 
    
         
            +
                  Chef::Log.error( boom.backtrace.join("\n") )
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
              end
         
     | 
| 
      
 143 
     | 
    
         
            +
            end
         
     |