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,29 @@
|
|
1
|
+
require 'r10k/task'
|
2
|
+
require 'r10k/task/puppetfile'
|
3
|
+
|
4
|
+
module R10K
|
5
|
+
module Task
|
6
|
+
module Environment
|
7
|
+
class Deploy < R10K::Task::Base
|
8
|
+
|
9
|
+
attr_writer :update_puppetfile
|
10
|
+
|
11
|
+
def initialize(environment)
|
12
|
+
@environment = environment
|
13
|
+
|
14
|
+
@update_puppetfile = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
logger.notice "Deploying environment #{@environment.dirname}"
|
19
|
+
@environment.sync
|
20
|
+
|
21
|
+
if @update_puppetfile
|
22
|
+
task = R10K::Task::Puppetfile::Sync.new(@environment.puppetfile)
|
23
|
+
task_runner.insert_task_after(self, task)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'r10k/task'
|
2
|
+
|
3
|
+
module R10K
|
4
|
+
module Task
|
5
|
+
module Module
|
6
|
+
class Sync < R10K::Task::Base
|
7
|
+
def initialize(mod)
|
8
|
+
@mod = mod
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
logger.info "Deploying #{@mod.name} into #{@mod.basedir}"
|
13
|
+
@mod.sync
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'r10k/task'
|
2
|
+
require 'r10k/task/module'
|
3
|
+
|
4
|
+
require 'r10k/task_runner'
|
5
|
+
|
6
|
+
module R10K
|
7
|
+
module Task
|
8
|
+
module Puppetfile
|
9
|
+
class Sync < R10K::Task::Base
|
10
|
+
def initialize(puppetfile)
|
11
|
+
@puppetfile = puppetfile
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
logger.info "Loading modules from Puppetfile into queue"
|
16
|
+
|
17
|
+
@puppetfile.load
|
18
|
+
@puppetfile.modules.each do |mod|
|
19
|
+
task = R10K::Task::Module::Sync.new(mod)
|
20
|
+
task_runner.insert_task_after(self, task)
|
21
|
+
end
|
22
|
+
|
23
|
+
purge_task = Purge.new(@puppetfile)
|
24
|
+
task_runner.append_task purge_task
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class DeployModules < R10K::Task::Base
|
29
|
+
|
30
|
+
attr_accessor :module_names
|
31
|
+
|
32
|
+
def initialize(puppetfile)
|
33
|
+
@puppetfile = puppetfile
|
34
|
+
end
|
35
|
+
|
36
|
+
def call
|
37
|
+
logger.debug "Updating module list for Puppetfile #{@puppetfile.basedir}"
|
38
|
+
@puppetfile.load
|
39
|
+
load_modulemap!
|
40
|
+
|
41
|
+
existing = @modulemap.keys
|
42
|
+
|
43
|
+
warn_on_missing(existing, @module_names)
|
44
|
+
|
45
|
+
to_deploy = existing & @module_names
|
46
|
+
|
47
|
+
to_deploy.each do |mod_name|
|
48
|
+
mod = @modulemap[mod_name]
|
49
|
+
task = R10K::Task::Module::Sync.new(mod)
|
50
|
+
task_runner.insert_task_after(self, task)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def warn_on_missing(existing, requested)
|
57
|
+
missing_modules = requested - existing
|
58
|
+
|
59
|
+
unless missing_modules.empty?
|
60
|
+
task_runner.succeeded = false
|
61
|
+
|
62
|
+
missing_modules.each do |missing|
|
63
|
+
logger.warn "Unable to deploy module #{missing}: not listed in #{@puppetfile.puppetfile_path}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_modulemap!
|
69
|
+
@modulemap = @puppetfile.modules.inject({}) do |hash, mod|
|
70
|
+
hash[mod.name] = mod
|
71
|
+
hash
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Purge < R10K::Task::Base
|
77
|
+
def initialize(puppetfile)
|
78
|
+
@puppetfile = puppetfile
|
79
|
+
end
|
80
|
+
|
81
|
+
def call
|
82
|
+
moduledir = @puppetfile.moduledir
|
83
|
+
|
84
|
+
@puppetfile.load
|
85
|
+
|
86
|
+
stale_mods = @puppetfile.stale_contents
|
87
|
+
|
88
|
+
if stale_mods.empty?
|
89
|
+
logger.debug "No stale modules in #{moduledir}"
|
90
|
+
else
|
91
|
+
logger.info "Purging stale modules from #{moduledir}"
|
92
|
+
logger.debug "Stale modules in #{moduledir}: #{stale_mods.join(', ')}"
|
93
|
+
@puppetfile.purge!
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'r10k/task'
|
2
|
+
require 'r10k/logging'
|
3
|
+
|
4
|
+
module R10K
|
5
|
+
class TaskRunner
|
6
|
+
|
7
|
+
include R10K::Logging
|
8
|
+
|
9
|
+
attr_writer :succeeded
|
10
|
+
|
11
|
+
def initialize(opts)
|
12
|
+
@tasks = []
|
13
|
+
@succeeded = true
|
14
|
+
@errors = {}
|
15
|
+
|
16
|
+
@trace = opts.delete(:trace)
|
17
|
+
|
18
|
+
raise "Unrecognized options: #{opts.keys.join(', ')}" unless opts.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
catch :abort do
|
23
|
+
until @tasks.empty?
|
24
|
+
current = @tasks.first
|
25
|
+
current.task_runner = self
|
26
|
+
begin
|
27
|
+
current.call
|
28
|
+
rescue Interrupt => e
|
29
|
+
logger.error "Aborted!"
|
30
|
+
$stderr.puts e.backtrace.join("\n").red if @trace
|
31
|
+
@succeeded = false
|
32
|
+
throw :abort
|
33
|
+
rescue => e
|
34
|
+
logger.error "Task #{current} failed while running: #{e.message}"
|
35
|
+
$stderr.puts e.backtrace.join("\n").red if @trace
|
36
|
+
|
37
|
+
@errors[current] = e
|
38
|
+
@succeeded = false
|
39
|
+
end
|
40
|
+
@tasks.shift
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def prepend_task(task)
|
46
|
+
@tasks.unshift task
|
47
|
+
end
|
48
|
+
|
49
|
+
def append_task(task)
|
50
|
+
@tasks << task
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [R10K::Task] task_index The task to insert the task after
|
54
|
+
# @param [R10K::Task] new_task The task to insert
|
55
|
+
def insert_task_after(task_index, new_task)
|
56
|
+
if (index = @tasks.index(task_index))
|
57
|
+
index += 1
|
58
|
+
@tasks.insert(index, new_task)
|
59
|
+
else
|
60
|
+
@tasks.insert(0, new_task)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def succeeded?
|
65
|
+
@succeeded
|
66
|
+
end
|
67
|
+
|
68
|
+
def exit_value
|
69
|
+
@succeeded ? 0 : 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
module R10K
|
3
|
+
module Util
|
4
|
+
module Purgeable
|
5
|
+
# Mixin for purging stale directory contents.
|
6
|
+
#
|
7
|
+
# @abstract Classes using this mixin need to implement {#managed_directory} and
|
8
|
+
# {#desired_contents}
|
9
|
+
|
10
|
+
# @!method desired_contents
|
11
|
+
# @abstract Including classes must implement this method to list the
|
12
|
+
# expected filenames of managed_directory
|
13
|
+
# @return [Array<String>] A list of directory contents that should be present
|
14
|
+
|
15
|
+
# @!method managed_directory
|
16
|
+
# @abstract Including classes must implement this method to return the
|
17
|
+
# path to the directory that can be purged
|
18
|
+
# @return [String] The path to the directory to be purged
|
19
|
+
|
20
|
+
# @return [Array<String>] The present directory entries in `self.managed_directory`
|
21
|
+
def current_contents
|
22
|
+
dir = self.managed_directory
|
23
|
+
glob_exp = File.join(dir, '*')
|
24
|
+
|
25
|
+
Dir.glob(glob_exp).map do |fname|
|
26
|
+
File.basename fname
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Array<String>] Directory contents that are expected but not present
|
31
|
+
def pending_contents
|
32
|
+
desired_contents - current_contents
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Array<String>] Directory contents that are present but not expected
|
36
|
+
def stale_contents
|
37
|
+
current_contents - desired_contents
|
38
|
+
end
|
39
|
+
|
40
|
+
# Forcibly remove all unmanaged content in `self.managed_directory`
|
41
|
+
def purge!
|
42
|
+
stale_contents.each do |fname|
|
43
|
+
fpath = File.join(self.managed_directory, fname)
|
44
|
+
FileUtils.rm_rf fpath, :secure => true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/r10k/version.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'r10k/deployment/environment'
|
3
|
+
|
4
|
+
describe R10K::Deployment::Environment do
|
5
|
+
let(:remote) { 'git://github.com/adrienthebo/r10k-fixture-repo' }
|
6
|
+
let(:ref) { 'master' }
|
7
|
+
|
8
|
+
describe 'dirname' do
|
9
|
+
it 'uses the ref as the default dirname' do
|
10
|
+
subject = described_class.new(ref, remote, '/tmp')
|
11
|
+
subject.dirname.should == 'master'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'allows a specific dirname to be set' do
|
15
|
+
subject = described_class.new(ref, remote, '/tmp', 'sourcename_master')
|
16
|
+
subject.dirname.should == 'sourcename_master'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe R10K::Git::Cache, 'memoizing objects' do
|
4
|
+
|
5
|
+
it "returns the same object when a remote is duplicated" do
|
6
|
+
first = described_class.new('foo')
|
7
|
+
second = described_class.new('foo')
|
8
|
+
|
9
|
+
first.should be second
|
10
|
+
end
|
11
|
+
|
12
|
+
it "wipes the memoized objects when .clear! is called" do
|
13
|
+
first = described_class.new('foo')
|
14
|
+
described_class.clear!
|
15
|
+
second = described_class.new('foo')
|
16
|
+
|
17
|
+
first.should_not be second
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe R10K::Git::Cache do
|
22
|
+
|
23
|
+
after do
|
24
|
+
described_class.clear!
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'setting the cache root' do
|
28
|
+
it 'defaults to ~/.r10k/git' do
|
29
|
+
described_class.new('foo').cache_root.should match %r[/\.r10k/git]
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'uses the class cache root if set' do
|
33
|
+
described_class.stubs(:cache_root).returns '/var/spool/r10k'
|
34
|
+
described_class.new('foo').cache_root.should == '/var/spool/r10k'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe R10K::Git::WorkingDir do
|
4
|
+
|
5
|
+
before do
|
6
|
+
R10K::Git::Cache.stubs(:cache_root).returns '/tmp'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "initializing" do
|
10
|
+
it "generates a new cache for the remote" do
|
11
|
+
wd = described_class.new('master', 'git://github.com/adrienthebo/r10k-fixture-repo', '/tmp')
|
12
|
+
wd.cache.should be_kind_of R10K::Git::Cache
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'r10k/module/forge'
|
2
|
+
require 'semver'
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe R10K::Module::Forge do
|
6
|
+
before :each do
|
7
|
+
Object.expects(:systemu).never
|
8
|
+
end
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
s = stub(:debug2 => nil, :debug1 => nil, :debug => nil, :info => nil)
|
12
|
+
described_class.any_instance.stubs(:logger).returns s
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "implementing the Puppetfile spec" do
|
16
|
+
it "should implement 'branan/eight_hundred', '8.0.0'" do
|
17
|
+
described_class.should be_implement('branan/eight_hundred', '8.0.0')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should fail with an invalid full name" do
|
21
|
+
described_class.should_not be_implement('branan-eight_hundred', '8.0.0')
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should fail with an invalid version" do
|
25
|
+
described_class.should_not be_implement('branan-eight_hundred', 'not a semantic version')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "setting attributes" do
|
30
|
+
subject { described_class.new('branan/eight_hundred', '/moduledir', '8.0.0') }
|
31
|
+
|
32
|
+
its(:name) { should eq 'eight_hundred' }
|
33
|
+
its(:owner) { should eq 'branan' }
|
34
|
+
its(:full_name) { should eq 'branan/eight_hundred' }
|
35
|
+
its(:basedir) { should eq '/moduledir' }
|
36
|
+
its(:full_path) { should eq '/moduledir/eight_hundred' }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "when syncing" do
|
40
|
+
let(:fixture_modulepath) { File.expand_path('spec/fixtures/module/forge', PROJECT_ROOT) }
|
41
|
+
let(:empty_modulepath) { File.expand_path('spec/fixtures/empty', PROJECT_ROOT) }
|
42
|
+
|
43
|
+
describe "and the module is in sync" do
|
44
|
+
subject { described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0') }
|
45
|
+
|
46
|
+
it { should be_insync }
|
47
|
+
its(:version) { should eq '8.0.0' }
|
48
|
+
its(:current_version) { should eq '8.0.0' }
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "and the desired version is newer than the installed version" do
|
52
|
+
subject { described_class.new('branan/eight_hundred', fixture_modulepath, '80.0.0') }
|
53
|
+
|
54
|
+
it { should_not be_insync }
|
55
|
+
its(:version) { should eq '80.0.0' }
|
56
|
+
its(:current_version) { should eq '8.0.0' }
|
57
|
+
|
58
|
+
it "should try to upgrade the module" do
|
59
|
+
# "v80.0.0" ? Seriously? Where did the "v" come from?
|
60
|
+
expected = %w{upgrade --version=v80.0.0 --ignore-dependencies branan/eight_hundred}
|
61
|
+
subject.expects(:pmt).with(expected)
|
62
|
+
subject.sync!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "and the desired version is older than the installed version" do
|
67
|
+
subject { described_class.new('branan/eight_hundred', fixture_modulepath, '7.0.0') }
|
68
|
+
|
69
|
+
it { should_not be_insync }
|
70
|
+
its(:version) { should eq '7.0.0' }
|
71
|
+
its(:current_version) { should eq '8.0.0' }
|
72
|
+
|
73
|
+
it "should try to downgrade the module" do
|
74
|
+
# Again with the magical "v" prefix to the version.
|
75
|
+
expected = %w{upgrade --version=v7.0.0 --ignore-dependencies branan/eight_hundred}
|
76
|
+
subject.expects(:pmt).with(expected)
|
77
|
+
subject.sync!
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "and the module is not installed" do
|
82
|
+
subject { described_class.new('branan/eight_hundred', empty_modulepath, '8.0.0') }
|
83
|
+
|
84
|
+
it { should_not be_insync }
|
85
|
+
its(:version) { should eq '8.0.0' }
|
86
|
+
its(:current_version) { should eq SemVer::MIN }
|
87
|
+
|
88
|
+
it "should try to install the module" do
|
89
|
+
expected = %w{install --version=v8.0.0 --ignore-dependencies branan/eight_hundred}
|
90
|
+
subject.expects(:pmt).with(expected)
|
91
|
+
subject.sync!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|