clc-promote 0.4.5 → 0.7.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -8
- data/Rakefile +3 -0
- data/clc-promote.gemspec +3 -2
- data/lib/chef/knife/promote.rb +46 -17
- data/lib/kitchen/provisioner/environment.rb +1 -1
- data/lib/promote.rb +3 -2
- data/lib/promote/config.rb +11 -2
- data/lib/promote/cookbook.rb +17 -5
- data/lib/promote/git_repo.rb +22 -1
- data/lib/promote/node_finder.rb +18 -0
- data/lib/promote/promoter.rb +37 -12
- data/lib/promote/rake_tasks.rb +63 -18
- data/lib/promote/role_file.rb +26 -0
- data/lib/promote/uploader.rb +45 -13
- data/lib/promote/version.rb +2 -2
- data/lib/promote/versioner.rb +44 -9
- data/spec/unit/promote/config_spec.rb +70 -18
- data/spec/unit/promote/promoter_spec.rb +51 -8
- data/spec/unit/promote/uploader_spec.rb +97 -22
- data/spec/unit/promote/versioner_spec.rb +151 -16
- metadata +26 -4
data/lib/promote/rake_tasks.rb
CHANGED
@@ -13,18 +13,22 @@ module Promote
|
|
13
13
|
@uploader = Uploader.new(@config)
|
14
14
|
@promoter = Promoter.new(@config)
|
15
15
|
yield self if block_given?
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
define_version_environments
|
16
|
+
define_constrain_environment
|
17
|
+
define_promote_environment
|
18
|
+
define_promote_environments
|
20
19
|
define_sync_berksfile
|
21
20
|
define_sync_berksfiles
|
22
|
-
define_upload_cookbooks
|
23
21
|
define_upload_cookbook
|
24
|
-
|
22
|
+
define_upload_cookbooks
|
25
23
|
define_upload_data_bags
|
26
|
-
|
27
|
-
|
24
|
+
define_upload_environment
|
25
|
+
define_upload_roles
|
26
|
+
define_version_cookbook
|
27
|
+
define_version_cookbooks
|
28
|
+
define_version_environment
|
29
|
+
define_version_environments
|
30
|
+
define_version_role
|
31
|
+
define_version_roles
|
28
32
|
end
|
29
33
|
|
30
34
|
private
|
@@ -95,7 +99,17 @@ module Promote
|
|
95
99
|
def define_promote_environment
|
96
100
|
namespace "Promote" do
|
97
101
|
desc "Promote one environment from another"
|
98
|
-
task "promote_environment", :source_environment, :
|
102
|
+
task "promote_environment", :source_environment, :destination_environments do |task, args|
|
103
|
+
puts "Promoting constraints in #{args.source_environment} to #{args.destination_environments}"
|
104
|
+
@promoter.monitor_promotion(args.source_environment, args.destination_environments, 60 * 5)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def define_promote_environments
|
110
|
+
namespace "Promote" do
|
111
|
+
desc "Promote a list of environments from another"
|
112
|
+
task "promote_environments", :source_environment, :destination_environments do |task, args|
|
99
113
|
puts "Promoting constraints in #{args.source_environment} to #{args.destination_environment}"
|
100
114
|
deps = @promoter.promote_to(args.source_environment, args.destination_environment)
|
101
115
|
end
|
@@ -114,6 +128,27 @@ module Promote
|
|
114
128
|
end
|
115
129
|
end
|
116
130
|
|
131
|
+
def define_version_role
|
132
|
+
namespace "Promote" do
|
133
|
+
desc "Version a role"
|
134
|
+
task "version_role", :role do |task, args|
|
135
|
+
@versioner.version_role(args.role)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def define_version_roles
|
141
|
+
namespace "Promote" do
|
142
|
+
desc "Version all roles"
|
143
|
+
task "version_roles" do
|
144
|
+
puts "Version stamping changed roles..."
|
145
|
+
@versioner.version_roles.each do |result|
|
146
|
+
puts "Versioned role: #{result[:artifact]} v#{result[:version]}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
117
152
|
def define_upload_cookbooks
|
118
153
|
namespace "Promote" do
|
119
154
|
desc "Upload all cookbooks in an environment to the chef server"
|
@@ -144,15 +179,25 @@ module Promote
|
|
144
179
|
end
|
145
180
|
end
|
146
181
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
182
|
+
def define_upload_roles
|
183
|
+
namespace "Promote" do
|
184
|
+
desc "Upload roles to the chef server"
|
185
|
+
task "upload_roles" do
|
186
|
+
file_uploaded = @uploader.upload_roles
|
187
|
+
puts "Uploading #{file_uploaded} to #{@uploader.config.chef_server_url} as #{@uploader.config.node_name} using #{@uploader.config.client_key}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def define_upload_data_bags
|
193
|
+
namespace "Promote" do
|
194
|
+
desc "Upload data_bags to the chef server"
|
195
|
+
task "upload_data_bags" do
|
196
|
+
file_uploaded = @uploader.upload_data_bags
|
197
|
+
puts "Uploading #{file_uploaded} to #{@uploader.config.chef_server_url} as #{@uploader.config.node_name} using #{@uploader.config.client_key}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
156
201
|
|
157
202
|
end
|
158
203
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Promote
|
4
|
+
class RoleFile
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
def initialize(role_name, config)
|
8
|
+
@name = role_name
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def file_path
|
13
|
+
File.join(@config.role_directory, "#{name}.json")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def content
|
19
|
+
@content ||= get_role_content
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_role_content
|
23
|
+
JSON.parse(File.read(file_path))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/promote/uploader.rb
CHANGED
@@ -3,10 +3,11 @@ require 'chef/chef_fs/parallelizer'
|
|
3
3
|
require 'chef/chef_fs/config'
|
4
4
|
require 'chef/chef_fs/file_pattern'
|
5
5
|
require 'chef/chef_fs/file_system'
|
6
|
+
require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
|
6
7
|
require 'chef/config'
|
7
8
|
require 'chef/chef_fs/path_utils'
|
8
9
|
require 'chef/knife'
|
9
|
-
unless defined? Chef::Knife::CookbookUpload
|
10
|
+
unless defined? Chef::Knife::CookbookUpload
|
10
11
|
require 'chef/knife/cookbook_upload'
|
11
12
|
end
|
12
13
|
|
@@ -41,14 +42,19 @@ module Promote
|
|
41
42
|
|
42
43
|
upload_filtered_cookbooks(dirs, environment)
|
43
44
|
end
|
44
|
-
|
45
|
-
def upload_cookbook_directory(directory)
|
46
|
-
if !Dir.glob(File.join(
|
45
|
+
|
46
|
+
def upload_cookbook_directory(directory, ui = nil)
|
47
|
+
if !Dir.glob(File.join(directory, "*")).empty?
|
48
|
+
if ui
|
49
|
+
ui.info "Uploading cookbooks from #{directory} to #{Chef::Config[:chef_server_url]}"
|
50
|
+
end
|
47
51
|
knife = Chef::Knife::CookbookUpload.new()
|
48
52
|
knife.config[:all] = true
|
49
|
-
knife.config[:freeze] = true
|
53
|
+
knife.config[:freeze] = true
|
50
54
|
knife.config[:cookbook_path] = directory
|
51
55
|
knife.run
|
56
|
+
elsif ui
|
57
|
+
ui.info "No cookbooks found in #{directory}"
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
@@ -60,8 +66,29 @@ module Promote
|
|
60
66
|
upload_file("/environments/*.json")
|
61
67
|
end
|
62
68
|
|
69
|
+
def upload_roles
|
70
|
+
upload_file("/roles/*.json")
|
71
|
+
end
|
72
|
+
|
63
73
|
def upload_data_bags
|
64
|
-
|
74
|
+
data_bag_directory = File.join(config.temp_directory, "data_bags")
|
75
|
+
chef_fs_directory = Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(
|
76
|
+
{
|
77
|
+
'data_bags' => [data_bag_directory]
|
78
|
+
}
|
79
|
+
)
|
80
|
+
|
81
|
+
config.reset_temp_dir
|
82
|
+
|
83
|
+
Dir.glob(File.join(config.data_bag_directory,'**/*.json')).
|
84
|
+
reject{ |file| file.end_with?("_keys.json")}.each do |file|
|
85
|
+
new_path = file.sub(config.repo_root, config.temp_directory)
|
86
|
+
new_dir = File.dirname(new_path)
|
87
|
+
FileUtils.mkdir_p(new_dir) unless Dir.exist?(new_dir)
|
88
|
+
FileUtils.cp(file, new_path)
|
89
|
+
end
|
90
|
+
|
91
|
+
upload_file("/data_bags/**/*.json", chef_fs_directory)
|
65
92
|
end
|
66
93
|
|
67
94
|
attr_accessor :config
|
@@ -70,7 +97,7 @@ module Promote
|
|
70
97
|
|
71
98
|
def upload_filtered_cookbooks(dirs, environment = nil)
|
72
99
|
anything_to_upload = false
|
73
|
-
|
100
|
+
|
74
101
|
server_cookbooks = Utils.chef_server_cookbooks(config)
|
75
102
|
if environment.nil?
|
76
103
|
env_versions = nil
|
@@ -78,8 +105,7 @@ module Promote
|
|
78
105
|
env_versions = EnvironmentFile.new(environment, config).cookbook_versions
|
79
106
|
end
|
80
107
|
|
81
|
-
|
82
|
-
Dir.mkdir(config.temp_directory)
|
108
|
+
config.reset_temp_dir
|
83
109
|
|
84
110
|
dirs.each do | dir |
|
85
111
|
anything_to_upload = cookbooks_copied?(dir, server_cookbooks, env_versions) || anything_to_upload
|
@@ -108,10 +134,16 @@ module Promote
|
|
108
134
|
end
|
109
135
|
end
|
110
136
|
|
111
|
-
def upload_file(file_path)
|
112
|
-
fs_config = Chef::ChefFS::Config.new
|
137
|
+
def upload_file(file_path, src = Chef::ChefFS::Config.new.local_fs)
|
113
138
|
pattern = Chef::ChefFS::FilePattern.new(file_path)
|
114
|
-
|
139
|
+
|
140
|
+
Chef::ChefFS::FileSystem.copy_to(
|
141
|
+
pattern,
|
142
|
+
src,
|
143
|
+
Chef::ChefFS::Config.new.chef_fs,
|
144
|
+
nil,
|
145
|
+
Chef::Config
|
146
|
+
)
|
115
147
|
file_path
|
116
148
|
end
|
117
149
|
|
@@ -127,4 +159,4 @@ module Promote
|
|
127
159
|
end
|
128
160
|
end
|
129
161
|
end
|
130
|
-
end
|
162
|
+
end
|
data/lib/promote/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Promote
|
2
|
-
VERSION = '0.
|
3
|
-
end
|
2
|
+
VERSION = '0.7.8'
|
3
|
+
end
|
data/lib/promote/versioner.rb
CHANGED
@@ -13,9 +13,9 @@ module Promote
|
|
13
13
|
if cookbook.version.to_s != version
|
14
14
|
cookbook.version = Semverse::Version.new(version)
|
15
15
|
cookbook.stamp_commit(repo.sha1)
|
16
|
-
return {
|
17
|
-
:cookbook => cookbook_name,
|
18
|
-
:version => version,
|
16
|
+
return {
|
17
|
+
:cookbook => cookbook_name,
|
18
|
+
:version => version,
|
19
19
|
:sha1 => repo.sha1
|
20
20
|
}
|
21
21
|
end
|
@@ -49,6 +49,34 @@ module Promote
|
|
49
49
|
results
|
50
50
|
end
|
51
51
|
|
52
|
+
def version_role(role_name)
|
53
|
+
# if role doesn't include an override_attributes key, create it
|
54
|
+
file = File.join(config.role_directory, "#{role_name}.json")
|
55
|
+
role = JSON.parse(File.read(file))
|
56
|
+
if !role.has_key?("override_attributes")
|
57
|
+
overrides_hash = { "override_attributes" => {} }
|
58
|
+
role.merge!(overrides_hash)
|
59
|
+
File.open(file, 'w') do |out|
|
60
|
+
out << JSON.pretty_generate(role)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
version_json config.role_directory, role_name do | content |
|
65
|
+
content['override_attributes']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def version_roles
|
70
|
+
results = []
|
71
|
+
Dir.glob(File.join(config.role_directory, "*.json")).each do |file|
|
72
|
+
result = version_role(File.basename(file ,File.extname(file)))
|
73
|
+
if !result.nil?
|
74
|
+
results << result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
results
|
78
|
+
end
|
79
|
+
|
52
80
|
def constrain_environment(environment_name, cookbook_name)
|
53
81
|
dependencies = Cookbook.new(cookbook_name, config).dependencies
|
54
82
|
env_file = EnvironmentFile.new(environment_name, config)
|
@@ -56,12 +84,18 @@ module Promote
|
|
56
84
|
dependencies
|
57
85
|
end
|
58
86
|
|
59
|
-
def is_dirty(source_environment, target_environment, cookbook_name)
|
87
|
+
def is_dirty(source_environment, target_environment, cookbook_name, dependency_hash = nil)
|
60
88
|
target_env_file = EnvironmentFile.new(target_environment, config)
|
61
89
|
return true unless target_env_file.cookbook_versions.has_key?(cookbook_name)
|
62
|
-
|
90
|
+
|
63
91
|
source_env_file = EnvironmentFile.new(source_environment, config)
|
64
|
-
|
92
|
+
target_version_parts = target_env_file.cookbook_versions[cookbook_name].split('.')
|
93
|
+
target_version = target_version_parts[0..2].join('.')
|
94
|
+
target_hash = nil
|
95
|
+
if target_version_parts.count > 3
|
96
|
+
target_hash = target_version_parts[3]
|
97
|
+
end
|
98
|
+
return source_env_file.cookbook_versions[cookbook_name] != target_version || dependency_hash != target_hash
|
65
99
|
end
|
66
100
|
|
67
101
|
attr_accessor :config
|
@@ -92,12 +126,13 @@ module Promote
|
|
92
126
|
File.open(file, 'w') do |out|
|
93
127
|
out << JSON.pretty_generate(content)
|
94
128
|
end
|
95
|
-
{
|
96
|
-
:artifact => file_name,
|
97
|
-
:version => version,
|
129
|
+
{
|
130
|
+
:artifact => file_name,
|
131
|
+
:version => version,
|
98
132
|
:sha1 => repo.sha1
|
99
133
|
}
|
100
134
|
end
|
135
|
+
|
101
136
|
end
|
102
137
|
end
|
103
138
|
end
|
@@ -1,15 +1,17 @@
|
|
1
1
|
require 'promote'
|
2
2
|
|
3
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
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
4
|
+
let(:opts) {{
|
5
|
+
:repo_root => "root",
|
6
|
+
:cookbook_directory => "cookbooks",
|
7
|
+
:environment_directory => "environments",
|
8
|
+
:data_bag_directory => "data_bags",
|
9
|
+
:role_directory => "roles",
|
10
|
+
:temp_directory => "temp",
|
11
|
+
:node_name => "user",
|
12
|
+
:client_key => "key",
|
13
|
+
:chef_server_url => "url",
|
14
|
+
:bags => ['foo']}}
|
13
15
|
subject { Promote::Config.new(opts) }
|
14
16
|
|
15
17
|
it "assigns options to node_name attribute" do
|
@@ -33,20 +35,25 @@ describe Promote::Config do
|
|
33
35
|
it "assigns options to environment_directory attribute" do
|
34
36
|
expect(subject.environment_directory).to eq(opts[:environment_directory])
|
35
37
|
end
|
38
|
+
it "assigns options to role_directory attribute" do
|
39
|
+
expect(subject.role_directory).to eq(opts[:role_directory])
|
40
|
+
end
|
36
41
|
it "assigns options to temp_directory attribute" do
|
37
42
|
expect(subject.temp_directory).to eq(opts[:temp_directory])
|
38
43
|
end
|
44
|
+
it "assigns options to bags attribute" do
|
45
|
+
expect(subject.bags).to eq(opts[:bags])
|
46
|
+
end
|
39
47
|
it "can correctly convert to a hash" do
|
40
48
|
hash = subject.to_hash
|
41
49
|
expect(hash[:repo_root]).to eq(opts[:repo_root])
|
42
50
|
end
|
43
51
|
|
44
52
|
context "directories are not in options" do
|
45
|
-
let(:opts) {{
|
46
|
-
:node_name => "user",
|
47
|
-
:client_key => "key",
|
53
|
+
let(:opts) {{
|
54
|
+
:node_name => "user",
|
55
|
+
:client_key => "key",
|
48
56
|
:chef_server_url => "url"}}
|
49
|
-
subject { Promote::Config.new(opts) }
|
50
57
|
|
51
58
|
it "assigns repo_root to pwd" do
|
52
59
|
expect(subject.repo_root).to eq(Dir.pwd)
|
@@ -60,18 +67,31 @@ describe Promote::Config do
|
|
60
67
|
it "assigns environment_directory to environments off root" do
|
61
68
|
expect(subject.environment_directory).to eq(File.join(subject.repo_root, "environments"))
|
62
69
|
end
|
70
|
+
it "assigns role_directory to roles off root" do
|
71
|
+
expect(subject.role_directory).to eq(File.join(subject.repo_root, "roles"))
|
72
|
+
end
|
63
73
|
it "assigns temp_directory to tmp" do
|
64
74
|
expect(subject.temp_directory).to eq("/tmp/promote")
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
78
|
+
context "no --data-bag option is specified" do
|
79
|
+
let(:opts) {{
|
80
|
+
:node_name => "user",
|
81
|
+
:client_key => "key",
|
82
|
+
:chef_server_url => "url"}}
|
83
|
+
|
84
|
+
it "defaults to secrets_*" do
|
85
|
+
expect(subject.bags).to eq(['secrets_*'])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
68
89
|
context "directories are not in options but repo root is" do
|
69
|
-
let(:opts) {{
|
90
|
+
let(:opts) {{
|
70
91
|
:repo_root => "root",
|
71
|
-
:node_name => "user",
|
72
|
-
:client_key => "key",
|
92
|
+
:node_name => "user",
|
93
|
+
:client_key => "key",
|
73
94
|
:chef_server_url => "url"}}
|
74
|
-
subject { Promote::Config.new(opts) }
|
75
95
|
|
76
96
|
it "assigns repo_root to pwd" do
|
77
97
|
expect(subject.repo_root).to eq(opts[:repo_root])
|
@@ -85,8 +105,40 @@ describe Promote::Config do
|
|
85
105
|
it "assigns environment_directory to environments off root" do
|
86
106
|
expect(subject.environment_directory).to eq(File.join(subject.repo_root, "environments"))
|
87
107
|
end
|
108
|
+
it "assigns role_directory to roles off root" do
|
109
|
+
expect(subject.role_directory).to eq(File.join(subject.repo_root, "roles"))
|
110
|
+
end
|
88
111
|
it "assigns temp_directory to tmp" do
|
89
112
|
expect(subject.temp_directory).to eq("/tmp/promote")
|
90
113
|
end
|
91
114
|
end
|
92
|
-
|
115
|
+
|
116
|
+
context "reset_temp_dir" do
|
117
|
+
after {
|
118
|
+
FileUtils.rm_rf(opts[:temp_directory])
|
119
|
+
}
|
120
|
+
|
121
|
+
context "temp dir is populated" do
|
122
|
+
before {
|
123
|
+
opts[:temp_directory] = "/tmp/promote_tests"
|
124
|
+
Dir.mkdir(opts[:temp_directory])
|
125
|
+
FileUtils.touch(File.join(opts[:temp_directory], 'file.txt'))
|
126
|
+
}
|
127
|
+
|
128
|
+
it "empties the temp directory" do
|
129
|
+
subject.reset_temp_dir
|
130
|
+
|
131
|
+
expect(Dir[File.join(subject.temp_directory, '*')]).to be_empty
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "temp dir is populated" do
|
136
|
+
|
137
|
+
it "creates the temp directory" do
|
138
|
+
subject.reset_temp_dir
|
139
|
+
|
140
|
+
expect(Dir).to exist(subject.temp_directory)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|