puppet_agent_mgr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ require 'puppet_agent_mgr/common'
2
+
3
+ module PuppetAgentMgr
4
+ def self.manager
5
+ require 'puppet'
6
+ require 'json'
7
+
8
+ if Puppet.version =~ /^(\d+)/
9
+ case $1
10
+ when "2"
11
+ require 'puppet_agent_mgr/v2/manager'
12
+ return PuppetAgentMgr::V2::Manager.new
13
+
14
+ when "3"
15
+ require 'puppet_agent_mgr/v3/manager'
16
+ return PuppetAgentMgr::V3::Manager.new
17
+
18
+ else
19
+ raise "Cannot manage Puppet version %s" % $1
20
+ end
21
+ else
22
+ raise "Cannot determine the Puppet major version"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,195 @@
1
+ module PuppetAgentMgr
2
+ module Common
3
+ extend Common
4
+
5
+ # is a catalog being applied rigt now?
6
+ def stopped?
7
+ !applying?
8
+ end
9
+
10
+ # is the daemon running but not applying a catalog
11
+ def idling?
12
+ (daemon_present? && !applying?)
13
+ end
14
+
15
+ # is the agent enabled
16
+ def enabled?
17
+ !disabled?
18
+ end
19
+
20
+ # seconds since the last catalog was applied
21
+ def since_lastrun
22
+ (Time.now - lastrun).to_i
23
+ end
24
+
25
+ # if a resource is being managed, resource in the syntax File[/x] etc
26
+ def managing_resource?(resource)
27
+ managed_resources.include?(resource.downcase)
28
+ end
29
+
30
+ # how many resources are managed
31
+ def managed_resources_count
32
+ managed_resources.size
33
+ end
34
+
35
+ def run_in_foreground(clioptions)
36
+ command =["puppet", "agent", "--test", "--color=false"]
37
+ command.concat(clioptions)
38
+
39
+ %x[#{command.join(' ')}]
40
+ end
41
+
42
+ def run_in_background(clioptions)
43
+ command =["puppet", "agent", "--onetime", "--daemonize", "--color=false"]
44
+ command.concat(clioptions)
45
+
46
+ %x[#{command.join(' ')}]
47
+ end
48
+
49
+ def validate_name(name)
50
+ if name.length == 1
51
+ return false unless name =~ /\A[a-zA-Z]\Z/
52
+ else
53
+ return false unless name =~ /\A[a-zA-Z0-9_]+\Z/
54
+ end
55
+
56
+ true
57
+ end
58
+
59
+ def create_common_puppet_cli(noop, tags, environment, server)
60
+ opts = []
61
+
62
+ (host, port) = server.to_s.split(":")
63
+
64
+ raise("Invalid hostname '%s' specified" % host) if host && !(host =~ /\A(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])\Z/)
65
+ raise("Invalid master port '%s' specified" % port) if port && !(port =~ /\A\d+\Z/)
66
+ raise("Invalid environment '%s' specified" % environment) if environment && !validate_name(environment)
67
+
68
+ unless tags.empty?
69
+ [tags].flatten.each do |tag|
70
+ tag.split("::").each do |part|
71
+ raise("Invalid tag '%s' specified" % tag) unless validate_name(part)
72
+ end
73
+ end
74
+
75
+ opts << "--tags %s" % tags.join(",") if !tags.empty?
76
+ end
77
+
78
+ opts << "--noop" if noop
79
+ opts << "--environment %s" % environment if environment
80
+ opts << "--server %s" % host if host
81
+ opts << "--masterport %s" % port if port
82
+
83
+ opts
84
+ end
85
+
86
+ # do a run based on the following options:
87
+ #
88
+ # :foreground_run - runs in the foreground a --test run
89
+ # :signal_daemon - if the daemon is running, sends it USR1 to wake it up
90
+ # :noop - does a noop run if possible
91
+ # :tags - an array of tags to limit the run to
92
+ # :environment - the environment to run
93
+ # :server - the puppet master to use, can be some.host or some.host:port
94
+ #
95
+ # else a single background run will be attempted but this will fail if a idling
96
+ # daemon is present and :signal_daemon was false
97
+ def runonce!(options={})
98
+ valid_options = [:noop, :signal_daemon, :foreground_run, :tags, :environment, :server]
99
+
100
+ options.keys.each do |opt|
101
+ raise("Unknown option %s specified" % opt) unless valid_options.include?(opt)
102
+ end
103
+
104
+ raise "Puppet is currently applying a catalog, cannot run now" if applying?
105
+ raise "Puppet is disabled, cannot run now" if disabled?
106
+
107
+ noop = options.fetch(:noop, false)
108
+ signal_daemon = options.fetch(:signal_daemon, true)
109
+ foreground_run = options.fetch(:foreground_run, false)
110
+ environment = options[:environment]
111
+ tags = [ options[:tags] ].flatten.compact
112
+ server = options[:server]
113
+
114
+ clioptions = create_common_puppet_cli(noop, tags, environment, server)
115
+
116
+ if idling? && signal_daemon && !clioptions.empty?
117
+ raise "Cannot specify any custom puppet options when the daemon is running"
118
+ end
119
+
120
+ if foreground_run
121
+ run_in_foreground(clioptions)
122
+ elsif idling? && signal_daemon
123
+ signal_running_daemon
124
+ else
125
+ raise "Cannot run in the background if the daemon is present" if daemon_present?
126
+ run_in_background(clioptions)
127
+ end
128
+ end
129
+
130
+ # simple utility to return a hash with lots of useful information about the state of the agent
131
+ def status
132
+ status = {:applying => applying?,
133
+ :enabled => enabled?,
134
+ :daemon_present => daemon_present?,
135
+ :lastrun => lastrun,
136
+ :disable_message => lock_message,
137
+ :since_lastrun => (Time.now.to_i - lastrun)}
138
+
139
+ if !status[:enabled]
140
+ status[:status] = "disabled"
141
+
142
+ elsif status[:applying]
143
+ status[:status] = "applying a catalog"
144
+
145
+ elsif status[:daemon_present] && status[:applying]
146
+ status[:status] = "idling"
147
+
148
+ elsif !status[:applying]
149
+ status[:status] = "stopped"
150
+
151
+ end
152
+
153
+ status[:message] = "Currently %s; last completed run %s ago" % [status[:status], seconds_to_human(status[:since_lastrun])]
154
+
155
+ status
156
+ end
157
+
158
+ def atomic_file(file)
159
+ tempfile = Tempfile.new(File.basename(file), File.dirname(file))
160
+
161
+ yield(tempfile)
162
+
163
+ tempfile.close
164
+ File.rename(tempfile.path, file)
165
+ end
166
+
167
+ def seconds_to_human(seconds)
168
+ days = seconds / 86400
169
+ seconds -= 86400 * days
170
+
171
+ hours = seconds / 3600
172
+ seconds -= 3600 * hours
173
+
174
+ minutes = seconds / 60
175
+ seconds -= 60 * minutes
176
+
177
+ if days > 1
178
+ return "%d days %d hours %d minutes %02d seconds" % [days, hours, minutes, seconds]
179
+
180
+ elsif days == 1
181
+ return "%d day %d hours %d minutes %02d seconds" % [days, hours, minutes, seconds]
182
+
183
+ elsif hours > 0
184
+ return "%d hours %d minutes %02d seconds" % [hours, minutes, seconds]
185
+
186
+ elsif minutes > 0
187
+ return "%d minutes %02d seconds" % [minutes, seconds]
188
+
189
+ else
190
+ return "%02d seconds" % seconds
191
+
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,71 @@
1
+ module PuppetAgentMgr::V2
2
+ class Manager
3
+ include PuppetAgentMgr::Common
4
+
5
+ if Puppet.features.microsoft_windows?
6
+ require 'puppet_agent_mgr/v2/windows'
7
+ include Windows
8
+ else
9
+ require 'puppet_agent_mgr/v2/unix'
10
+ include Unix
11
+ end
12
+
13
+ # enables the puppet agent, it can now start applying catalogs again
14
+ def enable!
15
+ raise "Already enabled" if enabled?
16
+ File.unlink(Puppet[:puppetdlockfile])
17
+ end
18
+
19
+ # disable the puppet agent, on version 2.x the message is ignored
20
+ def disable!(msg=nil)
21
+ raise "Already disabled" unless enabled?
22
+ File.open(Puppet[:puppetdlockfile], "w") { }
23
+
24
+ ""
25
+ end
26
+
27
+ # all the managed resources
28
+ def managed_resources
29
+ # need to add some caching here based on mtime of the resources file
30
+ return [] unless File.exist?(Puppet[:resourcefile])
31
+
32
+ File.readlines(Puppet[:resourcefile]).map do |resource|
33
+ resource.chomp
34
+ end
35
+ end
36
+
37
+ # epoch time when the last catalog was applied
38
+ def lastrun
39
+ summary = load_summary
40
+
41
+ Integer(summary["time"].fetch("last_run", 0))
42
+ end
43
+
44
+ # the current lock message, always "" on 2.0
45
+ def lock_message
46
+ ""
47
+ end
48
+
49
+ # is the agent disabled
50
+ def disabled?
51
+ if File.exist?(Puppet[:puppetdlockfile])
52
+ if File::Stat.new(Puppet[:puppetdlockfile]).zero?
53
+ return true
54
+ end
55
+ end
56
+
57
+ false
58
+ end
59
+
60
+ # loads the summary file and makes sure that some keys are always present
61
+ def load_summary
62
+ summary = {"changes" => {}, "time" => {}, "resources" => {}, "version" => {}, "events" => {}}
63
+
64
+ summary.merge!(YAML.load_file(Puppet[:lastrunfile])) if File.exist?(Puppet[:lastrunfile])
65
+
66
+ summary["resources"] = {"failed"=>0, "changed"=>0, "total"=>0, "restarted"=>0, "out_of_sync"=>0}.merge!(summary["resources"])
67
+
68
+ summary
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,44 @@
1
+ module PuppetAgentMgr::V2
2
+ module Unix
3
+ extend Unix
4
+ # is the agent daemon currently in the unix process list?
5
+ def daemon_present?
6
+ if File.exist?(Puppet[:pidfile])
7
+ return has_process_for_pid?(File.read(Puppet[:pidfile]))
8
+ end
9
+
10
+ return false
11
+ end
12
+
13
+ # is the agent currently applying a catalog
14
+ def applying?
15
+ return false if disabled?
16
+
17
+ if File::Stat.new(Puppet[:puppetdlockfile]).size > 0
18
+ return has_process_for_pid(File.read(Puppet[:puppetdlockfile]))
19
+ end
20
+
21
+ return false
22
+ rescue
23
+ return false
24
+ end
25
+
26
+ def signal_running_daemon
27
+ pid = File.read(Puppet[:pidfile])
28
+
29
+ if has_process_for_pid?(pid)
30
+ begin
31
+ Process.kill("USR1", Integer(pid))
32
+ rescue Exception => e
33
+ raise "Failed to signal the puppet agent at pid %s: %s" % [pid, e.to_s]
34
+ end
35
+ else
36
+ run_in_background
37
+ end
38
+ end
39
+
40
+ def has_process_for_pid?(pid)
41
+ !!Process.kill(0, Integer(pid)) rescue false
42
+ end
43
+ end
44
+ end
@@ -0,0 +1 @@
1
+ raise "Window is not supported yet"
@@ -0,0 +1,75 @@
1
+ module PuppetAgentMgr::V3
2
+ class Manager
3
+ if Puppet.features.microsoft_windows?
4
+ require 'puppet_agent_mgr/v3/windows'
5
+ include Windows
6
+ else
7
+ require 'puppet_agent_mgr/v3/unix'
8
+ include Unix
9
+ end
10
+
11
+ include PuppetAgentMgr::Common
12
+
13
+ # enables the puppet agent, it can now start applying catalogs again
14
+ def enable!
15
+ raise "Already enabled" if enabled?
16
+ File.unlink(Puppet[:agent_disabled_lockfile])
17
+ end
18
+
19
+ # disable the puppet agent, on version 2.x the message is ignored
20
+ def disable!(msg=nil)
21
+ raise "Already disabled" unless enabled?
22
+
23
+ msg = "Disabled using the Ruby API at %s" % Time.now.strftime("%c") unless msg
24
+
25
+ atomic_file(Puppet[:agent_disabled_lockfile]) do |f|
26
+ f.print(JSON.dump(:disabled_message => msg))
27
+ end
28
+
29
+ msg
30
+ end
31
+
32
+ # all the managed resources
33
+ def managed_resources
34
+ # need to add some caching here based on mtime of the resources file
35
+ return [] unless File.exist?(Puppet[:resourcefile])
36
+
37
+ File.readlines(Puppet[:resourcefile]).map do |resource|
38
+ resource.chomp
39
+ end
40
+ end
41
+
42
+ # epoch time when the last catalog was applied
43
+ def lastrun
44
+ summary = load_summary
45
+
46
+ Integer(summary["time"].fetch("last_run", 0))
47
+ end
48
+
49
+ # the current lock message, always "" on 2.0
50
+ def lock_message
51
+ if disabled?
52
+ lock_data = JSON.parse(File.read(Puppet[:agent_disabled_lockfile]))
53
+ return lock_data["disabled_message"]
54
+ else
55
+ return ""
56
+ end
57
+ end
58
+
59
+ # is the agent disabled
60
+ def disabled?
61
+ File.exist?(Puppet[:agent_disabled_lockfile])
62
+ end
63
+
64
+ # loads the summary file and makes sure that some keys are always present
65
+ def load_summary
66
+ summary = {"changes" => {}, "time" => {}, "resources" => {}, "version" => {}, "events" => {}}
67
+
68
+ summary.merge!(YAML.load_file(Puppet[:lastrunfile])) if File.exist?(Puppet[:lastrunfile])
69
+
70
+ summary["resources"] = {"failed"=>0, "changed"=>0, "total"=>0, "restarted"=>0, "out_of_sync"=>0}.merge!(summary["resources"])
71
+
72
+ summary
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,44 @@
1
+ module PuppetAgentMgr::V3
2
+ module Unix
3
+ extend Unix
4
+ # is the agent daemon currently in the unix process list?
5
+ def daemon_present?
6
+ if File.exist?(Puppet[:agentpidfile])
7
+ return has_process_for_pid?(File.read(Puppet[:agentpidfile]))
8
+ end
9
+
10
+ return false
11
+ end
12
+
13
+ # is the agent currently applying a catalog
14
+ def applying?
15
+ return false if disabled?
16
+
17
+ if File::Stat.new(Puppet[:pidfile]).size > 0
18
+ return has_process_for_pid(File.read(Puppet[:pidfile]))
19
+ end
20
+
21
+ return false
22
+ rescue
23
+ return false
24
+ end
25
+
26
+ def signal_running_daemon
27
+ pid = File.read(Puppet[:agentpidfile])
28
+
29
+ if has_process_for_pid?(pid)
30
+ begin
31
+ Process.kill("USR1", Integer(pid))
32
+ rescue Exception => e
33
+ raise "Failed to signal the puppet agent at pid %s: %s" % [pid, e.to_s]
34
+ end
35
+ else
36
+ run_in_background
37
+ end
38
+ end
39
+
40
+ def has_process_for_pid?(pid)
41
+ !!Process.kill(0, Integer(pid)) rescue false
42
+ end
43
+ end
44
+ end
@@ -0,0 +1 @@
1
+ raise "Window is not supported yet"
@@ -0,0 +1,3 @@
1
+ module PuppetAgentMgr
2
+ VERSION = "0.0.2"
3
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppet_agent_mgr
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - R.I.Pienaar
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-09-27 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: A simple library that wraps the logic around the locks, pids, JSON filesu and YAML files that makes up the Puppet Agent status. Suppots Puppet 2.7.x and 3.0.x.
50
+ email: rip@devco.net
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - lib/puppet_agent_mgr/common.rb
59
+ - lib/puppet_agent_mgr/v3/windows.rb
60
+ - lib/puppet_agent_mgr/v3/manager.rb
61
+ - lib/puppet_agent_mgr/v3/unix.rb
62
+ - lib/puppet_agent_mgr/version.rb
63
+ - lib/puppet_agent_mgr/v2/windows.rb
64
+ - lib/puppet_agent_mgr/v2/manager.rb
65
+ - lib/puppet_agent_mgr/v2/unix.rb
66
+ - lib/puppet_agent_mgr.rb
67
+ has_rdoc: true
68
+ homepage: http://devco.net/
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options: []
73
+
74
+ require_paths:
75
+ - lib
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.3.7
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Puppet Agent Manager
102
+ test_files: []
103
+