r10k 0.0.1rc1 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/r10k CHANGED
@@ -1,5 +1,17 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'r10k/cli'
4
+ require 'colored'
4
5
 
5
- R10K::CLI.command.run(ARGV)
6
+ begin
7
+ R10K::CLI.command.run(ARGV)
8
+ rescue Interrupt
9
+ $stderr.puts "Aborted!".red
10
+ exit(1)
11
+ rescue SystemExit => e
12
+ exit(e.status)
13
+ rescue Exception => e
14
+ $stderr.puts "\nRuntime error: #{e.inspect}".red
15
+ $stderr.puts e.backtrace.join("\n").red if ARGV.include? '--trace'
16
+ exit(1)
17
+ end
data/lib/r10k.rb CHANGED
@@ -4,3 +4,4 @@ require 'r10k/root'
4
4
  require 'r10k/synchro/git'
5
5
  require 'r10k/librarian'
6
6
  require 'r10k/version'
7
+ require 'r10k/logging'
@@ -0,0 +1,7 @@
1
+ require 'r10k'
2
+
3
+ module R10K::Action
4
+ end
5
+
6
+ require 'r10k/action/module'
7
+ require 'r10k/action/environment'
@@ -0,0 +1,74 @@
1
+ require 'r10k/action'
2
+ require 'r10k/errors'
3
+ require 'r10k/action/module'
4
+ require 'r10k/deployment'
5
+ require 'r10k/logging'
6
+
7
+ require 'middleware'
8
+
9
+ module R10K::Action::Environment
10
+
11
+ class Deploy
12
+ # Middleware action to deploy an environment
13
+
14
+ include R10K::Logging
15
+
16
+ # @param [Object] app The next application in the middlware stack
17
+ # @param [R10K::Module] mod The module to deploy
18
+ def initialize(app, root)
19
+ @app, @root = app, root
20
+ end
21
+
22
+ # @param [Hash] env
23
+ #
24
+ # @option env [true, false] :update_cache
25
+ # @option env [true, false] :recurse
26
+ # @option env [true, false] :trace
27
+ def call(env)
28
+ @env = env
29
+
30
+ logger.notice "Deploying environment #{@root.name}"
31
+ FileUtils.mkdir_p @root.full_path
32
+ @root.sync! :update_cache => @env[:update_cache]
33
+
34
+ if @env[:recurse]
35
+ # Build a new middleware chain and run it
36
+ stack = Middleware::Builder.new
37
+ @root.modules.each { |mod| stack.use R10K::Action::Module::Deploy, mod }
38
+ stack.call(@env)
39
+ end
40
+
41
+ @app.call(@env)
42
+ rescue R10K::ExecutionFailure => e
43
+ logger.error "Could not synchronize #{@root.full_path}: #{e}".red
44
+ $stderr.puts e.backtrace.join("\n").red if @env[:trace]
45
+ @app.call(@env)
46
+ end
47
+ end
48
+
49
+ class Purge
50
+ # Middleware action to purge stale environments from a directory
51
+
52
+ include R10K::Logging
53
+
54
+ # @param [Object] app The next application in the middlware stack
55
+ # @param [String] path The directory path to purge
56
+ def initialize(app, path)
57
+ @app, @path = app, path
58
+ end
59
+
60
+ # @param [Hash] env
61
+ def call(env)
62
+ @env = env
63
+
64
+ stale_directories = R10K::Deployment.instance.collection.stale(@path)
65
+
66
+ stale_directories.each do |dir|
67
+ logger.notice "Purging stale environment #{dir.inspect}"
68
+ FileUtils.rm_rf(dir, :secure => true)
69
+ end
70
+
71
+ @app.call(@env)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ require 'r10k/action'
2
+ require 'r10k/errors'
3
+ require 'r10k/logging'
4
+
5
+ require 'middleware'
6
+
7
+ module R10K::Action::Module
8
+
9
+ class R10K::Action::Module::Deploy
10
+ # Middleware to deploy a module
11
+
12
+ include R10K::Logging
13
+
14
+ # @param [Object] app The next application in the middlware stack
15
+ # @param [R10K::Module] mod The module to deploy
16
+ def initialize(app, mod)
17
+ @app, @mod = app, mod
18
+ end
19
+
20
+ # @param [Hash] env
21
+ #
22
+ # @option env [true, false] :update_cache
23
+ def call(env)
24
+ @env = env
25
+
26
+ logger.notice "Deploying module #{@mod.name}"
27
+ @mod.sync! :update_cache => @env[:update_cache]
28
+
29
+ @app.call(@env)
30
+ rescue R10K::ExecutionFailure => e
31
+ logger.error "Could not synchronize #{@mod.full_path}: #{e}".red
32
+ $stderr.puts e.backtrace.join("\n").red if @env[:trace]
33
+ @app.call(@env)
34
+ end
35
+ end
36
+ end
data/lib/r10k/cli.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'r10k'
2
+ require 'r10k/logging'
2
3
  require 'cri'
