clc-promote 0.4.5 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|