├── README.markdown ├── agent ├── angelianotify │ └── README ├── bench │ ├── README.md │ └── agent │ │ ├── bench.ddl │ │ └── bench.rb ├── cap │ ├── README.md │ └── application │ │ └── cap.rb ├── collective │ ├── README.md │ ├── agent │ │ ├── collective.ddl │ │ └── collective.rb │ └── collective_plugins │ │ ├── plugins │ │ ├── agents │ │ │ ├── stomputil.ddl │ │ │ └── stomputil.rb │ │ └── security │ │ │ └── none.rb │ │ └── templates │ │ ├── classes │ │ ├── classes-1.txt │ │ ├── classes-2.txt │ │ └── classes-3.txt │ │ ├── facts │ │ ├── facts-1.yaml │ │ ├── facts-2.yaml │ │ └── facts-3.yaml │ │ └── server.cfg.erb ├── eximng │ ├── DDL.md │ ├── README.md │ ├── agent │ │ ├── eximng.ddl │ │ └── eximng.rb │ ├── application │ │ └── exim.rb │ ├── data │ │ ├── domain_mailq_data.ddl │ │ └── domain_mailq_data.rb │ ├── sbin │ │ └── eximctl │ ├── util │ │ └── eximng.rb │ ├── validator │ │ ├── exim_msgid_validator.ddl │ │ └── exim_msgid_validator.rb │ └── web │ │ ├── .gitignore │ │ ├── eximweb.rb │ │ ├── lib │ │ └── exim.rb │ │ ├── public │ │ ├── bootstrap.css │ │ └── js │ │ │ ├── bootstrap-alerts.js │ │ │ ├── bootstrap-dropdown.js │ │ │ ├── bootstrap-modal.js │ │ │ ├── bootstrap-popover.js │ │ │ ├── bootstrap-scrollspy.js │ │ │ ├── bootstrap-tabs.js │ │ │ └── bootstrap-twipsy.js │ │ ├── serverlist.yaml.dist │ │ └── views │ │ ├── generic_result_view.erb │ │ ├── layout.erb │ │ └── mailq_view.erb ├── haproxy │ └── agent │ │ ├── haproxy.ddl │ │ └── haproxy.rb ├── libvirt │ ├── README.md │ ├── agent │ │ ├── libvirt.ddl │ │ └── libvirt.rb │ └── application │ │ └── virt.rb ├── rndc │ ├── README.md │ └── agent │ │ ├── rndc.ddl │ │ └── rndc.rb ├── ts │ └── agent │ │ ├── ts.ddl │ │ └── ts.rb ├── urltest │ ├── COPYING │ ├── README.md │ ├── TODO │ ├── agent │ │ ├── urltest.ddl │ │ └── urltest.rb │ └── application │ │ └── urltest.rb └── vcsmgr │ ├── vcsmgr.ddl │ └── vcsmgr.rb ├── application └── plot │ ├── README.md │ └── application │ └── plot.rb ├── connector └── redis │ ├── discovery │ ├── redis.ddl │ └── redis.rb │ ├── redis.rb │ └── registration │ └── redis.rb ├── data └── augeas_match │ └── data │ ├── augeas_match_data.ddl │ └── augeas_match_data.rb ├── discovery ├── ec2 │ ├── README.md │ ├── ec2.ddl │ └── ec2.rb └── puppetdb │ └── discovery │ ├── puppetdb.ddl │ └── puppetdb.rb ├── mc-irb └── mc-irb ├── registration └── mcollective-registration-receiver.rb └── scripts └── package-updater.rb /README.markdown: -------------------------------------------------------------------------------- 1 | What is it? 2 | =========== 3 | 4 | A collection of plugins for The Marionette Collective. 5 | 6 | Most of the plugins that used to be here have been moved 7 | to Puppet Labs and can be found documented at: 8 | 9 | http://projects.puppetlabs.com/projects/mcollective-plugins/wiki/Wiki 10 | 11 | And the code lives at: 12 | 13 | https://github.com/puppetlabs/mcollective-plugins 14 | 15 | If you have any qyestions about these plugins please contact 16 | 17 | R.I.Pienaar 18 | -------------------------------------------------------------------------------- /agent/angelianotify/README: -------------------------------------------------------------------------------- 1 | This agent has been moved to https://github.com/ripienaar/angelia 2 | -------------------------------------------------------------------------------- /agent/bench/README.md: -------------------------------------------------------------------------------- 1 | What? 2 | ===== 3 | 4 | A MCollective agent that can be deployed into an existing mcollective 5 | setup and be used to start up multiple instances of mcollective on 6 | each node. 7 | 8 | The purpose is to assist in scale testing middleware, given 100 VMs 9 | you could comfortably start up to 1000 or even 1500 mcollective 10 | instances. 11 | 12 | 13 | Creating Instances? 14 | ------------------- 15 | 16 | Prior to creating instances we need to be sure there are no previous 17 | instances running: 18 | 19 | $ mco rpc bench destroy 20 | Discovering hosts using the mc method for 2 second(s) .... 1 21 | 22 | * [ ============================================================> ] 1 / 1 23 | 24 | 25 | master.example.com 26 | Instances: 0 27 | Running: 0 28 | Stopped: 0 29 | 30 | Summary of Instances: 31 | 32 | Total Instances: 0 33 | 34 | Finished processing 1 / 1 hosts in 75.92 ms 35 | 36 | You're now ready to create new instances which will share libdir, 37 | brokers etc all with your original mcollectived: 38 | 39 | $ mco rpc bench create_members count=10 40 | Discovering hosts using the mc method for 2 second(s) .... 1 41 | 42 | * [ ============================================================> ] 1 / 1 43 | 44 | Summary of Instances: 45 | 46 | Total Instances: 10 47 | 48 | Finished processing 1 / 1 hosts in 94.48 ms 49 | 50 | I recommend you test against a different ActiveMQ instance though so that 51 | when you reach the capacity limit of your ActiveMQ instance under test you 52 | can use the first one to destroy the bench collective: 53 | 54 | $ mco rpc bench create_members count=10 activemq_host=another.host.example.net 55 | 56 | Instance Status? 57 | ---------------- 58 | 59 | You can figure out the status of your bench instances: 60 | 61 | $ mco rpc bench status 62 | Discovering hosts using the mc method for 2 second(s) .... 1 63 | 64 | * [ ============================================================> ] 1 / 1 65 | 66 | 67 | master.example.com 68 | Instances: 10 69 | Running: 0 70 | Stopped: 10 71 | Members: [{:name=>"master.example.com-2", :pid=>nil}, 72 | {:name=>"master.example.com-8", :pid=>nil}, 73 | {:name=>"master.example.com-1", :pid=>nil}, 74 | {:name=>"master.example.com-7", :pid=>nil}, 75 | {:name=>"master.example.com-6", :pid=>nil}, 76 | {:name=>"master.example.com-3", :pid=>nil}, 77 | {:name=>"master.example.com-5", :pid=>nil}, 78 | {:name=>"master.example.com-9", :pid=>nil}, 79 | {:name=>"master.example.com-4", :pid=>nil}, 80 | {:name=>"master.example.com-0", :pid=>nil}] 81 | 82 | Summary of Instances: 83 | 84 | Total Instances: 10 85 | 86 | Summary of Running: 87 | 88 | Total Running Instances: 0 89 | 90 | Summary of Stopped: 91 | 92 | Total Stopped Instances: 10 93 | 94 | Finished processing 1 / 1 hosts in 84.50 ms 95 | 96 | Here they are all created but none of them are running. 97 | 98 | Starting the instances? 99 | ----------------------- 100 | 101 | Starting instances take a while because we sleep a bit between starting each 102 | one... 103 | 104 | $ mco rpc bench start 105 | Discovering hosts using the mc method for 2 second(s) .... 1 106 | 107 | * [ ============================================================> ] 1 / 1 108 | 109 | 110 | master.example.com 111 | Instances: 10 112 | Running: 10 113 | Stopped: 0 114 | 115 | Summary of Instances: 116 | 117 | Total Instances: 10 118 | 119 | Summary of Running: 120 | 121 | Total Running Instances: 10 122 | 123 | Summary of Stopped: 124 | 125 | Total Stopped Instances: 0 126 | 127 | Finished processing 1 / 1 hosts in 9627.61 ms 128 | 129 | If you set them up to run against the same ActiveMQ you should be able to 130 | 'mco ping' them, if on another ActiveMQ you should create a specific client.cfg 131 | file that points to that ActiveMQ at which point you should be able to ping the 132 | new nodes: 133 | 134 | $ mco ping 135 | master.example.com-2 time=333.55 ms 136 | master.example.com-1 time=364.56 ms 137 | master.example.com time=368.11 ms 138 | master.example.com-7 time=369.92 ms 139 | master.example.com-8 time=371.39 ms 140 | master.example.com-6 time=372.80 ms 141 | master.example.com-3 time=378.14 ms 142 | master.example.com-0 time=379.62 ms 143 | master.example.com-9 time=381.37 ms 144 | master.example.com-5 time=382.68 ms 145 | master.example.com-4 time=383.93 ms 146 | 147 | Here we share a single ActiveMQ so both the original and the bench instances show 148 | up 149 | 150 | Stopping the test? 151 | ------------------ 152 | 153 | Simply destroy the bench suite to get back where you started: 154 | 155 | 156 | $ mco rpc bench destroy 157 | Discovering hosts using the mc method for 2 second(s) .... 1 158 | 159 | * [ ============================================================> ] 1 / 1 160 | 161 | 162 | master.example.com 163 | Instances: 0 164 | Running: 0 165 | Stopped: 0 166 | 167 | Summary of Instances: 168 | 169 | Total Instances: 0 170 | 171 | Finished processing 1 / 1 hosts in 75.92 ms 172 | -------------------------------------------------------------------------------- /agent/bench/agent/bench.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "bench", 2 | :description => "Manage multiple mcollectived instances on a single node", 3 | :author => "R.I.Pienaar", 4 | :license => "ASL 2.0", 5 | :version => "0.6", 6 | :url => "http://devco.net/", 7 | :timeout => 30 8 | 9 | action "destroy", :description => "Stop and destroy all members" do 10 | display :always 11 | 12 | output :instance_count, 13 | :description => "Number of managed instances", 14 | :display_as => "Instances" 15 | 16 | output :instances_running, 17 | :description => "Number of instances currently running", 18 | :display_as => "Running" 19 | 20 | output :instances_stopped, 21 | :description => "Number of instances currently stopped", 22 | :display_as => "Stopped" 23 | 24 | summarize do 25 | aggregate sum(:instance_count, :format => "Total Instances: %d") 26 | end 27 | end 28 | 29 | action "create_members", :description => "Create new member servers" do 30 | input :count, 31 | :prompt => "Instance Count", 32 | :description => "Number of instances to create", 33 | :type => :number, 34 | :optional => false 35 | 36 | input :activemq_host, 37 | :prompt => "ActiveMQ host", 38 | :description => "Connect to a specific ActiveMQ host", 39 | :type => :string, 40 | :validation => '^.+$', 41 | :maxlength => 50, 42 | :optional => true 43 | 44 | output :status, 45 | :description => "Command exit code", 46 | :display_as => "Exit Code" 47 | 48 | output :instance_count, 49 | :description => "Number of managed instances", 50 | :display_as => "Instances" 51 | 52 | output :instances_running, 53 | :description => "Number of instances currently running", 54 | :display_as => "Running" 55 | 56 | output :instances_stopped, 57 | :description => "Number of instances currently stopped", 58 | :display_as => "Stopped" 59 | 60 | summarize do 61 | aggregate sum(:instance_count, :format => "Total Instances: %d") 62 | end 63 | end 64 | 65 | action "list", :description => "Names of known member servers" do 66 | display :always 67 | 68 | output :members, 69 | :description => "Known collective members", 70 | :display_as => "Members" 71 | end 72 | 73 | action "status", :description => "Status of all known member servers" do 74 | display :always 75 | 76 | output :members, 77 | :description => "Known collective members", 78 | :display_as => "Members" 79 | 80 | output :instance_count, 81 | :description => "Number of managed instances", 82 | :display_as => "Instances" 83 | 84 | output :instances_running, 85 | :description => "Number of instances currently running", 86 | :display_as => "Running" 87 | 88 | output :instances_stopped, 89 | :description => "Number of instances currently stopped", 90 | :display_as => "Stopped" 91 | 92 | summarize do 93 | aggregate sum(:instance_count, :format => "Total Instances: %d") 94 | aggregate sum(:instances_running, :format => "Total Running Instances: %d") 95 | aggregate sum(:instances_stopped, :format => "Total Stopped Instances: %d") 96 | end 97 | end 98 | 99 | action "start", :description => "Start all known member servers" do 100 | display :always 101 | 102 | output :instance_count, 103 | :description => "Number of managed instances", 104 | :display_as => "Instances" 105 | 106 | output :instances_running, 107 | :description => "Number of instances currently running", 108 | :display_as => "Running" 109 | 110 | output :instances_stopped, 111 | :description => "Number of instances currently stopped", 112 | :display_as => "Stopped" 113 | 114 | summarize do 115 | aggregate sum(:instance_count, :format => "Total Instances: %d") 116 | aggregate sum(:instances_running, :format => "Total Running Instances: %d") 117 | aggregate sum(:instances_stopped, :format => "Total Stopped Instances: %d") 118 | end 119 | end 120 | 121 | action "stop", :description => "Stop all known member servers" do 122 | display :always 123 | 124 | output :instance_count, 125 | :description => "Number of managed instances", 126 | :display_as => "Instances" 127 | 128 | output :instances_running, 129 | :description => "Number of instances currently running", 130 | :display_as => "Running" 131 | 132 | output :instances_stopped, 133 | :description => "Number of instances currently stopped", 134 | :display_as => "Stopped" 135 | 136 | summarize do 137 | aggregate sum(:instance_count, :format => "Total Instances: %d") 138 | aggregate sum(:instances_running, :format => "Total Running Instances: %d") 139 | aggregate sum(:instances_stopped, :format => "Total Stopped Instances: %d") 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /agent/bench/agent/bench.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Bench member, :pid => get_member_pid(member)} 77 | end 78 | 79 | instances_stats 80 | end 81 | 82 | action "start" do 83 | start_all_members 84 | instances_stats 85 | end 86 | 87 | action "stop" do 88 | stop_all_members 89 | instances_stats 90 | end 91 | 92 | def instances_stats 93 | reply[:instance_count] = get_members.size 94 | 95 | reply[:instances_running] = get_members.map do |member| 96 | get_member_pid(member) 97 | end.compact.size 98 | 99 | reply[:instances_stopped] = reply[:instance_count] - reply[:instances_running] 100 | end 101 | 102 | def start_all_members 103 | get_members.each do |member| 104 | start_member(member) 105 | sleep 0.2 106 | end 107 | end 108 | 109 | def stop_all_members 110 | get_members.each do |member| 111 | stop_member(member) 112 | end 113 | end 114 | 115 | def stop_member(identity) 116 | pid = member_running?(identity) 117 | if pid 118 | Log.info("Stopping collective member #{identity} with pid #{pid}") 119 | ::Process.kill(2, Integer(pid)) 120 | FileUtils.rm(get_member_pid(identity)) rescue nil 121 | end 122 | end 123 | 124 | def start_member(identity) 125 | reply.fail! "You need to create a collective first using rake create" if get_members.size == 0 126 | 127 | return true if member_running?(identity) 128 | 129 | memberdir = member_path(identity) 130 | pid = pid_path(identity) 131 | libdir = @config.libdir.join(":") 132 | config = File.join(memberdir, "etc", "server.cfg") 133 | 134 | cmd = "#{@ruby} -I #{libdir} #{@mcollectived} --config #{config} --pidfile #{pid}" 135 | 136 | Log.info("Starting member #{identity} with #{cmd} in #{memberdir}") 137 | 138 | status = run(cmd, :cwd => memberdir) 139 | end 140 | 141 | def member_running?(identity) 142 | pid = get_member_pid(identity) 143 | 144 | return false unless pid 145 | 146 | if File.directory?("/proc") 147 | return Integer(pid) if File.exist?("/proc/#{pid}") 148 | end 149 | 150 | false 151 | end 152 | 153 | def get_member_pid(identity) 154 | pidfile = pid_path(identity) 155 | 156 | return nil unless File.exist?(pidfile) 157 | 158 | Integer(File.read(pidfile)) 159 | end 160 | 161 | def get_members 162 | Dir.entries(@collectivedir).reject{|f| f.start_with?(".")} 163 | rescue 164 | [] 165 | end 166 | 167 | def pid_path(identity) 168 | File.join(@pidsdir, "#{identity}.pid") 169 | end 170 | 171 | def member_path(identity) 172 | File.join(@collectivedir, identity) 173 | end 174 | 175 | def create_member(identity, logfile, brokerhost) 176 | memberdir = member_path(identity) 177 | memberconfig = File.join(memberdir, "etc", "server.cfg") 178 | 179 | FileUtils.mkdir_p(File.join(memberdir, "etc")) 180 | 181 | Log.info("Creating member %s in %s" % [identity, memberdir]) 182 | 183 | render_config(@server_config, memberconfig, identity, logfile, brokerhost) 184 | end 185 | 186 | def render_config(source, dest, identity, log, brokerhost) 187 | source_lines = File.readlines(source) 188 | 189 | File.open(dest, "w") do |f| 190 | source_lines.each do |line| 191 | if line =~ /^identity/ 192 | line = "identity = %s" % identity 193 | elsif line =~ /^logfile/ 194 | line = "logfile = %s" % log 195 | elsif line =~ /plugin.activemq.pool.1.host/ 196 | line = "plugin.activemq.pool.1.host = %s" % brokerhost 197 | end 198 | 199 | f.puts line 200 | end 201 | 202 | f.puts "plugin.bench.activate_agent = false" 203 | end 204 | end 205 | end 206 | end 207 | end 208 | -------------------------------------------------------------------------------- /agent/cap/README.md: -------------------------------------------------------------------------------- 1 | A mco plugin that uses discovery to feed host lists 2 | to capistrano: 3 | 4 | mco cap invoke -W dom0=kvm1 USER=root COMMAND="service mcollective restart" 5 | 6 | Above will discover all nodes matching _dom0=kvm1_ and restart mcollective on them as the root user. 7 | -------------------------------------------------------------------------------- /agent/cap/application/cap.rb: -------------------------------------------------------------------------------- 1 | class MCollective::Application::Cap "collective", 2 | :description => "Manage multiple mcollectived instances on a single node", 3 | :author => "R.I.Pienaar", 4 | :license => "ASL 2.0", 5 | :version => "0.6", 6 | :url => "http://devco.net/", 7 | :timeout => 120 8 | 9 | action "destroy", :description => "Stop and destroy all members" do 10 | display :always 11 | 12 | output :instance_count, 13 | :description => "Number of managed instances", 14 | :display_as => "Instances" 15 | 16 | output :instances_running, 17 | :description => "Number of instances currently running", 18 | :display_as => "Running" 19 | 20 | output :instances_stopped, 21 | :description => "Number of instances currently stopped", 22 | :display_as => "Stopped" 23 | end 24 | 25 | action "clone_source_repo", :description => "Clone a git repo to use as source for the collective" do 26 | input :gitrepo, 27 | :prompt => "Git Repository", 28 | :description => "Any valid git repository path", 29 | :type => :string, 30 | :validation => '^.+$', 31 | :optional => false, 32 | :maxlength => 120 33 | 34 | input :branch, 35 | :prompt => "Branch", 36 | :description => "Branch to check out", 37 | :type => :string, 38 | :validation => '^.+$', 39 | :optional => false, 40 | :maxlength => 50 41 | 42 | output :status, 43 | :description => "Command exit code", 44 | :display_as => "Exit Code" 45 | end 46 | 47 | action "create_members", :description => "Create new member servers" do 48 | input :count, 49 | :prompt => "Instance Count", 50 | :description => "Number of instances to create", 51 | :type => :number, 52 | :optional => false 53 | 54 | input :version, 55 | :prompt => "Version", 56 | :description => "Git tag to check out", 57 | :type => :string, 58 | :validation => '^.+$', 59 | :optional => false, 60 | :maxlength => 50 61 | 62 | input :colllective, 63 | :prompt => "Main Collective", 64 | :description => "The main collective the instances will belong to", 65 | :type => :string, 66 | :validation => '^\w+$', 67 | :optional => false, 68 | :maxlength => 20 69 | 70 | input :subcollective, 71 | :prompt => "Subcollectives", 72 | :description => "Comma seperated list of sub collectives", 73 | :type => :string, 74 | :validation => '^[\w,]+$', 75 | :optional => false, 76 | :maxlength => 100 77 | 78 | input :server, 79 | :prompt => "ActiveMQ Server", 80 | :description => "ActiveMQ broker to connect to", 81 | :type => :string, 82 | :validation => '^[\w\.,]+$', 83 | :optional => false, 84 | :maxlength => 50 85 | 86 | input :port, 87 | :prompt => "ActiveMQ Port", 88 | :description => "ActiveMQ broker port to connect to", 89 | :type => :integer, 90 | :validation => '^\d+$', 91 | :optional => false, 92 | :maxlength => 20 93 | 94 | input :user, 95 | :prompt => "ActiveMQ User", 96 | :description => "User to use when connecting to the broker", 97 | :type => :string, 98 | :validation => '^.+$', 99 | :optional => false, 100 | :maxlength => 20 101 | 102 | input :password, 103 | :prompt => "ActiveMQ Password", 104 | :description => "Password to use when connecting to the broker", 105 | :type => :string, 106 | :validation => '^.+$', 107 | :optional => false, 108 | :maxlength => 20 109 | 110 | output :status, 111 | :description => "Command exit code", 112 | :display_as => "Exit Code" 113 | 114 | output :instance_count, 115 | :description => "Number of managed instances", 116 | :display_as => "Instances" 117 | 118 | output :instances_running, 119 | :description => "Number of instances currently running", 120 | :display_as => "Running" 121 | 122 | output :instances_stopped, 123 | :description => "Number of instances currently stopped", 124 | :display_as => "Stopped" 125 | end 126 | 127 | action "list", :description => "Names of known member servers" do 128 | display :always 129 | 130 | output :members, 131 | :description => "Known collective members", 132 | :display_as => "Members" 133 | end 134 | 135 | action "status", :description => "Status of all known member servers" do 136 | display :always 137 | 138 | output :members, 139 | :description => "Known collective members", 140 | :display_as => "Members" 141 | 142 | output :instance_count, 143 | :description => "Number of managed instances", 144 | :display_as => "Instances" 145 | 146 | output :instances_running, 147 | :description => "Number of instances currently running", 148 | :display_as => "Running" 149 | 150 | output :instances_stopped, 151 | :description => "Number of instances currently stopped", 152 | :display_as => "Stopped" 153 | end 154 | 155 | action "start", :description => "Start all known member servers" do 156 | display :always 157 | 158 | output :instance_count, 159 | :description => "Number of managed instances", 160 | :display_as => "Instances" 161 | 162 | output :instances_running, 163 | :description => "Number of instances currently running", 164 | :display_as => "Running" 165 | 166 | output :instances_stopped, 167 | :description => "Number of instances currently stopped", 168 | :display_as => "Stopped" 169 | end 170 | 171 | action "stop", :description => "Stop all known member servers" do 172 | display :always 173 | 174 | output :instance_count, 175 | :description => "Number of managed instances", 176 | :display_as => "Instances" 177 | 178 | output :instances_running, 179 | :description => "Number of instances currently running", 180 | :display_as => "Running" 181 | 182 | output :instances_stopped, 183 | :description => "Number of instances currently stopped", 184 | :display_as => "Stopped" 185 | end 186 | -------------------------------------------------------------------------------- /agent/collective/agent/collective.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Collective :out, :stderr => :err, :chomp => true, :cwd => @basedir) 62 | 63 | reply.fail! "Failed to run '#{command}'" unless reply[:status] == 0 64 | end 65 | 66 | action "list" do 67 | reply[:members] = get_members 68 | end 69 | 70 | action "status" do 71 | members = get_members 72 | 73 | reply[:instance_count] = members.size 74 | reply[:members] = [] 75 | members.each do |member| 76 | reply[:members] << {:name => member, :pid => get_member_pid(member)} 77 | end 78 | 79 | instances_stats 80 | end 81 | 82 | action "start" do 83 | start_all_members 84 | instances_stats 85 | end 86 | 87 | action "stop" do 88 | stop_all_members 89 | instances_stats 90 | end 91 | 92 | def instances_stats 93 | reply[:instance_count] = get_members.size 94 | 95 | reply[:instances_running] = get_members.map do |member| 96 | get_member_pid(member) 97 | end.compact.size 98 | 99 | reply[:instances_stopped] = reply[:instance_count] - reply[:instances_running] 100 | end 101 | 102 | def start_all_members 103 | get_members.each do |member| 104 | start_member(member) 105 | sleep 0.5 106 | end 107 | end 108 | 109 | def stop_all_members 110 | get_members.each do |member| 111 | stop_member(member) 112 | end 113 | end 114 | 115 | def stop_member(identity) 116 | pid = member_running?(identity) 117 | if pid 118 | Log.info("Stopping collective member #{identity} with pid #{pid}") 119 | ::Process.kill(2, Integer(pid)) 120 | FileUtils.rm(get_member_pid(identity)) rescue nil 121 | end 122 | end 123 | 124 | def start_member(identity) 125 | reply.fail! "You need to create a collective first using rake create" if get_members.size == 0 126 | 127 | return true if member_running?(identity) 128 | 129 | memberdir = member_path(identity) 130 | pid = pid_path(identity) 131 | libdir = File.join(memberdir, "lib") 132 | config = File.join(memberdir, "etc", "server.cfg") 133 | 134 | cmd = "ruby -I #{libdir} bin/mcollectived --config #{config} --pidfile #{pid}" 135 | Log.info("Starting member #{identity} with #{cmd} in #{memberdir}") 136 | 137 | status = run("ruby -I #{libdir} bin/mcollectived --config #{config} --pidfile #{pid}", :cwd => memberdir) 138 | end 139 | 140 | def member_running?(identity) 141 | pid = get_member_pid(identity) 142 | 143 | return false unless pid 144 | 145 | if File.directory?("/proc") 146 | return Integer(pid) if File.exist?("/proc/#{pid}") 147 | end 148 | 149 | false 150 | end 151 | 152 | def get_member_pid(identity) 153 | pidfile = pid_path(identity) 154 | 155 | return nil unless File.exist?(pidfile) 156 | 157 | Integer(File.read(pidfile)) 158 | end 159 | 160 | def get_members 161 | Dir.entries(@collectivedir).reject{|f| f.start_with?(".")} 162 | rescue 163 | [] 164 | end 165 | 166 | def pid_path(identity) 167 | File.join(@pidsdir, "#{identity}.pid") 168 | end 169 | 170 | def member_path(identity) 171 | File.join(@collectivedir, identity) 172 | end 173 | 174 | def create_member(identity, version, collective, subcollectives, stompserver, stompport, stompuser, stomppass) 175 | raise "Cannot find server.cfg template in #{@templatedir}" unless File.exist?(File.join(@templatedir, "server.cfg.erb")) 176 | 177 | memberdir = member_path(identity) 178 | membertemplate = File.join(@templatedir, "server.cfg.erb") 179 | memberconfig = File.join(memberdir, "etc", "server.cfg") 180 | 181 | Log.info("Creating member %s in %s using templates in %s" % [identity, memberdir, @templatedir]) 182 | 183 | out = ""; err = "" 184 | cmd = "git clone -q file:///%s %s" % [@clonedir, memberdir] 185 | status = run(cmd, :stdout => out, :stderr => err, :chomp => true) 186 | raise("Cloning member #{identity} failed while running #{cmd}: #{err}") unless status == 0 187 | 188 | out = ""; err = "" 189 | cmd = "git checkout -q %s" % version 190 | status = run(cmd, :stdout => out, :stderr => err, :chomp => true, :cwd => memberdir) 191 | raise("Cloning member #{identity} failed while running #{cmd}: #{err}") unless status == 0 192 | 193 | render_template(membertemplate, memberconfig, binding) 194 | FileUtils.cp(get_random_template_file("classes"), File.join(memberdir, "etc", "classes.txt")) 195 | FileUtils.cp(get_random_template_file("facts"), File.join(memberdir, "etc", "facts.yaml")) 196 | FileUtils.cp_r(File.join(@pluginsdir, "."), File.join(memberdir, "plugins", "mcollective")) 197 | end 198 | 199 | def get_random_template_file(type) 200 | files = Dir.entries(File.join(@templatedir, type)).reject{|f| f.start_with?(".")} 201 | 202 | File.join(@templatedir, type, files[rand(files.size)]) 203 | end 204 | 205 | def render_template(template, output, scope) 206 | tmpl = File.read(template) 207 | erb = ERB.new(tmpl, 0, "<>") 208 | File.open(output, "w") do |f| 209 | f.puts erb.result(scope) 210 | end 211 | end 212 | end 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/plugins/agents/stomputil.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "STOMP Connector Utility Agent", 2 | :description => "Various helpers and useful actions for the STOMP connector", 3 | :author => "R.I.Pienaar ", 4 | :license => "Apache v 2.0", 5 | :version => "1.1", 6 | :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", 7 | :timeout => 20 8 | 9 | action "peer_info", :description => "Get STOMP Connection Peer" do 10 | display :always 11 | 12 | output :protocol, 13 | :description => "IP Protocol in use", 14 | :display_as => "Protocol" 15 | 16 | output :destport, 17 | :description => "Destination Port", 18 | :display_as => "Port" 19 | 20 | output :desthost, 21 | :description => "Destination Host", 22 | :display_as => "Host" 23 | 24 | output :destaddr, 25 | :description => "Destination Address", 26 | :display_as => "Address" 27 | end 28 | 29 | action "reconnect", :description => "Re-creates the connection to the STOMP network" do 30 | display :always 31 | 32 | output :restarted, 33 | :description => "Did the restart complete succesfully?", 34 | :display_as => "Restarted" 35 | end 36 | 37 | action "subscription_info", :description => "Get a list of all subscriptions" do 38 | display :always 39 | 40 | output :subscriptions, 41 | :description => "Hash of subscription related information", 42 | :display_as => "Subscriptions" 43 | end 44 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/plugins/agents/stomputil.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Agent 3 | class Stomputil "STOMP Connector Utility Agent", 5 | :description => "Various helpers and useful actions for the STOMP connector", 6 | :author => "R.I.Pienaar ", 7 | :license => "Apache v 2.0", 8 | :version => "1.1", 9 | :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", 10 | :timeout => 20 11 | 12 | # Get the Stomp connection peer information 13 | action "peer_info" do 14 | peer = PluginManager["connector_plugin"].connection.socket.peeraddr 15 | 16 | reply[:protocol] = peer[0] 17 | reply[:destport] = peer[1] 18 | reply[:desthost] = peer[2] 19 | reply[:destaddr] = peer[3] 20 | end 21 | 22 | action "reconnect" do 23 | PluginManager["connector_plugin"].disconnect 24 | 25 | sleep 0.5 26 | 27 | PluginManager["connector_plugin"].connect 28 | 29 | ::Process.kill("USR1", $$) 30 | 31 | logger.info("Reconnected to middleware and reloaded all agents") 32 | 33 | reply[:restarted] = 1 34 | end 35 | 36 | action "subscription_info" do 37 | reply[:subscriptions] = PluginManager["connector_plugin"].connection.instance_variable_get("@subscriptions") 38 | end 39 | 40 | private 41 | def get_pid(process) 42 | pid = `pidof #{process}`.chomp.grep(/\d+/) 43 | 44 | pid.first 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/plugins/security/none.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Security 3 | # A YAML based security plugin that doesnt actually secure anything 4 | # it just does the needed serialization. 5 | # 6 | # You should *NOT* use this for every day running, this exist mostly 7 | # to make development and testing a bit less painful 8 | class None < Base 9 | require 'etc' 10 | require 'yaml' 11 | 12 | # Decodes a message by unserializing all the bits etc, it also validates 13 | # it as valid using the psk etc 14 | def decodemsg(msg) 15 | YAML.load(msg.payload) 16 | end 17 | 18 | # Encodes a reply 19 | def encodereply(sender, msg, requestid, requestcallerid=nil) 20 | YAML.dump(create_reply(requestid, sender, msg)) 21 | end 22 | 23 | # Encodes a request msg 24 | def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) 25 | request = create_request(requestid, filter, msg, @initiated_by, target_agent, target_collective, ttl) 26 | 27 | YAML.dump(request) 28 | end 29 | 30 | # Checks the md5 hash in the request body against our psk, the request sent for validation 31 | # should not have been deserialized already 32 | def validrequest?(req) 33 | @stats.validated 34 | return true 35 | end 36 | 37 | def callerid 38 | "user=#{Etc.getlogin}" 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/classes/classes-1.txt: -------------------------------------------------------------------------------- 1 | common 2 | apache 3 | apache::service 4 | mysql::server 5 | mysql 6 | roles::dev_server 7 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/classes/classes-2.txt: -------------------------------------------------------------------------------- 1 | common 2 | apache 3 | apache::service 4 | roles::web_server 5 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/classes/classes-3.txt: -------------------------------------------------------------------------------- 1 | common 2 | mysql::server 3 | mysql 4 | roles::db_server 5 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/facts/facts-1.yaml: -------------------------------------------------------------------------------- 1 | cluster: a 2 | customer: foo 3 | environment: production 4 | operatingsystem: CentOS 5 | architecture: i386 6 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/facts/facts-2.yaml: -------------------------------------------------------------------------------- 1 | cluster: b 2 | customer: foo 3 | environment: development 4 | operatingsystem: CentOS 5 | architecture: x86_64 6 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/facts/facts-3.yaml: -------------------------------------------------------------------------------- 1 | cluster: c 2 | customer: bar 3 | environment: staging 4 | operatingsystem: CentOS 5 | architecture: i386 6 | -------------------------------------------------------------------------------- /agent/collective/collective_plugins/templates/server.cfg.erb: -------------------------------------------------------------------------------- 1 | topicprefix = /topic/ 2 | collectives = <%= [collective, subcollectives].flatten.join(',') %> 3 | main_collective = <%= collective %> 4 | libdir = <%= memberdir %>/plugins 5 | logfile = <%= File.join(@logsdir, "#{identity}.log") %> 6 | loglevel = debug 7 | daemonize = 1 8 | identity = <%= identity %> 9 | 10 | # Plugins 11 | securityprovider = none 12 | 13 | direct_addressing = yes 14 | direct_addressing_threshold = 5 15 | 16 | connector = activemq 17 | plugin.activemq.pool.size = <%= stompserver.size %> 18 | 19 | <% stompserver.each_with_index do |server, idx| %> 20 | plugin.activemq.pool.<%= idx + 1 %>.host = <%= server %> 21 | plugin.activemq.pool.<%= idx + 1 %>.port = <%= stompport %> 22 | plugin.activemq.pool.<%= idx + 1 %>.user = <%= stompuser %> 23 | plugin.activemq.pool.<%= idx + 1 %>.password = <%= stomppass %> 24 | <% end %> 25 | 26 | factsource = yaml 27 | plugin.yaml = <%= memberdir %>/etc/facts.yaml 28 | 29 | classesfile = <%= memberdir %>/etc/classes.txt 30 | -------------------------------------------------------------------------------- /agent/eximng/README.md: -------------------------------------------------------------------------------- 1 | What? 2 | ===== 3 | 4 | A rework of my now ancient Exim agent for MCollective. The old agent predated 5 | SimpleRPC so it was a protocol on it's own, this new agent is fully SimpleRPC 6 | based. 7 | 8 | This version will handle bigger spools better - it will still be slow to fetch an 9 | entire spool with 10s of thousands of mails on it but you now have the ability to 10 | dig into the spool using the usual _exigrep_ features. The filters are done server 11 | side so should be much more efficient than before. 12 | 13 | A new Dialog interface was written that provides a lot of the same functionality 14 | minux some of the finer grained matching options. The Dialog interface is particularly 15 | good for commands like retry, rm, giveup etc where you need to operate on a message id 16 | as it will give you a convenient chooser to pick messages to operate on. It requires 17 | the _rdialog_ gem to be installed 18 | 19 | I am toying with a web interface but it's still a work in progress. Some of this code 20 | is not particularly DRY I will refactor once I have the worklow/approach of the web 21 | app down 100% and will then look at common points 22 | 23 | Usage? 24 | ====== 25 | 26 | The included application plugin does most of what is needed in general use: 27 | 28 | % mco exim --help 29 | 30 | MCollective Exim Manager 31 | 32 | Usage: mco exim [mailq|size|summary|exiwhat|rmbounces|rmfrozen|runq] 33 | Usage: mco exim runq 34 | Usage: mco exim [retry|markdelivered|freeze|thaw|giveup|rm] 35 | Usage: mco exim [addrecipient|markdelivered] 36 | Usage: mco exim setsender 37 | Usage: mco exim [mailq|size] 38 | Usage: mco exim test
39 | Usage: mco exim exigrep pattern 40 | 41 | --match-sender, --limit-sender SENDER Match sender pattern 42 | --match-recipient, --limit-recipient RECIPIENT Match recipient pattern 43 | --match-younger, --limit-younger Match younger than seconds 44 | --match-older, --limit-older SECONDS Match older than seconds 45 | --match-frozen, --limit-frozen Match only frozen messages 46 | --match-active, --limit-active Match only active messages 47 | 48 | Retrieve the mail queue 49 | ----------------------- 50 | 51 | The mail queue can be retrieved in whole or in part by using filters, the display will 52 | match what the built in mailq application would show so you can pipe the network wide 53 | mail queue into whatever existing scripts you have. 54 | 55 | $ mco exim mailq 56 | 57 | $ mco exim mailq --limit-frozen 58 | 59 | $ mco exim mailq --limit-sender foo.com 60 | 61 | TODO: delivery status of individual recipients on a message with many recipients is not 62 | handled, we simply don't return delivered addresses. 63 | 64 | Mail queue sizes 65 | ---------------- 66 | 67 | You can get a quick summary of mail queue sizes for your entire network: 68 | 69 | $ mco exim size 70 | 71 | mx1.your.net: total mail: 12 matched mail: 12 frozen mail: 0 72 | mx2.your.net: total mail: 21 matched mail: 21 frozen mail: 16 73 | mx3.your.net: total mail: 25 matched mail: 25 frozen mail: 3 74 | mx4.your.net: total mail: 31 matched mail: 31 frozen mail: 18 75 | -- -- -- 76 | 89 89 37 77 | 78 | As with the mail queue command you can use the standard matchers to limit: 79 | 80 | % mco exim size --limit-recipient foo.com 81 | 82 | mx1.your.net: total mail: 12 matched mail: 1 frozen mail: 0 83 | mx2.your.net: total mail: 21 matched mail: 14 frozen mail: 14 84 | mx3.your.net: total mail: 25 matched mail: 0 frozen mail: 0 85 | mx4.your.net: total mail: 31 matched mail: 15 frozen mail: 15 86 | -- -- -- 87 | 89 30 29 88 | 89 | Here the total mail still represent all mail on the server but the matched and 90 | frozen is limited to mail destined for foo.com 91 | 92 | The mail queue summary is your standard _exiqsumm_ output but for the entire network, 93 | as it's already a simple summary no matching is allowed: 94 | 95 | % mco exim summary 96 | Count Volume Oldest Newest Domain 97 | ----- ------ ------ ------ ------ 98 | 99 | 3 34KB 22h 18h 229-94.webeventstadium.com 100 | 1 1843 17h 17h alsg-italia.org 101 | 1 2048 14h 14h auditcomconsulting.cz 102 | . 103 | . 104 | . 105 | --------------------------------------------------------------- 106 | 121 2419KB 9d 43m TOTAL 107 | 108 | Managing the mail queue and its contents 109 | ---------------------------------------- 110 | 111 | You can remove, edit, freeze and thaw messages on the queue. 112 | 113 | Most of these actions are limited to single messages or the entire queue 114 | we hope to add matchers to retry, freeze, thaw and rm so that these actions 115 | can be taken on all messages matching recipient, sender etc. 116 | 117 | Common uses: 118 | 119 | Do a forced retry on a single message: 120 | 121 | % mco exim retry 1R6TJa-0000fA-5Z 122 | 123 | mx1.your.net: Message 1R6TJa-0000fA-5Z has been retried 124 | mx2.your.net: No message matching 1R6TJa-0000fA-5Z 125 | 126 | Mark a message as delivered, the exim queue daemon will remove it later: 127 | 128 | % mco exim markdelivered 1R6TJa-0000fA-5Z 129 | 130 | Mark a single recipient on a message as delivered: 131 | 132 | % mco exim markdelivered 1R6TJa-0000fA-5Z foo@example.com 133 | 134 | Freeze and thaw a message: 135 | 136 | % mco exim freeze 1R6TJa-0000fA-5Z 137 | % mco exim thaw 1R6TJa-0000fA-5Z 138 | 139 | Stop trying to deliver a message and create a non delivery report: 140 | 141 | % mco exim giveup 1R6TJa-0000fA-5Z 142 | 143 | Removes a message without creating a non delivery report: 144 | 145 | % mco exim rm 1R6TJa-0000fA-5Z 146 | 147 | Add a recipient to a message: 148 | 149 | % mco exim addrecipient 1R6TJa-0000fA-5Z foo@example.com 150 | 151 | mx1.your.net: foo@example.com has been added to message 1R6TJa-0000fA-5Z 152 | mx2.your.net: No message matching 1R6TJa-0000fA-5Z 153 | 154 | Edit the sender of a message: 155 | 156 | % mco exim setsender 1R6TJa-0000fA-5Z foo@example.com 157 | 158 | Remove all frozen mail from the queue: 159 | 160 | % mco exim rmfrozen 161 | 162 | mx1.your.net: 5 messages deleted 163 | mx2.your.net: 8 messages deleted 164 | mx3.your.net: 3 messages deleted 165 | 166 | Remove all mail with postmaster as sender: 167 | 168 | % mco exim rmbounces 169 | 170 | mx1.your.net: 5 messages deleted 171 | mx2.your.net: 8 messages deleted 172 | mx3.your.net: 3 messages deleted 173 | 174 | Do a normal background queue run: 175 | 176 | % mco exim runq 177 | 178 | Do a queue run for messages matching example.com: 179 | 180 | % mco exim runq example.com 181 | 182 | mx1.your.net: Delivery for pattern example.com has been scheduled 183 | mx2.your.net: Delivery for pattern example.com has been scheduled 184 | mx3.your.net: Delivery for pattern example.com has been scheduled 185 | 186 | Server Activity 187 | --------------- 188 | 189 | You can figure out what all your servers are doing using exiwhat, this agent will run 190 | exiwhat and so let you get a simple network wide view: 191 | 192 | % mco exim exiwhat 193 | 194 | mx1.your.net 195 | 1865 daemon: -q1h, listening for SMTP on port 25 (IPv6 and IPv4) 196 | 12285 handling incoming connection from (94.102.224.6) [94.102.224.6] 197 | 12290 handling incoming connection from 93-86-76-68.dynamic.isp.telekom.rs (discus) [93.86.76.68] 198 | 199 | mx2.your.net 200 | 2688 daemon: -q1h, listening for SMTP on port 25 (IPv6 and IPv4) 201 | 15593 handling incoming connection from (outgoing-26.annoyoushypobenthos.info) [209.222.114.228] 202 | 15655 handling incoming connection from (mqja.com) [88.250.93.236] 203 | 204 | You can also do a network wide _exigrep_ to find all log lines relating some activity, you should 205 | use this with caution as on busy servers the files can be huge and it can put considerable load 206 | on your servers, just like when you use exigrep on the CLI. 207 | 208 | % mco exim exigrep foo.org.uk 209 | 210 | mx1.your.net 211 | Matches: +++ 1R7DXr-0006vV-Pa has not completed +++ 212 | 2011-09-23 22:44:31 1R7DXr-0006vV-Pa <= <> R=1R5kqf-0006ro-TT U=exim P=local S=6323 T="Mail delivery failed: returning message to sender" 213 | 2011-09-24 07:41:17 1R7DXr-0006vV-Pa == foo.org.uk.606774.david@host117.enginereliable.com R=dnslookup T=remote_smtp defer (-18): Remote host host117.enginereliable.com [205.204.86.119] closed connection in response to end of data 214 | 2011-09-24 11:32:28 1R7DXr-0006vV-Pa == foo.org.uk.606774.david@host117.enginereliable.com R=dnslookup T=remote_smtp defer (-53): retry time not reached for any host 215 | 216 | 217 | You can quickly verify how your servers will route mail to a specific address: 218 | 219 | % mco exim test foo@gmail.com 220 | 221 | mx1.your.net 222 | Route: foo@gmail.com 223 | router = dnslookup, transport = remote_smtp 224 | host gmail-smtp-in.l.google.com [74.125.91.27] MX=5 225 | host alt1.gmail-smtp-in.l.google.com [209.85.143.27] MX=10 226 | host alt2.gmail-smtp-in.l.google.com [209.85.227.27] MX=20 227 | host alt3.gmail-smtp-in.l.google.com [74.125.79.26] MX=30 228 | host alt4.gmail-smtp-in.l.google.com [74.125.39.26] MX=40 229 | 230 | -------------------------------------------------------------------------------- /agent/eximng/application/exim.rb: -------------------------------------------------------------------------------- 1 | class MCollective::Application::Exim" 5 | usage "mco exim [retry|markdelivered|freeze|thaw|giveup|rm] " 6 | usage "mco exim [addrecipient|markdelivered] " 7 | usage "mco exim setsender " 8 | usage "mco exim [mailq|size]" 9 | usage "mco exim test
" 10 | usage "mco exim exigrep pattern" 11 | 12 | VALID_COMMANDS = ["mailq", "size", "summary", "exiwhat", "rmbounces", "rmfrozen", "runq", "addrecipient", "markdelivered", "setsender", "retry", "freeze", "thaw", "giveup", "rm", "delivermatching", "exigrep", "test"] 13 | MSGID_REQ_COMMANDS = ["setsender", "retry", "markdelivered", "freeze", "thaw", "giveup", "rm"] 14 | RECIP_OPT_COMMANDS = ["markdelivered"] 15 | RECIP_REQ_COMMANDS = ["addrecipient"] 16 | 17 | option :limit_sender, 18 | :description => "Match sender pattern", 19 | :arguments => ["--match-sender SENDER", "--limit-sender"], 20 | :required => false 21 | 22 | option :limit_recipient, 23 | :description => "Match recipient pattern", 24 | :arguments => ["--match-recipient RECIPIENT", "--limit-recipient"], 25 | :required => false 26 | 27 | option :limit_younger_than, 28 | :description => "Match younger than seconds", 29 | :arguments => ["--match-younger SECONDS", "--limit-younger"], 30 | :required => false 31 | 32 | option :limit_older_than, 33 | :description => "Match older than seconds", 34 | :arguments => ["--match-older SECONDS", "--limit-older"], 35 | :required => false 36 | 37 | option :limit_frozen_only, 38 | :description => "Match only frozen messages", 39 | :arguments => ["--match-frozen", "--limit-frozen"], 40 | :type => :bool, 41 | :required => false 42 | 43 | option :limit_unfrozen_only, 44 | :description => "Match only active messages", 45 | :arguments => ["--match-active", "--limit-active"], 46 | :type => :bool, 47 | :required => false 48 | 49 | def post_option_parser(configuration) 50 | configuration[:command] = ARGV.shift if ARGV.size > 0 51 | 52 | if MSGID_REQ_COMMANDS.include?(configuration[:command]) 53 | if ARGV.size > 0 54 | configuration[:message_id] = ARGV[0] 55 | else 56 | raise "#{configuration[:command]} requires a message id" 57 | end 58 | end 59 | 60 | if RECIP_REQ_COMMANDS.include?(configuration[:command]) 61 | if ARGV.size == 2 62 | configuration[:message_id] = ARGV[0] 63 | configuration[:recipient] = ARGV[1] 64 | else 65 | raise "#{configuration[:command]} requires a message id and recipient" 66 | end 67 | end 68 | 69 | if RECIP_OPT_COMMANDS.include?(configuration[:command]) 70 | if ARGV.size == 2 71 | configuration[:recipient] = ARGV[1] 72 | end 73 | end 74 | end 75 | 76 | def validate_configuration(configuration) 77 | raise "Please specify a command, see --help for details" unless configuration[:command] 78 | 79 | raise "Unknown command #{configuration[:command]}, see --help for full help" unless VALID_COMMANDS.include?(configuration[:command]) 80 | 81 | if configuration.include?(:message_id) 82 | raise "Invalid message id format for id #{configuration[:message_id]}" unless configuration[:message_id] =~ /^\w+-\w+-\w+$/ 83 | end 84 | end 85 | 86 | def exigrep_command(util) 87 | if ARGV.empty? 88 | raise("The exigrep command requires a pattern") 89 | else 90 | configuration[:pattern] = ARGV.first 91 | end 92 | 93 | puts util.exigrep(configuration) 94 | end 95 | 96 | def test_command(util) 97 | if ARGV.empty? 98 | raise("The test command requires an address") 99 | else 100 | configuration[:address] = ARGV.first 101 | end 102 | 103 | puts util.test(configuration) 104 | end 105 | 106 | def runq_command(util) 107 | unless ARGV.empty? 108 | configuration[:pattern] = ARGV.first 109 | end 110 | 111 | puts util.runq(configuration) 112 | end 113 | 114 | def setsender_command(util) 115 | if ARGV.size == 2 116 | configuration[:sender] = ARGV.first 117 | else 118 | raise "Please supply a sender" 119 | end 120 | 121 | puts util.setsender(configuration) 122 | end 123 | 124 | def main 125 | MCollective::Util.loadclass("MCollective::Util::EximNG") 126 | 127 | mc = rpcclient("eximng", :options => options) 128 | util = MCollective::Util::EximNG.new(mc) 129 | 130 | cmd = "#{configuration[:command]}_command" 131 | 132 | # if there are local foo_command methods, use that to 133 | # render foo, else use M::U::EximNG#foo else fail 134 | if respond_to?(cmd) 135 | send(cmd, util) 136 | elsif util.respond_to?(configuration[:command]) 137 | puts util.send(configuration[:command], configuration) 138 | else 139 | raise "Support for #{configuration[:command]} has not yet been implimented" 140 | end 141 | 142 | puts 143 | 144 | mc.disconnect 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /agent/eximng/data/domain_mailq_data.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "domain_mailq_data", 2 | :description => "Checks the mailq for mail to a certain domain", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "0.1", 6 | :url => "http:/devco.net/", 7 | :timeout => 1 8 | 9 | dataquery :description => "Counts the mail destined for a certain domain" do 10 | input :query, 11 | :prompt => "Domain", 12 | :description => "Domain to check the mail queue for", 13 | :type => :string, 14 | :validation => :shellsafe, 15 | :optional => false, 16 | :maxlength => 50 17 | 18 | output :size, 19 | :description => "Amount of mail matching the domain in the spool", 20 | :display_as => "Size", 21 | :default => 0 22 | end 23 | -------------------------------------------------------------------------------- /agent/eximng/data/domain_mailq_data.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Data 3 | class Domain_mailq_data out) 17 | 18 | shell.runcommand 19 | 20 | raise("Command #{command} failed with status #{status} and error: #{err}") unless shell.status.exitstatus == 0 21 | 22 | return out.chomp 23 | end 24 | 25 | def parse_mailq_output(output) 26 | messages = [] 27 | msg = nil 28 | 29 | output << "\n" 30 | 31 | output.each do |line| 32 | line.chomp! 33 | 34 | if line =~ /^\s*(.+?)\s+(.+?)\s+(.+-.+-.+) (<.*>)/ 35 | msg = {} 36 | msg[:recipients] = Array.new 37 | msg[:frozen] = false 38 | 39 | msg[:age] = $1 40 | msg[:size] = $2 41 | msg[:msgid] = $3 42 | msg[:sender] = $4 43 | 44 | msg[:frozen] = true if line =~ /frozen/ 45 | elsif line =~ /\s+(\S+?)@(.+)/ and msg 46 | msg[:recipients] << "#{$1}@#{$2}" 47 | elsif line =~ /^$/ && msg 48 | messages << msg 49 | msg = nil 50 | end 51 | end 52 | 53 | messages 54 | end 55 | end 56 | end 57 | end 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /agent/eximng/sbin/eximctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'mcollective' 4 | require 'rdialog' 5 | require 'tempfile' 6 | 7 | include MCollective::RPC 8 | 9 | class CancelPressed < RuntimeError; end 10 | class InvalidAddress < RuntimeError; end 11 | 12 | class EximUI 13 | def initialize(exim) 14 | @exim = exim 15 | @dialog = RDialog.new 16 | @dialog.backtitle = "Exim Collective" 17 | @dialog.itemhelp = true 18 | 19 | @tools = Array.new 20 | @tools << ["1", "Show mail queue", "mailq"] 21 | @tools << ["2", "Show mail queue summary", "summary"] 22 | @tools << ["3", "Show current exim activity", "exiwhat"] 23 | @tools << ["4", "Test an address", "test"] 24 | @tools << ["5", "Retry mail delivery", "retry"] 25 | @tools << ["6", "Add a recipient", "addrecipient"] 26 | @tools << ["7", "Edit the sender of a message", "setsender"] 27 | @tools << ["8", "Mark all recipients as delivered", "markalldelivered"] 28 | @tools << ["9", "Mark a recipient as delivered", "markrecipdelivered"] 29 | @tools << ["0", "Freeze message", "freeze"] 30 | @tools << ["a", "UnFreeze message", "thaw"] 31 | @tools << ["b", "Give up on a message (with bounce message)", "giveup"] 32 | @tools << ["c", "Removes a message", "rm"] 33 | @tools << ["d", "Remove all postmaster originated message", "rmbounces"] 34 | @tools << ["e", "Remove all frozen message", "rmfrozen"] 35 | @tools << ["f", "Do a normal queue run", "runq"] 36 | @tools << ["g", "Deliver all messages matching a string", "delivermatching"] 37 | @tools << ["h", "Re-discover agents", "discover"] 38 | @tools << ["x", "Exit", "quit"] 39 | end 40 | 41 | def quit_command(title) 42 | exit 43 | end 44 | 45 | # Does a discovery 46 | def discover_command(title) 47 | infobox("Doing discovery", title) 48 | @exim.mc.reset 49 | found = @exim.mc.discover 50 | textbox("Found exim agents on the following hosts:\n\n" + found.join("\n"), "Discovered Hosts") 51 | end 52 | 53 | def test_command(title) 54 | address = ask("Email address to test:", title) 55 | textbox(@exim.test(:address => address), title) 56 | end 57 | 58 | def retry_command(title) 59 | act_on_msgid(title) do |msgid| 60 | textbox(@exim.retry(:message_id => msgid), title) 61 | end 62 | end 63 | 64 | def addrecipient_command(title) 65 | act_on_msgid(title) do |msgid| 66 | recipient = ask("Recipient:", title) 67 | textbox(@exim.addrecipient(:message_id => msgid, :recipient => recipient), title) 68 | end 69 | end 70 | 71 | def setsender_command(title) 72 | act_on_msgid(title) do |msgid| 73 | sender = ask("Sender:", title) 74 | textbox(@exim.setsender(:message_id => msgid, :sender => sender), title) 75 | end 76 | end 77 | 78 | def markalldelivered_command(title) 79 | act_on_msgid(title) do |msgid| 80 | textbox(@exim.markdelivered(:message_id => msgid), title) 81 | end 82 | end 83 | 84 | def freeze_command(title) 85 | act_on_msgid(title) do |msgid| 86 | textbox(@exim.freeze(:message_id => msgid), title) 87 | end 88 | end 89 | 90 | def thaw_command(title) 91 | act_on_msgid(title) do |msgid| 92 | textbox(@exim.thaw(:message_id => msgid), title) 93 | end 94 | end 95 | 96 | def markrecipdelivered_command(title) 97 | act_on_msgid(title) do |msgid| 98 | recipient = ask("Recipient:", title) 99 | textbox(@exim.markdelivered(:message_id => msgid, :recipient => recipient), title) 100 | end 101 | end 102 | 103 | def giveup_command(title) 104 | act_on_msgid(title) do |msgid| 105 | textbox(@exim.giveup(:message_id => msgid), title) 106 | end 107 | end 108 | 109 | def rm_command(title) 110 | act_on_msgid(title) do |msgid| 111 | textbox(@exim.rm(:message_id => msgid), title) 112 | end 113 | end 114 | 115 | def delivermatching_command(title) 116 | pattern = ask("Pattern:", title) 117 | textbox(@exim.runq(:pattern => pattern), title) 118 | end 119 | 120 | def run 121 | discover_command("Discovering agents") 122 | 123 | while (result = menu(@tools)) do 124 | begin 125 | tool = gettool(result, @tools) 126 | 127 | cmd = "#{tool[1]}_command" 128 | 129 | if respond_to?(cmd) 130 | send(cmd, tool[0]) 131 | elsif @exim.respond_to?(tool[1]) 132 | textbox(@exim.send(tool[1], {}), tool[0]) 133 | else 134 | textbox("Support for #{tool[1]} has not yet been implimented", "Error") 135 | end 136 | rescue InvalidAddress => e 137 | textbox("Invalid email address entered", "Error") 138 | rescue CancelPressed => e 139 | # go back to the menu if cancel is pressed 140 | rescue SystemExit 141 | exit 142 | rescue Exception => e 143 | textbox("Unhandled exception: \n #{e.class}: #{e}\n" + e.backtrace.join("\n "), "Error") 144 | end 145 | end 146 | rescue CancelPressed 147 | end 148 | 149 | def act_on_msgid(title, &blk) 150 | if choice = choosemsg(title) 151 | yield(choice) 152 | else 153 | @dialog.msgbox("The mail queue is empty, nothing to operate on") 154 | end 155 | end 156 | 157 | # Choose a message from a list of the current mailq, returns the msgid 158 | def choosemsg(title) 159 | choices = [] 160 | 161 | @exim.mc.mailq do |r, s| 162 | s[:data][:mailq].each do |message| 163 | recipients = message[:recipients].join(' ')[0,30] 164 | frozen = "*** frozen *** " if message[:frozen] 165 | choices << [message[:msgid], "From: #{message[:sender]} To: #{recipients} #{frozen}"] 166 | end 167 | end 168 | 169 | msgid = choose(choices, title) 170 | end 171 | 172 | # wrappers to save typing 173 | def choose(items, title) 174 | @dialog.title = "\"#{title}\"" 175 | res = @dialog.menu(title, items) 176 | 177 | raise CancelPressed unless res 178 | 179 | res 180 | end 181 | 182 | # Show the string in a dialog box with a specified title 183 | def textbox(msg, title) 184 | Tempfile.open("exim") do |tmp| 185 | tmp.write("\n" + msg.to_s) 186 | tmp.close 187 | 188 | title.sub!(/"/, "\\\"") 189 | 190 | @dialog.title = "\"#{title}\"" 191 | @dialog.textbox(tmp.path) 192 | end 193 | end 194 | 195 | # Displays some text while you do something in the background 196 | def infobox(msg, title, height = 5, width = 40) 197 | @dialog.title = "\"#{title}\"" 198 | @dialog.infobox("\n" + msg, height, width) 199 | end 200 | 201 | # Presents the list of tools in a menu 202 | def menu(tools) 203 | items = Array.new 204 | 205 | tools.each do |t| 206 | items << [ t[0], t[1] ] 207 | end 208 | 209 | selected = choose(items, "Choose an operation") 210 | end 211 | 212 | # Ask the user for something, return the entered text 213 | def ask(what, title) 214 | @dialog.title = "\"#{title}\"" 215 | 216 | res = @dialog.inputbox(what) 217 | 218 | raise CancelPressed.new unless res 219 | 220 | res 221 | end 222 | 223 | def gettool(choice, tools) 224 | tools.each do |t| 225 | return [t[1], t[2]] if t[0] == choice 226 | end 227 | 228 | return nil 229 | end 230 | end 231 | 232 | mc = rpcclient("eximng") 233 | mc.progress = false 234 | 235 | MCollective::Util.loadclass("MCollective::Util::EximNG") 236 | exim = MCollective::Util::EximNG.new(mc) 237 | eximui = EximUI.new(exim) 238 | 239 | eximui.run 240 | -------------------------------------------------------------------------------- /agent/eximng/validator/exim_msgid_validator.ddl: -------------------------------------------------------------------------------- 1 | metadata :name => "Exim Message ID", 2 | :description => "Validates that a string is a Exim Message ID", 3 | :author => "R.I.Pienaar ", 4 | :license => "ASL 2.0", 5 | :version => "1.0", 6 | :url => "http://devco.net/", 7 | :timeout => 1 8 | -------------------------------------------------------------------------------- /agent/eximng/validator/exim_msgid_validator.rb: -------------------------------------------------------------------------------- 1 | module MCollective 2 | module Validator 3 | class Exim_msgidValidator 4 | def self.validate(msgid) 5 | Validator.typecheck(msgid, :string) 6 | 7 | raise "Not a valid Exim Message ID" unless msgid.match(/(?:[+-]\d{4} )?(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})/) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /agent/eximng/web/.gitignore: -------------------------------------------------------------------------------- 1 | serverlist.yaml 2 | -------------------------------------------------------------------------------- /agent/eximng/web/eximweb.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'rubygems' 4 | require 'sinatra' 5 | require 'mcollective' 6 | require 'lib/exim.rb' 7 | require 'cgi' 8 | 9 | class EximWeb < Sinatra::Base 10 | def initialize 11 | @exim = Exim.new 12 | super 13 | end 14 | 15 | set :static, true 16 | set :public, "public" 17 | 18 | helpers do 19 | include Rack::Utils 20 | alias_method :h, :escape_html 21 | 22 | def sanitize_email(email) 23 | email.gsub!(/^$/, "") 25 | 26 | if email == "" 27 | email = "postmaster" 28 | end 29 | 30 | return email 31 | end 32 | 33 | def display_result(result) 34 | if result.is_a?(String) 35 | result.split("\n").map{|r| h(r)}.join("
") 36 | elsif result.is_a?(Numeric) 37 | result.to_s 38 | else 39 | "
" + h(JSON.pretty_generate(result)) + "
" 40 | end 41 | end 42 | 43 | def label_for_code(code) 44 | case code 45 | when 0 46 | 'ok' 47 | when 1 48 | 'aborted' 49 | when 2 50 | 'unknown action' 51 | when 3 52 | 'missing data' 53 | when 4 54 | 'invalid data' 55 | when 5 56 | 'unknown error' 57 | end 58 | end 59 | end 60 | 61 | get '/mailq' do 62 | @mailq = @exim.mailq 63 | 64 | erb :mailq_view 65 | end 66 | 67 | get '/mailq/thaw/:id' do 68 | @action = "thaw" 69 | @ddl = @exim.ddl 70 | @results = [] 71 | 72 | unless params[:id] =~ /^\w+-\w+-\w+$/ 73 | @error = "#{params[:id]} is not a valid message id" 74 | else 75 | @results = @exim.thaw(params[:id]) 76 | end 77 | 78 | erb :generic_result_view 79 | end 80 | 81 | get '/mailq/freeze/:id' do 82 | @action = "freeze" 83 | @ddl = @exim.ddl 84 | @results = [] 85 | 86 | unless params[:id] =~ /^\w+-\w+-\w+$/ 87 | @error = "#{params[:id]} is not a valid message id" 88 | else 89 | @results = @exim.freeze(params[:id]) 90 | end 91 | 92 | erb :generic_result_view 93 | end 94 | 95 | get '/mailq/run' do 96 | @action = "runq" 97 | @ddl = @exim.ddl 98 | @results = @exim.runq 99 | 100 | erb :generic_result_view 101 | end 102 | 103 | get '/mailq/delete/frozen' do 104 | @action = "rmfrozen" 105 | @ddl = @exim.ddl 106 | @results = @exim.rmfrozen 107 | 108 | erb :generic_result_view 109 | end 110 | 111 | get '/mailq/delete/bounces' do 112 | @action = "rmbounces" 113 | @ddl = @exim.ddl 114 | @results = @exim.rmbounces 115 | 116 | erb :generic_result_view 117 | end 118 | 119 | get '/mailq/delete/:id' do 120 | @action = "rm" 121 | @ddl = @exim.ddl 122 | @results = [] 123 | 124 | unless params[:id] =~ /^\w+-\w+-\w+$/ 125 | @error = "#{params[:id]} is not a valid message id" 126 | else 127 | @results = @exim.rm(params[:id]) 128 | end 129 | 130 | erb :generic_result_view 131 | end 132 | 133 | get '/mailq/retrymsg/:id' do 134 | @action = "retrymsg" 135 | @ddl = @exim.ddl 136 | @results = [] 137 | 138 | unless params[:id] =~ /^\w+-\w+-\w+$/ 139 | @error = "#{params[:id]} is not a valid message id" 140 | else 141 | @results = @exim.retrymsg(params[:id]) 142 | end 143 | 144 | erb :generic_result_view 145 | end 146 | 147 | get '/exiwhat' do 148 | @action = "exiwhat" 149 | @ddl = @exim.ddl 150 | @results = @exim.exiwhat 151 | erb :generic_result_view 152 | end 153 | 154 | get '/size' do 155 | @action = "size" 156 | @ddl = @exim.ddl 157 | @results = @exim.size 158 | erb :generic_result_view 159 | end 160 | end 161 | 162 | EximWeb.run! 163 | -------------------------------------------------------------------------------- /agent/eximng/web/lib/exim.rb: -------------------------------------------------------------------------------- 1 | class Exim 2 | include MCollective::RPC 3 | 4 | def initialize 5 | @exim = rpcclient("eximng") 6 | @exim.instance_eval { undef :freeze } 7 | @exim.progress = false 8 | 9 | if File.exist?("serverlist.yaml") 10 | @exim.discover :hosts => YAML.parse_file("serverlist.yaml") 11 | else 12 | raise "A yaml file with server names is needed in serverlist.yaml" 13 | end 14 | end 15 | 16 | def mailq 17 | mailq = [] 18 | 19 | @exim.mailq do |r, s| 20 | begin 21 | mailq.concat s[:data][:mailq] 22 | rescue 23 | end 24 | end 25 | 26 | return mailq 27 | end 28 | 29 | def size 30 | @exim.size 31 | end 32 | 33 | def rm(msgid) 34 | @exim.rm(:msgid => msgid) 35 | end 36 | 37 | def thaw(msgid) 38 | @exim.thaw(:msgid => msgid) 39 | end 40 | 41 | def freeze(msgid) 42 | @exim.freeze(:msgid => msgid) 43 | end 44 | 45 | def retrymsg(msgid) 46 | @exim.retrymsg(:msgid => msgid) 47 | end 48 | 49 | def rmfrozen 50 | @exim.rmfrozen 51 | end 52 | 53 | def rmbounces 54 | @exim.rmbounces 55 | end 56 | 57 | def exiwhat 58 | @exim.exiwhat 59 | end 60 | 61 | def runq(pattern=nil) 62 | if pattern 63 | @exim.runq 64 | else 65 | @exim.runq(:pattern => pattern) 66 | end 67 | end 68 | 69 | def ddl 70 | @exim.ddl 71 | end 72 | 73 | 74 | def client 75 | @exim 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-alerts.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alerts.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2011 Twitter, Inc. 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 | 21 | !function( $ ){ 22 | 23 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) 24 | * ======================================================= */ 25 | 26 | var transitionEnd 27 | 28 | $(document).ready(function () { 29 | 30 | $.support.transition = (function () { 31 | var thisBody = document.body || document.documentElement 32 | , thisStyle = thisBody.style 33 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined 34 | return support 35 | })() 36 | 37 | // set CSS transition event type 38 | if ( $.support.transition ) { 39 | transitionEnd = "TransitionEnd" 40 | if ( $.browser.webkit ) { 41 | transitionEnd = "webkitTransitionEnd" 42 | } else if ( $.browser.mozilla ) { 43 | transitionEnd = "transitionend" 44 | } else if ( $.browser.opera ) { 45 | transitionEnd = "oTransitionEnd" 46 | } 47 | } 48 | 49 | }) 50 | 51 | /* ALERT CLASS DEFINITION 52 | * ====================== */ 53 | 54 | var Alert = function ( content, selector ) { 55 | this.$element = $(content) 56 | .delegate(selector || '.close', 'click', this.close) 57 | } 58 | 59 | Alert.prototype = { 60 | 61 | close: function (e) { 62 | var $element = $(this).parent('.alert-message') 63 | 64 | e && e.preventDefault() 65 | $element.removeClass('in') 66 | 67 | function removeElement () { 68 | $element.remove() 69 | } 70 | 71 | $.support.transition && $element.hasClass('fade') ? 72 | $element.bind(transitionEnd, removeElement) : 73 | removeElement() 74 | } 75 | 76 | } 77 | 78 | 79 | /* ALERT PLUGIN DEFINITION 80 | * ======================= */ 81 | 82 | $.fn.alert = function ( options ) { 83 | 84 | if ( options === true ) { 85 | return this.data('alert') 86 | } 87 | 88 | return this.each(function () { 89 | var $this = $(this) 90 | 91 | if ( typeof options == 'string' ) { 92 | return $this.data('alert')[options]() 93 | } 94 | 95 | $(this).data('alert', new Alert( this )) 96 | 97 | }) 98 | } 99 | 100 | $(document).ready(function () { 101 | new Alert($('body'), '.alert-message[data-alert] .close') 102 | }) 103 | 104 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdown 4 | * ============================================================ 5 | * Copyright 2011 Twitter, Inc. 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 | 21 | !function( $ ){ 22 | 23 | var d = 'a.menu, .dropdown-toggle' 24 | 25 | function clearMenus() { 26 | $(d).parent('li').removeClass('open') 27 | } 28 | 29 | $(function () { 30 | $('html').bind("click", clearMenus) 31 | $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' ) 32 | }) 33 | 34 | /* DROPDOWN PLUGIN DEFINITION 35 | * ========================== */ 36 | 37 | $.fn.dropdown = function ( selector ) { 38 | return this.each(function () { 39 | $(this).delegate(selector || d, 'click', function (e) { 40 | var li = $(this).parent('li') 41 | , isActive = li.hasClass('open') 42 | 43 | clearMenus() 44 | !isActive && li.toggleClass('open') 45 | return false 46 | }) 47 | }) 48 | } 49 | 50 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /agent/eximng/web/public/js/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#modal 4 | * ========================================================= 5 | * Copyright 2011 Twitter, Inc. 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 | 21 | !function( $ ){ 22 | 23 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) 24 | * ======================================================= */ 25 | 26 | var transitionEnd 27 | 28 | $(document).ready(function () { 29 | 30 | $.support.transition = (function () { 31 | var thisBody = document.body || document.documentElement 32 | , thisStyle = thisBody.style 33 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined 34 | return support 35 | })() 36 | 37 | // set CSS transition event type 38 | if ( $.support.transition ) { 39 | transitionEnd = "TransitionEnd" 40 | if ( $.browser.webkit ) { 41 | transitionEnd = "webkitTransitionEnd" 42 | } else if ( $.browser.mozilla ) { 43 | transitionEnd = "transitionend" 44 | } else if ( $.browser.opera ) { 45 | transitionEnd = "oTransitionEnd" 46 | } 47 | } 48 | 49 | }) 50 | 51 | 52 | /* MODAL PUBLIC CLASS DEFINITION 53 | * ============================= */ 54 | 55 | var Modal = function ( content, options ) { 56 | this.settings = $.extend({}, $.fn.modal.defaults) 57 | this.$element = $(content) 58 | .delegate('.close', 'click.modal', $.proxy(this.hide, this)) 59 | 60 | if ( options ) { 61 | $.extend( this.settings, options ) 62 | 63 | if ( options.show ) { 64 | this.show() 65 | } 66 | } 67 | 68 | return this 69 | } 70 | 71 | Modal.prototype = { 72 | 73 | toggle: function () { 74 | return this[!this.isShown ? 'show' : 'hide']() 75 | } 76 | 77 | , show: function () { 78 | var that = this 79 | this.isShown = true 80 | this.$element.trigger('show') 81 | 82 | escape.call(this) 83 | backdrop.call(this, function () { 84 | that.$element 85 | .appendTo(document.body) 86 | .show() 87 | 88 | if ($.support.transition && that.$element.hasClass('fade')) { 89 | that.$element[0].offsetWidth // force reflow 90 | } 91 | 92 | that.$element 93 | .addClass('in') 94 | .trigger('shown') 95 | }) 96 | 97 | return this 98 | } 99 | 100 | , hide: function (e) { 101 | e && e.preventDefault() 102 | 103 | var that = this 104 | this.isShown = false 105 | 106 | escape.call(this) 107 | 108 | this.$element 109 | .trigger('hide') 110 | .removeClass('in') 111 | 112 | function removeElement () { 113 | that.$element 114 | .hide() 115 | .trigger('hidden') 116 | 117 | backdrop.call(that) 118 | } 119 | 120 | $.support.transition && this.$element.hasClass('fade') ? 121 | this.$element.one(transitionEnd, removeElement) : 122 | removeElement() 123 | 124 | return this 125 | } 126 | 127 | } 128 | 129 | 130 | /* MODAL PRIVATE METHODS 131 | * ===================== */ 132 | 133 | function backdrop ( callback ) { 134 | var that = this 135 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 136 | if ( this.isShown && this.settings.backdrop ) { 137 | var doAnimate = $.support.transition && animate 138 | 139 | this.$backdrop = $('