3
4
 
4
5
  module R10K::CLI
@@ -21,6 +22,12 @@ module R10K::CLI
21
22
  R10K::Deployment.instance.configfile = value
22
23
  end
23
24
 
25
+ required :v, :verbose, 'Set verbosity level' do |value, cmd|
26
+ R10K::Logging.level = Integer(value)
27
+ end
28
+
29
+ flag :t, :trace, 'Display stack traces on application crash'
30
+
24
31
  run do |opts, args, cmd|
25
32
  puts cmd.help
26
33
  exit 0
@@ -31,3 +38,5 @@ end
31
38
 
32
39
  require 'r10k/cli/environment'
33
40
  require 'r10k/cli/module'
41
+ require 'r10k/cli/cache'
42
+ require 'r10k/cli/synchronize'
@@ -1,8 +1,8 @@
1
- require 'r10k/cli/environment'
1
+ require 'r10k/cli'
2
2
  require 'r10k/synchro/git'
3
3
  require 'cri'
4
4
 
5
- module R10K::CLI::Environment
5
+ module R10K::CLI
6
6
  module Cache
7
7
  def self.command
8
8
  @cmd ||= Cri::Command.define do
@@ -11,8 +11,17 @@ module R10K::CLI::Environment
11
11
  summary 'Update cache for all sources'
12
12
 
13
13
  run do |opts, args, cmd|
14
- R10K::Deployment.instance[:sources].each_pair do |name, source|
15
- synchro = R10K::Synchro::Git.new(source)
14
+ sources = R10K::Deployment.instance[:sources]
15
+
16
+ remotes = Set.new
17
+
18
+ sources.each_pair do |name, hash|
19
+ remotes << hash['remote']
20
+ end
21
+
22
+ remotes.each do |remote|
23
+ puts "Synchronizing #{remote}"
24
+ synchro = R10K::Synchro::Git.new(remote)
16
25
  synchro.cache
17
26
  end
18
27
  end
@@ -9,8 +9,6 @@ module R10K::CLI
9
9
  usage 'environment <subcommand>'
10
10
  summary 'Operate on a specific environment'
11
11
 
12
- required :e, :environment, 'Specify a particular environment'
13
-
14
12
  run do |opts, args, cmd|
15
13
  puts cmd.help
16
14
  exit 0
@@ -23,4 +21,4 @@ end
23
21
 
24
22
  require 'r10k/cli/environment/list'
25
23
  require 'r10k/cli/environment/deploy'
26
- require 'r10k/cli/environment/cache'
24
+ require 'r10k/cli/environment/stale'
@@ -1,43 +1,45 @@
1
1
  require 'r10k/cli/environment'
2
2
  require 'r10k/deployment'
3
- require 'cri'
3
+ require 'r10k/action'
4
4
 
5
- require 'fileutils'
5
+ require 'cri'
6
+ require 'middleware'
6
7
 
7
8
  module R10K::CLI::Environment
8
9
  module Deploy
9
10
  def self.command
10
11
  @cmd ||= Cri::Command.define do
11
12
  name 'deploy'
12
- usage 'deploy'
13
+ usage 'deploy <environment> <...>'
13
14
  summary 'Deploy an environment'
14
15
 
15
16
  flag :r, :recurse, 'Recursively update submodules'
16
-
17
- required :u, :update, "Enable or disable cache updating"
17
+ flag :u, :update, "Enable or disable cache updating"
18
18
 
19
19
  run do |opts, args, cmd|
20
20
  deployment = R10K::Deployment.instance
21
21
  env_list = deployment.environments
22
22
 
23
- update_cache = (defined? opts[:update]) ? (opts[:update] == 'true') : false
24
-
25
- if opts[:environment]
26
- environments = env_list.select {|env| env.name == opts[:environment]}
23
+ if not args.empty?
24
+ environments = env_list.select {|env| args.include? env.name }
27
25
  else
28
26
  environments = env_list
29
27
  end
30
28
 
31
- environments.each do |env|
32
- FileUtils.mkdir_p env.full_path
33
- env.sync! :update_cache => update_cache
34
-
35
- if opts[:recurse]
36
- env.modules.each do |mod|
37
- mod.sync! :update_cache => update_cache
38
- end
29
+ stack = Middleware::Builder.new do
30
+ environments.each do |env|
31
+ use R10K::Action::Environment::Deploy, env
39
32
  end
40
33
  end
34
+
35
+ # Prepare middleware environment
36
+ stack_env = {
37
+ :update_cache => opts[:update],
38
+ :recurse => opts[:recurse],
39
+ :trace => opts[:trace],
40
+ }
41
+
42
+ stack.call(stack_env)
41
43
  end
42
44
  end
43
45
  end
