promotion 1.4.7 → 2.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 108a5dc05ae4bb258ba20b5b00a77707a4031176
4
+ data.tar.gz: c5a25b54336e9b6f5247cbda54812da242c3f7a9
5
+ SHA512:
6
+ metadata.gz: 9497b89f2b3b4805d7db3f60fa64ee439475ad7e1f95b229876ecc94d3a120b02dda71c1a10fcfadd231b8620e5dc5af9c07634d23ec3b67dfdee68df74e6867
7
+ data.tar.gz: baa82d045a06c70f4118678baab14f6c6170f2209c76b441f6c2ae66dbaf91af90f57aebf578d12de1140b71740c162066a4120a7cc990fa7964d589301032fa
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ == Version 2.0
2
+ - Promotion no longer changes the system-wide files, such as /etc/rc.conf.local. Instead it just recommends what should be changed.
3
+ - Upgraded for Ruby 2.0, since 1.8.7 is no longer supported.
4
+ - Removed the log4r dependency in favour of the standard library Logger.
5
+
1
6
  == Version 1.4.7
2
7
  - Preserve empty lines in config files
3
8
 
data/README CHANGED
@@ -1,6 +1,5 @@
1
1
  = Promotion
2
- Promotion makes it possible to repeatedly deploy an application in a fast and
3
- reliable way.
2
+ Promotion makes it possible to repeatedly deploy an application in a fast and reliable way.
4
3
 
5
4
  == Staging
6
5
  A key concept is the *staging* area. It is a folder (+/var/staging+ by default)
@@ -48,11 +47,10 @@ files that need to be adjusted to enable an application to startup or run proper
48
47
  - /etc/sudoers
