promotion 1.4.7 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
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"