r10k 0.0.1rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/r10k +5 -0
- data/lib/r10k.rb +6 -0
- data/lib/r10k/cli.rb +33 -0
- data/lib/r10k/cli/environment.rb +26 -0
- data/lib/r10k/cli/environment/cache.rb +23 -0
- data/lib/r10k/cli/environment/deploy.rb +46 -0
- data/lib/r10k/cli/environment/list.rb +27 -0
- data/lib/r10k/cli/module.rb +25 -0
- data/lib/r10k/cli/module/deploy.rb +55 -0
- data/lib/r10k/cli/module/list.rb +45 -0
- data/lib/r10k/deployment.rb +60 -0
- data/lib/r10k/environment_collection.rb +53 -0
- data/lib/r10k/librarian.rb +31 -0
- data/lib/r10k/librarian/dsl.rb +20 -0
- data/lib/r10k/module.rb +45 -0
- data/lib/r10k/module/forge.rb +19 -0
- data/lib/r10k/module/git.rb +24 -0
- data/lib/r10k/root.rb +95 -0
- data/lib/r10k/synchro/git.rb +186 -0
- data/lib/r10k/util/interp.rb +25 -0
- data/lib/r10k/version.rb +3 -0
- metadata +117 -0
data/bin/r10k
ADDED
data/lib/r10k.rb
ADDED
data/lib/r10k/cli.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'cri'
|
3
|
+
|
4
|
+
module R10K::CLI
|
5
|
+
def self.command
|
6
|
+
@cmd ||= Cri::Command.define do
|
7
|
+
name 'r10k'
|
8
|
+
usage 'r10k <subcommand> [options]'
|
9
|
+
summary 'Killer robot powered Puppet environment deployment'
|
10
|
+
description <<-EOD
|
11
|
+
r10k is a suite of commands to help deploy and manage puppet code for
|
12
|
+
complex environments.
|
13
|
+
EOD
|
14
|
+
|
15
|
+
flag :h, :help, 'show help for this command' do |value, cmd|
|
16
|
+
puts cmd.help
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
required :c, :config, 'Specify a configuration file' do |value, cmd|
|
21
|
+
R10K::Deployment.instance.configfile = value
|
22
|
+
end
|
23
|
+
|
24
|
+
run do |opts, args, cmd|
|
25
|
+
puts cmd.help
|
26
|
+
exit 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'r10k/cli/environment'
|
33
|
+
require 'r10k/cli/module'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'r10k/cli'
|
2
|
+
require 'cri'
|
3
|
+
|
4
|
+
module R10K::CLI
|
5
|
+
module Environment
|
6
|
+
def self.command
|
7
|
+
@cmd ||= Cri::Command.define do
|
8
|
+
name 'environment'
|
9
|
+
usage 'environment <subcommand>'
|
10
|
+
summary 'Operate on a specific environment'
|
11
|
+
|
12
|
+
required :e, :environment, 'Specify a particular environment'
|
13
|
+
|
14
|
+
run do |opts, args, cmd|
|
15
|
+
puts cmd.help
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
self.command.add_command(Environment.command)
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'r10k/cli/environment/list'
|
25
|
+
require 'r10k/cli/environment/deploy'
|
26
|
+
require 'r10k/cli/environment/cache'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'r10k/cli/environment'
|
2
|
+
require 'r10k/synchro/git'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
module R10K::CLI::Environment
|
6
|
+
module Cache
|
7
|
+
def self.command
|
8
|
+
@cmd ||= Cri::Command.define do
|
9
|
+
name 'cache'
|
10
|
+
usage 'cache'
|
11
|
+
summary 'Update cache for all sources'
|
12
|
+
|
13
|
+
run do |opts, args, cmd|
|
14
|
+
R10K::Deployment.instance[:sources].each_pair do |name, source|
|
15
|
+
synchro = R10K::Synchro::Git.new(source)
|
16
|
+
synchro.cache
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
self.command.add_command(Cache.command)
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'r10k/cli/environment'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module R10K::CLI::Environment
|
8
|
+
module Deploy
|
9
|
+
def self.command
|
10
|
+
@cmd ||= Cri::Command.define do
|
11
|
+
name 'deploy'
|
12
|
+
usage 'deploy'
|
13
|
+
summary 'Deploy an environment'
|
14
|
+
|
15
|
+
flag :r, :recurse, 'Recursively update submodules'
|
16
|
+
|
17
|
+
required :u, :update, "Enable or disable cache updating"
|
18
|
+
|
19
|
+
run do |opts, args, cmd|
|
20
|
+
deployment = R10K::Deployment.instance
|
21
|
+
env_list = deployment.environments
|
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]}
|
27
|
+
else
|
28
|
+
environments = env_list
|
29
|
+
end
|
30
|
+
|
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
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
self.command.add_command(Deploy.command)
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'r10k/cli/environment'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
module R10K::CLI::Environment
|
6
|
+
module List
|
7
|
+
def self.command
|
8
|
+
@cmd ||= Cri::Command.define do
|
9
|
+
name 'list'
|
10
|
+
usage 'list'
|
11
|
+
summary 'List all available environments'
|
12
|
+
|
13
|
+
run do |opts, args, cmd|
|
14
|
+
deployment = R10K::Deployment.instance
|
15
|
+
output = deployment.environments.inject('') do |str, root|
|
16
|
+
str << " - "
|
17
|
+
str << "#{root.name}: #{root.full_path}"
|
18
|
+
str << "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
puts output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self.command.add_command(List.command)
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'r10k/cli'
|
2
|
+
require 'cri'
|
3
|
+
|
4
|
+
module R10K::CLI
|
5
|
+
module Module
|
6
|
+
def self.command
|
7
|
+
@cmd ||= Cri::Command.define do
|
8
|
+
name 'module'
|
9
|
+
usage 'module <subcommand>'
|
10
|
+
summary 'Operate on a specific puppet module'
|
11
|
+
|
12
|
+
required :e, :environment, 'Specify a particular environment'
|
13
|
+
|
14
|
+
run do |opts, args, cmd|
|
15
|
+
puts cmd.help
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
self.command.add_command(Module.command)
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'r10k/cli/module/deploy'
|
25
|
+
require 'r10k/cli/module/list'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'r10k/cli/module'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module R10K::CLI::Module
|
8
|
+
module Deploy
|
9
|
+
def self.command
|
10
|
+
@cmd ||= Cri::Command.define do
|
11
|
+
name 'deploy'
|
12
|
+
usage 'deploy <module name>'
|
13
|
+
summary 'Deploy a module'
|
14
|
+
|
15
|
+
required :u, :update, "Enable or disable cache updating"
|
16
|
+
|
17
|
+
run do |opts, args, cmd|
|
18
|
+
|
19
|
+
unless (module_name = args[0])
|
20
|
+
puts cmd.help
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
deployment = R10K::Deployment.instance
|
25
|
+
env_list = deployment.environments
|
26
|
+
|
27
|
+
update_cache = (defined? opts[:update]) ? (opts[:update] == 'true') : false
|
28
|
+
|
29
|
+
if opts[:environment]
|
30
|
+
environments = env_list.select {|env| env.name == opts[:environment]}
|
31
|
+
else
|
32
|
+
environments = env_list
|
33
|
+
end
|
34
|
+
|
35
|
+
environments.each do |env|
|
36
|
+
FileUtils.mkdir_p env.full_path
|
37
|
+
env.sync! :update_cache => update_cache
|
38
|
+
|
39
|
+
mods = env.modules.select { |mod| mod.name == module_name }
|
40
|
+
|
41
|
+
if mods.empty?
|
42
|
+
puts "No modules with name #{module_name} matched in environments #{env.map(&:name).inspect}".red
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
|
46
|
+
mods.each do |mod|
|
47
|
+
mod.sync! :update_cache => update_cache
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
self.command.add_command(Deploy.command)
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'r10k/cli/module'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
module R10K::CLI::Module
|
6
|
+
module List
|
7
|
+
def self.command
|
8
|
+
@cmd ||= Cri::Command.define do
|
9
|
+
name 'list'
|
10
|
+
usage 'list'
|
11
|
+
summary 'List modules that are instantiated in environments'
|
12
|
+
|
13
|
+
run do |opts, args, cmd|
|
14
|
+
deployment = R10K::Deployment.instance
|
15
|
+
env_list = deployment.environments
|
16
|
+
|
17
|
+
update_cache = (defined? opts[:update]) ? (opts[:update] == 'true') : false
|
18
|
+
|
19
|
+
if opts[:environment]
|
20
|
+
environments = env_list.select {|env| env.name == opts[:environment]}
|
21
|
+
else
|
22
|
+
environments = env_list
|
23
|
+
end
|
24
|
+
|
25
|
+
printree = {}
|
26
|
+
|
27
|
+
environments.each do |env|
|
28
|
+
module_names = env.modules.map(&:name)
|
29
|
+
|
30
|
+
printree[env.name] = module_names
|
31
|
+
end
|
32
|
+
|
33
|
+
printree.each_pair do |env_name, mod_list|
|
34
|
+
puts " - #{env_name}"
|
35
|
+
mod_list.each do |mod|
|
36
|
+
puts " #{mod}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
self.command.add_command(List.command)
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/synchro/git'
|
3
|
+
require 'r10k/environment_collection'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
class R10K::Deployment
|
7
|
+
# Model a full installation of module directories and modules.
|
8
|
+
|
9
|
+
def self.instance
|
10
|
+
@myself ||= self.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@configfile = File.join(Dir.getwd, "config.yaml")
|
15
|
+
@update_cache = true
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :configfile
|
19
|
+
|
20
|
+
# Load up all module roots
|
21
|
+
#
|
22
|
+
# @return [Array<R10K::Root>]
|
23
|
+
def environments
|
24
|
+
collection = R10K::EnvironmentCollection.new(config)
|
25
|
+
collection.to_a
|
26
|
+
end
|
27
|
+
|
28
|
+
# Serve up the loaded config if it's already been loaded, otherwise try to
|
29
|
+
# load a config in the current wd.
|
30
|
+
def config
|
31
|
+
load_config unless @config
|
32
|
+
@config
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Object] A top level key from the config hash
|
36
|
+
def setting(key)
|
37
|
+
self.config[key]
|
38
|
+
end
|
39
|
+
alias_method :[], :setting
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Load and store a config file, and set relevant options
|
44
|
+
#
|
45
|
+
# @param [String] configfile The path to the YAML config file
|
46
|
+
def load_config
|
47
|
+
File.open(@configfile) { |fh| @config = YAML.load(fh.read) }
|
48
|
+
apply_config_settings
|
49
|
+
@config
|
50
|
+
rescue => e
|
51
|
+
raise "Couldn't load #{configfile}: #{e}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Apply config settings to the relevant classes after a config has been loaded.
|
55
|
+
def apply_config_settings
|
56
|
+
if @config[:cachedir]
|
57
|
+
R10K::Synchro::Git.cache_root = @config[:cachedir]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
|
3
|
+
class R10K::EnvironmentCollection
|
4
|
+
|
5
|
+
attr_reader :update_cache
|
6
|
+
|
7
|
+
def initialize(config, options = {:update_cache => true})
|
8
|
+
@config = config
|
9
|
+
@environments = []
|
10
|
+
|
11
|
+
@update_cache = options.delete(:update_cache)
|
12
|
+
|
13
|
+
load_all
|
14
|
+
end
|
15
|
+
|
16
|
+
# List subdirectories that aren't associated with an env
|
17
|
+
#
|
18
|
+
# If a branch associated with an environment is deleted then the associated
|
19
|
+
# branch ceases to be tracked. This method will scan a directory for
|
20
|
+
# subdirectories and return any subdirectories that don't have an active
|
21
|
+
# branch associated.
|
22
|
+
#
|
23
|
+
# @param [String] basedir The directory to scan
|
24
|
+
#
|
25
|
+
# @return [Array<String>] A list of filenames
|
26
|
+
def untracked_environments(basedir)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Array<R10K::Root>]
|
31
|
+
def to_a
|
32
|
+
@environments
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def load_all
|
38
|
+
@config[:sources].each_pair do |repo_name, repo_config|
|
39
|
+
synchro = R10K::Synchro::Git.new(repo_config['remote'])
|
40
|
+
synchro.cache if @update_cache
|
41
|
+
|
42
|
+
if repo_config['ref']
|
43
|
+
@environments << R10K::Root.new(repo_config)
|
44
|
+
else
|
45
|
+
synchro.branches.each do |branch|
|
46
|
+
@environments << R10K::Root.new(repo_config.merge({'ref' => branch}))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@environments
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
|
4
|
+
class R10K::Librarian
|
5
|
+
|
6
|
+
attr_reader :forge
|
7
|
+
|
8
|
+
def initialize(puppetfile)
|
9
|
+
@puppetfile = puppetfile
|
10
|
+
@modules = []
|
11
|
+
@forge = 'forge.puppetlabs.com'
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
dsl = R10K::Librarian::DSL.new(self)
|
16
|
+
dsl.instance_eval(File.read(@puppetfile), @puppetfile)
|
17
|
+
|
18
|
+
@modules
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method only exists because people tried being excessively clever.
|
22
|
+
def set_forge(forge)
|
23
|
+
@forge = forge
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_module(name, args)
|
27
|
+
@modules << [name, args]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'r10k/librarian/dsl'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'r10k/librarian'
|
2
|
+
|
3
|
+
class R10K::Librarian::DSL
|
4
|
+
|
5
|
+
def initialize(librarian)
|
6
|
+
@librarian = librarian
|
7
|
+
end
|
8
|
+
|
9
|
+
def mod(name, args = [])
|
10
|
+
@librarian.add_module(name, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def forge(location)
|
14
|
+
@librarian.set_forge(location)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args)
|
18
|
+
raise NoMethodError, "unrecognized declaration '#{method}'"
|
19
|
+
end
|
20
|
+
end
|
data/lib/r10k/module.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
|
3
|
+
class R10K::Module
|
4
|
+
|
5
|
+
# Register an inheriting class for later generation
|
6
|
+
def self.inherited(klass)
|
7
|
+
@klasses ||= []
|
8
|
+
@klasses << klass
|
9
|
+
end
|
10
|
+
|
11
|
+
# Look up the implementing class and instantiate an object
|
12
|
+
#
|
13
|
+
# This method takes the arguments for normal object generation and checks all
|
14
|
+
# inheriting classes to see if they implement the behavior needed to create
|
15
|
+
# the requested object. It selects the first class that can implement an object
|
16
|
+
# with `name, args`, and generates an object of that class.
|
17
|
+
#
|
18
|
+
# @param [String] name The unique name of the module
|
19
|
+
# @param [String] path The root path to install the module in
|
20
|
+
# @param [Object] args An arbitary value or set of values that specifies the implementation
|
21
|
+
#
|
22
|
+
# @return [Object < R10K::Module] A member of the implementing subclass
|
23
|
+
def self.new(name, path, args)
|
24
|
+
if implementation = @klasses.find { |klass| klass.implements(name, args) }
|
25
|
+
obj = implementation.send(:allocate)
|
26
|
+
obj.send(:initialize, name, path, args)
|
27
|
+
obj
|
28
|
+
else
|
29
|
+
raise "Module #{name} with args #{args.inspect} doesn't have an implementation. (Are you using the right arguments?)"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :name, :path
|
34
|
+
|
35
|
+
def initialize(name, path, args)
|
36
|
+
@name, @path, @args = name, path, args
|
37
|
+
end
|
38
|
+
|
39
|
+
def full_path
|
40
|
+
File.join(@path, @name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require 'r10k/module/git'
|
45
|
+
require 'r10k/module/forge'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
|
4
|
+
class R10K::Module::Forge < R10K::Module
|
5
|
+
|
6
|
+
def self.implements(name, args)
|
7
|
+
args.is_a? String and args.match /\d+\.\d+\.\d+/
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(name, path, args)
|
11
|
+
super
|
12
|
+
|
13
|
+
@version = @args
|
14
|
+
end
|
15
|
+
|
16
|
+
def sync!(options = {})
|
17
|
+
puts "#{self.class.name}#sync! is not implemented. Doing nothing."
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
require 'r10k/synchro/git'
|
4
|
+
|
5
|
+
class R10K::Module::Git < R10K::Module
|
6
|
+
|
7
|
+
def self.implements(name, args)
|
8
|
+
args.is_a? Hash and args.has_key?(:git)
|
9
|
+
rescue
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(name, path, args)
|
14
|
+
super
|
15
|
+
|
16
|
+
@remote = @args[:git]
|
17
|
+
@ref = (@args[:ref] || 'master')
|
18
|
+
end
|
19
|
+
|
20
|
+
def sync!(options = {})
|
21
|
+
synchro = R10K::Synchro::Git.new(@remote)
|
22
|
+
synchro.sync(full_path, @ref, options)
|
23
|
+
end
|
24
|
+
end
|
data/lib/r10k/root.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
require 'r10k/synchro/git'
|
4
|
+
|
5
|
+
class R10K::Root
|
6
|
+
|
7
|
+
# @!attribute [r] name
|
8
|
+
# The directory name of this root
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# @!attribute [r] basedir
|
12
|
+
# The basedir to clone the root into
|
13
|
+
attr_reader :basedir
|
14
|
+
|
15
|
+
# @!attribute [r] remote
|
16
|
+
# The location of the remote git repository
|
17
|
+
attr_reader :remote
|
18
|
+
|
19
|
+
# @!attribute [r] ref
|
20
|
+
# The git ref to instantiate into the basedir
|
21
|
+
attr_reader :ref
|
22
|
+
|
23
|
+
def initialize(hash)
|
24
|
+
parse_initialize_hash(hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def sync!(options = {})
|
28
|
+
synchro = R10K::Synchro::Git.new(@remote)
|
29
|
+
recursive_needed = !(synchro.cloned?(full_path))
|
30
|
+
synchro.sync(full_path, @ref, options)
|
31
|
+
|
32
|
+
sync_modules!(options) if recursive_needed
|
33
|
+
end
|
34
|
+
|
35
|
+
def sync_modules!(options = {})
|
36
|
+
modules.each do |mod|
|
37
|
+
mod.sync!(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def modules
|
42
|
+
librarian = R10K::Librarian.new("#{full_path}/Puppetfile")
|
43
|
+
|
44
|
+
module_data = librarian.load
|
45
|
+
|
46
|
+
@modules = module_data.map do |mod|
|
47
|
+
name = mod[0]
|
48
|
+
args = mod[1]
|
49
|
+
R10K::Module.new(name, "#{full_path}/modules", args)
|
50
|
+
end
|
51
|
+
rescue Errno::ENOENT
|
52
|
+
puts "#{self}: #{full_path} does not exist, cannot enumerate modules."
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
|
56
|
+
def full_path
|
57
|
+
@full_path ||= File.expand_path(File.join @basedir, @name)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def parse_initialize_hash(hash)
|
63
|
+
if hash['name']
|
64
|
+
@name = hash.delete('name')
|
65
|
+
elsif hash['ref']
|
66
|
+
@name = hash['ref']
|
67
|
+
else
|
68
|
+
raise "Unable to resolve directory name from options #{hash.inspect}"
|
69
|
+
end
|
70
|
+
|
71
|
+
# XXX This could be metaprogrammed, but it seems like the road to madness.
|
72
|
+
|
73
|
+
if hash['basedir']
|
74
|
+
@basedir = hash.delete('basedir')
|
75
|
+
else
|
76
|
+
raise "'basedir' is a required value for #{self.class}.new"
|
77
|
+
end
|
78
|
+
|
79
|
+
if hash['remote']
|
80
|
+
@remote = hash.delete('remote')
|
81
|
+
else
|
82
|
+
raise "'remote' is a required value for #{self.class}.new"
|
83
|
+
end
|
84
|
+
|
85
|
+
if hash['ref']
|
86
|
+
@ref = hash.delete('ref')
|
87
|
+
else
|
88
|
+
raise "'ref' is a required value for #{self.class}.new"
|
89
|
+
end
|
90
|
+
|
91
|
+
unless hash.empty?
|
92
|
+
raise "#{self.class}.new only expects keys ['name', 'basedir', 'remote', 'ref'], got #{hash.keys.inspect}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'shellter'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module R10K::Synchro; end
|
6
|
+
|
7
|
+
class R10K::Synchro::Git
|
8
|
+
# Define a thingy that can synchronize git repositories.
|
9
|
+
#
|
10
|
+
# This class is built to be a general purpose mechanism for syncing and
|
11
|
+
# caching git repositories.
|
12
|
+
#
|
13
|
+
# Class instances are memoized based on the git remote path. This way if a
|
14
|
+
# single git repository is instantiated multiple times, the object cache
|
15
|
+
# will only be updated once.
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :cache_root
|
19
|
+
|
20
|
+
# @return [Hash<R10K::Synchro::Git>] A hash of memoized class instances
|
21
|
+
def synchros
|
22
|
+
@synchros ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Memoize class instances and return existing instances.
|
26
|
+
#
|
27
|
+
# This allows objects to mark themselves as cached to prevent unnecessary
|
28
|
+
# cache refreshes.
|
29
|
+
#
|
30
|
+
# @param [String] remote A git remote URL
|
31
|
+
# @return [R10K::Synchro::Git]
|
32
|
+
def new(remote)
|
33
|
+
unless synchros[remote]
|
34
|
+
obj = self.allocate
|
35
|
+
obj.send(:initialize, remote)
|
36
|
+
synchros[remote] = obj
|
37
|
+
end
|
38
|
+
synchros[remote]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :remote
|
43
|
+
|
44
|
+
# Instantiates a new git synchro and optionally prepares for caching
|
45
|
+
#
|
46
|
+
# @param [String] remote A git remote URL
|
47
|
+
def initialize(remote)
|
48
|
+
@remote = remote
|
49
|
+
|
50
|
+
if self.class.cache_root
|
51
|
+
@cache_path = File.join(self.class.cache_root, @remote.gsub(/[^@\w-]/, '-'))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Synchronize the local git repository.
|
56
|
+
#
|
57
|
+
# @param [String] path The destination path for the files
|
58
|
+
# @param [String] ref The git ref to instantiate at the destination path
|
59
|
+
def sync(path, ref, options = {:update_cache => true})
|
60
|
+
path = File.expand_path(path)
|
61
|
+
cache if options[:update_cache]
|
62
|
+
|
63
|
+
if self.cloned?(path)
|
64
|
+
fetch(path)
|
65
|
+
else
|
66
|
+
clone(path)
|
67
|
+
end
|
68
|
+
reset(path, ref)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [TrueClass] if the git repository is cached
|
72
|
+
def has_cache?
|
73
|
+
@cache_path and File.directory? @cache_path
|
74
|
+
end
|
75
|
+
|
76
|
+
# Determine if repo has been cloned into a specific dir
|
77
|
+
#
|
78
|
+
# @param [String] dirname The directory to check
|
79
|
+
#
|
80
|
+
# @return [true, false] If the repo has already been cloned
|
81
|
+
def cloned?(directory)
|
82
|
+
File.directory?(File.join(directory, '.git'))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Update the git object cache repository if it hasn't been done
|
86
|
+
#
|
87
|
+
# @return [true, nil] If the cache was actually updated
|
88
|
+
def cache
|
89
|
+
unless @cached
|
90
|
+
cache!
|
91
|
+
@cached = true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Force a cache refresh
|
96
|
+
def cache!
|
97
|
+
if has_cache?
|
98
|
+
git "--git-dir #{@cache_path} fetch --prune"
|
99
|
+
else
|
100
|
+
FileUtils.mkdir_p File.dirname(File.join(@cache_path))
|
101
|
+
git "clone --mirror #{@remote} #{@cache_path}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Retrieve a list of cached branches for the git repo associated with this
|
106
|
+
# object.
|
107
|
+
#
|
108
|
+
# @return [Array<String>] A list of all cached remote branches
|
109
|
+
def branches
|
110
|
+
cache
|
111
|
+
output = git "--git-dir #{@cache_path} branch"
|
112
|
+
output.split("\n").map { |str| str[2..-1] }
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# Perform a non-bare clone of a git repository.
|
118
|
+
#
|
119
|
+
# If a cachedir is available and the repo is already cached, it will be
|
120
|
+
# used as an object reference to speed up the clone.
|
121
|
+
#
|
122
|
+
# @param [String] path The directory to create the repo working directory
|
123
|
+
def clone(path)
|
124
|
+
if has_cache?
|
125
|
+
git "clone --reference #{@cache_path} #{@remote} #{path}"
|
126
|
+
else
|
127
|
+
FileUtils.mkdir_p path unless File.directory? path
|
128
|
+
git "clone #{@remote} #{path}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def fetch(path)
|
133
|
+
if has_cache?
|
134
|
+
git "fetch --prune #{@cache_path}", path
|
135
|
+
else
|
136
|
+
git "fetch --prune", path
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Reset a git repo with a working directory to a specific ref
|
141
|
+
#
|
142
|
+
# @param [String] path The path to the working directory of the git repo
|
143
|
+
# @param [String] ref The git reference to reset to.
|
144
|
+
def reset(path, ref)
|
145
|
+
|
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
|
+
begin
|
151
|
+
commit = git "--git-dir #{@cache_path} rev-parse #{ref}^{commit}"
|
152
|
+
rescue RuntimeError => e
|
153
|
+
commit = "origin/#{ref}"
|
154
|
+
end
|
155
|
+
|
156
|
+
git "reset --hard #{commit}", path
|
157
|
+
end
|
158
|
+
|
159
|
+
# Wrap git commands
|
160
|
+
#
|
161
|
+
# @param [String] command_line_args The arguments for the git prompt
|
162
|
+
# @param [String] git_dir An optional git working directory
|
163
|
+
#
|
164
|
+
# @return [String] The git command output
|
165
|
+
def git(command_line_args, git_dir = nil)
|
166
|
+
args = []
|
167
|
+
|
168
|
+
if git_dir
|
169
|
+
args << "--work-tree" << git_dir
|
170
|
+
args << "--git-dir" << "#{git_dir}/.git"
|
171
|
+
end
|
172
|
+
|
173
|
+
args << command_line_args.split(/\s+/)
|
174
|
+
|
175
|
+
result = Shellter.run!('git', args.join(' '))
|
176
|
+
puts "Execute: #{result.last_command}".green
|
177
|
+
|
178
|
+
stderr = result.stderr.read
|
179
|
+
stdout = result.stdout.read
|
180
|
+
|
181
|
+
puts stdout.blue unless stdout.empty?
|
182
|
+
puts stderr.red unless stderr.empty?
|
183
|
+
|
184
|
+
stdout
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
module R10K; end
|
3
|
+
module R10K::Util; end
|
4
|
+
|
5
|
+
module R10K::Util::Interp
|
6
|
+
|
7
|
+
# Interpolate a string with a given scope
|
8
|
+
#
|
9
|
+
# @param [String] string
|
10
|
+
# @param [Hash] scope
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
def interpolate_string(string, scope)
|
14
|
+
|
15
|
+
interp = string.clone
|
16
|
+
|
17
|
+
while (matchdata = interp.match /%\{.+?\}/)
|
18
|
+
var_name = matchdata[1].intern
|
19
|
+
var_data = scope[var_name]
|
20
|
+
interp.gsub!(/%\{#{var_name}\}/, var_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
interp
|
24
|
+
end
|
25
|
+
end
|
data/lib/r10k/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: r10k
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1rc1
|
5
|
+
prerelease: 5
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- adrien@somethingsinistral.net
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.3.0
|
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: 2.3.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: shellter
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.6
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.6
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: popen4
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.1.2
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.2
|
62
|
+
description: ! " R10K is an implementation of the Dynamic Puppet environments based
|
63
|
+
on git repositories\n as described in http://puppetlabs.com/blog/git-workflow-and-puppet-environments/.
|
64
|
+
It\n aggressively caches and tries to minimize network activity to ensure that
|
65
|
+
interactive\n deployment is as fast as possible.\n"
|
66
|
+
email:
|
67
|
+
executables:
|
68
|
+
- r10k
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files: []
|
71
|
+
files:
|
72
|
+
- bin/r10k
|
73
|
+
- lib/r10k/cli/environment/cache.rb
|
74
|
+
- lib/r10k/cli/environment/deploy.rb
|
75
|
+
- lib/r10k/cli/environment/list.rb
|
76
|
+
- lib/r10k/cli/environment.rb
|
77
|
+
- lib/r10k/cli/module/deploy.rb
|
78
|
+
- lib/r10k/cli/module/list.rb
|
79
|
+
- lib/r10k/cli/module.rb
|
80
|
+
- lib/r10k/cli.rb
|
81
|
+
- lib/r10k/deployment.rb
|
82
|
+
- lib/r10k/environment_collection.rb
|
83
|
+
- lib/r10k/librarian/dsl.rb
|
84
|
+
- lib/r10k/librarian.rb
|
85
|
+
- lib/r10k/module/forge.rb
|
86
|
+
- lib/r10k/module/git.rb
|
87
|
+
- lib/r10k/module.rb
|
88
|
+
- lib/r10k/root.rb
|
89
|
+
- lib/r10k/synchro/git.rb
|
90
|
+
- lib/r10k/util/interp.rb
|
91
|
+
- lib/r10k/version.rb
|
92
|
+
- lib/r10k.rb
|
93
|
+
homepage: http://github.com/adrienthebo/r10k
|
94
|
+
licenses: []
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>'
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.3.1
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.23
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Dynamic Puppet environments with Git
|
117
|
+
test_files: []
|