49
48
  - /var/cron/tabs/*
50
49
 
51
- Promotion generates these files based on the set of all of the deployment descriptors it finds
52
- in the staging area. It will preserve the original contents of the file, and append generated
53
- contents after the following special demarcation line:
54
- #---promotion---
55
- This respects the need for an administrator to add custom contents (above the line).
50
+ Deployment descriptors can include recommended settings for these sensitive system files, but it
51
+ does not change them. That would be too intrusive. Instead, Promotion reads the files to check if the
52
+ recommended entries are present, and if they are not it displays a message for the admin explaining
53
+ what needs to be done.
56
54
 
57
55
  An important aspect is that a deployment is *idempotent* - you can do it as many
58
56
  times as you like and the result will be the same. This means its safe to make a
@@ -67,7 +65,7 @@ to suit your system's paths.
67
65
 
68
66
  == Installation
69
67
  *WARNING*::
70
- Promotion will makes changes to your operating system, including important system-wide files.
68
+ Promotion will makes changes to your operating system.
71
69
 
72
70
  DO NOT USE ON A PRODUCTION SYSTEM without rigorous testing in a virtual machine first.
73
71
 
@@ -235,9 +233,7 @@ The +User+ is the user running the process, typically an unprivileged user named
235
233
  Leave blank to run the startup script as root (eg. as when dropping privileges).
236
234
 
237
235
  === +Crontab+
238
- Cron jobs are often needed for an application to run smoothly. Instead of creating them by hand,
239
- you can specify each job as part of the deployment descriptor and let promotion set up the crontab
240
- automatically.
236
+ Cron jobs are often needed for an application to run smoothly.
241
237
  <Crontab>
242
238
  <Schedule User="root" Hour="2" Minute="7"
243
239
  Comment="Backup the entire database at 2:07am each morning">
@@ -3,8 +3,8 @@ require 'promotion/config'
3
3
  require 'promotion/enforcer'
4
4
  require 'promotion/generator'
5
5
  require 'promotion/evolver'
6
- require 'log4r'
7
- include Log4r
6
+ require 'logger'
7
+ require 'time'
8
8
 
9
9
  module Promotion # :nodoc:
10
10
 
@@ -18,10 +18,11 @@ class Application
18
18
  # A global +$log+ logger is also created.
19
19
  def initialize(appname)
20
20
  @appname = appname
21
- $log = Logger.new(@appname)
22
- FileOutputter[@appname] = FileOutputter.new(@appname, :filename => Files::Log)
23
- FileOutputter[@appname].formatter = PatternFormatter.new(:pattern => "%d %m")
24
- $log.outputters = FileOutputter[@appname]
21
+ $log = Logger.new(Files::Log, 0) # rotate log file at 1M limit
22
+ $log.formatter = proc { |severity, datetime, progname, message|
23
+ "#{datetime.iso8601()} #{severity} #{message}\n"
24
+ }
25
+ $log.level = Logger::INFO
25
26
  end
26
27
 
27
28
  # Promotes an application into production.
@@ -3,65 +3,39 @@ module Generator
3
3
  module Crontab
4
4
 
5
5
  # The crontab for each user must be deployed using the crontab tool
6
- def self.generate(specs)
7
- sym = "Crontab"
8
- begin
9
- schedules = {} # keyed on user, value is array of schedule elements
10
- specs.each { |spec|
11
- spec.elements.each("/Specification/Crontab/Schedule") { |sched|
12
- user = sched.attributes["User"]
13
- schedules[user] = [] unless schedules.has_key?(user)
14
- schedules[user] << sched
15
- }
16
- }
17
- schedules.each { |user, crontablist|
18
- generatedContents = self.contents(user, crontablist)
19
- userCrontab = File.expand_path(user, Folders::Crontabs)
20
- begin
21
- tempFilename = user + ".tmp"
22
- tempFile = File.new(tempFilename, File::WRONLY | File::CREAT | File::TRUNC)
23
- tempFile.puts(generatedContents)
24
- # previous cron jobs are *NOT* preserved - the tab is competely generated!
25
- # otherwise we accumulate the cron-generated warning messages at the top of the file
26
- ensure
27
- tempFile.close unless tempFile.nil? || tempFile.closed?
28
- end
29
- $log.info("Checking temporary crontab written to #{tempFilename}.")
30
- crontabResults = `#{Files::Crontab} -u #{user} #{tempFilename}`
31
- if $?.exitstatus == 0
32
- $log.info("crontab confirms that crontab syntax is correct for user #{user}.")
33
- else
34
- $log.error(crontabResults)
35
- raise
36
- end
37
- FileUtils.rm_f(tempFilename)
38
- $log.info("New crontab installed for user #{user}")
39
- }
40
- rescue => e
41
- $log.error("Error occurred while generating crontab\n#{e.message}" + e.backtrace.join("\n"))
42
- exit 1
43
- end
44
- end
45
-
46
- # Generates the contents for /var/cron/tabs/{user}, containing scheduled jobs
47
- def self.contents(user, crontablist)
48
- contents = []
49
- contents << "# /var/cron/tabs/#{user} - #{user}'s crontab \n#\n"
50
- contents << "#minute hour mday month wday command\n#\n\n"
51
- crontablist.each { |sched|
52
- if sched.attributes["Comment"]
53
- contents << "#______________________________\n"
54
- contents << "# #{sched.attributes["Comment"]} \n"
6
+ def self.check(specs)
7
+ schedules = {} # keyed on user, value is array of schedule elements
8
+ specs.each { |spec|
9
+ spec.elements.each("/Specification/Crontab/Schedule") { |sched|
10
+ user = sched.attributes["User"]
11
+ schedules[user] = [] unless schedules.has_key?(user)
12
+ schedules[user] << sched
13
+ }
14
+ }
15
+ schedules.each { |user, crontablist|
16
+ userCrontab = File.expand_path(user, "/var/cron/tabs/")
17
+ contents = IO.readlines(userCrontab).collect!{ |s| s.gsub(/\s/,"") }
18
+ proposals = []
19
+ crontablist.each { |sched|
20
+ minute = sched.attributes["Minute"] || "*"
21
+ hour = sched.attributes["Hour"] || "*"
22
+ mday = sched.attributes["DayOfMonth"] || "*"
23
+ month = sched.attributes["Month"] || "*"
24
+ wday = sched.attributes["DayOfWeek"] || "*"
25
+ cmd = (sched.elements["Command"].cdatas[0]).value().strip()
26
+ needed = minute + "\t" + hour + "\t" + mday + "\t" + month + "\t" + wday + "\t" + cmd
27
+ unless contents.include?(needed.gsub(/\s/,""))
28
+ if sched.attributes["Comment"]
29
+ proposals << "# #{sched.attributes["Comment"]}"
30
+ end
31
+ proposals << needed
32
+ end
33
+ }
34
+ if proposals.size > 0
35
+ header = "# min\thour\tmday\tmonth\twday\tcommand\n"
36
+ puts("\nSuggested changes to /var/cron/tabs/#{user}:", header, proposals.join("\n"), "\n")
55
37
  end
56
- minute = sched.attributes["Minute"] || "*"
57
- hour = sched.attributes["Hour"] || "*"
58
- mday = sched.attributes["DayOfMonth"] || "*"
59
- month = sched.attributes["Month"] || "*"
60
- wday = sched.attributes["DayOfWeek"] || "*"
61
- cmd = (sched.elements["Command"].cdatas[0]).value().strip()
62
- contents << minute + "\t" + hour + "\t" + mday + "\t" + month + "\t" + wday + "\t" + cmd + "\n"
63
38
  }
64
- return(contents.join(""))
65
39
  end
66
40
 
67
41
  end
@@ -3,29 +3,12 @@ module Generator
3
3
  module Newsyslog
4
4
 
5
5
  # Writes the Newsyslog configuration
6
- def self.generate(specs)
7
- sym = "Newsyslog"
8
- begin
9
- originalContents = Promotion::Generator::original_contents_for(sym)
10
- newContents = originalContents + Marker + "\n" + contents(specs)
11
- Promotion::Generator::write_file_for(sym, newContents)
12
- rescue => e
13
- $log.error("Error occurred while generating #{sym}\n#{e.message}" + e.backtrace.join("\n"))
14
- exit 1
15
- end
16
- end
17
-
18
- # Generates the contents for /etc/newsyslog.conf, which controls rolling and retention of logs
19
- def self.contents(specs)
20
- contents = []
21
- contents << "# This section of the file should not be edited\n"
22
- contents << "# It was generated by the promotion application and will be overwritten\n"
23
- contents << "# when the next promotion occurs.\n"
24
- contents << "# The previous section will be preserved\n\n"
25
- contents << "# Filename Owner:Group Mode Count Size When Flags Command\n"
6
+ def self.check(specs)
7
+ contents = IO.readlines("/etc/newsyslog.conf").collect!{ |s| s.gsub(/\s/,"") }
8
+ proposals = []
26
9
  specs.each { |spec|
27
10
  spec.elements.each("/Specification/Newsyslog") { |nsl|
28
- contents << "%-40s" % nsl.text()
11
+ needed = nsl.text() << "\t"
29
12
  owner = nsl.attributes["Owner"]
30
13
  group = nsl.attributes["Group"]
31
14
  if owner.nil? or group.nil?
@@ -33,31 +16,35 @@ module Newsyslog
33
16
  else
34
17
  og = owner + ":" + group
35
18
  end
36
- contents << "%-20s" % og
37
- contents << "%-7s" % (nsl.attributes["Mode"] || "0640")
38
- contents << "%-7s" % (nsl.attributes["Count"] || "*")
39
- contents << "%-7s" % (nsl.attributes["Size"] || "*")
19
+ needed << og << "\t"
20
+ needed << (nsl.attributes["Mode"] || "0640") << "\t"
21
+ needed << (nsl.attributes["Count"] || "*") << "\t"
22
+ needed << (nsl.attributes["Size"] || "*") << "\t"
40
23
  # treat as $-format if starts with D,W,M; treat as integer if integer
41
24
  # else treat as @-format if digits with a 'T'
42
25
  sched = nsl.attributes["When"] || "*"
43
26
  if sched[0..0] == "D" or sched[0..0] == "W" or sched[0..0] == "M"
44
- contents << "$%-9s" % sched
27
+ needed << sched << "\t"
45
28
  elsif sched.include?("T")
46
- contents << "@%-9s" % sched
29
+ needed << "@" << sched << "\t"
47
30
  else
48
- contents << "%-9s" % sched
31
+ needed << sched << "\t"
49
32
  end
50
33
  # We normally do not want to add rotation messages or compress
51
- contents << "Z" if (nsl.attributes["Zip"] || "false") == "true"
52
- contents << "B" if (nsl.attributes["Binary"] || "true") == "true"
34
+ needed << "Z" if (nsl.attributes["Zip"] || "false") == "true"
35
+ needed << "B" if (nsl.attributes["Binary"] || "true") == "true"
53
36
  restart = nsl.attributes["Restart"] # service to restart
54
37
  if restart
55
- contents << " /etc/rc.d/#{restart} restart "
38
+ needed << "\t/etc/rc.d/#{restart} restart >/dev/null 2>&1 "
56
39
  end
57
- contents << "\n"
40
+ # see if its already present - check the pattern ignoring whitespace
41
+ proposals << needed unless contents.include?(needed.gsub(/\s/,""))
58
42
  }
59
43
  }
60
- return(contents.join(""))
44
+ if proposals.size > 0
45
+ header = "# Filename\t\tOwner:Group\tMode\tCount\tSize\tWhen\tFlags\tCommand\n"
46
+ puts("\nSuggested changes to /etc/newsyslog.conf:", header, proposals.join("\n"), "\n")
47
+ end
61
48
  end
62
49
 
63
50
  end
@@ -3,39 +3,29 @@ module Generator
3
3
  module Profile
4
4
 
5
5
  # Writes the system profile
6
- def self.generate(specs)
7
- sym = "Profile"
8
- begin
9
- originalContents = Promotion::Generator::original_contents_for(sym)
10
- newContents = originalContents + Marker + "\n" + contents(specs)
11
- Promotion::Generator::write_file_for(sym, newContents)
12
- rescue => e
13
- $log.error("Error occurred while generating #{sym}\n#{e.message}" + e.backtrace.join("\n"))
14
- exit 1
15
- end
16
- end
17
-
18
- # Generates the contents for /etc/profile, containing environment variables and aliases
19
- def self.contents(specs)
20
- contents = []
21
- contents << "# This section of the file should not be edited\n"
22
- contents << "# It was generated by the promotion application and will be overwritten\n"
23
- contents << "# when the next promotion occurs.\n"
24
- contents << "# The previous section will be preserved\n"
6
+ def self.check(specs)
7
+ contents = IO.readlines("/etc/profile").collect!{ |s| s.strip() }
8
+ proposals = []
25
9
  specs.each { |spec|
26
10
  spec.elements.each("/Specification/Environment") { |env|
27
11
  env.elements.each("Variable") { |var|
28
12
  t = var.cdatas.length > 0 ? var.cdatas[0].to_s() : var.text()
29
- contents << "export #{var.attributes["Name"]}=\"#{t}\" \n"
13
+ needed = "export #{var.attributes["Name"]}=\"#{t}\""
14
+ unless contents.include?(needed.strip())
15
+ proposals << needed
16
+ end
30
17
  }
31
18
  env.elements.each("Alias") { |ali|
32
19
  contents << "# #{ali.attributes["Comment"]}\n" if ali.attributes["Comment"]
33
20
  t = ali.cdatas.length > 0 ? ali.cdatas[0].to_s() : ali.text()
34
- contents << "alias #{ali.attributes["Name"]}='#{t}' \n"
21
+ needed = "alias #{ali.attributes["Name"]}='#{t}'"
22
+ unless contents.include?(needed.strip())
23
+ proposals << needed
24
+ end
35
25
  }
36
26
  }
37
27
  }
38
- return(contents.join(""))
28
+ puts("\nSuggested changes to /etc/profile:", proposals.join("\n"), "\n") if proposals.size > 0
39
29
  end
40
30
 
41
31
  end
@@ -3,27 +3,10 @@ module Generator
3
3
  module Rcconf
4
4
 
5
5
  # Writes the system file /etc/rc.conf.local
6
- def self.generate(specs)
7
- sym = "Rc_conf"
8
- begin
9
- originalContents = Promotion::Generator::original_contents_for(sym)
10
- newContents = originalContents + Marker + "\n" + contents(specs)
11
- Promotion::Generator::write_file_for(sym, newContents)
12
- rescue => e
13
- $log.error("Error occurred while generating #{sym}\n#{e.message}" + e.backtrace.join("\n"))
14
- exit 1
15
- end
16
- end
17
-
18
- # Generates the contents for /etc/rc.conf.local, containing instructions for running daemons
19
- def self.contents(specs)
20
- contents = []
21
- contents << "# This section of the file should not be edited\n"
22
- contents << "# It was generated by the promotion application and will be overwritten\n"
23
- contents << "# when the next promotion occurs.\n"
24
- contents << "# The previous section will be preserved\n"
25
- contents << "# ---------------------------\n"
26
- contents << "# generated application flags\n"
6
+ def self.check(specs)
7
+ contents = IO.readlines("/etc/rc.conf.local").collect!{ |s| s.strip() }
8
+ proposals = []
9
+ # Extract package scripts and sort them by increasing priority
27
10
  daemonSpecs = specs.reject {|s| s.elements["/Specification/Daemon"].nil? }
28
11
  daemonSpecs.sort!() { |a, b|
29
12
  ap = a.elements["/Specification/Daemon"].attributes["Priority"].to_i || 10
@@ -31,18 +14,22 @@ module Rcconf
31
14
  ap <=> bp
32
15
  }
33
16
  pkgScripts = []
34
- daemonSpecs.each { |spec| # allow for multiple daemons in a spec
35
- spec.each_element("Daemon") { |d|
17
+ daemonSpecs.each { |spec|
18
+ spec.each_element("Daemon") { |d| # allow for multiple daemons in a spec
36
19
  # Name defaults to the name of the spec, but may be overridden by the optional Name attribute
37
20
  name = d.attributes["Name"] || spec.attributes["Name"]
38
21
  flags = d.attributes["Flags"]
39
- contents << "#{name}_flags=\"#{flags}\" \n"
22
+ needed = "#{name}_flags=\"#{flags}\""
23
+ proposals << needed unless contents.include?(needed.strip())
40
24
  pkgScripts << name
41
25
  }
42
26
  }
43
- contents << "\n# rc.d(8) packages scripts, started in the specified order and stopped in reverse order\n"
44
- contents << "pkg_scripts=\"${pkg_scripts} #{pkgScripts.join(" ")}\" \n"
45
- return(contents.join(""))
27
+ puts("\nSuggested changes to /etc/rc.conf.local:", proposals.join("\n")) if proposals.size > 0
28
+ pkgList = pkgScripts.join(" ")
29
+ unless contents.join("\n") =~ /pkg_scripts.*#{pkgList}/
30
+ puts("pkg_scripts=\"${pkg_scripts} #{pkgList}\" ")
31
+ end
32
+ puts("") if proposals.size > 0
46
33
  end
47
34
 
48
35
  end
@@ -3,51 +3,23 @@ module Generator
3
3
  module Sudoers
4
4
 
5
5
  # Writes the sudoers file after testing it with visudo
6
- def self.generate(specs)
7
- sym = "Sudoers"
8
- begin
9
- originalContents = Promotion::Generator::original_contents_for(sym)
10
- newContents = originalContents + Marker + "\n" + contents(specs)
11
- tempFilename = Promotion::Generator::write_file_for(sym, newContents, true)
12
- $log.info("Checking temporary sudoers written to #{tempFilename}.")
13
- visudoResults = `#{Files::Visudo} -c -f #{tempFilename}`
14
- if visudoResults =~ /parsed OK/
15
- $log.info("visudo confirms that sudoers syntax is correct.")
16
- else
17
- $log.error(visudoResults)
18
- raise
19
- end
20
- Promotion::Generator::write_file_for("Sudoers", newContents)
21
- FileUtils.rm_f(tempFilename)
22
- rescue => e
23
- $log.error("Error occurred while generating sudoers\n#{e.message}" + e.backtrace.join("\n"))
24
- exit 1
25
- end
26
- end
27
-
28
- # Generates the contents for /etc/sudoers, containing environment variables and aliases
29
- def self.contents(specs)
30
- contents = []
31
- contents << "# This section of the file should not be edited\n"
32
- contents << "# It was generated by the promotion application and will be overwritten\n"
33
- contents << "# when the next promotion occurs.\n"
34
- contents << "# The previous section will be preserved\n\n"
35
- contents << "Defaults timestamp_timeout=55\n\n"
36
- contents << "root ALL = (ALL) ALL \n"
37
- contents << "# people in group wheel may run all commands \n"
38
- contents << "%wheel ALL = (ALL) ALL \n\n"
39
- contents << "# Generated user privilege specifications \n"
6
+ def self.check(specs)
7
+ contents = IO.readlines("/etc/sudoers").collect!{ |s| s.strip() }
8
+ proposals = []
40
9
  specs.each { |spec|
41
10
  spec.elements.each("/Specification/Sudoers/UserPrivilege") { |priv|
42
- contents << "%-16s" % priv.attributes["User"]
43
- contents << " ALL = "
44
- contents << "(#{priv.attributes["Runas"]}) " if priv.attributes["Runas"]
11
+ needed = "%-16s" % priv.attributes["User"]
12
+ needed << " ALL = "
13
+ needed << "(#{priv.attributes["Runas"]}) " if priv.attributes["Runas"]
45
14
  pwd = (priv.attributes["Password"] || "false").downcase() == "true"
46
- contents << (pwd ? " " : "NOPASSWD: ")
47
- contents << "#{priv.text().strip()} \n"
15
+ needed << (pwd ? " " : "NOPASSWD: ")
16
+ needed << "#{priv.text().strip()}"
17
+ proposals << needed unless contents.include?(needed.strip())
48
18
  }
49
19
  }
50
- return(contents.join(""))
20
+ if proposals.size > 0
21
+ puts("\nSuggested changes to /etc/sudoers:", proposals.join("\n"), "\n") if proposals.size > 0
22
+ end
51
23
  end
52
24
 
53
25
  end
@@ -8,16 +8,14 @@ require 'promotion/generator/crontab'
8
8
  module Promotion
9
9
  module Generator
10
10
 
11
- Marker = "#---promotion---" # add this marker to a config file to preserve the contents before it
12
-
13
11
  def self.start()
14
- $log.info("\n#{'_'*40}\nGenerating common environment files")
12
+ $log.info("\n#{'_'*40}\nChecking common environment files")
15
13
  specs = self.gather_specs()
16
- Profile.generate(specs)
17
- Rcconf.generate(specs)
18
- Newsyslog.generate(specs)
19
- Sudoers.generate(specs)
20
- Crontab.generate(specs)
14
+ Profile.check(specs)
15
+ Rcconf.check(specs)
16
+ Newsyslog.check(specs)
17
+ Sudoers.check(specs)
18
+ Crontab.check(specs)
21
19
  end
22
20
 
23
21
  # Gathers specs from all deployment descriptors in folders with the staging area
@@ -40,29 +38,5 @@ module Generator
40
38
  end
41
39
  end
42
40
 
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)
46
- begin
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
51
- ensure
52
- f.close unless f.nil? || f.closed?
53
- end
54
- return(filename)
55
- end
56
-
57
- # Returns a string of the new content for the file up to the +marker+
58
- def self.original_contents_for(sym)
59
- begin
60
- originalContents = IO.readlines(Files.const_get(sym), nil)[0].split(Marker)[0]
61
- rescue
62
- originalContents = ""
63
- end
64
- return(originalContents)
65
- end
66
-
67
41
  end
68
42
  end
metadata CHANGED
@@ -1,59 +1,36 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: promotion
3
- version: !ruby/object:Gem::Version
4
- hash: 9
5
- prerelease:
6
- segments:
7
- - 1
8
- - 4
9
- - 7
10
- version: 1.4.7
3
+ version: !ruby/object:Gem::Version
4
+ version: '2.0'
11
5
  platform: ruby
12
- authors:
6
+ authors:
13
7
  - Richard Kernahan
14
8
  autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
-
18
- date: 2013-10-18 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
21
- name: log4r
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 7
29
- segments:
30
- - 1
31
- - 1
32
- - 10
33
- version: 1.1.10
34
- type: :runtime
35
- version_requirements: *id001
36
- description: "\t\tThe Promotion tool is designed to make it easy and quick to deploy an application\n\
37
- \t\tinto production. Originally built for use with OpenBSD, it can be used on an *nix\n\
38
- \t\tsystem by adjusting a few paths (in config.rb).\n\n\
39
- \t\tTo deploy or install an application you just need to copy a few files into place, right?\n\
40
- \t\tWell, the folders need to be there first of course, oh and the permissions need to be set,\n\
41
- \t\tand I guess we need the right users set up before file ownerships can be set correctly,\n\
42
- \t\twhich means we need groups before that ... ok, so there is more to it than copying a few files.\n\n\
43
- \t\tThere are also system-wide settings that may need to be modified to support an application,\n\
44
- \t\tsuch as environment variables in /etc/profile, /etc/sudoers,\n\
45
- \t\tstartup scripts in /etc/rc.conf.local, and /var/cron/tabs/* cron jobs.\n\n\
46
- \t\tPromotion handles all of this based on an XML deployment descriptor for each application,\n\
47
- \t\tallowing rapid, reliable redeployment with a single line command. It also manages database\n\
48
- \t\tschema migration.\n"
11
+ date: 2013-10-30 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: "\t\tThe Promotion tool is designed to make it easy and quick to deploy
14
+ an application\n\t\tinto production. Originally built for use with OpenBSD, it can
15
+ be used on an *nix\n\t\tsystem by adjusting a few paths (in config.rb).\n\n\t\tTo
16
+ deploy or install an application you just need to copy a few files into place, right?\n\t\tWell,
17
+ the folders need to be there first of course, oh and the permissions need to be
18
+ set,\n\t\tand I guess we need the right users set up before file ownerships can
19
+ be set correctly,\n\t\twhich means we need groups before that ... ok, so there is
20
+ more to it than copying a few files.\n\n\t\tThere are also system-wide settings
21
+ that may need to be modified to support an application,\n\t\tsuch as environment
22
+ variables in /etc/profile, /etc/sudoers,\n\t\tstartup scripts in /etc/rc.conf.local,
23
+ and /var/cron/tabs/* cron jobs.\n\t\tPromotion does not modify these sensitive files,
24
+ but it does say what's missing or needs to be changed.\n\n\t\tPromotion handles
25
+ all of this based on an XML deployment descriptor for each application,\n\t\tallowing
26
+ rapid, reliable redeployment with a single line command. It also manages database\n\t\tschema
27
+ migration.\n"
49
28
  email: dev.promotion@finalstep.com.au
50
29
  executables: []
51
-
52
30
  extensions: []
53
-
54
- extra_rdoc_files:
31
+ extra_rdoc_files:
55
32
  - README
56
- files:
33
+ files:
57
34
  - bin/promote
58
35
  - bin/evolve
59
36
  - bin/devolve
@@ -76,41 +53,32 @@ files:
76
53
  - README
77
54
  homepage: http://rubygems.org/gems/promotion
78
55
  licenses: []
79
-
80
- post_install_message: "\n\n To install the executables (promote, evolve, devolve, mkdeploy)\n issue the following command:\n\
81
- \t$ sudo ruby -rubygems -e \"require 'promotion/install'\"\n\n "
82
- rdoc_options:
56
+ metadata: {}
57
+ post_install_message: "\n\n To install the executables (promote, evolve, devolve,
58
+ mkdeploy)\n issue the following command:\n\t$ sudo ruby -rubygems -e \"require
59
+ 'promotion/install'\"\n\n "
60
+ rdoc_options:
83
61
  - --title
84
62
  - Promotion
85
63
  - --main
86
64
  - README
87
- require_paths:
65
+ require_paths:
88
66
  - lib
89
- required_ruby_version: !ruby/object:Gem::Requirement
90
- none: false
91
- requirements:
92
- - - ">="
93
- - !ruby/object:Gem::Version
94
- hash: 3
95
- segments:
96
- - 0
97
- version: "0"
98
- required_rubygems_version: !ruby/object:Gem::Requirement
99
- none: false
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- hash: 3
104
- segments:
105
- - 0
106
- version: "0"
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
107
77
  requirements: []
108
-
109
78
  rubyforge_project: promotion
110
- rubygems_version: 1.8.24
79
+ rubygems_version: 2.0.3
111
80
  signing_key:
112
- specification_version: 3
113
- summary: Promotion makes it possible to repeatedly deploy an application in a fast and reliable way.
81
+ specification_version: 4
82
+ summary: Promotion makes it possible to repeatedly deploy an application in a fast
83
+ and reliable way.
114
84
  test_files: []
115
-
116
- has_rdoc: "true"