promotion 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|