knife-github 0.0.3 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|