@@ -0,0 +1,34 @@
1
+ require 'r10k/cli/environment'
2
+ require 'r10k/deployment'
3
+ require 'cri'
4
+
5
+ module R10K::CLI::Environment
6
+ module Stale
7
+ def self.command
8
+ @cmd ||= Cri::Command.define do
9
+ name 'stale'
10
+ usage 'stale <directory> [directory ...]'
11
+ summary 'List all stale environments'
12
+
13
+ run do |opts, args, cmd|
14
+ deployment = R10K::Deployment.instance
15
+
16
+ if args.empty?
17
+ $stderr.print "ERROR: ".red
18
+ $stderr.puts "#{cmd.name} requires one or more directories"
19
+ $stderr.puts cmd.help
20
+ exit(1)
21
+ end
22
+
23
+ args.each do |dir|
24
+ puts "Stale environments in #{dir}:"
25
+ output = deployment.collection.stale(dir).each do |stale_dir|
26
+ puts " - #{stale_dir}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ self.command.add_command(Stale.command)
34
+ end
@@ -9,10 +9,10 @@ module R10K::CLI::Module
9
9
  def self.command
10
10
  @cmd ||= Cri::Command.define do
11
11
  name 'deploy'
12
- usage 'deploy <module name>'
12
+ usage 'deploy [module name] <module name> ...'
13
13
  summary 'Deploy a module'
14
14
 
15
- required :u, :update, "Enable or disable cache updating"
15
+ flag :u, :update, "Update module cache"
16
16
 
17
17
  run do |opts, args, cmd|
18
18
 
@@ -21,10 +21,7 @@ module R10K::CLI::Module
21
21
  exit 1
22
22
  end
23
23
 
24
- deployment = R10K::Deployment.instance
25
- env_list = deployment.environments
26
-
27
- update_cache = (defined? opts[:update]) ? (opts[:update] == 'true') : false
24
+ env_list = R10K::Deployment.instance.environments
28
25
 
29
26
  if opts[:environment]
30
27
  environments = env_list.select {|env| env.name == opts[:environment]}
@@ -33,9 +30,6 @@ module R10K::CLI::Module
33
30
  end
34
31
 
35
32
  environments.each do |env|
36
- FileUtils.mkdir_p env.full_path
37
- env.sync! :update_cache => update_cache
38
-
39
33
  mods = env.modules.select { |mod| mod.name == module_name }
40
34
 
41
35
  if mods.empty?
@@ -43,9 +37,15 @@ module R10K::CLI::Module
43
37
  exit 1
44
38
  end
45
39
 
46
- mods.each do |mod|
47
- mod.sync! :update_cache => update_cache
48
- end
40
+ stack = Middleware::Builder.new
41
+ mods.each { |mod| stack.use R10K::Action::Module::Deploy, mod }
42
+
43
+ stack_env = {
44
+ :update_cache => opts[:update],
45
+ :trace => opts[:trace],
46
+ }
47
+
48
+ stack.call(stack_env)
49
49
  end
50
50
  end
51
51
  end
@@ -0,0 +1,45 @@
1
+ require 'r10k/cli'
2
+ require 'r10k/deployment'
3
+ require 'r10k/action/environment'
4
+
5
+ require 'middleware'
6
+ require 'cri'
7
+
8
+ module R10K::CLI
9
+ module Synchronize
10
+ def self.command
11
+ @cmd ||= Cri::Command.define do
12
+ name 'synchronize'
13
+ usage 'synchronize <options>'
14
+ summary 'Fully synchronize all environments'
15
+
16
+ flag :u, :update, "Update cache before running"
17
+
18
+ run do |opts, args, cmd|
19
+ deployment = R10K::Deployment.instance
20
+ environments = deployment.environments
21
+ directories = (deployment.config[:purgedirs] || [])
22
+
23
+ stack = Middleware::Builder.new do
24
+ environments.each do |env|
25
+ use R10K::Action::Environment::Deploy, env
26
+ end
27
+
28
+ directories.each do |dir|
29
+ use R10K::Action::Environment::Purge, dir
30
+ end
31
+ end
32
+
33
+ stack_env = {
34
+ :update_cache => opts[:update],
35
+ :trace => opts[:trace],
36
+ :recurse => true,
37
+ }
38
+
39
+ stack.call(stack_env)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ self.command.add_command(Synchronize.command)
45
+ end
@@ -1,6 +1,6 @@
1
1
  require 'r10k'
2
2
  require 'r10k/synchro/git'
3
- require 'r10k/environment_collection'
3
+ require 'r10k/deployment/environment_collection'
4
4
  require 'yaml'
5
5
 
6
6
  class R10K::Deployment
@@ -21,10 +21,14 @@ class R10K::Deployment
21
21
  #
22
22
  # @return [Array<R10K::Root>]
23
23
  def environments
24
- collection = R10K::EnvironmentCollection.new(config)
25
24
  collection.to_a
26
25
  end
27
26
 
27
+ def collection
28
+ load_config unless @config
29
+ @collection
30
+ end
31
+
28
32
  # Serve up the loaded config if it's already been loaded, otherwise try to
29
33
  # load a config in the current wd.
30
34
  def config
