knife-github 0.0.3 → 0.0.6
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.
- data/.gitignore +3 -0
- data/README.md +9 -6
- data/Rakefile +1 -0
- data/knife-github.gemspec +1 -0
- data/lib/chef/knife/github_base.rb +231 -29
- data/lib/chef/knife/github_baselist.rb +102 -0
- data/lib/chef/knife/github_cleanup.rb +120 -0
- data/lib/chef/knife/github_compare.rb +27 -172
- data/lib/chef/knife/github_deploy.rb +335 -0
- data/lib/chef/knife/github_diff.rb +126 -0
- data/lib/chef/knife/github_download.rb +104 -0
- data/lib/chef/knife/github_list.rb +22 -159
- data/lib/chef/knife/github_search.rb +88 -0
- data/lib/knife-github/version.rb +1 -1
- metadata +26 -3
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,6 @@ You can configure the following attributes within your knife.rb
|
|
12
12
|
knife[:github_organizations] = [ 'customer-cookbooks', 'central-cookbooks' ]
|
13
13
|
knife[:github_link] = 'ssh'
|
14
14
|
knife[:github_api_version] = 'v3'
|
15
|
-
knife[:github_cache] = 900
|
16
15
|
knife[:github_ssl_verify_mode] = 'verify_none'
|
17
16
|
|
18
17
|
###### github_url
|
@@ -30,11 +29,15 @@ Options are <tt>ssh</tt> <tt>git</tt> <tt>http</tt> <tt>https</tt> <tt>svn</tt>
|
|
30
29
|
###### github_api_version \<optional\>
|
31
30
|
The current and default version of the api is <tt>v3</tt> but this will allow you to target older versions if needed.
|
32
31
|
|
33
|
-
###### github_cache \<optional\>
|
34
|
-
This will be the lifetime of the cache files in seconds, default <tt>900</tt>. Cache files will be created into the: ~/.chef directory.
|
35
|
-
We use cache files to offload the api calls and increase the performance for additional executions.
|
36
|
-
|
37
32
|
###### github_ssl_verify_mode \<optional\>
|
38
33
|
The plugin is using the underlying knife http implementation, hence it will have the same options to handle ssl.
|
39
|
-
Currently the options are: <tt>verify_peer</tt> <tt>verify_none</tt>
|
34
|
+
Currently the options are: <tt>verify_peer</tt> <tt>verify_none</tt>
|
35
|
+
|
36
|
+
Other
|
37
|
+
=====
|
38
|
+
|
39
|
+
Cache files will be created into the: ~/.chef directory.
|
40
|
+
We use cache files to offload the api calls and increase the performance for additional executions.
|
41
|
+
Updated to any repo inside the organization will cause the cache files to update.
|
42
|
+
But in case of any problems, the cache files can be safely deleted.
|
40
43
|
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/knife-github.gemspec
CHANGED
@@ -17,6 +17,7 @@
|
|
17
17
|
#
|
18
18
|
|
19
19
|
# require 'chef/knife'
|
20
|
+
require "knife-github/version"
|
20
21
|
|
21
22
|
class Chef
|
22
23
|
class Knife
|
@@ -27,6 +28,7 @@ class Chef
|
|
27
28
|
|
28
29
|
deps do
|
29
30
|
require 'chef/mixin/shell_out'
|
31
|
+
require 'mixlib/versioning'
|
30
32
|
end
|
31
33
|
|
32
34
|
option :github_url,
|
@@ -51,10 +53,15 @@ class Chef
|
|
51
53
|
:description => "SSL verify mode: verify_peer, verify_none (default: verify_peer)",
|
52
54
|
:boolean => true
|
53
55
|
|
54
|
-
option :
|
55
|
-
:long => "--
|
56
|
-
:description => "
|
56
|
+
option :github_tmp,
|
57
|
+
:long => "--github_tmp PATH",
|
58
|
+
:description => "A path where temporary files for the diff function will be made default: /tmp/gitdiff)"
|
57
59
|
|
60
|
+
option :github_no_update,
|
61
|
+
:long => "--github_no_update",
|
62
|
+
:description => "Turn github update checking off",
|
63
|
+
:boolean => true
|
64
|
+
|
58
65
|
def validate_base_options
|
59
66
|
unless locate_config_value('github_url')
|
60
67
|
ui.error "Github URL not specified"
|
@@ -64,22 +71,47 @@ class Chef
|
|
64
71
|
ui.error "Github organization(s) not specified"
|
65
72
|
exit 1
|
66
73
|
end
|
74
|
+
unless locate_config_value('github_no_update')
|
75
|
+
check_gem_version
|
76
|
+
end
|
67
77
|
|
68
78
|
@github_url = locate_config_value("github_url")
|
69
79
|
@github_organizations = locate_config_value("github_organizations")
|
70
|
-
@github_cache = (locate_config_value("github_cache") || 900).to_i
|
71
80
|
@github_link = locate_config_value("github_link") || 'ssh'
|
72
81
|
@github_api_version = locate_config_value("github_api_version") || 'v3'
|
73
82
|
@github_ssl_verify_mode = locate_config_value("github_ssl_verify_mode") || 'verify_peer'
|
83
|
+
@github_tmp = locate_config_value("github_tmp") || '/var/tmp/gitdiff'
|
84
|
+
@github_tmp = "#{@github_tmp}#{Process.pid}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_gem_version
|
88
|
+
url = 'http://rubygems.org/api/v1/gems/knife-github.json'
|
89
|
+
result = `curl -L -s #{url}`
|
90
|
+
begin
|
91
|
+
json = JSON.parse(result)
|
92
|
+
webversion = Mixlib::Versioning.parse(json['version'])
|
93
|
+
thisversion = Mixlib::Versioning.parse(::Knife::Github::VERSION)
|
94
|
+
if webversion > thisversion
|
95
|
+
ui.info "INFO: New version (#{webversion.to_s}) of knife-github is available!"
|
96
|
+
ui.info "INFO: Turn off this message with --github_no_update or add knife[:github_no_update] = true to your configuration"
|
97
|
+
end
|
98
|
+
Chef::Log.debug("local_gem_version : " + thisversion.to_s)
|
99
|
+
Chef::Log.debug("repo_gem_version : " + webversion.to_s)
|
100
|
+
Chef::Log.debug("repo_downloads : " + json['version_downloads'].to_s)
|
101
|
+
Chef::Log.debug("repo_total_downloads : " + json['downloads'].to_s)
|
102
|
+
|
103
|
+
rescue
|
104
|
+
ui.info "INFO: Cannot verify gem version information from rubygems.org"
|
105
|
+
ui.info "INFO: Turn off this message with --github_no_update or add knife[:github_no_update] = true to your configuration"
|
106
|
+
end
|
74
107
|
end
|
75
108
|
|
76
109
|
def display_debug_info
|
77
|
-
Chef::Log.debug("github_url: " + @github_url.to_s)
|
78
|
-
Chef::Log.debug("github_org: " + @github_organizations.to_s)
|
79
|
-
Chef::Log.debug("github_api: " + @github_api_version.to_s)
|
80
|
-
Chef::Log.debug("github_link: " + @github_link.to_s)
|
81
|
-
Chef::Log.debug("
|
82
|
-
Chef::Log.debug("github_ssl_mode: " + @github_ssl_verify_mode.to_s)
|
110
|
+
Chef::Log.debug("github_url : " + @github_url.to_s)
|
111
|
+
Chef::Log.debug("github_org : " + @github_organizations.to_s)
|
112
|
+
Chef::Log.debug("github_api : " + @github_api_version.to_s)
|
113
|
+
Chef::Log.debug("github_link : " + @github_link.to_s)
|
114
|
+
Chef::Log.debug("github_ssl_mode : " + @github_ssl_verify_mode.to_s)
|
83
115
|
end
|
84
116
|
|
85
117
|
def locate_config_value(key)
|
@@ -87,8 +119,9 @@ class Chef
|
|
87
119
|
config[key] || Chef::Config[:knife][key]
|
88
120
|
end
|
89
121
|
|
90
|
-
def
|
91
|
-
|
122
|
+
def get_repo_clone_link
|
123
|
+
link = locate_config_value('github_link')
|
124
|
+
repo_link = case link
|
92
125
|
when 'ssh' then 'ssh_url'
|
93
126
|
when 'http' then 'clone_url'
|
94
127
|
when 'https' then 'clone_url'
|
@@ -97,34 +130,203 @@ class Chef
|
|
97
130
|
when 'git' then 'git_url'
|
98
131
|
else 'ssh_url'
|
99
132
|
end
|
100
|
-
|
133
|
+
return repo_link
|
134
|
+
end
|
135
|
+
|
136
|
+
def get_all_repos(orgs)
|
137
|
+
# Parse every org and merge all into one hash
|
138
|
+
repos = {}
|
139
|
+
orgs.each do |org|
|
140
|
+
get_repos(org).each { |repo| name = repo['name'] ; repos["#{name}"] = repo }
|
141
|
+
end
|
142
|
+
repos
|
143
|
+
end
|
144
|
+
|
145
|
+
def get_repos(org)
|
146
|
+
dns_name = get_dns_name(@github_url)
|
147
|
+
file_cache = "#{ENV['HOME']}/.chef/.#{dns_name.downcase}_#{org.downcase}"
|
148
|
+
|
149
|
+
if File.exists?(file_cache + ".json")
|
150
|
+
json = JSON.parse(File.read(file_cache + ".json"))
|
151
|
+
json_updated = Time.parse(json['updated_at'])
|
152
|
+
Chef::Log.info("#{org} - cache created at : " + json_updated.to_s)
|
153
|
+
repo_updated = get_org_updated_time(org)
|
154
|
+
Chef::Log.info("#{org} - repos updated at : " + repo_updated.to_s)
|
155
|
+
|
156
|
+
unless json_updated >= repo_updated
|
157
|
+
# update cache file
|
158
|
+
create_cache_file(file_cache + ".cache", org)
|
159
|
+
create_cache_json(file_cache + ".json", org)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
create_cache_file(file_cache + ".cache", org)
|
163
|
+
create_cache_json(file_cache + ".json", org)
|
164
|
+
end
|
165
|
+
|
166
|
+
# use cache files
|
167
|
+
JSON.parse(File.read(file_cache + ".cache"))
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_cache_json(file, org)
|
171
|
+
Chef::Log.debug("Updating the cache file: #{file}")
|
172
|
+
url = @github_url + "/api/" + @github_api_version + "/orgs/" + org
|
173
|
+
params = {'response' => 'json'}
|
174
|
+
result = send_request(url, params)
|
175
|
+
File.open(file, 'w') { |f| f.write(JSON.pretty_generate(result)) }
|
176
|
+
end
|
177
|
+
|
178
|
+
def create_cache_file(file, org)
|
179
|
+
Chef::Log.debug("Updating the cache file: #{file}")
|
180
|
+
result = get_repos_github(org)
|
181
|
+
File.open(file, 'w') { |f| f.write(JSON.pretty_generate(result)) }
|
182
|
+
end
|
183
|
+
|
184
|
+
def get_org_updated_time(org)
|
185
|
+
url = @github_url + "/api/" + @github_api_version + "/orgs/" + org
|
186
|
+
params = {'response' => 'json'}
|
187
|
+
result = send_request(url, params)
|
188
|
+
Time.parse(result['updated_at'])
|
189
|
+
end
|
190
|
+
|
191
|
+
def get_repos_github(org)
|
192
|
+
# Get all repo's for the org from github
|
193
|
+
arr = []
|
194
|
+
page = 1
|
195
|
+
url = @github_url + "/api/" + @github_api_version + "/orgs/" + org + "/repos"
|
196
|
+
while true
|
197
|
+
params = {'response' => 'json', 'page' => page }
|
198
|
+
result = send_request(url, params)
|
199
|
+
break if result.nil? || result.count < 1
|
200
|
+
result.each { |key|
|
201
|
+
if key['tags_url']
|
202
|
+
tags = get_tags(key)
|
203
|
+
key['tags'] = tags unless tags.nil? || tags.empty?
|
204
|
+
key['latest_tag'] = get_latest_tag(tags)
|
205
|
+
arr << key
|
206
|
+
else
|
207
|
+
arr << key
|
208
|
+
end
|
209
|
+
}
|
210
|
+
page = page + 1
|
211
|
+
end
|
212
|
+
arr
|
213
|
+
end
|
214
|
+
|
215
|
+
def get_tags(repo)
|
216
|
+
params = {'response' => 'json'}
|
217
|
+
tags = send_request(repo['tags_url'], params)
|
218
|
+
tags
|
219
|
+
end
|
220
|
+
|
221
|
+
def get_latest_tag(tags)
|
222
|
+
return "" if tags.nil? || tags.empty?
|
223
|
+
tags_arr =[]
|
224
|
+
tags.each do |tag|
|
225
|
+
tags_arr.push(Mixlib::Versioning.parse(tag['name'])) if tag['name'] =~ /^(\d*)\.(\d*)\.(\d*)$/
|
226
|
+
end
|
227
|
+
return "" if tags_arr.nil? || tags_arr.empty?
|
228
|
+
return tags_arr.sort.last.to_s
|
229
|
+
end
|
230
|
+
|
231
|
+
def get_dns_name(url)
|
232
|
+
url = url.downcase.gsub("http://","") if url.downcase.start_with?("http://")
|
233
|
+
url = url.downcase.gsub("https://","") if url.downcase.start_with?("https://")
|
234
|
+
url
|
101
235
|
end
|
102
236
|
|
103
237
|
def send_request(url, params = {})
|
104
|
-
params
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
238
|
+
unless params.empty?
|
239
|
+
params_arr = []
|
240
|
+
params.sort.each { |elem|
|
241
|
+
params_arr << elem[0].to_s + '=' + CGI.escape(elem[1].to_s).gsub('+', '%20').gsub(' ','%20')
|
242
|
+
}
|
243
|
+
data = params_arr.join('&')
|
244
|
+
url = "#{url}?#{data}"
|
245
|
+
end
|
246
|
+
|
247
|
+
if @github_ssl_verify_mode == "verify_none"
|
248
|
+
config[:ssl_verify_mode] = :verify_none
|
249
|
+
elsif @github_ssl_verify_mode == "verify_peer"
|
250
|
+
config[:ssl_verify_mode] = :verify_peer
|
251
|
+
end
|
252
|
+
|
253
|
+
Chef::Log.debug("URL: " + url.to_s)
|
254
|
+
|
255
|
+
uri = URI.parse(url)
|
116
256
|
req_body = Net::HTTP::Get.new(uri.request_uri)
|
117
257
|
request = Chef::REST::RESTRequest.new("GET", uri, req_body, headers={})
|
118
|
-
|
258
|
+
|
119
259
|
response = request.call
|
120
|
-
|
121
|
-
|
260
|
+
|
261
|
+
unless response.is_a?(Net::HTTPOK) then
|
122
262
|
puts "Error #{response.code}: #{response.message}"
|
123
263
|
puts JSON.pretty_generate(JSON.parse(response.body))
|
124
264
|
puts "URL: #{url}"
|
125
265
|
exit 1
|
126
266
|
end
|
127
|
-
|
267
|
+
|
268
|
+
begin
|
269
|
+
json = JSON.parse(response.body)
|
270
|
+
rescue
|
271
|
+
ui.warn "The result on the RESTRequest is not in json format"
|
272
|
+
ui.warn "Output: " + response.body
|
273
|
+
exit 1
|
274
|
+
end
|
275
|
+
return json
|
276
|
+
end
|
277
|
+
|
278
|
+
def get_clone(url, cookbook)
|
279
|
+
if ! File.directory? @github_tmp
|
280
|
+
Dir.mkdir("#{@github_tmp}")
|
281
|
+
end
|
282
|
+
Dir.mkdir("#{@github_tmp}/git")
|
283
|
+
ui.info("Getting #{@cookbook_name} from #{url}")
|
284
|
+
output = `git clone #{url} #{@github_tmp}/git/#{cookbook} 2>&1`
|
285
|
+
if $?.exitstatus != 0
|
286
|
+
Chef::Log.error("Could not clone the repository for: #{cookbook}")
|
287
|
+
FileUtils.remove_entry(@github_tmp)
|
288
|
+
exit 1
|
289
|
+
end
|
290
|
+
return true
|
291
|
+
end
|
292
|
+
|
293
|
+
def add_tag(version)
|
294
|
+
cpath = cookbook_path_valid?(@cookbook_name, false)
|
295
|
+
Dir.chdir(cpath)
|
296
|
+
Chef::Log.debug "Adding tag"
|
297
|
+
output = `git tag -a "#{version}" -m "Added tag #{version}" 2>&1`
|
298
|
+
if $?.exitstatus != 0
|
299
|
+
Chef::Log.error("Could not add tag for: #{@cookbook_name}")
|
300
|
+
FileUtils.remove_entry(@github_tmp)
|
301
|
+
exit 1
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def cookbook_path_valid?(cookbook_name, check_exists)
|
306
|
+
cookbook_path = config[:cookbook_path] || Chef::Config[:cookbook_path]
|
307
|
+
if cookbook_path.nil? || cookbook_path.empty?
|
308
|
+
Chef::Log.error("Please specify a cookbook path")
|
309
|
+
exit 1
|
310
|
+
end
|
311
|
+
|
312
|
+
unless File.exists?(cookbook_path.first) && File.directory?(cookbook_path.first)
|
313
|
+
Chef::Log.error("Cannot find the directory: #{cookbook_path.first}")
|
314
|
+
exit 1
|
315
|
+
end
|
316
|
+
|
317
|
+
cookbook_path = File.join(cookbook_path.first,cookbook_name)
|
318
|
+
if check_exists
|
319
|
+
if File.exists?(cookbook_path)
|
320
|
+
ui.info("Processing [S] #{cookbook_name}")
|
321
|
+
Chef::Log.info("Path to #{cookbook_path} already exists, skipping.")
|
322
|
+
return nil
|
323
|
+
end
|
324
|
+
else
|
325
|
+
if ! File.exists?(cookbook_path)
|
326
|
+
return nil
|
327
|
+
end
|
328
|
+
end
|
329
|
+
return cookbook_path
|
128
330
|
end
|
129
331
|
|
130
332
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Sander Botman (<sbotman@schubergphilis.com>)
|
3
|
+
# Copyright:: Copyright (c) 2013 Sander Botman.
|
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
|
+
|
19
|
+
|
20
|
+
require 'chef/knife'
|
21
|
+
|
22
|
+
class Chef
|
23
|
+
class Knife
|
24
|
+
module GithubBaseList
|
25
|
+
|
26
|
+
def self.included(includer)
|
27
|
+
includer.class_eval do
|
28
|
+
|
29
|
+
option :fields,
|
30
|
+
:long => "--fields 'NAME, NAME'",
|
31
|
+
:description => "The fields to output, comma-separated"
|
32
|
+
|
33
|
+
option :fieldlist,
|
34
|
+
:long => "--fieldlist",
|
35
|
+
:description => "The available fields to output/filter",
|
36
|
+
:boolean => true
|
37
|
+
|
38
|
+
option :noheader,
|
39
|
+
:long => "--noheader",
|
40
|
+
:description => "Removes header from output",
|
41
|
+
:boolean => true
|
42
|
+
|
43
|
+
def display_info(data, columns, match = [])
|
44
|
+
object_list = []
|
45
|
+
|
46
|
+
if config[:fields]
|
47
|
+
config[:fields].split(',').each { |n| object_list << ui.color(("#{n}").capitalize.strip, :bold) }
|
48
|
+
else
|
49
|
+
columns.each { |c| r = c.split(","); object_list << ui.color(("#{r.last}").strip, :bold) }
|
50
|
+
end
|
51
|
+
|
52
|
+
col = object_list.count
|
53
|
+
object_list = [] if config[:noheader]
|
54
|
+
|
55
|
+
data.each do |k,v|
|
56
|
+
if config[:fields]
|
57
|
+
config[:fields].downcase.split(',').each { |n| object_list << ((v["#{n}".strip]).to_s || 'n/a') }
|
58
|
+
else
|
59
|
+
color = :white
|
60
|
+
if config[:mismatch] && !match.empty? && !config[:all]
|
61
|
+
matches = []; match.each { |m| matches << v[m].to_s }
|
62
|
+
next if matches.uniq.count == 1
|
63
|
+
color = :yellow
|
64
|
+
end
|
65
|
+
columns.each { |c| r = c.split(","); object_list << ui.color((v["#{r.first}"]).to_s, color) || 'n/a' }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
puts ui.list(object_list, :uneven_columns_across, col)
|
70
|
+
display_object_fields(data) if locate_config_value(:fieldlist)
|
71
|
+
end
|
72
|
+
|
73
|
+
def display_object_fields(object)
|
74
|
+
exit 1 if object.nil? || object.empty?
|
75
|
+
object_fields = [
|
76
|
+
ui.color('Key', :bold),
|
77
|
+
ui.color('Type', :bold),
|
78
|
+
ui.color('Value', :bold)
|
79
|
+
]
|
80
|
+
object.first.each do |n|
|
81
|
+
if n.class == Hash
|
82
|
+
n.keys.each do |k,v|
|
83
|
+
object_fields << ui.color(k, :yellow, :bold)
|
84
|
+
object_fields << n[k].class.to_s
|
85
|
+
if n[k].kind_of?(Array)
|
86
|
+
object_fields << '<Array>'
|
87
|
+
elsif n[k].kind_of?(Hash)
|
88
|
+
object_fields << '<Hash>'
|
89
|
+
else
|
90
|
+
object_fields << ("#{n[k]}").strip.to_s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
puts "\n"
|
96
|
+
puts ui.list(object_fields, :uneven_columns_across, 3)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Sander Botman (<sbotman@schubergphilis.com>)
|
3
|
+
# Copyright:: Copyright (c) 2013 Sander Botman.
|
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
|
+
# ---------------------------------------------------------------------------- #
|
19
|
+
# Abstract
|
20
|
+
# ---------------------------------------------------------------------------- #
|
21
|
+
# This code is intended to help you cleaning up your locate repo's.
|
22
|
+
# It will check if your repo if insync with the github and will not touch if
|
23
|
+
# this is not the case. Then it will check if you have any branches local
|
24
|
+
# and not on the github.
|
25
|
+
#
|
26
|
+
# If it cannot find any uncommitted changes, it will safely remove your repo.
|
27
|
+
# It's good practice to cleanup and re-download repos because this way they
|
28
|
+
# can move from organization to organization.
|
29
|
+
# ---------------------------------------------------------------------------- #
|
30
|
+
#
|
31
|
+
require 'chef/knife'
|
32
|
+
|
33
|
+
module KnifeGithubCleanup
|
34
|
+
class GithubCleanup < Chef::Knife
|
35
|
+
|
36
|
+
deps do
|
37
|
+
require 'chef/knife/github_base'
|
38
|
+
include Chef::Knife::GithubBase
|
39
|
+
require 'chef/mixin/shell_out'
|
40
|
+
end
|
41
|
+
|
42
|
+
banner "knife github cleanup [REPO] (options)"
|
43
|
+
category "github"
|
44
|
+
|
45
|
+
option :all,
|
46
|
+
:short => "-a",
|
47
|
+
:long => "--all",
|
48
|
+
:description => "Delete all repos from cookbook path.",
|
49
|
+
:boolean => true
|
50
|
+
|
51
|
+
option :force,
|
52
|
+
:short => "-f",
|
53
|
+
:long => "--force",
|
54
|
+
:description => "Force deletion even if commits are still present.",
|
55
|
+
:boolean => true
|
56
|
+
|
57
|
+
def run
|
58
|
+
|
59
|
+
#executing shell commands
|
60
|
+
extend Chef::Mixin::ShellOut
|
61
|
+
|
62
|
+
# validate base options from base module.
|
63
|
+
validate_base_options
|
64
|
+
|
65
|
+
# Display information if debug mode is on.
|
66
|
+
display_debug_info
|
67
|
+
|
68
|
+
# Gather all repo information from github.
|
69
|
+
all_repos = get_all_repos(@github_organizations.reverse)
|
70
|
+
|
71
|
+
# Get all chef cookbooks and versions (hopefully chef does the error handeling).
|
72
|
+
cookbooks = rest.get_rest("/cookbooks?num_version=1")
|
73
|
+
|
74
|
+
# Get the cookbook names from the command line
|
75
|
+
@cookbook_name = name_args.first unless name_args.empty?
|
76
|
+
if @cookbook_name
|
77
|
+
# repo = all_repos.select { |k,v| v["name"] == @cookbook_name }
|
78
|
+
repo_cleanup(@cookbook_name)
|
79
|
+
elsif config[:all]
|
80
|
+
cookbooks.each do |c,v|
|
81
|
+
repo_cleanup(c)
|
82
|
+
end
|
83
|
+
else
|
84
|
+
Chef::Log.error("Please specify a repo name")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def repo_cleanup(repo)
|
89
|
+
cookbook_path = config[:cookbook_path] || Chef::Config[:cookbook_path]
|
90
|
+
cookbook = File.join(cookbook_path.first,repo)
|
91
|
+
if File.exists?(cookbook)
|
92
|
+
if repo_status_clean?(repo, cookbook)
|
93
|
+
# delete the repo
|
94
|
+
ui.info("Processing [D] #{repo}")
|
95
|
+
FileUtils.remove_entry(cookbook)
|
96
|
+
end
|
97
|
+
else
|
98
|
+
puts "cannot find repo path for: #{repo}" unless config[:all]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def repo_status_clean?(repo, cookbook)
|
103
|
+
shell_out!("git fetch", :cwd => cookbook)
|
104
|
+
status = shell_out!("git status", :cwd => cookbook)
|
105
|
+
unless status.stdout == "# On branch master\nnothing to commit (working directory clean)\n"
|
106
|
+
ui.info("Processing [C] #{repo} (Action needed!)")
|
107
|
+
status.stdout.lines.each { |l| puts l.sub( /^/, " ") }
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
log = shell_out!("git log --branches --not --remotes --simplify-by-decoration --decorate --oneline", :cwd => cookbook)
|
111
|
+
unless log.stdout.empty?
|
112
|
+
ui.info("Processing [B] #{repo} (Action needed!)")
|
113
|
+
ui.info(" Please check your branches, one of them has unsaved changes")
|
114
|
+
log.stdout.lines.each { |l| puts l.sub( /^/, " ") }
|
115
|
+
return false
|
116
|
+
end
|
117
|
+
return true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|