promotion 1.0.7

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,207 @@
1
+ require 'erb'
2
+ require 'rexml/document'
3
+ require 'fileutils'
4
+
5
+ module Promotion # :nodoc:
6
+
7
+ # Generator generates the system-wide files that can only be assembled by taking into
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
14
+
15
+ # Returns the XML specification for the selected application
16
+ attr_accessor :specs
17
+
18
+ # Creates a new Generator.
19
+ #
20
+ # Note that if any generated file contains a line starting with the +marker+
21
+ # (eg. "#---promotion---"), we preserve the contents up to that point, and replace
22
+ # the contents from that point onwards.
23
+ def initialize()
24
+ @specs = []
25
+ @marker = "#---promotion---" # add this marker to a config file to preserve the contents before it
26
+ end
27
+
28
+ # Gathers the specs for all applications in the staging area, then generate:
29
+ # - /etc/profile
30
+ # - /etc/rc.conf.local
31
+ # - /etc/sudoers
32
+ # - /var/cron/tabs/*
33
+ def start()
34
+ $log.info("\n#{'_'*40}\nGenerating common environment files")
35
+ gather_specs()
36
+ generate_standard("Profile")
37
+ generate_standard("Rc_conf")
38
+ generate_sudoers()
39
+ generate_crontab()
40
+ end
41
+
42
+ # Gathers specs from all deployment descriptors in folders with the staging area
43
+ def gather_specs()
44
+ begin
45
+ search = Folders::Staging + "/*/" + Files::Spec
46
+ $log.debug("Searching for application specs with Dir[#{search}]")
47
+ specfiles = Dir[search]
48
+ specfiles.each { |filename|
49
+ specfile = File.new(filename, File::RDONLY)
50
+ doc = REXML::Document.new(specfile)
51
+ @specs << doc.root
52
+ specfile.close()
53
+ }
54
+ return(specs)
55
+ rescue => e
56
+ $log.error("Error while gathering application specs\n#{e.message}" + e.backtrace.join("\n"))
57
+ exit 1
58
+ end
59
+ end
60
+
61
+ # Convenience method to create an ERB generator based on the template corresponding
62
+ # to a name. For example, if +sym+ is "Profile", we use the filename Templates::Profile
63
+ # as the template.
64
+ #
65
+ # Returns the ERB generator
66
+ def generator_for(sym)
67
+ begin
68
+ templateFilename = File.expand_path(Templates.const_get(sym), Folders::Templates)
69
+ raise "Missing template for #{sym}" unless File.exists?(templateFilename)
70
+ $log.debug("Generating with template #{templateFilename}")
71
+ templateFile = File.new(templateFilename, File::RDONLY)
72
+ template = templateFile.read
73
+ ensure
74
+ templateFile.close() unless templateFile.nil? || templateFile.closed?
75
+ end
76
+ generator = ERB.new(template, nil, '<>')
77
+ return(generator)
78
+ end
79
+
80
+ # Convenience method to generate content for a system file, preserving the content
81
+ # up to the +marker+, and appending the generated content.
82
+ #
83
+ # Returns a string of the new content for the file
84
+ def contents_for(sym, generator, definedBinding)
85
+ generatedContents = generator.result(definedBinding)
86
+ begin
87
+ originalContents = IO.readlines(Files.const_get(sym),"")[0].split(@marker)[0]
88
+ rescue
89
+ originalContents = ""
90
+ end
91
+ newContents = [originalContents, @marker, generatedContents].join("\n")
92
+ return(newContents)
93
+ end
94
+
95
+ # Sometimes we need to run a process on the new content in a temporary file
96
+ # instead of writing the content directly to a new file (eg. visudo)
97
+ def write_temp_file_for(sym, newContents)
98
+ begin
99
+ tempFilename = Files.const_get(sym)+".tmp"
100
+ tempFile = File.new(tempFilename, File::WRONLY | File::CREAT | File::TRUNC)
101
+ tempFile.puts(newContents)
102
+ ensure
103
+ tempFile.close unless tempFile.nil? || tempFile.closed?
104
+ end
105
+ return(tempFilename)
106
+ end
107
+
108
+ # Write the approved contents to the system file
109
+ def write_file_for(sym, newContents)
110
+ begin
111
+ generatedFilename = Files.const_get(sym)
112
+ generatedFile = File.new(generatedFilename, File::WRONLY | File::CREAT | File::TRUNC)
113
+ generatedFile.puts(newContents)
114
+ $log.info("Generated #{sym} written to #{generatedFilename}.")
115
+ ensure
116
+ generatedFile.close unless generatedFile.nil? || generatedFile.closed?
117
+ end
118
+ end
119
+
120
+ # Convenience method for standard configuration files:
121
+ # - creates a generator using the relevant template
122
+ # - generate the new contents
123
+ # - write the contents to the system file
124
+ # This is useful when the system file can be written directly without an intermediate
125
+ # checking step (such as visudo).
126
+ def generate_standard(sym)
127
+ begin
128
+ generator = generator_for(sym)
129
+ newContents = contents_for(sym, generator, binding)
130
+ write_file_for(sym, newContents)
131
+ rescue => e
132
+ $log.error("Error occurred while generating #{sym}\n#{e.message}" + e.backtrace.join("\n"))
133
+ exit 1
134
+ end
135
+ end
136
+
137
+ # The sudoers file must be verified by the visudo command
138
+ def generate_sudoers()
139
+ begin
140
+ generator = generator_for("Sudoers")
141
+ newContents = contents_for("Sudoers", generator, binding)
142
+ tempFilename = write_temp_file_for("Sudoers", newContents)
143
+ $log.info("Checking temporary sudoers written to #{tempFilename}.")
144
+ visudoResults = `#{Files::Visudo} -c -f #{tempFilename}`
145
+ if visudoResults =~ /parsed OK/
146
+ $log.info("visudo confirms that sudoers syntax is correct.")
147
+ else
148
+ $log.error(visudoResults)
149
+ raise
150
+ end
151
+ write_file_for("Sudoers", newContents)
152
+ FileUtils.rm_f(tempFilename)
153
+ $log.info("New sudoers written to #{Files::Sudoers}")
154
+ rescue => e
155
+ $log.error("Error occurred while generating sudoers\n#{e.message}" + e.backtrace.join("\n"))
156
+ exit 1
157
+ end
158
+ end
159
+
160
+ # The crontab for each user must be deployed using the crontab tool so this
161
+ # method cannot use the convenience methods because the filenames are user-specific
162
+ def generate_crontab()
163
+ begin
164
+ schedules = {} # keyed on user, value is array of schedule elements
165
+ @specs.each { |spec|
166
+ spec.elements.each("/Specification/Crontab/Schedule") { |sched|
167
+ user = sched.attributes["User"]
168
+ schedules[user] = [] unless schedules.has_key?(user)
169
+ schedules[user] << sched
170
+ }
171
+ }
172
+ generator = generator_for("Crontab")
173
+ schedules.each { |user, crontablist|
174
+ generatedContents = generator.result(binding)
175
+ userCrontab = File.expand_path(user, Folders::Crontabs)
176
+ begin
177
+ originalContents = IO.readlines(userCrontab,"")[0].split(@marker)[0]
178
+ rescue
179
+ originalContents = ""
180
+ end
181
+ newContents = [originalContents, @marker, generatedContents].join("\n")
182
+ begin
183
+ tempFilename = user + ".tmp"
184
+ tempFile = File.new(tempFilename, File::WRONLY | File::CREAT | File::TRUNC)
185
+ tempFile.puts(newContents)
186
+ ensure
187
+ tempFile.close unless tempFile.nil? || tempFile.closed?
188
+ end
189
+ $log.info("Checking temporary crontab written to #{tempFilename}.")
190
+ crontabResults = `#{Files::Crontab} -u #{user} #{tempFilename}`
191
+ if crontabResults == ""
192
+ $log.info("crontab confirms that crontab syntax is correct for user #{user}.")
193
+ else
194
+ $log.error(crontabResults)
195
+ raise
196
+ end
197
+ FileUtils.rm_f(tempFilename)
198
+ $log.info("New crontab installed for user #{user}")
199
+ }
200
+ rescue => e
201
+ $log.error("Error occurred while generating crontab\n#{e.message}" + e.backtrace.join("\n"))
202
+ exit 1
203
+ end
204
+ end
205
+
206
+ end
207
+ end
data/lib/promotion.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Promotion Module includes:
2
+ # - Promotion::Application
3
+ # - Promotion::Enforcer
4
+ # - Promotion::Generator
5
+ # - Promotion::Evolver
6
+ # and the configuration modules found in promotion/config.rb
7
+ module Promotion
8
+ end
data/promotion.xsd ADDED
@@ -0,0 +1,323 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+
3
+ <xsd:schema targetNamespace="http://finalstep.com.au/promotion/v100"
4
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6
+ xmlns="http://finalstep.com.au/promotion/v100">
7
+
8
+ <xsd:element name="Specification">
9
+ <xsd:complexType>
10
+ <xsd:sequence>
11
+ <xsd:element ref="Description" />
12
+ <xsd:element ref="Groups" minOccurs="0" maxOccurs="1" />
13
+ <xsd:element ref="Users" minOccurs="0" maxOccurs="1" />
14
+ <xsd:element ref="Folders" minOccurs="0" maxOccurs="unbounded" />
15
+ <xsd:element ref="Files" minOccurs="0" maxOccurs="unbounded" />
16
+ <xsd:element ref="Allfiles" minOccurs="0" maxOccurs="unbounded" />
17
+ <xsd:element ref="Zipfile" minOccurs="0" maxOccurs="unbounded" />
18
+ <xsd:element ref="Sudoers" minOccurs="0" maxOccurs="1" />
19
+ <xsd:element ref="Daemon" minOccurs="0" maxOccurs="1" />
20
+ <xsd:element ref="Environment" minOccurs="0" maxOccurs="1" />
21
+ <xsd:element ref="Crontab" minOccurs="0" maxOccurs="1" />
22
+ <xsd:element ref="Database" minOccurs="0" maxOccurs="1" />
23
+ </xsd:sequence>
24
+ <xsd:attribute name="Name" type="xsd:NMTOKEN" use="required" />
25
+ <xsd:attribute name="Title" type="xsd:string" use="required" />
26
+ </xsd:complexType>
27
+ </xsd:element>
28
+
29
+ <xsd:element name="Description">
30
+ <xsd:complexType mixed="true" />
31
+ </xsd:element>
32
+
33
+ <xsd:element name="Groups">
34
+ <xsd:complexType>
35
+ <xsd:sequence>
36
+ <xsd:element ref="Group" minOccurs="1" maxOccurs="unbounded" />
37
+ </xsd:sequence>
38
+ </xsd:complexType>
39
+ </xsd:element>
40
+
41
+ <xsd:element name="Group">
42
+ <xsd:complexType>
43
+ <xsd:attribute name="Name" type="xsd:NMTOKEN" use="required" />
44
+ <xsd:attribute name="Gid" type="xsd:integer" use="required" />
45
+ </xsd:complexType>
46
+ </xsd:element>
47
+
48
+ <xsd:element name="Users">
49
+ <xsd:complexType>
50
+ <xsd:sequence>
51
+ <xsd:element ref="User" minOccurs="1" maxOccurs="unbounded" />
52
+ </xsd:sequence>
53
+ </xsd:complexType>
54
+ </xsd:element>
55
+
56
+ <xsd:element name="User">
57
+ <xsd:complexType>
58
+ <xsd:attribute name="Uid" type="xsd:integer" use="required" />
59
+ <xsd:attribute name="Name" type="xsd:NMTOKEN" use="required" />
60
+ <xsd:attribute name="Gid" type="xsd:integer" use="required" />
61
+ <xsd:attribute name="Class" type="xsd:NMTOKEN" use="required" />
62
+ <xsd:attribute name="Home" type="xsd:string" use="required" />
63
+ <xsd:attribute name="Gecos" type="xsd:string" use="required" />
64
+ <xsd:attribute name="Shell" type="SHELL" use="required" />
65
+ <xsd:attribute name="Groups" type="xsd:string" use="optional" />
66
+ </xsd:complexType>
67
+ </xsd:element>
68
+
69
+ <xsd:element name="Folders">
70
+ <xsd:complexType>
71
+ <xsd:sequence>
72
+ <xsd:element ref="Folder" minOccurs="1" maxOccurs="unbounded" />
73
+ </xsd:sequence>
74
+ <xsd:attributeGroup ref="FolderAttributes" />
75
+ </xsd:complexType>
76
+ </xsd:element>
77
+
78
+ <xsd:element name="Folder">
79
+ <xsd:complexType>
80
+ <xsd:simpleContent>
81
+ <xsd:extension base="xsd:normalizedString">
82
+ <xsd:attributeGroup ref="FolderAttributes" />
83
+ <xsd:attribute name="Clear" type="xsd:boolean" use="optional" />
84
+ </xsd:extension>
85
+ </xsd:simpleContent>
86
+ </xsd:complexType>
87
+ </xsd:element>
88
+
89
+ <xsd:element name="Files">
90
+ <xsd:complexType>
91
+ <xsd:sequence>
92
+ <xsd:element ref="File" minOccurs="1" maxOccurs="unbounded" />
93
+ <xsd:element ref="Link" minOccurs="0" maxOccurs="unbounded" />
94
+ </xsd:sequence>
95
+ <xsd:attributeGroup ref="FileAttributes"/>
96
+ </xsd:complexType>
97
+ </xsd:element>
98
+
99
+ <xsd:element name="Allfiles">
100
+ <xsd:complexType>
101
+ <xsd:simpleContent>
102
+ <xsd:extension base="xsd:normalizedString">
103
+ <xsd:attributeGroup ref="FileAttributes"/>
104
+ </xsd:extension>
105
+ </xsd:simpleContent>
106
+ </xsd:complexType>
107
+ </xsd:element>
108
+
109
+ <xsd:element name="File">
110
+ <xsd:complexType>
111
+ <xsd:simpleContent>
112
+ <xsd:extension base="xsd:normalizedString">
113
+ <xsd:attributeGroup ref="FileAttributes" />
114
+ <xsd:attribute name="Empty" type="xsd:boolean" use="optional" />
115
+ <xsd:attribute name="Overwrite" type="xsd:boolean" default="true" />
116
+ <xsd:attribute name="Backup" type="xsd:boolean" default="false" />
117
+ </xsd:extension>
118
+ </xsd:simpleContent>
119
+ </xsd:complexType>
120
+ </xsd:element>
121
+
122
+ <xsd:element name="Link">
123
+ <xsd:complexType>
124
+ <xsd:simpleContent>
125
+ <xsd:extension base="xsd:normalizedString">
126
+ <xsd:attributeGroup ref="FolderAttributes"/>
127
+ <xsd:attribute name="Target" type="xsd:normalizedString"/>
128
+ </xsd:extension>
129
+ </xsd:simpleContent>
130
+ </xsd:complexType>
131
+ </xsd:element>
132
+
133
+ <xsd:element name="Zipfile">
134
+ <xsd:complexType>
135
+ <xsd:sequence>
136
+ <xsd:element ref="Zip" minOccurs="1" maxOccurs="1"/>
137
+ <xsd:element ref="Source" minOccurs="1" maxOccurs="unbounded" />
138
+ </xsd:sequence>
139
+ </xsd:complexType>
140
+ </xsd:element>
141
+
142
+ <xsd:element name="Zip">
143
+ <xsd:complexType>
144
+ <xsd:simpleContent>
145
+ <xsd:extension base="xsd:normalizedString">
146
+ <xsd:attributeGroup ref="FolderAttributes" />
147
+ </xsd:extension>
148
+ </xsd:simpleContent>
149
+ </xsd:complexType>
150
+ </xsd:element>
151
+
152
+ <xsd:element name="Source">
153
+ <xsd:complexType>
154
+ <xsd:simpleContent>
155
+ <xsd:extension base="xsd:normalizedString" />
156
+ </xsd:simpleContent>
157
+ </xsd:complexType>
158
+ </xsd:element>
159
+
160
+ <xsd:element name="Sudoers">
161
+ <xsd:complexType>
162
+ <xsd:sequence>
163
+ <xsd:element ref="UserPrivilege" minOccurs="1" maxOccurs="unbounded" />
164
+ </xsd:sequence>
165
+ </xsd:complexType>
166
+ </xsd:element>
167
+
168
+ <xsd:element name="UserPrivilege">
169
+ <xsd:complexType>
170
+ <xsd:simpleContent>
171
+ <xsd:extension base="xsd:string">
172
+ <xsd:attribute name="User" type="xsd:NMTOKEN" use="required" />
173
+ <xsd:attribute name="Password" type="xsd:boolean" use="optional" default="false"/>
174
+ <xsd:attribute name="Runas" type="xsd:NMTOKEN" use="optional" />
175
+ </xsd:extension>
176
+ </xsd:simpleContent>
177
+ </xsd:complexType>
178
+ </xsd:element>
179
+
180
+ <xsd:element name="Daemon">
181
+ <xsd:complexType>
182
+ <xsd:attributeGroup ref="ExecutionAttributes"/>
183
+ </xsd:complexType>
184
+ </xsd:element>
185
+
186
+ <xsd:element name="Command">
187
+ <xsd:simpleType>
188
+ <xsd:restriction base="xsd:string" />
189
+ </xsd:simpleType>
190
+ </xsd:element>
191
+
192
+ <xsd:element name="Environment">
193
+ <xsd:complexType>
194
+ <xsd:sequence>
195
+ <xsd:choice>
196
+ <xsd:element ref="Variable" minOccurs="0" maxOccurs="unbounded" />
197
+ <xsd:element ref="Alias" minOccurs="0" maxOccurs="unbounded" />
198
+ </xsd:choice>
199
+ </xsd:sequence>
200
+ </xsd:complexType>
201
+ </xsd:element>
202
+
203
+ <xsd:element name="Variable">
204
+ <xsd:complexType>
205
+ <xsd:simpleContent>
206
+ <xsd:extension base="xsd:string">
207
+ <xsd:attribute name="Name" type="xsd:NMTOKEN" use="required" />
208
+ <xsd:attribute name="Comment" type="xsd:string" use="optional" />
209
+ </xsd:extension>
210
+ </xsd:simpleContent>
211
+ </xsd:complexType>
212
+ </xsd:element>
213
+
214
+ <xsd:element name="Alias">
215
+ <xsd:complexType>
216
+ <xsd:simpleContent>
217
+ <xsd:extension base="xsd:string">
218
+ <xsd:attribute name="Name" type="xsd:NMTOKEN" use="required" />
219
+ <xsd:attribute name="Comment" type="xsd:string" use="optional" />
220
+ </xsd:extension>
221
+ </xsd:simpleContent>
222
+ </xsd:complexType>
223
+ </xsd:element>
224
+
225
+ <xsd:element name="Crontab">
226
+ <xsd:complexType>
227
+ <xsd:sequence>
228
+ <xsd:element ref="Schedule" minOccurs="1" maxOccurs="unbounded" />
229
+ </xsd:sequence>
230
+ </xsd:complexType>
231
+ </xsd:element>
232
+
233
+ <xsd:element name="Schedule">
234
+ <xsd:complexType>
235
+ <xsd:sequence>
236
+ <xsd:element ref="Command" minOccurs="1" maxOccurs="1" />
237
+ </xsd:sequence>
238
+ <xsd:attribute name="User" type="xsd:NMTOKEN" use="required" />
239
+ <xsd:attribute name="Minute" type="MINUTE" use="optional" />
240
+ <xsd:attribute name="Hour" type="HOUR" use="optional" />
241
+ <xsd:attribute name="DayOfMonth" type="DAYOFMONTH" use="optional" />
242
+ <xsd:attribute name="Month" type="MONTH" use="optional" />
243
+ <xsd:attribute name="DayOfWeek" type="DAYOFWEEK" use="optional" />
244
+ <xsd:attribute name="Comment" type="xsd:string" use="optional" />
245
+ </xsd:complexType>
246
+ </xsd:element>
247
+
248
+ <xsd:element name="Database">
249
+ <xsd:complexType>
250
+ <xsd:simpleContent>
251
+ <xsd:extension base="xsd:string">
252
+ <xsd:attribute name="database" type="xsd:string" use="optional"/>
253
+ </xsd:extension>
254
+ </xsd:simpleContent>
255
+ </xsd:complexType>
256
+ </xsd:element>
257
+
258
+ <xsd:attributeGroup name="FolderAttributes">
259
+ <xsd:attribute name="Group" type="xsd:NMTOKEN" use="optional" default="bin"/>
260
+ <xsd:attribute name="Owner" type="xsd:NMTOKEN" use="optional" default="root"/>
261
+ <xsd:attribute name="Mode" type="MODE" use="optional" default="0640"/>
262
+ </xsd:attributeGroup>
263
+
264
+ <xsd:attributeGroup name="FileAttributes">
265
+ <xsd:attribute name="Source" type="xsd:string" use="optional" />
266
+ <xsd:attributeGroup ref="FolderAttributes"/>
267
+ </xsd:attributeGroup>
268
+
269
+ <xsd:attributeGroup name="ExecutionAttributes">
270
+ <xsd:attribute name="Priority" type="xsd:nonNegativeInteger" use="optional" />
271
+ <xsd:attribute name="Flags" type="xsd:string" use="required" />
272
+ <xsd:attribute name="User" type="xsd:string" use="required" />
273
+ </xsd:attributeGroup>
274
+
275
+ <xsd:simpleType name="MODE">
276
+ <xsd:restriction base="xsd:string">
277
+ <xsd:pattern value="[0-7]{4}"/>
278
+ </xsd:restriction>
279
+ </xsd:simpleType>
280
+
281
+ <xsd:simpleType name="SHELL">
282
+ <xsd:restriction base="xsd:string">
283
+ <xsd:enumeration value="/sbin/nologin" />
284
+ <xsd:enumeration value="/bin/ksh" />
285
+ </xsd:restriction>
286
+ </xsd:simpleType>
287
+
288
+ <xsd:simpleType name="MINUTE">
289
+ <xsd:restriction base="xsd:integer">
290
+ <xsd:minInclusive value="0"/>
291
+ <xsd:maxInclusive value="59"/>
292
+ </xsd:restriction>
293
+ </xsd:simpleType>
294
+
295
+ <xsd:simpleType name="HOUR">
296
+ <xsd:restriction base="xsd:integer">
297
+ <xsd:minInclusive value="0"/>
298
+ <xsd:maxInclusive value="23"/>
299
+ </xsd:restriction>
300
+ </xsd:simpleType>
301
+
302
+ <xsd:simpleType name="DAYOFMONTH">
303
+ <xsd:restriction base="xsd:integer">
304
+ <xsd:minInclusive value="1"/>
305
+ <xsd:maxInclusive value="31"/>
306
+ </xsd:restriction>
307
+ </xsd:simpleType>
308
+
309
+ <xsd:simpleType name="MONTH">
310
+ <xsd:restriction base="xsd:integer">
311
+ <xsd:minInclusive value="1"/>
312
+ <xsd:maxInclusive value="12"/>
313
+ </xsd:restriction>
314
+ </xsd:simpleType>
315
+
316
+ <xsd:simpleType name="DAYOFWEEK">
317
+ <xsd:restriction base="xsd:integer">
318
+ <xsd:minInclusive value="0"/>
319
+ <xsd:maxInclusive value="7"/>
320
+ </xsd:restriction>
321
+ </xsd:simpleType>
322
+
323
+ </xsd:schema>
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: promotion
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 7
10
+ version: 1.0.7
11
+ platform: ruby
12
+ authors:
13
+ - Richard Kernahan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-09-19 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: "\t\tThe Promotion tool is designed to make it easy and quick to deploy an application\n\
22
+ \t\tinto production. Originally built for use with OpenBSD, it can be used on an *nix\n\
23
+ \t\tsystem by adjusting a few paths (in <code>config.rb</code>).\n\n\
24
+ \t\tTo deploy or install an application you just need to copy a few files into place, right?\n\
25
+ \t\tWell, the folders need to be there first of course, oh and the permissions need to be set,\n\
26
+ \t\tand I guess we need the right users set up before file ownerships can be set correctly,\n\
27
+ \t\twhich means we need groups before that ... ok, so there is more to it than copying a few files.\n\n\
28
+ \t\tThere are also system-wide settings that may need to be modified to support an application,\n\
29
+ \t\tsuch as environment variables in <code>/etc/profile</code>, <code>/etc/sudoers</code>,\n\
30
+ \t\tstartup scripts in <code>/etc/rc.conf.local</code>, and <code>/var/cron/tabs/*</code> cron jobs.\n\n\
31
+ \t\tPromotion handles all of this based on an XML deployment descriptor for each application,\n\
32
+ \t\tallowing rapid, reliable redeployment with a single line command. It also manages database\n\
33
+ \t\tschema migration.\n"
34
+ email: rec.dev@finalstep.com.au
35
+ executables: []
36
+
37
+ extensions:
38
+ - ext/promotion/extconf.rb
39
+ extra_rdoc_files: []
40
+
41
+ files:
42
+ - bin/promote
43
+ - bin/evolve
44
+ - bin/devolve
45
+ - ext/promotion/extconf.rb
46
+ - lib/promotion.rb
47
+ - lib/promotion/application.rb
48
+ - lib/promotion/config.rb
49
+ - lib/promotion/enforcer.rb
50
+ - lib/promotion/evolver.rb
51
+ - lib/promotion/generator.rb
52
+ - lib/promotion/erb/crontab.erb
53
+ - lib/promotion/erb/profile.erb
54
+ - lib/promotion/erb/rc.conf.local.erb
55
+ - lib/promotion/erb/sudoers.erb
56
+ - .yardopts
57
+ - deploy.xml
58
+ - promotion.xsd
59
+ - CHANGELOG
60
+ - README
61
+ - VERSION
62
+ homepage: http://rubygems.org/gems/promotion
63
+ licenses: []
64
+
65
+ post_install_message: "\n Executables have been installed in /usr/local/sbin:\n * promote\n * evolve\n * devolve\n "
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project: promotion
91
+ rubygems_version: 1.8.24
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Promotion makes it possible to repeatedly deploy an application in a fast and reliable way.
95
+ test_files: []
96
+
97
+ has_rdoc: "true"