@@ -47,8 +51,6 @@ class R10K::Deployment
47
51
  File.open(@configfile) { |fh| @config = YAML.load(fh.read) }
48
52
  apply_config_settings
49
53
  @config
50
- rescue => e
51
- raise "Couldn't load #{configfile}: #{e}"
52
54
  end
53
55
 
54
56
  # Apply config settings to the relevant classes after a config has been loaded.
@@ -56,5 +58,6 @@ class R10K::Deployment
56
58
  if @config[:cachedir]
57
59
  R10K::Synchro::Git.cache_root = @config[:cachedir]
58
60
  end
61
+ @collection = R10K::EnvironmentCollection.new(@config)
59
62
  end
60
63
  end
@@ -4,15 +4,22 @@ class R10K::EnvironmentCollection
4
4
 
5
5
  attr_reader :update_cache
6
6
 
7
- def initialize(config, options = {:update_cache => true})
7
+ def initialize(config, options = {:update_cache => false})
8
8
  @config = config
9
9
  @environments = []
10
10
 
11
11
  @update_cache = options.delete(:update_cache)
12
-
13
12
  load_all
14
13
  end
15
14
 
15
+ def current(basedir)
16
+ basedir = File.expand_path(basedir)
17
+ tracked_envs = @environments.select do |env|
18
+ envdir = File.expand_path(env.basedir)
19
+ envdir == basedir
20
+ end
21
+ end
22
+
16
23
  # List subdirectories that aren't associated with an env
17
24
  #
18
25
  # If a branch associated with an environment is deleted then the associated
@@ -23,8 +30,17 @@ class R10K::EnvironmentCollection
23
30
  # @param [String] basedir The directory to scan
24
31
  #
25
32
  # @return [Array<String>] A list of filenames
26
- def untracked_environments(basedir)
27
- raise NotImplementedError
33
+ def stale(basedir)
34
+ basedir = File.expand_path(basedir)
35
+
36
+ all_dirs = Dir.glob("#{basedir}/*").map do |file|
37
+ File.basename(file) if File.directory?(file)
38
+ end.compact
39
+ current_dirs = current(basedir).map(&:name)
40
+
41
+ stale_dirs = all_dirs - current_dirs
42
+
43
+ stale_dirs.map {|dir| File.join(basedir, dir)}
28
44
  end
29
45
 
30
46
  # @return [Array<R10K::Root>]
@@ -0,0 +1,7 @@
1
+ require 'r10k'
2
+
3
+ module R10K
4
+ class ExecutionFailure < Exception
5
+ attr_accessor :exit_code, :stdout, :stderr
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ require 'r10k'
2
+
3
+ require 'log4r'
4
+ require 'log4r/configurator'
5
+
6
+ module R10K::Logging
7
+
8
+ include Log4r
9
+
10
+ def logger
11
+ unless @logger
12
+ @logger = Log4r::Logger.new(self.class.name)
13
+ @logger.add R10K::Logging.outputter
14
+ end
15
+ @logger
16
+ end
17
+
18
+ class << self
19
+
20
+ include Log4r
21
+ def included(klass)
22
+ unless @log4r_loaded
23
+ Configurator.custom_levels(*%w{DEBUG2 DEBUG1 DEBUG INFO NOTICE WARN ERROR FATAL})
24
+ Logger.global.level = Log4r::ALL
25
+ @log4r_loaded = true
26
+ end
27
+ end
28
+
29
+ def level
30
+ @level || Log4r::WARN # Default level is WARN
31
+ end
32
+
33
+ def level=(val)
34
+ outputter.level = val
35
+ @level = val
36
+ end
37
+
38
+ def formatter
39
+ @formatter ||= Log4r::PatternFormatter.new(:pattern => '[%C - %l] %m')
40
+ end
41
+
42
+ def outputter
43
+ @outputter ||= Log4r::StderrOutputter.new('console',
44
+ :level => self.level,
45
+ :formatter => formatter
46
+ )
47
+ end
48
+ end
49
+ end
@@ -1,19 +1,84 @@
1
1
  require 'r10k'
2
2
  require 'r10k/module'
3
+ require 'r10k/errors'
4
+ require 'r10k/logging'
5
+
6
+ require 'systemu'
7
+ require 'semver'
8
+ require 'json'
3
9
 
4
10
  class R10K::Module::Forge < R10K::Module
5
11
 
6
12
  def self.implements(name, args)
7
- args.is_a? String and args.match /\d+\.\d+\.\d+/
13
+ args.is_a? String and SemVer.valid?(args)
8
14
  end
9
15
 
16
+ include R10K::Logging
17
+
10
18
  def initialize(name, path, args)
11
19
  super
12
20
 
13
- @version = @args
21
+ @full_name = name
22
+
23
+ @owner, @name = name.split('/')
24
+ @version = SemVer.new(@args)
14
25
  end
15
26
 
16
27
  def sync!(options = {})
