pagerduty-pd_sync 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6fac563e7255e747b0ffff23e56056ef7477e945
4
+ data.tar.gz: a8359337b1b4558df37e79b1a1f826f0521a6b42
5
+ SHA512:
6
+ metadata.gz: b24f4216c8be7d2cccb2d53c37c1fb0641fb6cf34cd211796f7e6fc2affed2461c0c7852d3edd4c0135eef7af04118458c1a1574049691368cd95622fdc84c60
7
+ data.tar.gz: 13537752201b76d4922da5c507af0cc2d4818653904c2a0f67fa0eedba203517f0047cd851c206c863599ad5733b78c0ca85ab49da2924bdfad70514ac39666f
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pd-sync-chef.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,220 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pd_sync (0.1.0)
5
+ berkshelf
6
+ chef
7
+ json
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.4.0)
13
+ berkshelf (4.3.2)
14
+ addressable (~> 2.3, >= 2.3.4)
15
+ berkshelf-api-client (~> 2.0, >= 2.0.2)
16
+ buff-config (~> 1.0)
17
+ buff-extensions (~> 1.0)
18
+ buff-shell_out (~> 0.1)
19
+ celluloid (= 0.16.0)
20
+ celluloid-io (~> 0.16.1)
21
+ cleanroom (~> 1.0)
22
+ faraday (~> 0.9)
23
+ httpclient (~> 2.7)
24
+ minitar (~> 0.5, >= 0.5.4)
25
+ octokit (~> 4.0)
26
+ retryable (~> 2.0)
27
+ ridley (~> 4.5)
28
+ solve (~> 2.0)
29
+ thor (~> 0.19)
30
+ berkshelf-api-client (2.0.2)
31
+ faraday (~> 0.9.1)
32
+ httpclient (~> 2.7.0)
33
+ ridley (~> 4.5)
34
+ buff-config (1.0.1)
35
+ buff-extensions (~> 1.0)
36
+ varia_model (~> 0.4)
37
+ buff-extensions (1.0.0)
38
+ buff-ignore (1.1.1)
39
+ buff-ruby_engine (0.1.0)
40
+ buff-shell_out (0.2.0)
41
+ buff-ruby_engine (~> 0.1.0)
42
+ builder (3.2.2)
43
+ celluloid (0.16.0)
44
+ timers (~> 4.0.0)
45
+ celluloid-io (0.16.2)
46
+ celluloid (>= 0.16.0)
47
+ nio4r (>= 1.1.0)
48
+ chef (12.5.1)
49
+ chef-config (= 12.5.1)
50
+ chef-zero (~> 4.2, >= 4.2.2)
51
+ diff-lcs (~> 1.2, >= 1.2.4)
52
+ erubis (~> 2.7)
53
+ ffi-yajl (~> 2.2)
54
+ highline (~> 1.6, >= 1.6.9)
55
+ mixlib-authentication (~> 1.3)
56
+ mixlib-cli (~> 1.4)
57
+ mixlib-log (~> 1.3)
58
+ mixlib-shellout (~> 2.0)
59
+ net-ssh (~> 2.6)
60
+ net-ssh-multi (~> 1.1)
61
+ ohai (>= 8.6.0.alpha.1, < 9)
62
+ plist (~> 3.1.0)
63
+ pry (~> 0.9)
64
+ rspec-core (~> 3.2)
65
+ rspec-expectations (~> 3.2)
66
+ rspec-mocks (~> 3.2)
67
+ rspec_junit_formatter (~> 0.2.0)
68
+ serverspec (~> 2.7)
69
+ specinfra (~> 2.10)
70
+ syslog-logger (~> 1.6)
71
+ chef-config (12.5.1)
72
+ mixlib-config (~> 2.0)
73
+ mixlib-shellout (~> 2.0)
74
+ chef-zero (4.6.2)
75
+ ffi-yajl (~> 2.2)
76
+ hashie (>= 2.0, < 4.0)
77
+ mixlib-log (~> 1.3)
78
+ rack
79
+ uuidtools (~> 2.1)
80
+ cleanroom (1.0.0)
81
+ coderay (1.1.0)
82
+ diff-lcs (1.2.5)
83
+ erubis (2.7.0)
84
+ faraday (0.9.2)
85
+ multipart-post (>= 1.2, < 3)
86
+ ffi (1.9.10)
87
+ ffi-yajl (2.2.3)
88
+ libyajl2 (~> 1.2)
89
+ hashie (3.4.4)
90
+ highline (1.7.8)
91
+ hitimes (1.2.3)
92
+ httpclient (2.7.2)
93
+ ipaddress (0.8.3)
94
+ json (1.8.3)
95
+ libyajl2 (1.2.0)
96
+ method_source (0.8.2)
97
+ minitar (0.5.4)
98
+ mixlib-authentication (1.4.0)
99
+ mixlib-log
100
+ rspec-core (~> 3.2)
101
+ rspec-expectations (~> 3.2)
102
+ rspec-mocks (~> 3.2)
103
+ mixlib-cli (1.5.0)
104
+ mixlib-config (2.2.1)
105
+ mixlib-log (1.6.0)
106
+ mixlib-shellout (2.2.6)
107
+ molinillo (0.4.4)
108
+ multi_json (1.11.3)
109
+ multipart-post (2.0.0)
110
+ net-scp (1.2.1)
111
+ net-ssh (>= 2.6.5)
112
+ net-ssh (2.9.4)
113
+ net-ssh-gateway (1.2.0)
114
+ net-ssh (>= 2.6.5)
115
+ net-ssh-multi (1.2.1)
116
+ net-ssh (>= 2.6.5)
117
+ net-ssh-gateway (>= 1.2.0)
118
+ net-telnet (0.1.1)
119
+ nio4r (1.2.1)
120
+ octokit (4.3.0)
121
+ sawyer (~> 0.7.0, >= 0.5.3)
122
+ ohai (8.15.1)
123
+ chef-config (>= 12.5.0.alpha.1, < 13)
124
+ ffi (~> 1.9)
125
+ ffi-yajl (~> 2.2)
126
+ ipaddress
127
+ mixlib-cli
128
+ mixlib-config (~> 2.0)
129
+ mixlib-log
130
+ mixlib-shellout (~> 2.0)
131
+ plist (~> 3.1)
132
+ systemu (~> 2.6.4)
133
+ wmi-lite (~> 1.0)
134
+ plist (3.1.0)
135
+ pry (0.10.1)
136
+ coderay (~> 1.1.0)
137
+ method_source (~> 0.8.1)
138
+ slop (~> 3.4)
139
+ rack (1.6.4)
140
+ rake (10.3.2)
141
+ retryable (2.0.3)
142
+ ridley (4.5.0)
143
+ addressable
144
+ buff-config (~> 1.0)
145
+ buff-extensions (~> 1.0)
146
+ buff-ignore (~> 1.1)
147
+ buff-shell_out (~> 0.1)
148
+ celluloid (~> 0.16.0)
149
+ celluloid-io (~> 0.16.1)
150
+ chef-config (>= 12.5.0)
151
+ erubis
152
+ faraday (~> 0.9.0)
153
+ hashie (>= 2.0.2, < 4.0.0)
154
+ httpclient (~> 2.7)
155
+ json (>= 1.7.7)
156
+ mixlib-authentication (>= 1.3.0)
157
+ retryable (~> 2.0)
158
+ semverse (~> 1.1)
159
+ varia_model (~> 0.4.0)
160
+ rspec (3.2.0)
161
+ rspec-core (~> 3.2.0)
162
+ rspec-expectations (~> 3.2.0)
163
+ rspec-mocks (~> 3.2.0)
164
+ rspec-core (3.2.2)
165
+ rspec-support (~> 3.2.0)
166
+ rspec-expectations (3.2.0)
167
+ diff-lcs (>= 1.2.0, < 2.0)
168
+ rspec-support (~> 3.2.0)
169
+ rspec-its (1.2.0)
170
+ rspec-core (>= 3.0.0)
171
+ rspec-expectations (>= 3.0.0)
172
+ rspec-mocks (3.2.1)
173
+ diff-lcs (>= 1.2.0, < 2.0)
174
+ rspec-support (~> 3.2.0)
175
+ rspec-support (3.2.2)
176
+ rspec_junit_formatter (0.2.3)
177
+ builder (< 4)
178
+ rspec-core (>= 2, < 4, != 2.12.0)
179
+ sawyer (0.7.0)
180
+ addressable (>= 2.3.5, < 2.5)
181
+ faraday (~> 0.8, < 0.10)
182
+ semverse (1.2.1)
183
+ serverspec (2.33.0)
184
+ multi_json
185
+ rspec (~> 3.0)
186
+ rspec-its
187
+ specinfra (~> 2.53)
188
+ sfl (2.2)
189
+ slop (3.6.0)
190
+ solve (2.0.3)
191
+ molinillo (~> 0.4.2)
192
+ semverse (~> 1.1)
193
+ specinfra (2.57.1)
194
+ net-scp
195
+ net-ssh (>= 2.7, < 4.0)
196
+ net-telnet
197
+ sfl
198
+ syslog-logger (1.6.8)
199
+ systemu (2.6.5)
200
+ thor (0.19.1)
201
+ timers (4.0.4)
202
+ hitimes
203
+ uuidtools (2.1.5)
204
+ varia_model (0.4.1)
205
+ buff-extensions (~> 1.0)
206
+ hashie (>= 2.0.2, < 4.0.0)
207
+ wmi-lite (1.0.0)
208
+
209
+ PLATFORMS
210
+ ruby
211
+
212
+ DEPENDENCIES
213
+ bundler
214
+ pd_sync!
215
+ pry
216
+ rake
217
+ rspec
218
+
219
+ BUNDLED WITH
220
+ 1.11.2
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: [:spec]
@@ -0,0 +1,198 @@
1
+ #
2
+ # Author:: Tim Heckman (<ops@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2016 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'berkshelf'
19
+ require 'chef/knife'
20
+
21
+ class Chef
22
+ class Knife
23
+ class PdSync < Knife
24
+
25
+ attr_reader :altered_cookbooks
26
+
27
+ banner 'knife pd sync [--restore --why-run]'
28
+
29
+ deps do
30
+ require 'uri'
31
+ require 'socket'
32
+ require 'timeout'
33
+ require 'chef/cookbook_version'
34
+ require 'chef/data_bag_item'
35
+ require 'chef/data_bag'
36
+ require 'chef/knife/cookbook_bulk_delete'
37
+ require 'chef/knife/data_bag_create'
38
+ require 'chef/knife/data_bag_delete'
39
+ require 'chef/knife/data_bag_from_file'
40
+ require 'chef/knife/environment_from_file'
41
+ require 'chef/knife/role_from_file'
42
+ require 'chef/knife/cookbook_upload'
43
+ require 'berkshelf'
44
+ require 'berkshelf/berksfile'
45
+ require 'mixlib/shellout'
46
+
47
+ require 'pagerduty/chef_server/synclock'
48
+ require 'pagerduty/chef_server/sync'
49
+
50
+ Chef::Knife::CookbookUpload.load_deps
51
+ Chef::Knife::CookbookBulkDelete.load_deps
52
+ Chef::Knife::DataBagCreate.load_deps
53
+ Chef::Knife::DataBagDelete.load_deps
54
+ Chef::Knife::DataBagFromFile.load_deps
55
+ Chef::Knife::EnvironmentFromFile.load_deps
56
+ Chef::Knife::RoleFromFile.load_deps
57
+ end
58
+
59
+ option :restore,
60
+ short: '-r',
61
+ long: '--restore',
62
+ description: 'Upload all cookbooks regardless of whether checksums have changed',
63
+ boolean: true,
64
+ default: false
65
+
66
+ option :why_run,
67
+ short: '-W',
68
+ long: '--why-run',
69
+ description: 'Show what operations will be made, without actually performing them (does not work with --restore)',
70
+ boolean: true,
71
+ default: false
72
+
73
+ def run
74
+ @altered_cookbooks = nil
75
+ if config[:restore]
76
+ ui.warn 'pd sync will delete and reupload all cookbooks!'
77
+ plugin = Chef::Knife::CookbookBulkDelete.new
78
+ plugin.name_args = Array('.')
79
+ plugin.config[:yes] = true
80
+ plugin.config[:purge] = true
81
+ converge_by "delete all existing cookbooks" do
82
+ plugin.run
83
+ end
84
+ end
85
+ lockfile = '/tmp/restore_chef.lock'
86
+ user = Chef::Config[:node_name] || 'unknown'
87
+ converge_by 'perform pre-syn checks' do
88
+ preflight_checks
89
+ end
90
+ lock = PagerDuty::ChefServer::SyncLock.new(
91
+ lockfile, chef_server, localhost, user, local_branch
92
+ )
93
+ converge_by 'acquire lock' do
94
+ lock.lock
95
+ end
96
+ sync = PagerDuty::ChefServer::Sync.new(
97
+ vendor_dir: vendor_dir,
98
+ why_run: config[:why_run]
99
+ )
100
+ begin
101
+ @altered_cookbooks = sync.run
102
+ update_commit
103
+ rescue StandardError => e
104
+ ui.warn(e.message)
105
+ ui.warn(e.backtrace)
106
+ ensure
107
+ converge_by 'release lock' do
108
+ lock.unlock
109
+ end
110
+ end
111
+ end
112
+
113
+ def preflight_checks
114
+ vdir = File.join(Dir.pwd, 'vendor')
115
+ if vendor_dir != vdir
116
+ ui.confirm("vendor directory (#{vendor_dir}) is different than standard one(#{vdir}), continue?")
117
+ end
118
+ if local_branch != 'master'
119
+ ui.confirm("You are deploying a non-master branch(#{local_branch}), continue?")
120
+ end
121
+ check_commit
122
+ end
123
+
124
+ def check_commit
125
+ if origin_commit.nil?
126
+ ui.confirm('failed to determine the origin/master. sync anyway?')
127
+ elsif local_branch == 'master' && local_commit != origin_commit
128
+ ui.confirm('local master branch is different than origin, sync anyway?')
129
+ end
130
+ end
131
+
132
+ def vendor_dir
133
+ Chef::Config[:cookbook_path].first
134
+ end
135
+
136
+ def local_branch
137
+ %x(git symbolic-ref --short HEAD).strip! || 'unknown'
138
+ end
139
+
140
+ def chef_server
141
+ URI(Chef::Config[:chef_server_url]).host
142
+ end
143
+
144
+ def origin_commit
145
+ @origin_commit||= begin
146
+ Timeout::timeout(5) do
147
+ commit = Mixlib::ShellOut.new("git ls-remote origin master | awk '{ print $1 }'")
148
+ commit.run_command
149
+ commit.exitstatus == 0 ? commit.stdout.strip : nil
150
+ end
151
+ rescue Timeout::Error
152
+ nil
153
+ end
154
+ end
155
+
156
+ def local_commit
157
+ @local_commit ||= %x(git rev-parse master).strip! || 'unknown'
158
+ end
159
+
160
+ def localhost
161
+ @localhost ||= Socket.gethostname
162
+ end
163
+
164
+ def converge_by(msg)
165
+ if config[:why_run]
166
+ ui.info('Will '+msg)
167
+ else
168
+ yield if block_given?
169
+ end
170
+ end
171
+
172
+ def update_commit
173
+ ui.info("updating commit from #{origin_commit} => #{local_commit}")
174
+ file = Tempfile.new(['restorechef', '.json'])
175
+
176
+ unless Chef::DataBag.list.keys.include?('metadata')
177
+ plugin = Chef::Knife::DataBagCreate.new
178
+ plugin.name_args = Array('metadata')
179
+ converge_by 'create data bag metadata' do
180
+ plugin.run
181
+ end
182
+ end
183
+ begin
184
+ file.write(JSON.dump({ id: 'commit', commit: local_commit }))
185
+ file.flush
186
+ dbag = Chef::Knife::DataBagFromFile.new
187
+ dbag.name_args = ['metadata', file.path]
188
+ converge_by 'update commit' do
189
+ dbag.run
190
+ end
191
+ ensure
192
+ file.close
193
+ file.unlink
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,208 @@
1
+ #
2
+ # Author:: Tim Heckman (<ops@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2016 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'pagerduty/chef_server/sync_helper'
19
+
20
+ module PagerDuty
21
+ module ChefServer
22
+ # rubocop:disable Metrics/ClassLength
23
+ class Sync
24
+
25
+ # require_relative 'sync_helper'
26
+ include PagerDuty::ChefServer::SyncHelper
27
+
28
+ attr_reader :ui, :why_run, :cookbook_dir, :ignore_patterns
29
+
30
+ def initialize(opts={})
31
+ require 'tempfile'
32
+ require 'json'
33
+ require 'mixlib/shellout'
34
+ require 'chef/cookbook_version'
35
+ require 'chef/data_bag_item'
36
+ require 'chef/data_bag'
37
+ require 'berkshelf'
38
+ require 'berkshelf/berksfile'
39
+ @cookbook_dir = opts[:vendor_dir]
40
+ @why_run = opts[:why_run]
41
+ @ui = opts[:ui] || Chef::Knife.ui
42
+ if File.exist?(ignore_file)
43
+ @ignore_patterns = File.read(ignore_file).lines.map{|l| File.join(chef_repo_dir, l).strip}
44
+ else
45
+ ui.info('.pd-ignore absent, nothing will be ignored')
46
+ @ignore_patterns = false
47
+ end
48
+ end
49
+
50
+ def run
51
+ berkshelf_install
52
+ sync_cookbooks
53
+ upload_databags
54
+ upload_environments
55
+ upload_roles
56
+ end
57
+
58
+ def sync_cookbooks
59
+ altered_cookbooks = Hash.new{|h, k| h[k] = []}
60
+
61
+ if remote_cookbooks.empty?
62
+ upload_all_cookbooks
63
+ else
64
+ unless new_cookbooks.empty?
65
+ upload_cookbooks(new_cookbooks)
66
+ altered_cookbooks[:added] = new_cookbooks
67
+ end
68
+ stale_cookbooks.each do |cb|
69
+ delete_cookbook(cb)
70
+ altered_cookbooks[:deleted] << cb
71
+ end
72
+ updated_cookbooks.each do |cb|
73
+ replace_cookbook(cb)
74
+ altered_cookbooks[:updated] << cb
75
+ end
76
+ end
77
+ end
78
+
79
+ def berkshelf_install
80
+ path = File.expand_path(File.join(chef_repo_dir, 'Berksfile'))
81
+ ui.info(ui.color("using Berksfile: #{path} for berkshelf install", :yellow))
82
+ berksfile = Berkshelf::Berksfile.from_file(path, { except: 'tests' } )
83
+ FileUtils.rm_rf(cookbook_dir)
84
+ berksfile.vendor(cookbook_dir)
85
+ end
86
+
87
+ def local_cookbooks
88
+ local_checksums.keys.sort
89
+ end
90
+
91
+ def remote_cookbooks
92
+ @remote_cookbooks ||= Chef::CookbookVersion.list.keys.sort
93
+ end
94
+
95
+ def remote_commit
96
+ @remote_commit ||= begin
97
+ if Chef::DataBag.list.keys.include?('metadata')
98
+ Chef::DataBagItem.load('metadata', 'commit').raw_data['commit']
99
+ else
100
+ {}
101
+ end
102
+ end
103
+ end
104
+
105
+ def cookbook_segments
106
+ Chef::CookbookVersion::COOKBOOK_SEGMENTS
107
+ end
108
+
109
+ def remote_checksums
110
+ @remote_checksums ||= begin
111
+ c = {}
112
+ remote_cookbooks.each do |cb|
113
+ c[cb] = {}
114
+ cbm = Chef::CookbookVersion.load(cb).manifest
115
+ cookbook_segments.each do |m|
116
+ cbm_sort = cbm[m].sort { |x, y| x['name'] <=> y['name'] }
117
+ cbm_sort = cbm_sort.sort { |x, y| x['checksum'] <=> y['checksum'] }
118
+ cbm_sort.each do |file|
119
+ file.delete(:url)
120
+ file.delete(:path)
121
+ file.delete(:specificity)
122
+ end
123
+ c[cb][m] = cbm_sort
124
+ end
125
+ end
126
+ c
127
+ end
128
+ end
129
+
130
+ def local_checksums
131
+ @local_checksums ||= begin
132
+ c = {}
133
+ cbl = Chef::CookbookLoader.new(Array(cookbook_dir))
134
+ cbl.load_cookbooks
135
+ cbl_sort = cbl.values.map(&:name).map(&:to_s).sort
136
+
137
+ cbl_sort.each do |cb|
138
+ print "#{cb} => "
139
+ c[cb] = {}
140
+ cookbook_segments.each do |m|
141
+ cbm_sort = cbl[cb].manifest[m].sort { |x, y| x['name'] <=> y['name'] }
142
+ cbm_sort = cbm_sort.sort { |x, y| x['checksum'] <=> y['checksum'] }
143
+ cbm_sort.each do |file|
144
+ file.delete(:path)
145
+ file.delete(:specificity)
146
+ end
147
+ c[cb][m] = cbm_sort
148
+ end
149
+ diff = diff(c[cb], remote_checksums[cb])
150
+ if diff.empty?
151
+ ui.info(ui.color( 'match', :green))
152
+ else
153
+ ui.info(ui.color( 'mismatch', :yellow))
154
+ ui.output(diff(c[cb], remote_checksums[cb]))
155
+ end
156
+ sleep 0.1 # was printing too fast to be useful :(
157
+ end
158
+ c
159
+ end
160
+ end
161
+
162
+ def diff(mf1, mf2)
163
+ diffs = Hash.new{|h, k| h[k]= []}
164
+ mf2 = {} if mf2.nil?
165
+ mf1 = {} if mf1.nil?
166
+ segments = (mf1.keys + mf2.keys).sort.uniq
167
+ different_parts = segments.select{|segment| mf1[segment]!= mf2[segment]}
168
+ different_parts.each do |segment|
169
+ files = (Array(mf1[segment]) + Array(mf2[segment])).map{|f| f['name']}.uniq
170
+ files.each do |file|
171
+ f1 = Array(mf1[segment]).detect{|f|f['name'] == file} || {}
172
+ f2 = Array(mf2[segment]).detect{|f|f['name'] == file} || {}
173
+ unless f1['checksum'] == f2['checksum']
174
+ diffs[segment] << file #unless file =~ /metadata\.(rb|json)/
175
+ end
176
+ end
177
+ end
178
+ diffs
179
+ end
180
+
181
+ def different_cookbook?(cb)
182
+ !diff(local_checksums[cb], remote_checksums[cb]).empty?
183
+ end
184
+
185
+ def new_cookbooks
186
+ local_cookbooks - remote_cookbooks
187
+ end
188
+
189
+ def stale_cookbooks
190
+ remote_cookbooks - local_cookbooks
191
+ end
192
+
193
+ def updated_cookbooks
194
+ (local_cookbooks & remote_cookbooks).select do |cb|
195
+ different_cookbook?(cb)
196
+ end
197
+ end
198
+
199
+ def replace_cookbook(cb)
200
+ converge_by "Replace cookbook #{cb}" do
201
+ delete_cookbook(cb)
202
+ upload_cookbook(cb)
203
+ end
204
+ end
205
+ end
206
+ # rubocop:enable Metrics/ClassLength
207
+ end
208
+ end
@@ -0,0 +1,163 @@
1
+ #
2
+ # Author:: Tim Heckman (<ops@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2016 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module PagerDuty
19
+ module ChefServer
20
+ module SyncHelper
21
+
22
+ def ignored?(path)
23
+ ignore_patterns && ignore_patterns.any?{|pattern| File.fnmatch?(pattern, path)}
24
+ end
25
+
26
+ def converge_by(msg)
27
+ if why_run
28
+ ui.info(ui.color("Would ", :cyan)+ msg)
29
+ else
30
+ yield
31
+ end
32
+ end
33
+
34
+ def chef_repo_dir
35
+ File.expand_path('..', cookbook_dir)
36
+ end
37
+
38
+ def role_dir
39
+ File.join(chef_repo_dir, 'roles')
40
+ end
41
+
42
+ def databag_dir
43
+ File.join(chef_repo_dir, 'data_bags')
44
+ end
45
+
46
+ def environment_dir
47
+ File.join(chef_repo_dir, 'environments')
48
+ end
49
+
50
+
51
+ def data_bag_from_file(name, path)
52
+ converge_by "Create data bag #{name} from #{path}" do
53
+ knife Chef::Knife::DataBagFromFile, name, path
54
+ end
55
+ end
56
+
57
+ def upload_environments
58
+ Dir[environment_dir+'/*'].reject{|f| File.directory?(f)}.each do |path|
59
+ converge_by "Create environment from #{path}" do
60
+ knife Chef::Knife::EnvironmentFromFile, path
61
+ end
62
+ end
63
+ end
64
+
65
+ def upload_roles
66
+ Dir[role_dir+'/*'].each do |path|
67
+ converge_by "Create role from #{path}" do
68
+ knife Chef::Knife::RoleFromFile, path
69
+ end
70
+ end
71
+ end
72
+
73
+ def upload_all_cookbooks
74
+ converge_by 'Upload all cookbooks' do
75
+ knife(Chef::Knife::CookbookUpload) do |config|
76
+ config[:all] = true
77
+ config[:cookbook_path] = cookbook_dir
78
+ end
79
+ end
80
+ end
81
+
82
+ def upload_cookbook(cb)
83
+ converge_by "Upload cookbook #{cb}" do
84
+ knife(Chef::Knife::CookbookUpload, cb) do |config|
85
+ config[:cookbook_path] = cookbook_dir
86
+ config[:depends] = false
87
+ end
88
+ end
89
+ end
90
+
91
+ def upload_cookbooks(cbcb)
92
+ sorted_cookbooks = cbcb.sort # definite order + nice printout for why_run
93
+ converge_by "Upload cookbooks #{sorted_cookbooks.join(', ')}" do
94
+ knife Chef::Knife::CookbookUpload, *sorted_cookbooks do |config|
95
+ config[:cookbook_path] = cookbook_dir
96
+ config[:depends] = false
97
+ end
98
+ end
99
+ end
100
+
101
+ def delete_cookbook(cb)
102
+ converge_by "Delete cookbook #{cb}" do
103
+ knife Chef::Knife::CookbookDelete, cb do |config|
104
+ config[:yes] = true
105
+ config[:all] = true
106
+ end
107
+ end
108
+ end
109
+
110
+ def upload_databags
111
+ ui.info(ui.color('updating data bags in batch mode', :yellow))
112
+ existing_databags = Chef::DataBag.list.keys
113
+ Dir[databag_dir+'/*'].each do |db_path|
114
+ if ignored?(db_path)
115
+ ui.info(ui.color("Ignored:", :magenta) + db_path)
116
+ next
117
+ end
118
+ db_name = File.basename(db_path)
119
+ unless existing_databags.include?(db_name)
120
+ create_databag(db_name)
121
+ end
122
+ Dir[db_path+'/*'].each do |path|
123
+ unless ignored?(path)
124
+ data_bag_from_file(File.basename(db_path), path)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def create_databag(data_bag_name)
131
+ converge_by "Create data bag #{data_bag_name}" do
132
+ knife Chef::Knife::DataBagCreate, data_bag_name
133
+ end
134
+ end
135
+
136
+ def data_bag_from_hash(databag_name, data)
137
+ tmp = Tempfile.new(['restorechef', '.json'])
138
+ begin
139
+ tmp.write(JSON.dump(data))
140
+ tmp.close
141
+ converge_by "create data bag #{databag_name}" do
142
+ data_bag_from_file(databag_name, tmp.path)
143
+ end
144
+ ensure
145
+ tmp.unlink
146
+ end
147
+ end
148
+
149
+ def knife(klass, *name_args)
150
+ klass.load_deps
151
+ plugin = klass.new
152
+ yield plugin.config if Kernel.block_given?
153
+ plugin.name_args = name_args
154
+ plugin.run
155
+ end
156
+
157
+ def ignore_file
158
+ File.join(chef_repo_dir, '.pd-ignore')
159
+ end
160
+ end
161
+ end
162
+ end
163
+
@@ -0,0 +1,111 @@
1
+ #
2
+ # Author:: Tim Heckman (<ops@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2016 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'fileutils'
19
+ require 'chef/knife'
20
+
21
+ module PagerDuty
22
+ module ChefServer
23
+ class LockUnavailable < IOError; end
24
+ class ChefCronRunning < StandardError; end
25
+
26
+ class SyncLock
27
+ def initialize(lockfile, server_hostname, local_hostname, user, branch, force_lock = false)
28
+ @lockfile = lockfile
29
+ @server_hostname = server_hostname.include?('.') ? server_hostname.split('.')[0] : server_hostname
30
+ @local_hostname = local_hostname
31
+ @user = user
32
+ @branch = branch
33
+ @opts = parse_opts({ announce: true, lock: true, force: force_lock })
34
+ end
35
+
36
+ # get lock
37
+ def lock(time = Time.now)
38
+ verify_lockable
39
+
40
+ @f_lock = procure_lock if @opts[:lock] || @opts[:force]
41
+ end
42
+
43
+ # remove lock
44
+ def unlock
45
+ if @opts[:lock]
46
+ Chef::Knife.ui.info 'removing lockfile'
47
+ @f_lock.flock(File::LOCK_UN)
48
+ @f_lock.close
49
+ FileUtils.rm(@f_lock.path)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def verify_lockable
56
+ unless local_chef_server? && @opts[:force]
57
+ @opts[:lock] = false
58
+ Chef::Knife.ui.warn(
59
+ 'Please be careful, this looks to be running on a system other than a chef server. '\
60
+ 'As such, there will be *no* locking. Hold on to your butts...'
61
+ )
62
+ sleep 5
63
+ end
64
+ end
65
+
66
+ def parse_opts(options)
67
+ options[:announce] &&= false unless remote_chef_server?
68
+ options[:lock] &&= false unless local_chef_server?
69
+ options
70
+ end
71
+
72
+ def local_chef_server?
73
+ @local_hostname.include? 'chef'
74
+ end
75
+
76
+ def remote_chef_server?
77
+ @server_hostname.include? 'chef'
78
+ end
79
+
80
+ def procure_lock
81
+ Chef::Knife.ui.info 'trying to obtain exclusive lock...'
82
+
83
+ # create a file handle for the lockfile -- create if it doesn't exist and
84
+ # make it read/write
85
+ lf = File.open(@lockfile, File::CREAT|File::RDWR, 0644)
86
+
87
+ unless lf.flock(File::LOCK_NB|File::LOCK_EX)
88
+ # if we fail to obtain the lock figure out who has lock and for
89
+ # how long so we can display that information
90
+ ld = JSON.parse(lf.read.strip)
91
+ msg = if (ld['user'].strip == @user)
92
+ "according to lockfile you've had lock for" \
93
+ " #{Time.now.to_i - ld['ts']} second(s)."
94
+ else
95
+ "unable to get exclusive lock, currently held by" \
96
+ " #{ld['user']} for #{Time.now.to_i - ld['ts']} second(s)" \
97
+ end
98
+ # close the file handle
99
+ lf.close
100
+ Chef::Knife.ui.fatal msg
101
+ raise LockUnavailable, msg
102
+ end
103
+ # we could get lock, so write our information to the file
104
+ lf.write(JSON.dump({ user: @user, ts: Time.now.to_i }))
105
+ lf.flush
106
+ Chef::Knife.ui.info 'lock obtained'
107
+ lf
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ Gem::Specification.new do |spec|
3
+ spec.name = 'pagerduty-pd_sync'
4
+ spec.version = '0.1.1'
5
+ spec.authors = ['Tim Heckman']
6
+ spec.email = ['ops+pd_sync@pagerduty.com']
7
+ spec.licenses = ['Apache 2.0']
8
+
9
+ spec.summary = 'A knife plugin to support the PagerDuty Chef workflow'
10
+ spec.description = 'A knife plugin to support the PagerDuty Chef workflow'
11
+ spec.homepage = 'https://github.com/PagerDuty/pd-sync-chef'
12
+
13
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
14
+ spec.require_paths = ['lib']
15
+
16
+ spec.required_ruby_version = '>= 2.1.4'
17
+
18
+ spec.add_runtime_dependency 'chef', '~> 12'
19
+ spec.add_runtime_dependency 'berkshelf', '~> 0'
20
+ spec.add_runtime_dependency 'json', '~> 0'
21
+
22
+ spec.add_development_dependency 'bundler', '~> 0'
23
+ spec.add_development_dependency 'pry', '~> 0'
24
+ spec.add_development_dependency 'rake', '~> 0'
25
+ spec.add_development_dependency 'rspec', '~> 0'
26
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pagerduty-pd_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Tim Heckman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chef
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: berkshelf
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A knife plugin to support the PagerDuty Chef workflow
112
+ email:
113
+ - ops+pd_sync@pagerduty.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - Gemfile
120
+ - Gemfile.lock
121
+ - Rakefile
122
+ - lib/chef/knife/pd_sync.rb
123
+ - lib/pagerduty/chef_server/sync.rb
124
+ - lib/pagerduty/chef_server/sync_helper.rb
125
+ - lib/pagerduty/chef_server/synclock.rb
126
+ - pagerduty-pd_sync.gemspec
127
+ homepage: https://github.com/PagerDuty/pd-sync-chef
128
+ licenses:
129
+ - Apache 2.0
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 2.1.4
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.2.2
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: A knife plugin to support the PagerDuty Chef workflow
151
+ test_files: []