puppet_agent_mgr 0.0.2

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.
@@ -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
+