17
- puts "#{self.class.name}#sync! is not implemented. Doing nothing."
28
+ return if insync?
29
+
30
+ if insync?
31
+ logger.debug1 "Module #{@full_name} already matches version #{@version}"
32
+ elsif File.exist? metadata_path
33
+ logger.debug "Module #{@full_name} is installed but doesn't match version #{@version}, upgrading"
34
+ cmd = []
35
+ cmd << 'upgrade'
36
+ cmd << "--version=#{@version}"
37
+ cmd << "--ignore-dependencies"
38
+ cmd << @full_name
39
+ pmt cmd
40
+ else
41
+ logger.debug "Module #{@full_name} is not installed"
42
+ cmd = []
43
+ cmd << 'install'
44
+ cmd << "--version=#{@version}"
45
+ cmd << "--ignore-dependencies"
46
+ cmd << @full_name
47
+ pmt cmd
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def current_version
54
+ SemVer.new(metadata['version'])
55
+ end
56
+
57
+ def insync?
58
+ @version == current_version
59
+ rescue
60
+ false
61
+ end
62
+
63
+ def metadata
64
+ JSON.parse(File.read(metadata_path))
65
+ end
66
+
67
+ def metadata_path
68
+ File.join(full_path, 'metadata.json')
69
+ end
70
+
71
+ def pmt(args)
72
+ cmd = "puppet module --modulepath '#{@path}' #{args.join(' ')}"
73
+ logger.debug1 "Execute: #{cmd}"
74
+ status, stdout, stderr = systemu(cmd)
75
+ unless status == 0
76
+ e = R10K::ExecutionFailure.new("#{cmd.inspect} returned with non-zero exit value #{status.inspect}")
77
+ e.exit_code = status
78
+ e.stdout = stdout
79
+ e.stderr = stderr
80
+ raise e
81
+ end
82
+ stdout
18
83
  end
19
84
  end
@@ -1,5 +1,8 @@
1
1
  require 'r10k'
2
- require 'shellter'
2
+ require 'r10k/errors'
3
+ require 'r10k/logging'
4
+
5
+ require 'systemu'
3
6
  require 'fileutils'
4
7
 
5
8
  module R10K::Synchro; end
@@ -39,6 +42,8 @@ class R10K::Synchro::Git
39
42
  end
40
43
  end
41
44
 
45
+ include R10K::Logging
46
+
42
47
  attr_reader :remote
43
48
 
44
49
  # Instantiates a new git synchro and optionally prepares for caching
@@ -48,7 +53,7 @@ class R10K::Synchro::Git
48
53
  @remote = remote
49
54
 
50
55
  if self.class.cache_root
51
- @cache_path = File.join(self.class.cache_root, @remote.gsub(/[^@\w-]/, '-'))
56
+ @cache_path = File.join(self.class.cache_root, @remote.gsub(/[^@\w\.-]/, '-'))
52
57
  end
53
58
  end
54
59
 
@@ -69,7 +74,7 @@ class R10K::Synchro::Git
69
74
  end
70
75
 
71
76
  # @return [TrueClass] if the git repository is cached
72
- def has_cache?
77
+ def cached?
73
78
  @cache_path and File.directory? @cache_path
74
79
  end
75
80
 
@@ -94,10 +99,13 @@ class R10K::Synchro::Git
94
99
 
95
100
  # Force a cache refresh
96
101
  def cache!
97
- if has_cache?
98
- git "--git-dir #{@cache_path} fetch --prune"
102
+ if cached?
103
+ logger.debug "Updating existing cache at #{@cache_path}"
104
+ git "fetch --prune", :git_dir => @cache_path
99
105
  else
100
- FileUtils.mkdir_p File.dirname(File.join(@cache_path))
106
+ logger.debug "No cache for #{@remote.inspect}, forcing cache build"
107
+ cache_root = self.class.cache_root
108
+ FileUtils.mkdir_p cache_root unless File.exist? cache_root
101
109
  git "clone --mirror #{@remote} #{@cache_path}"
102
110
  end
103
111
  end
@@ -106,9 +114,9 @@ class R10K::Synchro::Git
106
114
  # object.
107
115
  #
108
116
  # @return [Array<String>] A list of all cached remote branches
109
- def branches
110
- cache
111
- output = git "--git-dir #{@cache_path} branch"
117
+ def branches(options = {:update_cache => false})
118
+ cache if (options[:update_cache] or not cached?)
119
+ output = git "branch", :git_dir => @cache_path
112
120
  output.split("\n").map { |str| str[2..-1] }
113
121
  end
114
122
 
@@ -121,8 +129,9 @@ class R10K::Synchro::Git
121
129
  #
122
130
  # @param [String] path The directory to create the repo working directory
123
131
  def clone(path)
124
- if has_cache?
132
+ if cached?
125
133
  git "clone --reference #{@cache_path} #{@remote} #{path}"
134
+ git "remote add cache #{@cache_path}", :path => path
126
135
  else
127
136
  FileUtils.mkdir_p path unless File.directory? path
