pagerduty-pd_sync 0.1.1

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 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: []