r10k 0.0.9 → 1.0.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/r10k +1 -1
- data/lib/r10k.rb +0 -4
- data/lib/r10k/cli.rb +9 -5
- data/lib/r10k/cli/deploy.rb +108 -0
- data/lib/r10k/cli/environment.rb +5 -1
- data/lib/r10k/cli/environment/deploy.rb +6 -28
- data/lib/r10k/cli/environment/list.rb +6 -10
- data/lib/r10k/cli/environment/stale.rb +6 -16
- data/lib/r10k/cli/module.rb +5 -1
- data/lib/r10k/cli/module/deploy.rb +5 -32
- data/lib/r10k/cli/module/list.rb +6 -27
- data/lib/r10k/cli/puppetfile.rb +76 -0
- data/lib/r10k/cli/synchronize.rb +8 -24
- data/lib/r10k/cli/version.rb +22 -0
- data/lib/r10k/deployment.rb +55 -26
- data/lib/r10k/deployment/config.rb +69 -0
- data/lib/r10k/{config → deployment/config}/loader.rb +9 -5
- data/lib/r10k/deployment/environment.rb +88 -0
- data/lib/r10k/deployment/source.rb +79 -0
- data/lib/r10k/errors.rb +3 -5
- data/lib/r10k/execution.rb +43 -0
- data/lib/r10k/git/cache.rb +131 -0
- data/lib/r10k/git/errors.rb +34 -0
- data/lib/r10k/git/repository.rb +74 -0
- data/lib/r10k/git/working_dir.rb +142 -0
- data/lib/r10k/logging.rb +6 -2
- data/lib/r10k/module.rb +10 -13
- data/lib/r10k/module/forge.rb +35 -22
- data/lib/r10k/module/git.rb +18 -8
- data/lib/r10k/puppetfile.rb +107 -0
- data/lib/r10k/task.rb +13 -0
- data/lib/r10k/task/deployment.rb +151 -0
- data/lib/r10k/task/environment.rb +29 -0
- data/lib/r10k/task/module.rb +18 -0
- data/lib/r10k/task/puppetfile.rb +99 -0
- data/lib/r10k/task_runner.rb +72 -0
- data/lib/r10k/util/purgeable.rb +50 -0
- data/lib/r10k/version.rb +1 -1
- data/spec/unit/deployment/environment_spec.rb +19 -0
- data/spec/unit/git/cache_spec.rb +37 -0
- data/spec/unit/git/working_dir_spec.rb +15 -0
- data/spec/unit/module/forge_spec.rb +95 -0
- data/spec/unit/module_spec.rb +29 -0
- metadata +79 -44
- data/lib/r10k/action.rb +0 -7
- data/lib/r10k/action/environment.rb +0 -74
- data/lib/r10k/action/module.rb +0 -36
- data/lib/r10k/cli/cache.rb +0 -32
- data/lib/r10k/config.rb +0 -46
- data/lib/r10k/deployment/environment_collection.rb +0 -75
- data/lib/r10k/librarian.rb +0 -31
- data/lib/r10k/librarian/dsl.rb +0 -20
- data/lib/r10k/root.rb +0 -98
- data/lib/r10k/synchro/git.rb +0 -226
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'r10k/logging'
|
2
|
+
require 'r10k/errors'
|
3
|
+
|
4
|
+
require 'systemu'
|
5
|
+
|
6
|
+
module R10K
|
7
|
+
module Execution
|
8
|
+
include R10K::Logging
|
9
|
+
|
10
|
+
# Execute a command and return stdout
|
11
|
+
#
|
12
|
+
# @params [String] cmd
|
13
|
+
# @params [Hash] opts
|
14
|
+
#
|
15
|
+
# @option opts [String] :event An optional log event name. Defaults to cmd.
|
16
|
+
#
|
17
|
+
# @raise [R10K::ExecutionFailure] If the executed command exited with a
|
18
|
+
# nonzero exit code.
|
19
|
+
#
|
20
|
+
# @return [String] the stdout from the command
|
21
|
+
def execute(cmd, opts = {})
|
22
|
+
|
23
|
+
event = opts[:event] || cmd
|
24
|
+
|
25
|
+
logger.debug1 "Execute: #{event.inspect}"
|
26
|
+
|
27
|
+
status, stdout, stderr = systemu(cmd)
|
28
|
+
|
29
|
+
logger.debug2 "[#{event}] STDOUT: #{stdout.chomp}" unless stdout.empty?
|
30
|
+
logger.debug2 "[#{event}] STDERR: #{stderr.chomp}" unless stderr.empty?
|
31
|
+
|
32
|
+
unless status == 0
|
33
|
+
msg = "#{cmd.inspect} returned with non-zero exit value #{status.exitstatus}"
|
34
|
+
e = R10K::ExecutionFailure.new(msg)
|
35
|
+
e.exit_code = status
|
36
|
+
e.stdout = stdout
|
37
|
+
e.stderr = stderr
|
38
|
+
raise e
|
39
|
+
end
|
40
|
+
stdout
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'r10k/logging'
|
2
|
+
require 'r10k/git/repository'
|
3
|
+
|
4
|
+
module R10K
|
5
|
+
module Git
|
6
|
+
class Cache < R10K::Git::Repository
|
7
|
+
# Mirror a git repository for use shared git object repositories
|
8
|
+
#
|
9
|
+
# @see man git-clone(1)
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# @!attribute [r] cache_root
|
14
|
+
# @return [String] The directory to use as the cache.
|
15
|
+
attr_writer :cache_root
|
16
|
+
|
17
|
+
def cache_root
|
18
|
+
@cache_root
|
19
|
+
end
|
20
|
+
|
21
|
+
# Memoize class instances and return existing instances.
|
22
|
+
#
|
23
|
+
# This allows objects to mark themselves as cached to prevent unnecessary
|
24
|
+
# cache refreshes.
|
25
|
+
#
|
26
|
+
# @param [String] remote A git remote URL
|
27
|
+
# @return [R10K::Synchro::Git]
|
28
|
+
def new(remote)
|
29
|
+
@repos ||= {}
|
30
|
+
unless @repos[remote]
|
31
|
+
obj = self.allocate
|
32
|
+
obj.send(:initialize, remote)
|
33
|
+
@repos[remote] = obj
|
34
|
+
end
|
35
|
+
@repos[remote]
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear!
|
39
|
+
@repos = {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
include R10K::Logging
|
44
|
+
|
45
|
+
# @!attribute [r] remote
|
46
|
+
# @return [String] The git repository remote
|
47
|
+
attr_reader :remote
|
48
|
+
|
49
|
+
# @!attribute [r] cache_root
|
50
|
+
# Where to keep the git object cache. Defaults to ~/.r10k/git if a class
|
51
|
+
# level value is not set.
|
52
|
+
# @return [String] The directory to use as the cache
|
53
|
+
attr_reader :cache_root
|
54
|
+
|
55
|
+
# @!attribute [r] path
|
56
|
+
# @return [String] The path to the git cache repository
|
57
|
+
attr_reader :path
|
58
|
+
|
59
|
+
# @param [String] remote
|
60
|
+
# @param [String] cache_root
|
61
|
+
def initialize(remote)
|
62
|
+
@remote = remote
|
63
|
+
|
64
|
+
@cache_root = self.class.cache_root || default_cache_root
|
65
|
+
|
66
|
+
@path = File.join(@cache_root, sanitized_dirname)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sync
|
70
|
+
if @synced
|
71
|
+
# XXX This gets really spammy. Might be good to turn it on later, but for
|
72
|
+
# general work it's way much.
|
73
|
+
#logger.debug "#{@remote} already synced this run, not syncing again"
|
74
|
+
else
|
75
|
+
sync!
|
76
|
+
@synced = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def sync!
|
81
|
+
if cached?
|
82
|
+
# XXX This gets really spammy. Might be good to turn it on later, but for
|
83
|
+
# general work it's way much.
|
84
|
+
#logger.debug "Updating existing cache at #{@path}"
|
85
|
+
git "fetch --prune", :git_dir => @path
|
86
|
+
else
|
87
|
+
logger.debug "Creating new git cache for #{@remote.inspect}"
|
88
|
+
FileUtils.mkdir_p cache_root unless File.exist? @cache_root
|
89
|
+
git "clone --mirror #{@remote} #{@path}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Array<String>] A list the branches for the git repository
|
94
|
+
def branches
|
95
|
+
output = git "branch", :git_dir => @path
|
96
|
+
output.split("\n").map do |str|
|
97
|
+
# the `git branch` command returns output like this:
|
98
|
+
# <pre>
|
99
|
+
# 0.11.x
|
100
|
+
# 0.12.x
|
101
|
+
# * master
|
102
|
+
# passenger_scoping
|
103
|
+
# </pre>
|
104
|
+
#
|
105
|
+
# The string index notation strips off the leading whitespace/asterisk
|
106
|
+
str[2..-1]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [true, false] If the repository has been locally cached
|
111
|
+
def cached?
|
112
|
+
File.exist? @path
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# Reformat the remote name into something that can be used as a directory
|
118
|
+
def sanitized_dirname
|
119
|
+
@remote.gsub(/[^@\w\.-]/, '-')
|
120
|
+
end
|
121
|
+
|
122
|
+
def default_cache_root
|
123
|
+
File.expand_path('~/.r10k/git')
|
124
|
+
end
|
125
|
+
|
126
|
+
def git_dir
|
127
|
+
@path
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module R10K
|
2
|
+
module Git
|
3
|
+
class NonexistentHashError < StandardError
|
4
|
+
# Raised when a hash was requested that can't be found in the repository
|
5
|
+
|
6
|
+
attr_reader :hash
|
7
|
+
attr_reader :git_dir
|
8
|
+
|
9
|
+
def initialize(msg = nil, git_dir = nil)
|
10
|
+
super(msg)
|
11
|
+
|
12
|
+
@git_dir = git_dir
|
13
|
+
end
|
14
|
+
|
15
|
+
HASHLIKE = %r[[A-Fa-f0-9]]
|
16
|
+
|
17
|
+
# Print a friendly error message if an object hash is given as the message
|
18
|
+
def message
|
19
|
+
msg = super
|
20
|
+
if msg and msg.match(HASHLIKE)
|
21
|
+
msg = "Could not locate hash #{msg.inspect} in repository"
|
22
|
+
elsif msg.nil?
|
23
|
+
msg = "Could not locate hash in repository"
|
24
|
+
end
|
25
|
+
|
26
|
+
if @git_dir
|
27
|
+
msg << " at #{@git_dir}. (Does the remote repository need to be updated?)"
|
28
|
+
end
|
29
|
+
|
30
|
+
msg
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'r10k/execution'
|
2
|
+
require 'r10k/git/errors'
|
3
|
+
|
4
|
+
module R10K
|
5
|
+
module Git
|
6
|
+
class Repository
|
7
|
+
# Define an abstract base class for git repositories.
|
8
|
+
|
9
|
+
include R10K::Execution
|
10
|
+
|
11
|
+
# @!attribute [r] remote
|
12
|
+
# @return [String] The URL to the git repository
|
13
|
+
attr_reader :remote
|
14
|
+
|
15
|
+
# @!attribute [r] basedir
|
16
|
+
# @return [String] The basedir for the working directory
|
17
|
+
attr_reader :basedir
|
18
|
+
|
19
|
+
# @!attribute [r] dirname
|
20
|
+
# @return [String] The name for the directory
|
21
|
+
attr_reader :dirname
|
22
|
+
|
23
|
+
# Resolve a ref to a commit hash
|
24
|
+
#
|
25
|
+
# @param [String] ref
|
26
|
+
#
|
27
|
+
# @return [String] The dereferenced hash of `ref`
|
28
|
+
def rev_parse(ref)
|
29
|
+
commit = git "rev-parse #{ref}^{commit}", :git_dir => git_dir
|
30
|
+
commit.chomp
|
31
|
+
rescue R10K::ExecutionFailure
|
32
|
+
raise R10K::Git::NonexistentHashError.new(ref, git_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Wrap git commands
|
36
|
+
#
|
37
|
+
# @param [String] command_line_args The arguments for the git prompt
|
38
|
+
# @param [Hash] opts
|
39
|
+
#
|
40
|
+
# @option opts [String] :git_dir
|
41
|
+
# @option opts [String] :work_tree
|
42
|
+
# @option opts [String] :work_tree
|
43
|
+
#
|
44
|
+
# @raise [R10K::ExecutionFailure] If the executed command exited with a
|
45
|
+
# nonzero exit code.
|
46
|
+
#
|
47
|
+
# @return [String] The git command output
|
48
|
+
def git(command_line_args, opts = {})
|
49
|
+
args = %w{git}
|
50
|
+
|
51
|
+
log_event = "git #{command_line_args}"
|
52
|
+
log_event << ", args: #{opts.inspect}" unless opts.empty?
|
53
|
+
|
54
|
+
|
55
|
+
if opts[:path]
|
56
|
+
args << "--git-dir #{opts[:path]}/.git"
|
57
|
+
args << "--work-tree #{opts[:path]}"
|
58
|
+
else
|
59
|
+
if opts[:git_dir]
|
60
|
+
args << "--git-dir #{opts[:git_dir]}"
|
61
|
+
end
|
62
|
+
if opts[:work_tree]
|
63
|
+
args << "--work-tree #{opts[:work_tree]}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
args << command_line_args
|
68
|
+
cmd = args.join(' ')
|
69
|
+
|
70
|
+
execute(cmd, :event => log_event)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'r10k/logging'
|
3
|
+
require 'r10k/git/cache'
|
4
|
+
|
5
|
+
module R10K
|
6
|
+
module Git
|
7
|
+
class WorkingDir < R10K::Git::Repository
|
8
|
+
# Implements sparse git repositories with shared objects
|
9
|
+
#
|
10
|
+
# Working directory instances use the git alternatives object store, so that
|
11
|
+
# working directories only contain checked out files and all object files are
|
12
|
+
# shared.
|
13
|
+
|
14
|
+
include R10K::Logging
|
15
|
+
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
# @!attribute [r] cache
|
19
|
+
# @return [R10K::Git::Cache] The object cache backing this working directory
|
20
|
+
attr_reader :cache
|
21
|
+
|
22
|
+
# @!attribute [r] ref
|
23
|
+
# @return [String] The git reference to use check out in the given directory
|
24
|
+
attr_reader :ref
|
25
|
+
|
26
|
+
# Instantiates a new git synchro and optionally prepares for caching
|
27
|
+
#
|
28
|
+
# @param [String] ref
|
29
|
+
# @param [String] remote
|
30
|
+
# @param [String] basedir
|
31
|
+
# @param [String] dirname
|
32
|
+
def initialize(ref, remote, basedir, dirname = nil)
|
33
|
+
@ref = ref
|
34
|
+
@remote = remote
|
35
|
+
@basedir = basedir
|
36
|
+
@dirname = dirname || ref
|
37
|
+
|
38
|
+
@full_path = File.join(@basedir, @dirname)
|
39
|
+
|
40
|
+
@cache = R10K::Git::Cache.new(@remote)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Synchronize the local git repository.
|
44
|
+
def sync
|
45
|
+
# TODO stop forcing a sync every time.
|
46
|
+
@cache.sync
|
47
|
+
|
48
|
+
if cloned?
|
49
|
+
fetch
|
50
|
+
else
|
51
|
+
clone
|
52
|
+
end
|
53
|
+
reset
|
54
|
+
end
|
55
|
+
|
56
|
+
# Determine if repo has been cloned into a specific dir
|
57
|
+
#
|
58
|
+
# @return [true, false] If the repo has already been cloned
|
59
|
+
def cloned?
|
60
|
+
File.directory? git_dir
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def set_cache_remote
|
66
|
+
# XXX This is crude but it'll ensure that the right remote is used for
|
67
|
+
# the cache.
|
68
|
+
if remote_url('cache') == @cache.path
|
69
|
+
logger.debug1 "Git repo #{@full_path} cache remote already set correctly"
|
70
|
+
else
|
71
|
+
git "remote set-url cache #{@cache.path}", :path => @full_path
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Perform a non-bare clone of a git repository.
|
76
|
+
def clone
|
77
|
+
# We do the clone against the target repo using the `--reference` flag so
|
78
|
+
# that doing a normal `git pull` on a directory will work.
|
79
|
+
git "clone --reference #{@cache.path} #{@remote} #{@full_path}"
|
80
|
+
git "remote add cache #{@cache.path}", :path => @full_path
|
81
|
+
end
|
82
|
+
|
83
|
+
def fetch
|
84
|
+
set_cache_remote
|
85
|
+
git "fetch --prune cache", :path => @full_path
|
86
|
+
end
|
87
|
+
|
88
|
+
# Reset a git repo with a working directory to a specific ref
|
89
|
+
def reset
|
90
|
+
commit = cache.rev_parse(@ref)
|
91
|
+
current = rev_parse('HEAD')
|
92
|
+
|
93
|
+
if commit == current
|
94
|
+
logger.debug1 "Git repo #{@full_path} is already at #{commit}, no need to reset"
|
95
|
+
return
|
96
|
+
end
|
97
|
+
|
98
|
+
begin
|
99
|
+
git "reset --hard #{commit}", :path => @full_path
|
100
|
+
rescue R10K::ExecutionFailure => e
|
101
|
+
logger.error "Unable to locate commit object #{commit} in git repo #{@full_path}"
|
102
|
+
raise
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Resolve a ref to a commit hash
|
107
|
+
#
|
108
|
+
# @param [String] ref
|
109
|
+
#
|
110
|
+
# @return [String] The dereferenced hash of `ref`
|
111
|
+
def rev_parse(ref)
|
112
|
+
commit = git "rev-parse #{ref}^{commit}", :path => @full_path
|
113
|
+
commit.chomp
|
114
|
+
rescue R10K::ExecutionFailure => e
|
115
|
+
logger.error "Could not resolve ref #{ref.inspect} for git repo #{@full_path}"
|
116
|
+
raise
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param [String] name The remote to retrieve the URl for
|
120
|
+
# @return [String] The git remote URL
|
121
|
+
def remote_url(remote_name)
|
122
|
+
output = git "remote -v", :path => @full_path
|
123
|
+
|
124
|
+
remotes = {}
|
125
|
+
|
126
|
+
output.each_line do |line|
|
127
|
+
if mdata = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)/)
|
128
|
+
name = mdata[1]
|
129
|
+
remote = mdata[2]
|
130
|
+
remotes[name] = remote
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
remotes[remote_name]
|
135
|
+
end
|
136
|
+
|
137
|
+
def git_dir
|
138
|
+
File.join(@full_path, '.git')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/r10k/logging.rb
CHANGED
@@ -7,17 +7,21 @@ module R10K::Logging
|
|
7
7
|
|
8
8
|
include Log4r
|
9
9
|
|
10
|
+
def logger_name
|
11
|
+
self.class.to_s
|
12
|
+
end
|
13
|
+
|
10
14
|
def logger
|
11
15
|
unless @logger
|
12
|
-
@logger = Log4r::Logger.new(self.
|
16
|
+
@logger = Log4r::Logger.new(self.logger_name)
|
13
17
|
@logger.add R10K::Logging.outputter
|
14
18
|
end
|
15
19
|
@logger
|
16
20
|
end
|
17
21
|
|
18
22
|
class << self
|
23
|
+
include Log4r
|
19
24
|
|
20
|
-
include Log4r
|
21
25
|
def included(klass)
|
22
26
|
unless @log4r_loaded
|
23
27
|
Configurator.custom_levels(*%w{DEBUG2 DEBUG1 DEBUG INFO NOTICE WARN ERROR FATAL})
|