promotion 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -2
- data/bin/mkdeploy +10 -0
- data/lib/promotion/application.rb +1 -2
- data/lib/promotion/config.rb +7 -4
- data/lib/promotion/generator.rb +33 -170
- data/promotion.xsd +18 -0
- metadata +5 -5
data/CHANGELOG
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
|
2
|
+
== Version 1.3.0
|
3
|
+
- Added generation of newsyslog.conf
|
4
|
+
- Added some more examples into the shell generated by mkdeploy
|
5
|
+
- eliminated ERB, replacing it with plain text generation
|
6
|
+
|
1
7
|
== Version 1.2.1
|
2
8
|
- Added Clear attribute to Folder.
|
3
|
-
- Fixed crontab generation. crontabs are generated completely - prior
|
4
|
-
contents is not preserved (due to the nature of crontabs)
|
9
|
+
- Fixed crontab generation. crontabs are generated completely - prior
|
10
|
+
contents is not preserved (due to the nature of crontabs)
|
5
11
|
|
6
12
|
== Version 1.2.0
|
7
13
|
- Make Promotion a better gem citizen by making the installation of executables
|
data/bin/mkdeploy
CHANGED
@@ -29,6 +29,7 @@ template =<<-EOT
|
|
29
29
|
<Files Owner="root" Group="wheel" Mode="0644" Source="conf">
|
30
30
|
<File>/etc/my.cnf</File>
|
31
31
|
<File>/etc/myapp.conf</File>
|
32
|
+
<Link Owner="root" Group="bin" Mode="0640" Target="/home/myapp/bak">/var/myappbak</Link>
|
32
33
|
</Files>
|
33
34
|
<Files Owner="root" Group="wheel" Mode="0750" Source="sbin">
|
34
35
|
<File>/usr/local/sbin/up!</File>
|
@@ -36,7 +37,14 @@ template =<<-EOT
|
|
36
37
|
<File>/usr/local/sbin/push!</File>
|
37
38
|
</Files>
|
38
39
|
<Allfiles Group="bin" Mode="0644" Owner="root" Source="conf">/var/axonsec/conf</Allfiles>
|
40
|
+
<Sudoers>
|
41
|
+
<UserPrivilege Password="false" Runas="admin" User="richard">/sbin/halt -p</UserPrivilege>
|
42
|
+
</Sudoers>
|
39
43
|
<Daemon Flags="" Priority="10" User="_myapp" />
|
44
|
+
<Environment>
|
45
|
+
<Variable Comment="Set up for Java" Name="JAVA_HOME">/usr/local/lib/jdk-1.7.0</Variable>
|
46
|
+
<Alias Name="ll">ls -la </Alias>
|
47
|
+
</Environment>
|
40
48
|
<Crontab>
|
41
49
|
<Schedule User="root" Hour="2" Minute="7"
|
42
50
|
Comment="Backup the entire database at 2:07am each morning">
|
@@ -44,6 +52,8 @@ template =<<-EOT
|
|
44
52
|
</Schedule>
|
45
53
|
</Crontab>
|
46
54
|
<Database>/usr/local/bin/mysql</Database>
|
55
|
+
<Newsyslog Owner="_myapp" Group="_myapp" Mode="0640" Count="10" When="D02"
|
56
|
+
Zip="false" Binary="true">/var/myapp/logs/myapp.log</Newsyslog>
|
47
57
|
</Specification>
|
48
58
|
EOT
|
49
59
|
|
@@ -27,9 +27,8 @@ class Application
|
|
27
27
|
# Promotes an application into production.
|
28
28
|
def promote()
|
29
29
|
enforcer = Promotion::Enforcer.new(@appname)
|
30
|
-
generator = Promotion::Generator.new()
|
31
30
|
enforcer.start()
|
32
|
-
|
31
|
+
Generator::start()
|
33
32
|
$log.info("Application #{@appname} successfully promoted.")
|
34
33
|
end
|
35
34
|
|
data/lib/promotion/config.rb
CHANGED
@@ -22,6 +22,8 @@ module Files
|
|
22
22
|
Sudoers = "/etc/sudoers"
|
23
23
|
# system path to rc.conf.local
|
24
24
|
Rc_conf = "/etc/rc.conf.local"
|
25
|
+
# system path to newsyslog.conf
|
26
|
+
Newsyslog = "/etc/newsyslog.conf"
|
25
27
|
# path to useradd executable
|
26
28
|
Useradd = "/usr/sbin/useradd"
|
27
29
|
# path to groupadd executable
|
@@ -36,13 +38,14 @@ end
|
|
36
38
|
|
37
39
|
# A section of the configuration file that contains ERB template filenames
|
38
40
|
module Templates
|
39
|
-
# filename for
|
41
|
+
# filename for template for /etc/profile
|
40
42
|
Profile = "profile.erb"
|
41
|
-
# filename for
|
43
|
+
# filename for template for /etc/rc.conf.local
|
42
44
|
Rc_conf = "rc.conf.local.erb"
|
43
|
-
# filename for
|
45
|
+
# filename for template for /etc/sudoers
|
44
46
|
Sudoers = "sudoers.erb"
|
45
47
|
# filename for Crontab template
|
46
48
|
Crontab = "crontab.erb"
|
49
|
+
# filename for template for /etc/newsyslog.conf
|
50
|
+
Newsyslog = "newsyslog.conf.erb"
|
47
51
|
end
|
48
|
-
|
data/lib/promotion/generator.rb
CHANGED
@@ -1,56 +1,36 @@
|
|
1
|
-
require 'erb'
|
2
1
|
require 'rexml/document'
|
3
|
-
require '
|
2
|
+
require 'promotion/generator/profile'
|
3
|
+
require 'promotion/generator/rcconf'
|
4
|
+
require 'promotion/generator/newsyslog'
|
5
|
+
require 'promotion/generator/sudoers'
|
6
|
+
require 'promotion/generator/crontab'
|
4
7
|
|
5
|
-
module Promotion
|
8
|
+
module Promotion
|
9
|
+
module Generator
|
6
10
|
|
7
|
-
#
|
8
|
-
# account all of the applications that are deployed (ie. found in the staging area).
|
9
|
-
#
|
10
|
-
# Such files as /etc/profile, /etc/sudoers, and crontabs might receive contributions
|
11
|
-
# from a several apps. For example, /etc/profile might need to set an environment variable
|
12
|
-
# like JAVA_HOME and MYAPP_BASE.
|
13
|
-
class Generator
|
11
|
+
Marker = "#---promotion---" # add this marker to a config file to preserve the contents before it
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
# Returns the XML specification for the selected application
|
18
|
-
attr_accessor :specs
|
19
|
-
|
20
|
-
# Creates a new Generator.
|
21
|
-
#
|
22
|
-
# Note that if any generated file contains a line starting with the +marker+
|
23
|
-
# (eg. "#---promotion---"), we preserve the contents up to that point, and replace
|
24
|
-
# the contents from that point onwards.
|
25
|
-
def initialize()
|
26
|
-
@specs = []
|
27
|
-
@marker = "#---promotion---" # add this marker to a config file to preserve the contents before it
|
28
|
-
end
|
29
|
-
|
30
|
-
# Gathers the specs for all applications in the staging area, then generate:
|
31
|
-
# - /etc/profile
|
32
|
-
# - /etc/rc.conf.local
|
33
|
-
# - /etc/sudoers
|
34
|
-
# - /var/cron/tabs/*
|
35
|
-
def start()
|
13
|
+
def self.start()
|
36
14
|
$log.info("\n#{'_'*40}\nGenerating common environment files")
|
37
|
-
gather_specs()
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
15
|
+
specs = self.gather_specs()
|
16
|
+
Profile.generate(specs)
|
17
|
+
Rcconf.generate(specs)
|
18
|
+
Newsyslog.generate(specs)
|
19
|
+
Sudoers.generate(specs)
|
20
|
+
Crontab.generate(specs)
|
21
|
+
end
|
43
22
|
|
44
23
|
# Gathers specs from all deployment descriptors in folders with the staging area
|
45
|
-
def gather_specs()
|
24
|
+
def self.gather_specs()
|
25
|
+
specs = []
|
46
26
|
begin
|
47
27
|
search = Folders::Staging + "/*/" + Files::Spec
|
48
28
|
$log.debug("Searching for application specs with Dir[#{search}]")
|
49
29
|
specfiles = Dir[search]
|
50
30
|
specfiles.each { |filename|
|
51
31
|
specfile = File.new(filename, File::RDONLY)
|
52
|
-
doc = REXML::Document.new(specfile)
|
53
|
-
|
32
|
+
doc = ::REXML::Document.new(specfile)
|
33
|
+
specs << doc.root
|
54
34
|
specfile.close()
|
55
35
|
}
|
56
36
|
return(specs)
|
@@ -60,145 +40,28 @@ class Generator
|
|
60
40
|
end
|
61
41
|
end
|
62
42
|
|
63
|
-
#
|
64
|
-
#
|
65
|
-
|
66
|
-
#
|
67
|
-
# Returns the ERB generator
|
68
|
-
def generator_for(sym)
|
43
|
+
# Write the approved contents to the system file
|
44
|
+
# or a temporary file instead if temp is true.
|
45
|
+
def self.write_file_for(sym, newContents, temp=false)
|
69
46
|
begin
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
template = templateFile.read
|
47
|
+
filename = Files.const_get(sym) + (temp ? ".tmp" : "")
|
48
|
+
f = File.new(filename, File::WRONLY | File::CREAT | File::TRUNC)
|
49
|
+
f.puts(newContents)
|
50
|
+
$log.info("Generated #{sym} written to #{filename}.") unless temp
|
75
51
|
ensure
|
76
|
-
|
52
|
+
f.close unless f.nil? || f.closed?
|
77
53
|
end
|
78
|
-
|
79
|
-
return(generator)
|
54
|
+
return(filename)
|
80
55
|
end
|
81
56
|
|
82
|
-
#
|
83
|
-
|
84
|
-
#
|
85
|
-
# Returns a string of the new content for the file
|
86
|
-
def contents_for(sym, generator, definedBinding)
|
87
|
-
generatedContents = generator.result(definedBinding)
|
57
|
+
# Returns a string of the new content for the file up to the +marker+
|
58
|
+
def self.original_contents_for(sym)
|
88
59
|
begin
|
89
|
-
originalContents = IO.readlines(Files.const_get(sym),"")[0].split(
|
60
|
+
originalContents = IO.readlines(Files.const_get(sym),"")[0].split(Marker)[0]
|
90
61
|
rescue
|
91
62
|
originalContents = ""
|
92
63
|
end
|
93
|
-
|
94
|
-
return(newContents)
|
95
|
-
end
|
96
|
-
|
97
|
-
# Sometimes we need to run a process on the new content in a temporary file
|
98
|
-
# instead of writing the content directly to a new file (eg. visudo)
|
99
|
-
def write_temp_file_for(sym, newContents)
|
100
|
-
begin
|
101
|
-
tempFilename = Files.const_get(sym)+".tmp"
|
102
|
-
tempFile = File.new(tempFilename, File::WRONLY | File::CREAT | File::TRUNC)
|
103
|
-
tempFile.puts(newContents)
|
104
|
-
ensure
|
105
|
-
tempFile.close unless tempFile.nil? || tempFile.closed?
|
106
|
-
end
|
107
|
-
return(tempFilename)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Write the approved contents to the system file
|
111
|
-
def write_file_for(sym, newContents)
|
112
|
-
begin
|
113
|
-
generatedFilename = Files.const_get(sym)
|
114
|
-
generatedFile = File.new(generatedFilename, File::WRONLY | File::CREAT | File::TRUNC)
|
115
|
-
generatedFile.puts(newContents)
|
116
|
-
$log.info("Generated #{sym} written to #{generatedFilename}.")
|
117
|
-
ensure
|
118
|
-
generatedFile.close unless generatedFile.nil? || generatedFile.closed?
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Convenience method for standard configuration files:
|
123
|
-
# - creates a generator using the relevant template
|
124
|
-
# - generate the new contents
|
125
|
-
# - write the contents to the system file
|
126
|
-
# This is useful when the system file can be written directly without an intermediate
|
127
|
-
# checking step (such as visudo).
|
128
|
-
def generate_standard(sym)
|
129
|
-
begin
|
130
|
-
generator = generator_for(sym)
|
131
|
-
newContents = contents_for(sym, generator, binding)
|
132
|
-
write_file_for(sym, newContents)
|
133
|
-
rescue => e
|
134
|
-
$log.error("Error occurred while generating #{sym}\n#{e.message}" + e.backtrace.join("\n"))
|
135
|
-
exit 1
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
# The sudoers file must be verified by the visudo command
|
140
|
-
def generate_sudoers()
|
141
|
-
begin
|
142
|
-
generator = generator_for("Sudoers")
|
143
|
-
newContents = contents_for("Sudoers", generator, binding)
|
144
|
-
tempFilename = write_temp_file_for("Sudoers", newContents)
|
145
|
-
$log.info("Checking temporary sudoers written to #{tempFilename}.")
|
146
|
-
visudoResults = `#{Files::Visudo} -c -f #{tempFilename}`
|
147
|
-
if visudoResults =~ /parsed OK/
|
148
|
-
$log.info("visudo confirms that sudoers syntax is correct.")
|
149
|
-
else
|
150
|
-
$log.error(visudoResults)
|
151
|
-
raise
|
152
|
-
end
|
153
|
-
write_file_for("Sudoers", newContents)
|
154
|
-
FileUtils.rm_f(tempFilename)
|
155
|
-
$log.info("New sudoers written to #{Files::Sudoers}")
|
156
|
-
rescue => e
|
157
|
-
$log.error("Error occurred while generating sudoers\n#{e.message}" + e.backtrace.join("\n"))
|
158
|
-
exit 1
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# The crontab for each user must be deployed using the crontab tool so this
|
163
|
-
# method cannot use the convenience methods because the filenames are user-specific
|
164
|
-
def generate_crontab()
|
165
|
-
begin
|
166
|
-
schedules = {} # keyed on user, value is array of schedule elements
|
167
|
-
@specs.each { |spec|
|
168
|
-
spec.elements.each("/Specification/Crontab/Schedule") { |sched|
|
169
|
-
user = sched.attributes["User"]
|
170
|
-
schedules[user] = [] unless schedules.has_key?(user)
|
171
|
-
schedules[user] << sched
|
172
|
-
}
|
173
|
-
}
|
174
|
-
generator = generator_for("Crontab")
|
175
|
-
schedules.each { |user, crontablist|
|
176
|
-
generatedContents = generator.result(binding)
|
177
|
-
userCrontab = File.expand_path(user, Folders::Crontabs)
|
178
|
-
begin
|
179
|
-
tempFilename = user + ".tmp"
|
180
|
-
tempFile = File.new(tempFilename, File::WRONLY | File::CREAT | File::TRUNC)
|
181
|
-
tempFile.puts(generatedContents)
|
182
|
-
# previous cron jobs are *NOT* preserved - the tab is competely generated!
|
183
|
-
# otherwise we'll double the crontab each time we run.
|
184
|
-
ensure
|
185
|
-
tempFile.close unless tempFile.nil? || tempFile.closed?
|
186
|
-
end
|
187
|
-
$log.info("Checking temporary crontab written to #{tempFilename}.")
|
188
|
-
crontabResults = `#{Files::Crontab} -u #{user} #{tempFilename}`
|
189
|
-
if crontabResults == ""
|
190
|
-
$log.info("crontab confirms that crontab syntax is correct for user #{user}.")
|
191
|
-
else
|
192
|
-
$log.error(crontabResults)
|
193
|
-
raise
|
194
|
-
end
|
195
|
-
FileUtils.rm_f(tempFilename)
|
196
|
-
$log.info("New crontab installed for user #{user}")
|
197
|
-
}
|
198
|
-
rescue => e
|
199
|
-
$log.error("Error occurred while generating crontab\n#{e.message}" + e.backtrace.join("\n"))
|
200
|
-
exit 1
|
201
|
-
end
|
64
|
+
return(originalContents)
|
202
65
|
end
|
203
66
|
|
204
67
|
end
|
data/promotion.xsd
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
<xsd:element ref="Environment" minOccurs="0" maxOccurs="1" />
|
21
21
|
<xsd:element ref="Crontab" minOccurs="0" maxOccurs="1" />
|
22
22
|
<xsd:element ref="Database" minOccurs="0" maxOccurs="1" />
|
23
|
+
<xsd:element ref="Newsyslog" minOccurs="0" maxOccurs="unbounded" />
|
23
24
|
</xsd:sequence>
|
24
25
|
<xsd:attribute name="Name" type="xsd:NMTOKEN" use="required" />
|
25
26
|
<xsd:attribute name="Title" type="xsd:string" use="required" />
|
@@ -255,6 +256,23 @@
|
|
255
256
|
</xsd:complexType>
|
256
257
|
</xsd:element>
|
257
258
|
|
259
|
+
<xsd:element name="Newsyslog">
|
260
|
+
<xsd:complexType>
|
261
|
+
<xsd:simpleContent>
|
262
|
+
<xsd:extension base="xsd:normalizedString">
|
263
|
+
<xsd:attributeGroup ref="FileAttributes" />
|
264
|
+
<xsd:attribute name="Count" type="xsd:integer" use="optional" default="10" />
|
265
|
+
<xsd:attribute name="Size" type="xsd:integer" use="optional" />
|
266
|
+
<xsd:attribute name="When" type="xsd:string" use="optional" default="24" />
|
267
|
+
<!-- treat as $-format if starts with D,W,M; treat as integer if integer
|
268
|
+
else treat as @-format if digits with a 'T' -->
|
269
|
+
<xsd:attribute name="Zip" type="xsd:boolean" use="optional" default="true"/>
|
270
|
+
<xsd:attribute name="Binary" type="xsd:boolean" use="optional" default="false"/>
|
271
|
+
</xsd:extension>
|
272
|
+
</xsd:simpleContent>
|
273
|
+
</xsd:complexType>
|
274
|
+
</xsd:element>
|
275
|
+
|
258
276
|
<xsd:attributeGroup name="FolderAttributes">
|
259
277
|
<xsd:attribute name="Group" type="xsd:NMTOKEN" use="optional" default="bin"/>
|
260
278
|
<xsd:attribute name="Owner" type="xsd:NMTOKEN" use="optional" default="root"/>
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: promotion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 1.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Richard Kernahan
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-09-
|
18
|
+
date: 2012-09-26 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: log4r
|