128
137
  git "clone #{@remote} #{path}"
@@ -130,10 +139,10 @@ class R10K::Synchro::Git
130
139
  end
131
140
 
132
141
  def fetch(path)
133
- if has_cache?
134
- git "fetch --prune #{@cache_path}", path
142
+ if cached?
143
+ git "fetch --prune cache", :path => path
135
144
  else
136
- git "fetch --prune", path
145
+ git "fetch --prune origin", :path => path
137
146
  end
138
147
  end
139
148
 
@@ -142,45 +151,76 @@ class R10K::Synchro::Git
142
151
  # @param [String] path The path to the working directory of the git repo
143
152
  # @param [String] ref The git reference to reset to.
144
153
  def reset(path, ref)
154
+ commit = resolve_commit(ref)
145
155
 
146
- # Helloooo, hackery. Try to parse the ref as a commit object. If that fails
147
- # this probably means that the ref is a remote branch. For the sake of
148
- # brevity this code blindly makes that assumption on the failure of
149
- # `git rev-parse`.
150
156
  begin
151
- commit = git "--git-dir #{@cache_path} rev-parse #{ref}^{commit}"
152
- rescue RuntimeError => e
153
- commit = "origin/#{ref}"
157
+ git "reset --hard #{commit}", :path => path
158
+ rescue R10K::ExecutionFailure => e
159
+ logger.error "Unable to locate commit object #{commit} in git repo #{path}"
160
+ raise
154
161
  end
162
+ end
155
163
 
156
- git "reset --hard #{commit}", path
164
+ # Resolve a ref to a commit hash
165
+ #
166
+ # @param [String] ref
167
+ #
168
+ # @return [String] The dereferenced hash of `ref`
169
+ def resolve_commit(ref)
170
+ commit = git "rev-parse #{ref}^{commit}", :git_dir => @cache_path
171
+ commit.chomp
172
+ rescue R10K::ExecutionFailure => e
173
+ logger.error "Could not resolve ref #{ref.inspect} for git cache #{@cache_path}"
174
+ raise
157
175
  end
158
176
 
159
177
  # Wrap git commands
160
178
  #
161
179
  # @param [String] command_line_args The arguments for the git prompt
162
- # @param [String] git_dir An optional git working directory
180
+ # @param [Hash] opts
181
+ #
182
+ # @option opts [String] :git_dir
183
+ # @option opts [String] :work_tree
184
+ # @option opts [String] :work_tree
163
185
  #
164
186
  # @return [String] The git command output
165
- def git(command_line_args, git_dir = nil)
166
- args = []
187
+ def git(command_line_args, opts = {})
188
+ args = %w{git}
167
189
 
168
- if git_dir
169
- args << "--work-tree" << git_dir
170
- args << "--git-dir" << "#{git_dir}/.git"
190
+ log_event = "git #{command_line_args}"
191
+ log_event << ", args: #{opts.inspect}" unless opts.empty?
192
+
193
+
194
+ if opts[:path]
195
+ args << "--git-dir #{opts[:path]}/.git"
196
+ args << "--work-tree #{opts[:path]}"
197
+ else
198
+ if opts[:git_dir]
199
+ args << "--git-dir #{opts[:git_dir]}"
200
+ end
201
+ if opts[:work_tree]
202
+ args << "--work-tree #{opts[:work_tree]}"
203
+ end
171
204
  end
172
205
 
173
- args << command_line_args.split(/\s+/)
206
+ logger.debug1 "Execute: '#{log_event}'"
174
207
 
175
- result = Shellter.run!('git', args.join(' '))
176
- puts "Execute: #{result.last_command}".green
208
+ args << command_line_args
209
+ cmd = args.join(' ')
177
210
 
178
- stderr = result.stderr.read
179
- stdout = result.stdout.read
211
+ status, stdout, stderr = systemu(cmd)
180
212
 
181
- puts stdout.blue unless stdout.empty?
182
- puts stderr.red unless stderr.empty?
213
+ logger.debug2 "[#{log_event}] STDOUT: #{stdout.chomp}" unless stdout.empty?
214
+ logger.debug2 "[#{log_event}] STDERR: #{stderr.chomp}" unless stderr.empty?
183
215
 
216
+ unless status == 0
217
+ msg = "#{cmd.inspect} returned with non-zero exit value #{status.exitstatus}"
218
+ e = R10K::ExecutionFailure.new(msg)
219
+ e.exit_code = status
220
+ e.stdout = stdout
221
+ e.stderr = stderr
222
+ raise e
223
+ end
184
224
  stdout
185
225
  end
186
226
  end
data/lib/r10k/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module R10K
2
- VERSION = '0.0.1rc1'
2
+ VERSION = '0.0.1'
3
3
  end
