clc-promote 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +14 -0
- data/Rakefile +6 -0
- data/clc-promote.gemspec +24 -0
- data/lib/kitchen/provisioner/environment.rb +79 -0
- data/lib/promote/version.rb +3 -0
- data/spec/unit/kitchen/provisioner/environment_spec.rb +104 -0
- data/spec/unit/promote/config_spec.rb +92 -0
- data/spec/unit/promote/cookbook_spec.rb +250 -0
- data/spec/unit/promote/promoter_spec.rb +59 -0
- data/spec/unit/promote/uploader_spec.rb +208 -0
- data/spec/unit/promote/versioner_spec.rb +291 -0
- data/spec/unit/stubs/cookbook_1/Berksfile +3 -0
- data/spec/unit/stubs/cookbook_1/metadata.rb +9 -0
- data/spec/unit/support/dummy_log.rb +47 -0
- data/spec/unit/support/dummy_metadata.rb +20 -0
- metadata +28 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 674a20f898ac27d30598579081401a13f660e374
|
4
|
+
data.tar.gz: 55dd1f8a1927e4ff519e40bc33762abd56d40680
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd0d30a5d62a51f14c8f30398971ac4cc4be3252c0152817e4ee71aaa66224bf481d2936fd537bfeadd28cbe0a2e990b181604d6b5aa72011140fd7c32d0c090
|
7
|
+
data.tar.gz: 1d1462b21bddf6cb3ed0bb5bd05929f79732e70b8ef782600b72c018e2d7f9c6cb5bb12dca81bbb8a02b8db4d7edac92aaca47c11ea04fd21ed449136f1766fc
|
data/.gitignore
ADDED
data/Rakefile
ADDED
data/clc-promote.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
2
|
+
require 'promote/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'clc-promote'
|
6
|
+
s.version = Promote::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.extra_rdoc_files = ['README.md']
|
9
|
+
s.summary = 'Provides versioning and promotion logic for chef artifacts.'
|
10
|
+
s.description = s.summary
|
11
|
+
s.authors = ['CenturyLink Cloud']
|
12
|
+
s.email = 'matt.wrock@CenturyLinkCloud.com'
|
13
|
+
s.homepage = 'https://github.com/tier3/DevOps/gems/clc-promote'
|
14
|
+
|
15
|
+
s.require_path = 'lib'
|
16
|
+
s.files = `git ls-files -z`.split("\x0")
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'clc-git', '~> 1.2', '>= 1.2.8'
|
20
|
+
s.add_runtime_dependency 'berkshelf', '~> 3.1', '>= 3.1.4'
|
21
|
+
|
22
|
+
s.add_development_dependency 'rspec', '~> 3.0', '>= 3.0.0'
|
23
|
+
s.add_development_dependency 'rake', '~> 10.3', '>= 10.3.2'
|
24
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require "berkshelf/downloader"
|
3
|
+
require "kitchen/provisioner/chef_zero"
|
4
|
+
require "promote"
|
5
|
+
|
6
|
+
module Kitchen
|
7
|
+
|
8
|
+
module Provisioner
|
9
|
+
|
10
|
+
class Environment < ChefZero
|
11
|
+
|
12
|
+
def create_sandbox
|
13
|
+
super
|
14
|
+
prepare_environment_dependencies
|
15
|
+
create_node
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def create_node
|
21
|
+
node_file = File.join(instance.busser[:test_base_path], "nodes/#{instance.suite.name}.json")
|
22
|
+
if File.exist?(node_file)
|
23
|
+
node = JSON.parse(File.read(node_file))
|
24
|
+
node[:run_list] = config[:run_list]
|
25
|
+
File.open(node_file, 'w') do |out|
|
26
|
+
out << JSON.pretty_generate(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def prepare_environment_dependencies
|
32
|
+
if !config[:client_rb].has_key?(:environment)
|
33
|
+
info("No environment specified to lock")
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
environment = config[:client_rb][:environment]
|
38
|
+
repo = File.dirname(config[:environments_path])
|
39
|
+
promote_config = Promote::Config.new(:repo_root => repo)
|
40
|
+
env_file = Promote::EnvironmentFile.new(environment, promote_config)
|
41
|
+
|
42
|
+
info("comparing current cookbook versions to those in #{environment}")
|
43
|
+
Kitchen.mutex.synchronize do
|
44
|
+
berks_path = File.join(config[:kitchen_root], 'Berksfile')
|
45
|
+
::Berkshelf.set_format :null
|
46
|
+
berks = ::Berkshelf::Berksfile.from_file(berks_path)
|
47
|
+
downloader = ::Berkshelf::Downloader.new(berks)
|
48
|
+
berks.install
|
49
|
+
my_deps = berks.list
|
50
|
+
|
51
|
+
my_deps.each do | dep |
|
52
|
+
next if dep.location.respond_to?(:relative_path)
|
53
|
+
env_version = env_file.cookbook_versions[dep.name]
|
54
|
+
|
55
|
+
if !env_version.nil? && dep.locked_version.to_s != env_version
|
56
|
+
if dep.name == File.basename(config[:kitchen_root])
|
57
|
+
raise "cookbook '#{dep.name}' is outdated. Expected v#{env_version} from #{environment}"
|
58
|
+
end
|
59
|
+
|
60
|
+
info("replacing v#{dep.locked_version} of #{dep.name} with #{env_version}")
|
61
|
+
downloader.download(dep.name, env_version) do |stash|
|
62
|
+
FileUtils.copy_entry(stash, File.join(tmpbooks_dir, dep.name))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# we do this because the vendoring converts metadayta.rb to json
|
68
|
+
# any subsequent berks command on the vendored cookbook will fail
|
69
|
+
FileUtils.rm_rf Dir.glob("#{tmpbooks_dir}/**/Berksfile*")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def tmpbooks_dir
|
74
|
+
File.join(sandbox_path, "cookbooks")
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'kitchen'
|
2
|
+
require 'kitchen/configurable'
|
3
|
+
require 'kitchen/provisioner/environment'
|
4
|
+
|
5
|
+
describe Kitchen::Provisioner::Environment do
|
6
|
+
let(:logged_output) { StringIO.new }
|
7
|
+
let(:logger) { Logger.new(logged_output) }
|
8
|
+
let(:config) do
|
9
|
+
{ :test_base_path => "/b", :kitchen_root => "/cookbook_4", :log_level => :info, :sudo => true, :client_rb => client_rb_config, :environments_path => '/env/path' }
|
10
|
+
end
|
11
|
+
let(:suite) do
|
12
|
+
double('suite', :name => "fries")
|
13
|
+
end
|
14
|
+
let(:transport) do
|
15
|
+
double('transport', :sudo => config[:sudo], :shell => "bourne")
|
16
|
+
end
|
17
|
+
let(:instance) do
|
18
|
+
double('instance', :name => "coolbeans", :logger => logger, :suite => suite, :transport => transport, :busser => {:test_base_path => 'base_path'})
|
19
|
+
end
|
20
|
+
let(:downloader) { double('downloader') }
|
21
|
+
let(:fake_berks) { double('berksfile', :install => nil, :update => nil, :list => [
|
22
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1'), :location => nil),
|
23
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2'), :location => nil),
|
24
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'), :location => nil)
|
25
|
+
]) }
|
26
|
+
let(:client_rb_config) { {:environment => 'environment'} }
|
27
|
+
let (:env_file) { double('env', :cookbook_versions => {
|
28
|
+
'cookbook_2' => '1.1.1',
|
29
|
+
'cookbook_3' => '2.2.2',
|
30
|
+
'cookbook_4' => '3.3.3'
|
31
|
+
})}
|
32
|
+
|
33
|
+
subject { Kitchen::Provisioner::Environment.new(config).finalize_config!(instance) }
|
34
|
+
|
35
|
+
before {
|
36
|
+
allow(FileUtils).to receive(:rm_rf)
|
37
|
+
allow(FileUtils).to receive(:copy_entry)
|
38
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
39
|
+
allow(Promote::EnvironmentFile).to receive(:new).and_return(env_file)
|
40
|
+
allow(Berkshelf::Downloader).to receive(:new).and_return(downloader)
|
41
|
+
}
|
42
|
+
|
43
|
+
context "when no environment specified" do
|
44
|
+
let(:client_rb_config) { {} }
|
45
|
+
|
46
|
+
it "does not get berks dependencies" do
|
47
|
+
expect(fake_berks).not_to receive(:list)
|
48
|
+
subject.create_sandbox
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "wnem local cookbooks are outdated" do
|
53
|
+
let (:env_file) { double('env', :cookbook_versions => {
|
54
|
+
'cookbook_2' => '2.2.2',
|
55
|
+
'cookbook_3' => '2.2.2',
|
56
|
+
'cookbook_4' => '3.3.3'
|
57
|
+
})}
|
58
|
+
|
59
|
+
it "downloads the outdated cookbooks" do
|
60
|
+
expect(downloader).to receive(:download).with('cookbook_2', '2.2.2')
|
61
|
+
subject.create_sandbox
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when environment file is missing a dependency" do
|
66
|
+
let (:env_file) { double('env', :cookbook_versions => {
|
67
|
+
'cookbook_2' => '2.2.2',
|
68
|
+
'cookbook_4' => '3.3.3'
|
69
|
+
})}
|
70
|
+
|
71
|
+
it "downloads only the outdated cookbook" do
|
72
|
+
expect(downloader).to receive(:download).with('cookbook_2', '2.2.2')
|
73
|
+
subject.create_sandbox
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when cookbook under test is outdated" do
|
78
|
+
let (:env_file) { double('env', :cookbook_versions => {
|
79
|
+
'cookbook_2' => '1.1.1',
|
80
|
+
'cookbook_3' => '2.2.2',
|
81
|
+
'cookbook_4' => '4.4.4'
|
82
|
+
})}
|
83
|
+
|
84
|
+
it "downloads only the outdated cookbooks" do
|
85
|
+
expect{subject.create_sandbox}.to raise_error(/4\.4\.4/)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when outdated cookbook is from a local source" do
|
90
|
+
let(:fake_berks) { double('berksfile', :install => nil, :update => nil, :list => [
|
91
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1'), :location => nil),
|
92
|
+
double('Dependency',
|
93
|
+
:name => 'cookbook_3',
|
94
|
+
:locked_version => Semverse::Version.new('1.1.1'),
|
95
|
+
:location => double('location', :relative_path => '../cookbook')),
|
96
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'), :location => nil)
|
97
|
+
]) }
|
98
|
+
|
99
|
+
it "downloads only the outdated cookbooks" do
|
100
|
+
expect(downloader).not_to receive(:download)
|
101
|
+
subject.create_sandbox
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'promote'
|
2
|
+
|
3
|
+
describe Promote::Config do
|
4
|
+
let(:opts) {{
|
5
|
+
:repo_root => "root",
|
6
|
+
:cookbook_directory => "cookbooks",
|
7
|
+
:environment_directory => "environments",
|
8
|
+
:data_bag_directory => "data_bags",
|
9
|
+
:temp_directory => "temp",
|
10
|
+
:node_name => "user",
|
11
|
+
:client_key => "key",
|
12
|
+
:chef_server_url => "url"}}
|
13
|
+
subject { Promote::Config.new(opts) }
|
14
|
+
|
15
|
+
it "assigns options to node_name attribute" do
|
16
|
+
expect(subject.node_name).to eq(opts[:node_name])
|
17
|
+
end
|
18
|
+
it "assigns options to client_key attribute" do
|
19
|
+
expect(subject.client_key).to eq(opts[:client_key])
|
20
|
+
end
|
21
|
+
it "assigns options to chef_server_url attribute" do
|
22
|
+
expect(subject.chef_server_url).to eq(opts[:chef_server_url])
|
23
|
+
end
|
24
|
+
it "assigns options to repo_root attribute" do
|
25
|
+
expect(subject.repo_root).to eq(opts[:repo_root])
|
26
|
+
end
|
27
|
+
it "assigns options to cookbook_directory attribute" do
|
28
|
+
expect(subject.cookbook_directory).to eq(opts[:cookbook_directory])
|
29
|
+
end
|
30
|
+
it "assigns options to data_bag_directory attribute" do
|
31
|
+
expect(subject.data_bag_directory).to eq(opts[:data_bag_directory])
|
32
|
+
end
|
33
|
+
it "assigns options to environment_directory attribute" do
|
34
|
+
expect(subject.environment_directory).to eq(opts[:environment_directory])
|
35
|
+
end
|
36
|
+
it "assigns options to temp_directory attribute" do
|
37
|
+
expect(subject.temp_directory).to eq(opts[:temp_directory])
|
38
|
+
end
|
39
|
+
it "can correctly convert to a hash" do
|
40
|
+
hash = subject.to_hash
|
41
|
+
expect(hash[:repo_root]).to eq(opts[:repo_root])
|
42
|
+
end
|
43
|
+
|
44
|
+
context "directories are not in options" do
|
45
|
+
let(:opts) {{
|
46
|
+
:node_name => "user",
|
47
|
+
:client_key => "key",
|
48
|
+
:chef_server_url => "url"}}
|
49
|
+
subject { Promote::Config.new(opts) }
|
50
|
+
|
51
|
+
it "assigns repo_root to pwd" do
|
52
|
+
expect(subject.repo_root).to eq(Dir.pwd)
|
53
|
+
end
|
54
|
+
it "assigns cookbook_directory to cookbooks off root" do
|
55
|
+
expect(subject.cookbook_directory).to eq(File.join(subject.repo_root, "cookbooks"))
|
56
|
+
end
|
57
|
+
it "assigns data_bag_directory to data_bags off root" do
|
58
|
+
expect(subject.data_bag_directory).to eq(File.join(subject.repo_root, "data_bags"))
|
59
|
+
end
|
60
|
+
it "assigns environment_directory to environments off root" do
|
61
|
+
expect(subject.environment_directory).to eq(File.join(subject.repo_root, "environments"))
|
62
|
+
end
|
63
|
+
it "assigns temp_directory to tmp" do
|
64
|
+
expect(subject.temp_directory).to eq("/tmp/promote")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "directories are not in options but repo root is" do
|
69
|
+
let(:opts) {{
|
70
|
+
:repo_root => "root",
|
71
|
+
:node_name => "user",
|
72
|
+
:client_key => "key",
|
73
|
+
:chef_server_url => "url"}}
|
74
|
+
subject { Promote::Config.new(opts) }
|
75
|
+
|
76
|
+
it "assigns repo_root to pwd" do
|
77
|
+
expect(subject.repo_root).to eq(opts[:repo_root])
|
78
|
+
end
|
79
|
+
it "assigns cookbook_directory to cookbooks off root" do
|
80
|
+
expect(subject.cookbook_directory).to eq(File.join(subject.repo_root, "cookbooks"))
|
81
|
+
end
|
82
|
+
it "assigns data_bag_directory to data_bags off root" do
|
83
|
+
expect(subject.data_bag_directory).to eq(File.join(subject.repo_root, "data_bags"))
|
84
|
+
end
|
85
|
+
it "assigns environment_directory to environments off root" do
|
86
|
+
expect(subject.environment_directory).to eq(File.join(subject.repo_root, "environments"))
|
87
|
+
end
|
88
|
+
it "assigns temp_directory to tmp" do
|
89
|
+
expect(subject.temp_directory).to eq("/tmp/promote")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'promote'
|
2
|
+
|
3
|
+
describe Promote::Cookbook do
|
4
|
+
let(:cookbook_dir) {'/tmp/promote_test_cookbooks/stubs'}
|
5
|
+
let(:config) { Promote::Config.new({
|
6
|
+
:cookbook_directory => cookbook_dir})
|
7
|
+
}
|
8
|
+
let(:fake_berks) { double('berksfile', :list => nil, :install => nil, :update => nil) }
|
9
|
+
|
10
|
+
before(:all) {
|
11
|
+
cb_dir = '/tmp/promote_test_cookbooks'
|
12
|
+
FileUtils.rm_rf(cb_dir) if Dir.exist?(cb_dir)
|
13
|
+
Dir.mkdir(cb_dir)
|
14
|
+
FileUtils.cp_r(File.join(File.dirname(File.dirname(__FILE__)), 'stubs'), cb_dir)
|
15
|
+
}
|
16
|
+
|
17
|
+
subject { Promote::Cookbook.new('cookbook_1', config) }
|
18
|
+
|
19
|
+
describe "dependencies" do
|
20
|
+
before {
|
21
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
22
|
+
allow(fake_berks).to receive(:list).and_return([
|
23
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
24
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
25
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
26
|
+
])
|
27
|
+
}
|
28
|
+
|
29
|
+
it "returns dependencies from lockfile" do
|
30
|
+
expect(subject.dependencies.keys.count).to be 3
|
31
|
+
expect(subject.dependencies['cookbook_2'].to_s).to eq '1.1.1'
|
32
|
+
expect(subject.dependencies['cookbook_3'].to_s).to eq '2.2.2'
|
33
|
+
expect(subject.dependencies['cookbook_4'].to_s).to eq '3.3.3'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "metadata_dependencies" do
|
38
|
+
|
39
|
+
it "returns dependencies from metadata.rb" do
|
40
|
+
expect(subject.metadata_dependencies.keys.count).to be 1
|
41
|
+
expect(subject.metadata_dependencies['cookbook_2'].to_s).to eq '= 1.1.1'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "path" do
|
46
|
+
|
47
|
+
it "returns the correct path of the cookbook" do
|
48
|
+
expect(subject.path).to eq(File.join(cookbook_dir, 'cookbook_1'))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "version" do
|
53
|
+
|
54
|
+
it "reads the current version" do
|
55
|
+
expect(subject.version.to_s).to eq('1.0.0')
|
56
|
+
end
|
57
|
+
|
58
|
+
it "writes changed version to metadata.rb" do
|
59
|
+
subject.version = Semverse::Version.new('2.2.2')
|
60
|
+
expect(Promote::Cookbook.new('cookbook_1', config).version.to_s).to eq '2.2.2'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns the new version after a version has changed" do
|
64
|
+
subject.version = Semverse::Version.new('2.2.2')
|
65
|
+
expect(subject.version.to_s).to eq('2.2.2')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "stamp_commit" do
|
70
|
+
it "writes the sha1 to the end of the metadata file" do
|
71
|
+
subject.stamp_commit('commit_1')
|
72
|
+
expect(subject.raw_metadata).to end_with "\n#sha1 'commit_1'"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not write the sha1 m ore than once" do
|
76
|
+
subject.stamp_commit('commit_1')
|
77
|
+
subject.stamp_commit('commit_2')
|
78
|
+
expect(subject.raw_metadata).not_to include "commit_1"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "sync_berksfile" do
|
83
|
+
before {
|
84
|
+
allow(File).to receive(:exist?).with(/Berksfile$/).and_return(true)
|
85
|
+
allow(File).to receive(:exist?).with(/Berksfile.lock/).and_return(true)
|
86
|
+
}
|
87
|
+
|
88
|
+
it "Installs berks dependencies" do
|
89
|
+
dummy = double('berksfile')
|
90
|
+
expect(Berkshelf::Berksfile).to receive(:from_file).with(
|
91
|
+
File.join(subject.path, "Berksfile")).and_return(dummy)
|
92
|
+
expect(dummy).to receive(:install)
|
93
|
+
|
94
|
+
subject.sync_berksfile
|
95
|
+
end
|
96
|
+
|
97
|
+
it "Updates berks dependencies when asked to update" do
|
98
|
+
dummy = double('berksfile')
|
99
|
+
expect(Berkshelf::Berksfile).to receive(:from_file).with(
|
100
|
+
File.join(subject.path, "Berksfile")).and_return(dummy)
|
101
|
+
expect(dummy).to receive(:update)
|
102
|
+
|
103
|
+
subject.sync_berksfile(true)
|
104
|
+
end
|
105
|
+
|
106
|
+
context "update berksfile with no lock file" do
|
107
|
+
before {
|
108
|
+
allow(File).to receive(:exist?).with(/Berksfile.lock/).and_return(false)
|
109
|
+
}
|
110
|
+
|
111
|
+
it "Installs berks dependencies instead of update" do
|
112
|
+
dummy = double('berksfile')
|
113
|
+
expect(Berkshelf::Berksfile).to receive(:from_file).with(
|
114
|
+
File.join(subject.path, "Berksfile")).and_return(dummy)
|
115
|
+
expect(dummy).to receive(:install)
|
116
|
+
|
117
|
+
subject.sync_berksfile(true)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "dependencies_changed_after_update?" do
|
123
|
+
before {
|
124
|
+
allow(File).to receive(:exist?).and_return(true)
|
125
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
126
|
+
allow(fake_berks).to receive(:list).and_return([
|
127
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
128
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
129
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
130
|
+
])
|
131
|
+
}
|
132
|
+
|
133
|
+
it "performs a berks update" do
|
134
|
+
expect(fake_berks).to receive(:update)
|
135
|
+
subject.dependencies_changed_after_update?
|
136
|
+
end
|
137
|
+
|
138
|
+
context "there is no change" do
|
139
|
+
|
140
|
+
before {
|
141
|
+
allow(fake_berks).to receive(:list).and_return(
|
142
|
+
[
|
143
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
144
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
145
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
146
|
+
],
|
147
|
+
[
|
148
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
149
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
150
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
151
|
+
]
|
152
|
+
)
|
153
|
+
}
|
154
|
+
|
155
|
+
it "returns false" do
|
156
|
+
expect(subject.dependencies_changed_after_update?).to be false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "there is a version change" do
|
161
|
+
|
162
|
+
before {
|
163
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
164
|
+
allow(fake_berks).to receive(:list).and_return(
|
165
|
+
[
|
166
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
167
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
168
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
169
|
+
],
|
170
|
+
[
|
171
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
172
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.3')),
|
173
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
174
|
+
]
|
175
|
+
)
|
176
|
+
}
|
177
|
+
|
178
|
+
it "returns true" do
|
179
|
+
expect(subject.dependencies_changed_after_update?).to be true
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "a dependency is removed" do
|
184
|
+
|
185
|
+
before {
|
186
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
187
|
+
allow(fake_berks).to receive(:list).and_return(
|
188
|
+
[
|
189
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
190
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
191
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
192
|
+
],
|
193
|
+
[
|
194
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
195
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
196
|
+
]
|
197
|
+
)
|
198
|
+
}
|
199
|
+
|
200
|
+
it "returns true" do
|
201
|
+
expect(subject.dependencies_changed_after_update?).to be true
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context "a dependency is added" do
|
206
|
+
|
207
|
+
before {
|
208
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
209
|
+
allow(fake_berks).to receive(:list).and_return(
|
210
|
+
[
|
211
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
212
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
213
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3'))
|
214
|
+
],
|
215
|
+
[
|
216
|
+
double('Dependency', :name => 'cookbook_2', :locked_version => Semverse::Version.new('1.1.1')),
|
217
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
218
|
+
double('Dependency', :name => 'cookbook_4', :locked_version => Semverse::Version.new('3.3.3')),
|
219
|
+
double('Dependency', :name => 'cookbook_5', :locked_version => Semverse::Version.new('3.3.3'))
|
220
|
+
]
|
221
|
+
)
|
222
|
+
}
|
223
|
+
|
224
|
+
it "returns true" do
|
225
|
+
expect(subject.dependencies_changed_after_update?).to be true
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context "the cookbook under test has changed" do
|
230
|
+
|
231
|
+
before {
|
232
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
233
|
+
allow(fake_berks).to receive(:list).and_return(
|
234
|
+
[
|
235
|
+
double('Dependency', :name => 'cookbook_1', :locked_version => Semverse::Version.new('1.1.1')),
|
236
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
237
|
+
],
|
238
|
+
[
|
239
|
+
double('Dependency', :name => 'cookbook_1', :locked_version => Semverse::Version.new('1.1.2')),
|
240
|
+
double('Dependency', :name => 'cookbook_3', :locked_version => Semverse::Version.new('2.2.2')),
|
241
|
+
]
|
242
|
+
)
|
243
|
+
}
|
244
|
+
|
245
|
+
it "returns false" do
|
246
|
+
expect(subject.dependencies_changed_after_update?).to be false
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'promote'
|
2
|
+
|
3
|
+
describe Promote::Promoter do
|
4
|
+
context "Promote one environment to another" do
|
5
|
+
let(:config) { Promote::Config.new({
|
6
|
+
:node_name => 'user',
|
7
|
+
:cookbook_directory => '/cookbooks',
|
8
|
+
:client_key => 'key',
|
9
|
+
:chef_server_url => 'https://some.chef.server'}) }
|
10
|
+
let(:fake_file){double('file')}
|
11
|
+
let(:env1) do
|
12
|
+
<<-EOS
|
13
|
+
{
|
14
|
+
"name": "QA1",
|
15
|
+
"chef_type": "environment",
|
16
|
+
"json_class": "Chef::Environment",
|
17
|
+
"cookbook_versions": {
|
18
|
+
"build-essential": "2.0.0",
|
19
|
+
"newrelic": "2.0.0",
|
20
|
+
"platform_haproxy": "2.0.0"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
EOS
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:env2) do
|
27
|
+
<<-EOS
|
28
|
+
{
|
29
|
+
"name": "QA1",
|
30
|
+
"chef_type": "environment",
|
31
|
+
"json_class": "Chef::Environment",
|
32
|
+
"cookbook_versions": {
|
33
|
+
"build-essential": "1.0.0",
|
34
|
+
"platform_haproxy": "1.0.0"
|
35
|
+
}
|
36
|
+
}
|
37
|
+
EOS
|
38
|
+
end
|
39
|
+
|
40
|
+
before {
|
41
|
+
allow(File).to receive(:read).with(/env1\.json$/).and_return(env1)
|
42
|
+
allow(File).to receive(:read).with(/env2\.json$/).and_return(env2)
|
43
|
+
allow(File).to receive(:open).with(/env2\.json$/, "w").and_yield(fake_file)
|
44
|
+
}
|
45
|
+
|
46
|
+
subject { Promote::Promoter.new(config) }
|
47
|
+
|
48
|
+
it "copies the cookbook constraints" do
|
49
|
+
expect(fake_file).to receive(:<<).with(an_instance_of(String)) do |arg|
|
50
|
+
parsed = JSON.parse(arg)
|
51
|
+
expect(parsed['cookbook_versions'].length).to eq(3)
|
52
|
+
expect(parsed['cookbook_versions']['build-essential']).to eq('2.0.0')
|
53
|
+
expect(parsed['cookbook_versions']['newrelic']).to eq('2.0.0')
|
54
|
+
expect(parsed['cookbook_versions']['platform_haproxy']).to eq('2.0.0')
|
55
|
+
end
|
56
|
+
subject.promote_to("env1", "env2")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'promote'
|
2
|
+
require_relative '../support/dummy_metadata'
|
3
|
+
|
4
|
+
describe Promote::Uploader do
|
5
|
+
let(:knife_config) {{ :cookbook_path => "path"}}
|
6
|
+
let(:temp_dir) { "/tmp/berks" }
|
7
|
+
let(:config) { Promote::Config.new({
|
8
|
+
:node_name => 'user',
|
9
|
+
:cookbook_directory => '/cookbooks',
|
10
|
+
:client_key => 'key',
|
11
|
+
:temp_directory => temp_dir,
|
12
|
+
:chef_server_url => 'https://some.chef.server'}) }
|
13
|
+
|
14
|
+
subject { Promote::Uploader.new(config) }
|
15
|
+
|
16
|
+
context "upload cookbooks" do
|
17
|
+
let(:env_name) { "QA1" }
|
18
|
+
let(:metadata) { PromoteSpecs::DummyMetadata.new({'version' => "1.1.1"}) }
|
19
|
+
let(:environment) do
|
20
|
+
<<-EOS
|
21
|
+
{
|
22
|
+
"name": "#{env_name}",
|
23
|
+
"chef_type": "environment",
|
24
|
+
"json_class": "Chef::Environment",
|
25
|
+
"cookbook_versions": {
|
26
|
+
"int_cookbook1": "1.1.1",
|
27
|
+
"int_cookbook3": "1.1.1",
|
28
|
+
"cookbook3": "3.3.3"
|
29
|
+
}
|
30
|
+
}
|
31
|
+
EOS
|
32
|
+
end
|
33
|
+
let(:berks_cookbooks) {[
|
34
|
+
"/berks/cookbook1-1.1.1",
|
35
|
+
"/berks/cookbook1-1.1.2",
|
36
|
+
"/berks/cookbook2-2.2.2",
|
37
|
+
"/berks/cookbook3-3.3.3",
|
38
|
+
"/berks/cookbook4-3.3.3",
|
39
|
+
]}
|
40
|
+
let(:internal_cookbooks) {[
|
41
|
+
"/cookbooks/int_cookbook1",
|
42
|
+
"/cookbooks/int_cookbook2",
|
43
|
+
"/cookbooks/int_cookbook3",
|
44
|
+
]}
|
45
|
+
let(:server_cookbooks){{
|
46
|
+
"cookbook1" => {"versions" => [{"version" => "1.1.1"}]},
|
47
|
+
"cookbook2" => {"versions" => [{"version" => "1.1.1"}]},
|
48
|
+
"int_cookbook1" => {"versions" => [{"version" => "1.1.1"}]},
|
49
|
+
"int_cookbook2" => {"versions" => [{"version" => "2.2.2"}]}
|
50
|
+
}}
|
51
|
+
let(:rest){double('rest')}
|
52
|
+
let(:knife) { instance_double('CookbookUpload', :run => nil) }
|
53
|
+
before {
|
54
|
+
allow(File).to receive(:read).with(File.join(config.environment_directory, "#{env_name}.json")).and_return(environment)
|
55
|
+
allow(File).to receive(:read).with(/metadata\.json$/).and_return('')
|
56
|
+
allow(Chef::REST).to receive(:new).and_return(rest)
|
57
|
+
allow(rest).to receive(:get_rest).and_return(server_cookbooks)
|
58
|
+
allow(Chef::Cookbook::Metadata).to receive(:new).and_return(metadata)
|
59
|
+
allow(Chef::Knife::CookbookUpload).to receive(:new).and_return(knife)
|
60
|
+
allow(Dir).to receive(:glob).with(File.expand_path("~/.berkshelf/cookbooks/*")).and_return(berks_cookbooks)
|
61
|
+
allow(Dir).to receive(:glob).with(File.join(config.cookbook_directory, "*")).and_return(internal_cookbooks)
|
62
|
+
allow(Dir).to receive(:mkdir)
|
63
|
+
allow(Dir).to receive(:exist?).and_return(false)
|
64
|
+
allow(knife).to receive(:config).and_return(knife_config)
|
65
|
+
allow(FileUtils).to receive(:copy_entry)
|
66
|
+
allow(File).to receive(:exist?).with(/metadata\.rb$/).and_return(true)
|
67
|
+
}
|
68
|
+
|
69
|
+
it "copies updated cookbooks to temp directory" do
|
70
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/berks/cookbook1-1.1.1", File.join(config.temp_directory, "cookbook1"))
|
71
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/berks/cookbook1-1.1.2", File.join(config.temp_directory, "cookbook1"))
|
72
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/berks/cookbook4-3.3.3", File.join(config.temp_directory, "cookbook4"))
|
73
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/berks/cookbook2-2.2.2", File.join(config.temp_directory, "cookbook2"))
|
74
|
+
expect(FileUtils).to receive(:copy_entry).with("/berks/cookbook3-3.3.3", File.join(config.temp_directory, "cookbook3"))
|
75
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/cookbooks/int_cookbook2", File.join(config.temp_directory, "int_cookbook2"))
|
76
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/cookbooks/int_cookbook1", File.join(config.temp_directory, "int_cookbook1"))
|
77
|
+
expect(FileUtils).to receive(:copy_entry).with("/cookbooks/int_cookbook3", File.join(config.temp_directory, "int_cookbook3"))
|
78
|
+
subject.upload_cookbooks(env_name)
|
79
|
+
end
|
80
|
+
it "uploads cookbooks to chef server" do
|
81
|
+
expect(knife).to receive(:run)
|
82
|
+
|
83
|
+
subject.upload_cookbooks(env_name)
|
84
|
+
end
|
85
|
+
it "Creates temp directory" do
|
86
|
+
expect(Dir).to receive(:mkdir).with(config.temp_directory)
|
87
|
+
|
88
|
+
subject.upload_cookbooks(env_name)
|
89
|
+
end
|
90
|
+
it "uploads cookbooks from config temp path" do
|
91
|
+
subject.upload_cookbooks(env_name)
|
92
|
+
expect(knife.config[:cookbook_path]).to eq(config.temp_directory)
|
93
|
+
end
|
94
|
+
it "uploads all cookbooks" do
|
95
|
+
subject.upload_cookbooks(env_name)
|
96
|
+
expect(knife.config[:all]).to eq(true)
|
97
|
+
end
|
98
|
+
it "freezes all cookbooks" do
|
99
|
+
subject.upload_cookbooks(env_name)
|
100
|
+
expect(knife.config[:freeze]).to eq(true)
|
101
|
+
end
|
102
|
+
|
103
|
+
context "no environment is given" do
|
104
|
+
it "copies updated cookbooks and does not filter by environment" do
|
105
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/berks/cookbook1-1.1.1", File.join(config.temp_directory, "cookbook1"))
|
106
|
+
expect(FileUtils).to receive(:copy_entry).with("/berks/cookbook1-1.1.2", File.join(config.temp_directory, "cookbook1"))
|
107
|
+
expect(FileUtils).to receive(:copy_entry).with("/berks/cookbook4-3.3.3", File.join(config.temp_directory, "cookbook4"))
|
108
|
+
expect(FileUtils).to receive(:copy_entry).with("/berks/cookbook2-2.2.2", File.join(config.temp_directory, "cookbook2"))
|
109
|
+
expect(FileUtils).to receive(:copy_entry).with("/berks/cookbook3-3.3.3", File.join(config.temp_directory, "cookbook3"))
|
110
|
+
expect(FileUtils).to receive(:copy_entry).with("/cookbooks/int_cookbook2", File.join(config.temp_directory, "int_cookbook2"))
|
111
|
+
expect(FileUtils).not_to receive(:copy_entry).with("/cookbooks/int_cookbook1", File.join(config.temp_directory, "int_cookbook1"))
|
112
|
+
expect(FileUtils).to receive(:copy_entry).with("/cookbooks/int_cookbook3", File.join(config.temp_directory, "int_cookbook3"))
|
113
|
+
subject.upload_cookbooks
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "temp directory already exists" do
|
118
|
+
before {allow(Dir).to receive(:exist?).and_return(true)}
|
119
|
+
|
120
|
+
it "deletes temp directory" do
|
121
|
+
expect(FileUtils).to receive(:rm_rf).with(config.temp_directory)
|
122
|
+
subject.upload_cookbooks(env_name)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "no metadata.rb file exists for cookbook" do
|
127
|
+
before {allow(File).to receive(:exist?).with(/rb$/).and_return(false)}
|
128
|
+
|
129
|
+
it "loads json instead of ruby file" do
|
130
|
+
expect(metadata).to receive(:from_json).at_least(1).times
|
131
|
+
expect(metadata).not_to receive(:from_file)
|
132
|
+
subject.upload_cookbooks(env_name)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "nothing to upload" do
|
137
|
+
let(:berks_cookbooks) {[]}
|
138
|
+
let(:internal_cookbooks) {[]}
|
139
|
+
|
140
|
+
it "does not call knife" do
|
141
|
+
expect(Chef::Knife::CookbookUpload).not_to receive(:new)
|
142
|
+
|
143
|
+
subject.upload_cookbooks(env_name)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "config missing node_name" do
|
148
|
+
let(:config) { Promote::Config.new({
|
149
|
+
:client_key => 'key',
|
150
|
+
:chef_server_url => 'https://some.chef.server'}) }
|
151
|
+
|
152
|
+
it "raises error that node_name is missing" do
|
153
|
+
expect{subject.upload_cookbooks(env_name)}.to raise_error(/node_name/)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "config missing client_key" do
|
158
|
+
let(:config) { Promote::Config.new({
|
159
|
+
:node_name => 'user',
|
160
|
+
:chef_server_url => 'https://some.chef.server'}) }
|
161
|
+
|
162
|
+
it "raises error that client_key is missing" do
|
163
|
+
expect{subject.upload_cookbooks(env_name)}.to raise_error(/client_key/)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "config missing chef_server_url" do
|
168
|
+
let(:config) { Promote::Config.new({
|
169
|
+
:node_name => 'user',
|
170
|
+
:client_key => 'key'}) }
|
171
|
+
|
172
|
+
it "raises error that chef_server_url is missing" do
|
173
|
+
expect{subject.upload_cookbooks(env_name)}.to raise_error(/chef_server_url/)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "upload json artifacts" do
|
179
|
+
context "upload an environment" do
|
180
|
+
let(:env_name) { "my_test" }
|
181
|
+
|
182
|
+
it "uploads the environment" do
|
183
|
+
expect(Chef::ChefFS::FileSystem).to receive(:copy_to).with(an_instance_of(Chef::ChefFS::FilePattern), anything(), anything(), anything(), anything()) do |arg|
|
184
|
+
expect(arg.pattern).to eq(File.join("/environments", "#{env_name}.json"))
|
185
|
+
end
|
186
|
+
subject.upload_environment(env_name)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "upload all environments" do
|
191
|
+
it "uploads the environment" do
|
192
|
+
expect(Chef::ChefFS::FileSystem).to receive(:copy_to).with(an_instance_of(Chef::ChefFS::FilePattern), anything(), anything(), anything(), anything()) do |arg|
|
193
|
+
expect(arg.pattern).to eq(File.join("/environments/*.json"))
|
194
|
+
end
|
195
|
+
subject.upload_environments
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context "upload data_bags" do
|
200
|
+
it "uploads the data_bags" do
|
201
|
+
expect(Chef::ChefFS::FileSystem).to receive(:copy_to).with(an_instance_of(Chef::ChefFS::FilePattern), anything(), anything(), anything(), anything()) do |arg|
|
202
|
+
expect(arg.pattern).to eq("/data_bags/**/*.json")
|
203
|
+
end
|
204
|
+
subject.upload_data_bags
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require 'promote'
|
2
|
+
require_relative '../support/dummy_log'
|
3
|
+
|
4
|
+
describe Promote::Versioner do
|
5
|
+
before(:all) {
|
6
|
+
cb_dir = '/tmp/promote_test_cookbooks'
|
7
|
+
FileUtils.rm_rf(cb_dir) if Dir.exist?(cb_dir)
|
8
|
+
Dir.mkdir(cb_dir)
|
9
|
+
FileUtils.cp_r(File.join(File.dirname(File.dirname(__FILE__)), 'stubs'), cb_dir)
|
10
|
+
}
|
11
|
+
|
12
|
+
let(:cookbook_dir) {'/tmp/promote_test_cookbooks/stubs'}
|
13
|
+
let(:test_cookbook) {'cookbook_1'}
|
14
|
+
let(:config) {Promote::Config.new(:cookbook_directory => cookbook_dir)}
|
15
|
+
let(:fake_file) { double('file') }
|
16
|
+
|
17
|
+
let(:artifact_file) { "" }
|
18
|
+
let(:fake_berks) { double('berksfile', :install => nil) }
|
19
|
+
before {
|
20
|
+
allow(Berkshelf::Berksfile).to receive(:from_file).and_return(fake_berks)
|
21
|
+
allow_any_instance_of(Promote::GitRepo).to receive(:git).and_return(Git::Base.new)
|
22
|
+
allow_any_instance_of(Git::Base).to receive(:tags).and_return([
|
23
|
+
double("tag", :name => '1.0', :sha => 'abc'),
|
24
|
+
double("tag", :name => '1.1', :sha => 'def'),
|
25
|
+
double("tag", :name => '1.2', :sha => 'ghi')
|
26
|
+
])
|
27
|
+
allow_any_instance_of(Git::Base).to receive(:log).with(10000).and_return(
|
28
|
+
PromoteSpecs::DummyLog.new([
|
29
|
+
{:msg => 'blah', :sha => 'aaa'},
|
30
|
+
{:msg => 'CI:bumping', :sha => 'bbb'},
|
31
|
+
{:msg => 'blah', :sha => 'ccc'}
|
32
|
+
]))
|
33
|
+
config = Promote::Config.new
|
34
|
+
}
|
35
|
+
|
36
|
+
subject { Promote::Versioner.new(config) }
|
37
|
+
|
38
|
+
context 'version_cookbook' do
|
39
|
+
context 'when versioning a new cookbook version' do
|
40
|
+
it "writes the new version and sha to the file" do
|
41
|
+
subject.version_cookbook(test_cookbook)
|
42
|
+
expect(Promote::Cookbook.new(test_cookbook, config).version.to_s).to eq('1.2.2')
|
43
|
+
end
|
44
|
+
|
45
|
+
it "adds the new version and sha to the cookbook" do
|
46
|
+
subject.version_cookbook(test_cookbook)
|
47
|
+
expect(Promote::Cookbook.new(test_cookbook, config).raw_metadata).to end_with("#sha1 'aaa'")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when versioning a cookbook with no changes' do
|
52
|
+
it "does not write to metadata rb" do
|
53
|
+
expect(subject.version_cookbook(test_cookbook)).to be nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'version_cookbooks' do
|
59
|
+
let(:cookbooks) {[
|
60
|
+
File.join(subject.config.cookbook_directory, "cookbook1"),
|
61
|
+
File.join(subject.config.cookbook_directory, "cookbook2"),
|
62
|
+
File.join(subject.config.cookbook_directory, "cookbook3")
|
63
|
+
]}
|
64
|
+
before {
|
65
|
+
allow(subject).to receive(:version_cookbook)
|
66
|
+
allow(Dir).to receive(:glob).with(
|
67
|
+
File.join(subject.config.cookbook_directory, "*")).and_return(cookbooks)
|
68
|
+
}
|
69
|
+
|
70
|
+
it "versions each cookbook" do
|
71
|
+
cookbooks.each do |cookbook|
|
72
|
+
expect(subject).to receive(:version_cookbook).with(File.basename(cookbook))
|
73
|
+
end
|
74
|
+
subject.version_cookbooks
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'version_environment' do
|
79
|
+
before {
|
80
|
+
regex = File.join(config.environment_directory,'test.json')
|
81
|
+
allow(File).to receive(:read).with(regex).and_return(artifact_file)
|
82
|
+
allow(File).to receive(:open).with(regex, "w").and_yield(fake_file)
|
83
|
+
}
|
84
|
+
|
85
|
+
context 'when versioning a new environment with no version' do
|
86
|
+
let(:artifact_file) do
|
87
|
+
<<-EOS
|
88
|
+
{
|
89
|
+
"name": "QA1",
|
90
|
+
"chef_type": "environment",
|
91
|
+
"json_class": "Chef::Environment",
|
92
|
+
"override_attributes": {
|
93
|
+
"environment_parent": "QA"
|
94
|
+
}
|
95
|
+
}
|
96
|
+
EOS
|
97
|
+
end
|
98
|
+
|
99
|
+
it "writes the new version and sha to the file" do
|
100
|
+
parsed = JSON.parse(artifact_file)
|
101
|
+
parsed['override_attributes']['version'] = '1.2.2'
|
102
|
+
parsed['override_attributes']['sha1'] = 'aaa'
|
103
|
+
expect(fake_file).to receive(:<<).with(JSON.pretty_generate(parsed))
|
104
|
+
subject.version_environment('test')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when versioning a new environment with old version' do
|
109
|
+
|
110
|
+
let(:artifact_file) do
|
111
|
+
<<-EOS
|
112
|
+
{
|
113
|
+
"name": "QA1",
|
114
|
+
"chef_type": "environment",
|
115
|
+
"json_class": "Chef::Environment",
|
116
|
+
"override_attributes": {
|
117
|
+
"environment_parent": "QA",
|
118
|
+
"version": "1.0.0",
|
119
|
+
"sha1": "ccc"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
EOS
|
123
|
+
end
|
124
|
+
|
125
|
+
it "writes the new version and sha to the file" do
|
126
|
+
parsed = JSON.parse(artifact_file)
|
127
|
+
parsed['override_attributes']['version'] = '1.2.2'
|
128
|
+
parsed['override_attributes']['sha1'] = 'aaa'
|
129
|
+
expect(fake_file).to receive(:<<).with(JSON.pretty_generate(parsed))
|
130
|
+
subject.version_environment('test')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when commiting an environment with no changes' do
|
135
|
+
let(:artifact_file) do
|
136
|
+
<<-EOS
|
137
|
+
{
|
138
|
+
"name": "QA1",
|
139
|
+
"chef_type": "environment",
|
140
|
+
"json_class": "Chef::Environment",
|
141
|
+
"override_attributes": {
|
142
|
+
"environment_parent": "QA",
|
143
|
+
"version": "1.2.2",
|
144
|
+
"sha1": "aaa"
|
145
|
+
}
|
146
|
+
}
|
147
|
+
EOS
|
148
|
+
end
|
149
|
+
|
150
|
+
it "does not write to the file" do
|
151
|
+
expect(fake_file).not_to receive(:<<)
|
152
|
+
subject.version_environment('test')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'version_environments' do
|
158
|
+
let(:environments) {%w{dir/environment1.json dir/environment2.json dir/environment3.json}}
|
159
|
+
before {
|
160
|
+
allow(subject).to receive(:version_environment)
|
161
|
+
allow(Dir).to receive(:glob).with(
|
162
|
+
File.join(subject.config.environment_directory, "*.json")).and_return(environments)
|
163
|
+
}
|
164
|
+
|
165
|
+
it "versions each environment" do
|
166
|
+
environments.each do |environment|
|
167
|
+
expect(subject).to receive(:version_environment).with(
|
168
|
+
File.basename(environment ,File.extname(environment)))
|
169
|
+
end
|
170
|
+
subject.version_environments
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'constrain_environment' do
|
175
|
+
before {
|
176
|
+
regex = File.join(config.environment_directory,'test.json')
|
177
|
+
allow(File).to receive(:read).with(regex).and_return(artifact_file)
|
178
|
+
allow(File).to receive(:open).with(regex, "w").and_yield(fake_file)
|
179
|
+
}
|
180
|
+
|
181
|
+
let(:artifact_file) do
|
182
|
+
<<-EOS
|
183
|
+
{
|
184
|
+
"name": "QA1",
|
185
|
+
"chef_type": "environment",
|
186
|
+
"json_class": "Chef::Environment"
|
187
|
+
}
|
188
|
+
EOS
|
189
|
+
end
|
190
|
+
before {
|
191
|
+
allow(File).to receive(:exist?).with(/Berksfile$/).and_return(true)
|
192
|
+
allow(fake_berks).to receive(:list).and_return([
|
193
|
+
double('Dependency', :name => 'cookbook1', :locked_version => Semverse::Version.new('1.1.1')),
|
194
|
+
double('Dependency', :name => 'cookbook2', :locked_version => Semverse::Version.new('2.2.2')),
|
195
|
+
double('Dependency', :name => 'cookbook3', :locked_version => Semverse::Version.new('3.3.3'))
|
196
|
+
])
|
197
|
+
}
|
198
|
+
it "writes the cookbook constraints to the file" do
|
199
|
+
expect(fake_file).to receive(:<<).with(an_instance_of(String)) do |arg|
|
200
|
+
parsed = JSON.parse(arg)
|
201
|
+
expect(parsed['cookbook_versions']['cookbook1']).to eq('1.1.1')
|
202
|
+
expect(parsed['cookbook_versions']['cookbook2']).to eq('2.2.2')
|
203
|
+
expect(parsed['cookbook_versions']['cookbook3']).to eq('3.3.3')
|
204
|
+
end
|
205
|
+
subject.constrain_environment('test', 'test_cookbook')
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context 'is_dirty' do
|
210
|
+
before {
|
211
|
+
regex = File.join(config.environment_directory,'test.json')
|
212
|
+
allow(File).to receive(:read).with(regex).and_return(artifact_file)
|
213
|
+
allow(File).to receive(:open).with(regex, "w").and_yield(fake_file)
|
214
|
+
allow(File).to receive(:read).with(/new.json/).and_return(
|
215
|
+
<<-EOS
|
216
|
+
{
|
217
|
+
"name": "test",
|
218
|
+
"chef_type": "environment",
|
219
|
+
"json_class": "Chef::Environment",
|
220
|
+
"cookbook_versions": {
|
221
|
+
"cookbook1": "1.1.1",
|
222
|
+
"cookbook2": "2.2.2",
|
223
|
+
"cookbook3": "3.3.3"
|
224
|
+
}
|
225
|
+
}
|
226
|
+
EOS
|
227
|
+
)
|
228
|
+
}
|
229
|
+
|
230
|
+
context "cookbook is dirty" do
|
231
|
+
|
232
|
+
let(:artifact_file) do
|
233
|
+
<<-EOS
|
234
|
+
{
|
235
|
+
"name": "QA1",
|
236
|
+
"chef_type": "environment",
|
237
|
+
"json_class": "Chef::Environment",
|
238
|
+
"cookbook_versions": {
|
239
|
+
"cookbook1": "1.1.1",
|
240
|
+
"cookbook2": "1.1.1",
|
241
|
+
"cookbook3": "3.3.3"
|
242
|
+
}
|
243
|
+
}
|
244
|
+
EOS
|
245
|
+
end
|
246
|
+
|
247
|
+
it "returns dirty" do
|
248
|
+
expect(subject.is_dirty('new', 'test', 'cookbook2')).to be(true)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
context "cookbook is not dirty" do
|
252
|
+
let(:artifact_file) do
|
253
|
+
<<-EOS
|
254
|
+
{
|
255
|
+
"name": "QA1",
|
256
|
+
"chef_type": "environment",
|
257
|
+
"json_class": "Chef::Environment",
|
258
|
+
"cookbook_versions": {
|
259
|
+
"cookbook1": "1.1.1",
|
260
|
+
"cookbook2": "2.2.2",
|
261
|
+
"cookbook3": "3.3.3"
|
262
|
+
}
|
263
|
+
}
|
264
|
+
EOS
|
265
|
+
end
|
266
|
+
|
267
|
+
it "returns clean" do
|
268
|
+
expect(subject.is_dirty('new', 'test', 'cookbook2')).to be(false)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
context "cookbook is new to environment" do
|
272
|
+
let(:artifact_file) do
|
273
|
+
<<-EOS
|
274
|
+
{
|
275
|
+
"name": "QA1",
|
276
|
+
"chef_type": "environment",
|
277
|
+
"json_class": "Chef::Environment",
|
278
|
+
"cookbook_versions": {
|
279
|
+
"cookbook1": "1.1.1",
|
280
|
+
"cookbook3": "3.3.3"
|
281
|
+
}
|
282
|
+
}
|
283
|
+
EOS
|
284
|
+
end
|
285
|
+
|
286
|
+
it "returns dirty" do
|
287
|
+
expect(subject.is_dirty('new', 'test', 'cookbook2')).to be(true)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module PromoteSpecs
|
4
|
+
|
5
|
+
class DummyLog < Git::Log
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(commits)
|
9
|
+
@commits = commits
|
10
|
+
@author = nil
|
11
|
+
@grep = nil
|
12
|
+
@object = nil
|
13
|
+
@path = nil
|
14
|
+
@since = nil
|
15
|
+
@skip = nil
|
16
|
+
@until = nil
|
17
|
+
@between = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def size
|
21
|
+
run_log.size rescue nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def first
|
25
|
+
run_log.first rescue nil
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def run_log
|
31
|
+
filtered = @commits.select { |c| @grep.nil? ? true : c[:msg] == @grep }
|
32
|
+
filtered.map { |c| DummyCommit.new(c[:sha]) }
|
33
|
+
ensure
|
34
|
+
@grep = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class DummyCommit
|
40
|
+
def initialize(sha)
|
41
|
+
@sha=sha
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :sha
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PromoteSpecs
|
2
|
+
|
3
|
+
class DummyMetadata < Chef::Cookbook::Metadata
|
4
|
+
def initialize(hash = nil)
|
5
|
+
@hash = hash
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_file(file_path)
|
9
|
+
@name = File.basename(File.dirname(file_path))
|
10
|
+
idx = @name.rindex('-')
|
11
|
+
if !idx.nil?
|
12
|
+
@version = @name[idx+1,@name.length-idx]
|
13
|
+
@name = @name[0,idx]
|
14
|
+
else
|
15
|
+
from_hash(@hash) unless @hash.nil?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clc-promote
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- CenturyLink Cloud
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: clc-git
|
@@ -97,8 +97,12 @@ extensions: []
|
|
97
97
|
extra_rdoc_files:
|
98
98
|
- README.md
|
99
99
|
files:
|
100
|
+
- ".gitignore"
|
100
101
|
- README.md
|
102
|
+
- Rakefile
|
103
|
+
- clc-promote.gemspec
|
101
104
|
- lib/chef/knife/promote.rb
|
105
|
+
- lib/kitchen/provisioner/environment.rb
|
102
106
|
- lib/promote.rb
|
103
107
|
- lib/promote/config.rb
|
104
108
|
- lib/promote/cookbook.rb
|
@@ -108,7 +112,18 @@ files:
|
|
108
112
|
- lib/promote/rake_tasks.rb
|
109
113
|
- lib/promote/uploader.rb
|
110
114
|
- lib/promote/utils.rb
|
115
|
+
- lib/promote/version.rb
|
111
116
|
- lib/promote/versioner.rb
|
117
|
+
- spec/unit/kitchen/provisioner/environment_spec.rb
|
118
|
+
- spec/unit/promote/config_spec.rb
|
119
|
+
- spec/unit/promote/cookbook_spec.rb
|
120
|
+
- spec/unit/promote/promoter_spec.rb
|
121
|
+
- spec/unit/promote/uploader_spec.rb
|
122
|
+
- spec/unit/promote/versioner_spec.rb
|
123
|
+
- spec/unit/stubs/cookbook_1/Berksfile
|
124
|
+
- spec/unit/stubs/cookbook_1/metadata.rb
|
125
|
+
- spec/unit/support/dummy_log.rb
|
126
|
+
- spec/unit/support/dummy_metadata.rb
|
112
127
|
homepage: https://github.com/tier3/DevOps/gems/clc-promote
|
113
128
|
licenses: []
|
114
129
|
metadata: {}
|
@@ -132,5 +147,15 @@ rubygems_version: 2.4.1
|
|
132
147
|
signing_key:
|
133
148
|
specification_version: 4
|
134
149
|
summary: Provides versioning and promotion logic for chef artifacts.
|
135
|
-
test_files:
|
150
|
+
test_files:
|
151
|
+
- spec/unit/kitchen/provisioner/environment_spec.rb
|
152
|
+
- spec/unit/promote/config_spec.rb
|
153
|
+
- spec/unit/promote/cookbook_spec.rb
|
154
|
+
- spec/unit/promote/promoter_spec.rb
|
155
|
+
- spec/unit/promote/uploader_spec.rb
|
156
|
+
- spec/unit/promote/versioner_spec.rb
|
157
|
+
- spec/unit/stubs/cookbook_1/Berksfile
|
158
|
+
- spec/unit/stubs/cookbook_1/metadata.rb
|
159
|
+
- spec/unit/support/dummy_log.rb
|
160
|
+
- spec/unit/support/dummy_metadata.rb
|
136
161
|
has_rdoc:
|