data/lib/semver.rb ADDED
@@ -0,0 +1,124 @@
1
+ # Ripped off from puppetlabs/puppet
2
+
3
+ # We need to subclass Numeric to force range comparisons not to try to iterate over SemVer
4
+ # and instead use numeric comparisons (eg >, <, >=, <=)
5
+ # Ruby 1.8 already did this for all ranges, but Ruby 1.9 changed range include behavior
6
+ class SemVer < Numeric
7
+ include Comparable
8
+
9
+ VERSION = /^v?(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z-]*|)$/
10
+ SIMPLE_RANGE = /^v?(\d+|[xX])(?:\.(\d+|[xX])(?:\.(\d+|[xX]))?)?$/
11
+
12
+ def self.valid?(ver)
13
+ VERSION =~ ver
14
+ end
15
+
16
+ def self.find_matching(pattern, versions)
17
+ versions.select { |v| v.matched_by?("#{pattern}") }.sort.last
18
+ end
19
+
20
+ def self.pre(vstring)
21
+ vstring =~ /-/ ? vstring : vstring + '-'
22
+ end
23
+
24
+ def self.[](range)
25
+ range.gsub(/([><=])\s+/, '\1').split(/\b\s+(?!-)/).map do |r|
26
+ case r
27
+ when SemVer::VERSION
28
+ SemVer.new(pre(r)) .. SemVer.new(r)
29
+ when SemVer::SIMPLE_RANGE
30
+ r += ".0" unless SemVer.valid?(r.gsub(/x/i, '0'))
31
+ SemVer.new(r.gsub(/x/i, '0'))...SemVer.new(r.gsub(/(\d+)\.x/i) { "#{$1.to_i + 1}.0" } + '-')
32
+ when /\s+-\s+/
33
+ a, b = r.split(/\s+-\s+/)
34
+ SemVer.new(pre(a)) .. SemVer.new(b)
35
+ when /^~/
36
+ ver = r.sub(/~/, '').split('.').map(&:to_i)
37
+ start = (ver + [0] * (3 - ver.length)).join('.')
38
+
39
+ ver.pop unless ver.length == 1
40
+ ver[-1] = ver.last + 1
41
+
42
+ finish = (ver + [0] * (3 - ver.length)).join('.')
43
+ SemVer.new(pre(start)) ... SemVer.new(pre(finish))
44
+ when /^>=/
45
+ ver = r.sub(/^>=/, '')
46
+ SemVer.new(pre(ver)) .. SemVer::MAX
47
+ when /^<=/
48
+ ver = r.sub(/^<=/, '')
49
+ SemVer::MIN .. SemVer.new(ver)
50
+ when /^>/
51
+ if r =~ /-/
52
+ ver = [r[1..-1]]
53
+ else
54
+ ver = r.sub(/^>/, '').split('.').map(&:to_i)
55
+ ver[2] = ver.last + 1
56
+ end
57
+ SemVer.new(ver.join('.') + '-') .. SemVer::MAX
58
+ when /^</
59
+ ver = r.sub(/^</, '')
60
+ SemVer::MIN ... SemVer.new(pre(ver))
61
+ else
62
+ (1..1)
63
+ end
64
+ end.inject { |a,e| a & e }
65
+ end
66
+
67
+ attr_reader :major, :minor, :tiny, :special
68
+
69
+ def initialize(ver)
70
+ unless SemVer.valid?(ver)
71
+ raise ArgumentError.new("Invalid version string '#{ver}'!")
72
+ end
73
+
74
+ @major, @minor, @tiny, @special = VERSION.match(ver).captures.map do |x|
75
+ # Because Kernel#Integer tries to interpret hex and octal strings, which
76
+ # we specifically do not want, and which cannot be overridden in 1.8.7.
77
+ Float(x).to_i rescue x
78
+ end
79
+ end
80
+
81
+ def <=>(other)
82
+ other = SemVer.new("#{other}") unless other.is_a? SemVer
83
+ return self.major <=> other.major unless self.major == other.major
84
+ return self.minor <=> other.minor unless self.minor == other.minor
85
+ return self.tiny <=> other.tiny unless self.tiny == other.tiny
86
+
87
+ return 0 if self.special == other.special
88
+ return 1 if self.special == ''
89
+ return -1 if other.special == ''
90
+
91
+ return self.special <=> other.special
92
+ end
93
+
94
+ def matched_by?(pattern)
95
+ # For the time being, this is restricted to exact version matches and
96
+ # simple range patterns. In the future, we should implement some or all of
97
+ # the comparison operators here:
98
+ # https://github.com/isaacs/node-semver/blob/d474801/semver.js#L340
99
+
100
+ case pattern
101
+ when SIMPLE_RANGE
102
+ pattern = SIMPLE_RANGE.match(pattern).captures
103
+ pattern[1] = @minor unless pattern[1] && pattern[1] !~ /x/i
104
+ pattern[2] = @tiny unless pattern[2] && pattern[2] !~ /x/i
105
+ [@major, @minor, @tiny] == pattern.map { |x| x.to_i }
106
+ when VERSION
107
+ self == SemVer.new(pattern)
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ def inspect
114
+ @vstring || "v#{@major}.#{@minor}.#{@tiny}#{@special}"
115
+ end
116
+ alias :to_s :inspect
117
+
118
+ MIN = SemVer.new('0.0.0-')
119
+ MIN.instance_variable_set(:@vstring, 'vMIN')
120
+
121
+ MAX = SemVer.new('8.0.0')
122
+ MAX.instance_variable_set(:@major, (1.0/0)) # => Infinity
123
+ MAX.instance_variable_set(:@vstring, 'vMAX')
124
+ end
metadata CHANGED
@@ -1,16 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: r10k
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1rc1
5
- prerelease: 5
4
+ version: 0.0.1
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - adrien@somethingsinistral.net
8
+ - Adrien Thebo
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-26 00:00:00.000000000 Z
12
+ date: 2013-01-04 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: colored
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: cri
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -28,13 +44,13 @@ dependencies:
28
44
  - !ruby/object:Gem::Version
29
45
  version: 2.3.0
30
46
  - !ruby/object:Gem::Dependency
31
- name: shellter
47
+ name: systemu
32
48
  requirement: !ruby/object:Gem::Requirement
33
49
  none: false
34
50
  requirements:
35
51
  - - ~>
36
52
  - !ruby/object:Gem::Version
37
- version: 0.9.6
53
+ version: 2.5.2
38
54
  type: :runtime
39
55
  prerelease: false
40
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,15 +58,15 @@ dependencies:
42
58
  requirements:
43
59
  - - ~>
44
60
  - !ruby/object:Gem::Version
45
- version: 0.9.6
61
+ version: 2.5.2
46
62
  - !ruby/object:Gem::Dependency
47
- name: popen4
63
+ name: middleware
48
64
  requirement: !ruby/object:Gem::Requirement
49
65
  none: false
50
66
  requirements:
51
67
  - - ~>
52
68
  - !ruby/object:Gem::Version
53
- version: 0.1.2
69
+ version: 0.1.0
54
70
  type: :runtime
55
71
  prerelease: false
56
72
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,30 +74,69 @@ dependencies:
58
74
  requirements:
59
75
  - - ~>
60
76
  - !ruby/object:Gem::Version
61
- version: 0.1.2
77
+ version: 0.1.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: json
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.7.6
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.7.6
94
+ - !ruby/object:Gem::Dependency
95
+ name: log4r
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: 1.1.10
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 1.1.10
62
110
  description: ! " R10K is an implementation of the Dynamic Puppet environments based
63
111
  on git repositories\n as described in http://puppetlabs.com/blog/git-workflow-and-puppet-environments/.
64
112
  It\n aggressively caches and tries to minimize network activity to ensure that
65
113
  interactive\n deployment is as fast as possible.\n"
66
- email:
114
+ email: adrien@somethingsinistral.net
67
115
  executables:
68
116
  - r10k
69
117
  extensions: []
70
118
  extra_rdoc_files: []
71
119
  files:
72
120
  - bin/r10k
73
- - lib/r10k/cli/environment/cache.rb
121
+ - lib/r10k/action/environment.rb
122
+ - lib/r10k/action/module.rb
123
+ - lib/r10k/action.rb
124
+ - lib/r10k/cli/cache.rb
74
125
  - lib/r10k/cli/environment/deploy.rb
75
126
  - lib/r10k/cli/environment/list.rb
127
+ - lib/r10k/cli/environment/stale.rb
76
128
  - lib/r10k/cli/environment.rb
77
129
  - lib/r10k/cli/module/deploy.rb
78
130
  - lib/r10k/cli/module/list.rb
79
131
  - lib/r10k/cli/module.rb
132
+ - lib/r10k/cli/synchronize.rb
80
133
  - lib/r10k/cli.rb
134
+ - lib/r10k/deployment/environment_collection.rb
81
135
  - lib/r10k/deployment.rb
82
- - lib/r10k/environment_collection.rb
136
+ - lib/r10k/errors.rb
83
137
  - lib/r10k/librarian/dsl.rb
84
138
  - lib/r10k/librarian.rb
139
+ - lib/r10k/logging.rb
85
140
  - lib/r10k/module/forge.rb
86
141
  - lib/r10k/module/git.rb
87
142
  - lib/r10k/module.rb
@@ -90,6 +145,7 @@ files:
90
145
  - lib/r10k/util/interp.rb
91
146
  - lib/r10k/version.rb
92
147
  - lib/r10k.rb
148
+ - lib/semver.rb
93
149
  homepage: http://github.com/adrienthebo/r10k
94
150
  licenses: []
95
151
  post_install_message:
@@ -105,9 +161,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
161
  required_rubygems_version: !ruby/object:Gem::Requirement
106
162
  none: false
107
163
  requirements:
108
- - - ! '>'
164
+ - - ! '>='
109
165
  - !ruby/object:Gem::Version
110
- version: 1.3.1
166
+ version: '0'
111
167
  requirements: []
112
168
  rubyforge_project:
113
169
  rubygems_version: 1.8.23