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
@@ -25,28 +25,14 @@ class Chef
|
|
25
25
|
|
26
26
|
deps do
|
27
27
|
require 'chef/knife/github_base'
|
28
|
-
|
29
28
|
include Chef::Knife::GithubBase
|
29
|
+
require 'chef/knife/github_baselist'
|
30
|
+
include Chef::Knife::GithubBaseList
|
30
31
|
end
|
31
32
|
|
32
33
|
banner "knife github compare [COOKBOOK] (options)"
|
33
34
|
category "github"
|
34
35
|
|
35
|
-
option :fields,
|
36
|
-
:long => "--fields 'NAME, NAME'",
|
37
|
-
:description => "The fields to output, comma-separated"
|
38
|
-
|
39
|
-
option :noheader,
|
40
|
-
:long => "--noheader",
|
41
|
-
:description => "Removes header from output",
|
42
|
-
:boolean => true
|
43
|
-
|
44
|
-
option :all,
|
45
|
-
:short => "-a",
|
46
|
-
:long => "--all",
|
47
|
-
:description => "Get all cookbooks from github.",
|
48
|
-
:boolean => true
|
49
|
-
|
50
36
|
option :mismatch,
|
51
37
|
:short => "-m",
|
52
38
|
:long => "--mismatch",
|
@@ -54,7 +40,6 @@ class Chef
|
|
54
40
|
:boolean => true
|
55
41
|
|
56
42
|
def run
|
57
|
-
# extend Chef::Mixin::ShellOut
|
58
43
|
# validate base options from base module.
|
59
44
|
validate_base_options
|
60
45
|
|
@@ -64,31 +49,31 @@ class Chef
|
|
64
49
|
# Gather all repo information from github.
|
65
50
|
get_all_repos = get_all_repos(@github_organizations.reverse)
|
66
51
|
|
52
|
+
# Get all chef cookbooks and versions.
|
53
|
+
cookbooks = rest.get_rest("/cookbooks?num_version=1")
|
67
54
|
|
68
|
-
#
|
69
|
-
|
70
|
-
|
55
|
+
#Get the github link
|
56
|
+
git_link = get_repo_clone_link
|
71
57
|
|
72
58
|
# Filter all repo information based on the tags that we can find
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
cookbook = k
|
77
|
-
cb_and_ver[k].nil? || cb_and_ver[k]['versions'].nil? ? version = "" : version = cb_and_ver[k]['versions'][0]['version']
|
78
|
-
ssh_url = v['ssh_url']
|
79
|
-
gh_tag = v['latest_tag']
|
80
|
-
all_repos[cookbook] = { 'name' => cookbook, 'latest_cb_tag' => version, 'ssh_url' => ssh_url, 'latest_gh_tag' => gh_tag }
|
81
|
-
}
|
59
|
+
if config[:fields] || config[:fieldlist]
|
60
|
+
all_repos = get_all_repos
|
61
|
+
config[:fields] = "name" if config[:fields].nil? || config[:fields].empty?
|
82
62
|
else
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
63
|
+
all_repos = {}
|
64
|
+
if config[:all]
|
65
|
+
get_all_repos.each { |k,v|
|
66
|
+
cookbooks[k].nil? || cookbooks[k]['versions'].nil? ? version = "" : version = cookbooks[k]['versions'][0]['version']
|
67
|
+
all_repos[k] = { 'name' => k, 'latest_cb_tag' => version, 'git_url' => v[git_link], 'latest_gh_tag' => v['latest_tag'] }
|
68
|
+
}
|
69
|
+
else
|
70
|
+
cookbooks.each { |k,v|
|
71
|
+
get_all_repos[k].nil? || get_all_repos[k][git_link].nil? ? gh_url = ui.color("ERROR: Cannot find cookbook!", :red) : gh_url = get_all_repos[k][git_link]
|
72
|
+
get_all_repos[k].nil? || get_all_repos[k]['latest_tag'].nil? ? gh_tag = ui.color("ERROR: No tags!", :red) : gh_tag = get_all_repos[k]['latest_tag']
|
73
|
+
all_repos[k] = { 'name' => k, 'latest_cb_tag' => v['versions'][0]['version'], 'git_url' => gh_url, 'latest_gh_tag' => gh_tag }
|
74
|
+
}
|
75
|
+
end
|
90
76
|
end
|
91
|
-
|
92
77
|
|
93
78
|
# Filter only on the cookbook name if its given on the command line
|
94
79
|
@cookbook_name = name_args.first unless name_args.empty?
|
@@ -98,145 +83,15 @@ class Chef
|
|
98
83
|
repos = all_repos
|
99
84
|
end
|
100
85
|
|
86
|
+
columns = [ 'name,Name', 'latest_cb_tag,Tag', 'git_url,Link', 'latest_gh_tag,Tag' ]
|
87
|
+
match = [ 'latest_cb_tag', 'latest_gh_tag' ]
|
101
88
|
|
102
|
-
|
103
|
-
|
104
|
-
object_list = []
|
105
|
-
config[:fields].split(',').each { |n| object_list << ui.color(("#{n}").strip, :bold) }
|
89
|
+
if repos.nil? || repos.empty?
|
90
|
+
Chef::Log.error("No repositories found.")
|
106
91
|
else
|
107
|
-
|
108
|
-
ui.color('Cookbook', :bold),
|
109
|
-
ui.color('Tag', :bold),
|
110
|
-
ui.color('Github', :bold),
|
111
|
-
ui.color('Tag', :bold)
|
112
|
-
]
|
113
|
-
end
|
114
|
-
|
115
|
-
columns = object_list.count
|
116
|
-
object_list = [] if config[:noheader]
|
117
|
-
|
118
|
-
repos.each do |k,r|
|
119
|
-
if config[:fields]
|
120
|
-
config[:fields].downcase.split(',').each { |n| object_list << ((r[("#{n}").strip]).to_s || 'n/a') }
|
121
|
-
else
|
122
|
-
next if config[:mismatch] && (r['latest_gh_tag'] == r['latest_cb_tag'])
|
123
|
-
r['latest_gh_tag'] == r['latest_cb_tag'] ? color = :white : color = :yellow
|
124
|
-
color = :white if config[:all]
|
125
|
-
|
126
|
-
object_list << ui.color((r['name'] || 'n/a'), color)
|
127
|
-
object_list << ui.color((r['latest_cb_tag'] || 'n/a'), color)
|
128
|
-
object_list << ui.color((r['ssh_url'] || 'n/a'), color)
|
129
|
-
object_list << ui.color((r['latest_gh_tag'] || 'n/a'), color)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
puts ui.list(object_list, :uneven_columns_across, columns)
|
134
|
-
|
135
|
-
end
|
136
|
-
|
137
|
-
def get_all_repos(orgs)
|
138
|
-
# Parse every org and merge all into one hash
|
139
|
-
repos = {}
|
140
|
-
orgs.each do |org|
|
141
|
-
get_repos(org).each { |repo| name = repo['name'] ; repos["#{name}"] = repo }
|
92
|
+
display_info(repos, columns, match)
|
142
93
|
end
|
143
|
-
repos
|
144
|
-
end
|
145
|
-
|
146
|
-
|
147
94
|
|
148
|
-
def get_repos(org)
|
149
|
-
dns_name = get_dns_name(@github_url)
|
150
|
-
file_cache = "#{ENV['HOME']}/.chef/.#{dns_name.downcase}_#{org.downcase}.cache"
|
151
|
-
if File.exists?(file_cache)
|
152
|
-
Chef::Log.debug("#{org} cache is created: " + (Time.now - File.ctime(file_cache)).to_i.to_s + " seconds ago.")
|
153
|
-
if Time.now - File.ctime(file_cache) > @github_cache
|
154
|
-
# update cache file
|
155
|
-
create_cache_file(file_cache, org)
|
156
|
-
end
|
157
|
-
else
|
158
|
-
create_cache_file(file_cache, org)
|
159
|
-
end
|
160
|
-
# use cache files
|
161
|
-
JSON.parse(File.read(file_cache))
|
162
|
-
end
|
163
|
-
|
164
|
-
def create_cache_file(file_cache, org)
|
165
|
-
Chef::Log.debug("Updating the cache file: #{file_cache}")
|
166
|
-
result = get_repos_github(org)
|
167
|
-
File.open(file_cache, 'w') { |file| file.write(JSON.pretty_generate(result)) }
|
168
|
-
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
def get_repos_github(org)
|
173
|
-
# Get all repo's for the org from github
|
174
|
-
arr = []
|
175
|
-
page = 1
|
176
|
-
url = @github_url + "/api/" + @github_api_version + "/orgs/" + org + "/repos"
|
177
|
-
while true
|
178
|
-
params = { 'page' => page }
|
179
|
-
result = send_request(url, params)
|
180
|
-
break if result.nil? || result.count < 1
|
181
|
-
result.each { |key|
|
182
|
-
if key['tags_url']
|
183
|
-
tags = get_tags(key)
|
184
|
-
key['tags'] = tags unless tags.nil? || tags.empty?
|
185
|
-
key['latest_tag'] = tags.first['name'] unless tags.nil? || tags.empty?
|
186
|
-
arr << key
|
187
|
-
else
|
188
|
-
arr << key
|
189
|
-
end
|
190
|
-
}
|
191
|
-
page = page + 1
|
192
|
-
end
|
193
|
-
arr
|
194
|
-
end
|
195
|
-
|
196
|
-
|
197
|
-
def get_tags(repo)
|
198
|
-
tags = send_request(repo['tags_url'])
|
199
|
-
tags
|
200
|
-
end
|
201
|
-
|
202
|
-
|
203
|
-
def get_dns_name(url)
|
204
|
-
url = url.downcase.gsub("http://","") if url.downcase.start_with?("http://")
|
205
|
-
url = url.downcase.gsub("https://","") if url.downcase.start_with?("https://")
|
206
|
-
url
|
207
|
-
end
|
208
|
-
|
209
|
-
|
210
|
-
def send_request(url, params = {})
|
211
|
-
params['response'] = 'json'
|
212
|
-
|
213
|
-
params_arr = []
|
214
|
-
params.sort.each { |elem|
|
215
|
-
params_arr << elem[0].to_s + '=' + CGI.escape(elem[1].to_s).gsub('+', '%20').gsub(' ','%20')
|
216
|
-
}
|
217
|
-
data = params_arr.join('&')
|
218
|
-
|
219
|
-
if url.nil? || url.empty?
|
220
|
-
puts "Error: Please specify a valid Github URL."
|
221
|
-
exit 1
|
222
|
-
end
|
223
|
-
|
224
|
-
github_url = "#{url}?#{data}"
|
225
|
-
# Chef::Log.debug("URL: #{github_url}")
|
226
|
-
|
227
|
-
uri = URI.parse(github_url)
|
228
|
-
req_body = Net::HTTP::Get.new(uri.request_uri)
|
229
|
-
request = Chef::REST::RESTRequest.new("GET", uri, req_body, headers={})
|
230
|
-
|
231
|
-
response = request.call
|
232
|
-
|
233
|
-
if !response.is_a?(Net::HTTPOK) then
|
234
|
-
puts "Error #{response.code}: #{response.message}"
|
235
|
-
puts JSON.pretty_generate(JSON.parse(response.body))
|
236
|
-
puts "URL: #{url}"
|
237
|
-
exit 1
|
238
|
-
end
|
239
|
-
json = JSON.parse(response.body)
|
240
95
|
end
|
241
96
|
|
242
97
|
end
|
@@ -0,0 +1,335 @@
|
|
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 specific to our company workflow
|
22
|
+
# When a cookbook is released it is tagged to a specific version
|
23
|
+
# This version should match the cookbook version (from the metadata)
|
24
|
+
# This version is then pinned against specific environments
|
25
|
+
#
|
26
|
+
# This class expects you to have pushed all your changes to github
|
27
|
+
# It will then do the rest
|
28
|
+
#
|
29
|
+
# All modes presume you have used github download to download a cookbook or
|
30
|
+
# are creating a new cookbook
|
31
|
+
#
|
32
|
+
# There are two modes of operation
|
33
|
+
# Development (default)
|
34
|
+
#
|
35
|
+
# This will take a cookbook name
|
36
|
+
# Do some basic version checks (if the current cookbook is frozen) and
|
37
|
+
# upload it
|
38
|
+
#
|
39
|
+
# If the cookbook is frozen it will force you to choose a new version
|
40
|
+
# and update the metadata accordingly
|
41
|
+
#
|
42
|
+
# Final (-f)
|
43
|
+
#
|
44
|
+
# You will be forced to select a new version.
|
45
|
+
# You can choose via the options whether to increment the Major/minor or patch
|
46
|
+
# revision numbers
|
47
|
+
# The version will be tagged
|
48
|
+
# Uploaded to the Chef server and frozen
|
49
|
+
#
|
50
|
+
# Version numbers
|
51
|
+
#
|
52
|
+
# You can choose a specific version number by specifying it on the command
|
53
|
+
# line.
|
54
|
+
#
|
55
|
+
# If you do not specify a version, the version will be the version in your
|
56
|
+
# cookbook's metadata
|
57
|
+
#
|
58
|
+
# A warning is issued if the version is lower than the version in github
|
59
|
+
# ---------------------------------------------------------------------------- #
|
60
|
+
require 'chef/knife'
|
61
|
+
|
62
|
+
class Chef
|
63
|
+
class Knife
|
64
|
+
|
65
|
+
class GithubDeploy < Knife
|
66
|
+
deps do
|
67
|
+
require 'chef/knife/github_base'
|
68
|
+
include Chef::Knife::GithubBase
|
69
|
+
require 'chef/cookbook_loader'
|
70
|
+
require 'chef/cookbook_uploader'
|
71
|
+
end
|
72
|
+
|
73
|
+
banner "knife github deploy COOKBOOK [VERSION] (options)"
|
74
|
+
category "github"
|
75
|
+
|
76
|
+
option :final,
|
77
|
+
:short => "-f",
|
78
|
+
:long => "--final",
|
79
|
+
:description => "Bump version, make git tag and freeze",
|
80
|
+
:boolean => true,
|
81
|
+
:default => false
|
82
|
+
|
83
|
+
option :major,
|
84
|
+
:short => "-M",
|
85
|
+
:long => "--major",
|
86
|
+
:description => "In final mode, increase the major version ie. X.x.x",
|
87
|
+
:boolean => true,
|
88
|
+
:default => false
|
89
|
+
|
90
|
+
option :minor,
|
91
|
+
:short => "-m",
|
92
|
+
:long => "--minor",
|
93
|
+
:description => "In final mode, increase the minor version ie. x.X.x",
|
94
|
+
:boolean => true,
|
95
|
+
:default => false
|
96
|
+
|
97
|
+
option :patch,
|
98
|
+
:short => "-p",
|
99
|
+
:long => "--patch",
|
100
|
+
:description => "In final mode, increase the minor version ie. x.x.X (Default)",
|
101
|
+
:boolean => true,
|
102
|
+
:default => true
|
103
|
+
|
104
|
+
def run
|
105
|
+
|
106
|
+
# validate base options from base module.
|
107
|
+
validate_base_options
|
108
|
+
|
109
|
+
# Display information if debug mode is on.
|
110
|
+
display_debug_info
|
111
|
+
|
112
|
+
# Gather all repo information from github.
|
113
|
+
get_all_repos = get_all_repos(@github_organizations.reverse)
|
114
|
+
|
115
|
+
# Get all chef cookbooks and versions (hopefully chef does the error handeling).
|
116
|
+
cookbooks = rest.get_rest("/cookbooks?num_version=1")
|
117
|
+
|
118
|
+
@versions = []
|
119
|
+
cookbook_version = nil
|
120
|
+
@cookbook_name = name_args.first unless name_args.empty?
|
121
|
+
cookbook_version = name_args[1] unless name_args[1].nil?
|
122
|
+
|
123
|
+
if @cookbook_name
|
124
|
+
repo = get_all_repos.select { |k,v| v["name"] == @cookbook_name }
|
125
|
+
else
|
126
|
+
Chef::Log.error("Cookbook not in git. You must add it to git to use deploy")
|
127
|
+
exit 1
|
128
|
+
end
|
129
|
+
|
130
|
+
if repo.empty?
|
131
|
+
Chef::Log.error("Cookbook not in git. You must add it to git to use deploy")
|
132
|
+
exit 1
|
133
|
+
end
|
134
|
+
|
135
|
+
# is the cookbook in the cookbook_path?
|
136
|
+
if cookbook_path_valid?(@cookbook_name, false).nil?
|
137
|
+
Chef::Log.error("Cookbook is not in cookbook path")
|
138
|
+
ui.info("HINT: knife github download #{@cookbook_name}")
|
139
|
+
exit 1
|
140
|
+
end
|
141
|
+
|
142
|
+
# ----------------------------- #
|
143
|
+
# The version can come
|
144
|
+
# 1. From the command line
|
145
|
+
# 2. From the cookbook
|
146
|
+
# ----------------------------- #
|
147
|
+
if cookbook_version.nil?
|
148
|
+
cookbook_version = get_cookbook_version()
|
149
|
+
end
|
150
|
+
# Next check to see if the version in git is way ahead
|
151
|
+
if ! get_all_repos[@cookbook_name]['latest_tag'].nil?
|
152
|
+
cb1 = Mixlib::Versioning.parse(cookbook_version)
|
153
|
+
cb2 = Mixlib::Versioning.parse(get_all_repos[@cookbook_name]['latest_tag'])
|
154
|
+
if(cb2 > cb1)
|
155
|
+
ui.confirm "Version in github #{cb2} is greater than the version you want to deploy #{cb1} - Continue"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
inChef = true
|
160
|
+
isFrozen = false
|
161
|
+
if (config[:major] || config[:minor])
|
162
|
+
config[:patch] = false
|
163
|
+
end
|
164
|
+
|
165
|
+
begin
|
166
|
+
isFrozen = rest.get_rest("cookbooks/#{@cookbook_name}/#{cookbook_version}").frozen_version?
|
167
|
+
rescue
|
168
|
+
ui.warn "#{@cookbook_name} is not yet in chef"
|
169
|
+
inChef = false
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
if config[:final]
|
174
|
+
ui.info "Using Final mode"
|
175
|
+
|
176
|
+
else
|
177
|
+
ui.info "Using Development mode"
|
178
|
+
end
|
179
|
+
ui.info "Cookbook is frozen" if isFrozen
|
180
|
+
|
181
|
+
# Might be first upload so need to catch that cookbook does not exist!
|
182
|
+
get_cookbook_chef_versions() unless ! inChef
|
183
|
+
|
184
|
+
if config[:final]
|
185
|
+
cookbook_version = up_version(cookbook_version)
|
186
|
+
|
187
|
+
if repo[@cookbook_name]['tags'].select { |k| k['name'] == cookbook_version }.empty?
|
188
|
+
ui.info("Cookbook #{cookbook_version} has no tag in Git")
|
189
|
+
ui.confirm("Shall I add a tag for you?")
|
190
|
+
set_cookbook_version(cookbook_version)
|
191
|
+
add_tag(cookbook_version)
|
192
|
+
else
|
193
|
+
checkout_tag(cookbook_version)
|
194
|
+
set_cookbook_version(cookbook_version)
|
195
|
+
end
|
196
|
+
|
197
|
+
do_commit(cookbook_version, true)
|
198
|
+
end
|
199
|
+
|
200
|
+
# In Dev mode the version of the cookbook does not need to change
|
201
|
+
# If however the cookbook is frozen, then the version has to change
|
202
|
+
if ! config[:final] && isFrozen
|
203
|
+
cookbook_version = up_version(cookbook_version)
|
204
|
+
set_cookbook_version(cookbook_version)
|
205
|
+
do_commit(cookbook_version, false)
|
206
|
+
end
|
207
|
+
|
208
|
+
# If we have gotten this far we can just upload the cookbook
|
209
|
+
cookbook_upload()
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
def up_version(version)
|
214
|
+
while true do
|
215
|
+
ui.info("Trying to deploy version #{version}")
|
216
|
+
if @versions.include?(version)
|
217
|
+
ui.info("Version #{version} is already in chef")
|
218
|
+
ui.confirm("Shall I bump the version (No to Cancel)")
|
219
|
+
version = choose_version(version)
|
220
|
+
else
|
221
|
+
break
|
222
|
+
end
|
223
|
+
end
|
224
|
+
version
|
225
|
+
end
|
226
|
+
|
227
|
+
def choose_version(version)
|
228
|
+
if version =~ /(\d+)\.(\d+)\.(\d+)/
|
229
|
+
major = $1
|
230
|
+
minor = $1
|
231
|
+
patch = $3
|
232
|
+
major = major.to_i + 1 if config[:major]
|
233
|
+
minor = minor.to_i + 1 if config[:minor]
|
234
|
+
patch = patch.to_i + 1 if config[:patch]
|
235
|
+
version = "#{major}.#{minor}.#{patch}"
|
236
|
+
Chef::Log.debug("New version is #{version}")
|
237
|
+
else
|
238
|
+
Chef::Log.error("Version is in a format I cannot auto auto-update")
|
239
|
+
exit 1
|
240
|
+
end
|
241
|
+
version
|
242
|
+
end
|
243
|
+
|
244
|
+
def cookbook_upload()
|
245
|
+
# Git meuk should not be uploaded use chefignore file instead
|
246
|
+
# FileUtils.remove_entry("#{@github_tmp}/git/#{@cookbook_name}/.git")
|
247
|
+
args = ['cookbook', 'upload', @cookbook_name ]
|
248
|
+
if config[:final]
|
249
|
+
args.push "--freeze"
|
250
|
+
end
|
251
|
+
upload = Chef::Knife::CookbookUpload.new(args)
|
252
|
+
#upload.config[:cookbook_path] = "#{@github_tmp}/git"
|
253
|
+
# plugin will throw its own errors
|
254
|
+
upload.run
|
255
|
+
end
|
256
|
+
|
257
|
+
def checkout_tag(version)
|
258
|
+
ui.info "Checking out tag #{version}"
|
259
|
+
cpath = get_cookbook_path(@cookbook_name)
|
260
|
+
Dir.chdir(cpath);
|
261
|
+
`git checkout -b #{version}`
|
262
|
+
if !$?.exitstatus == 0
|
263
|
+
ui.error("Failed to checkout branch #{version} of #{@cookbook_name}")
|
264
|
+
exit 1
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def get_cookbook_chef_versions ()
|
269
|
+
cookbooks = rest.get_rest("/cookbooks/#{@cookbook_name}?num_version=all")
|
270
|
+
cookbooks[@cookbook_name]['versions'].each do |v|
|
271
|
+
@versions.push v['version']
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# ---------------------------------------------------------------------- #
|
276
|
+
# Get the version number in the git version of the cookbook
|
277
|
+
# ---------------------------------------------------------------------- #
|
278
|
+
def get_cookbook_version()
|
279
|
+
version = nil
|
280
|
+
cpath = get_cookbook_path(@cookbook_name)
|
281
|
+
File.foreach("#{cpath}/metadata.rb") do |line|
|
282
|
+
if line =~ /version.*"(.*)"/i
|
283
|
+
version = $1
|
284
|
+
break
|
285
|
+
end
|
286
|
+
end
|
287
|
+
if version.nil?
|
288
|
+
Chef::Log.error("Cannot get the version for cookbook #{@cookbook_name}")
|
289
|
+
exit 1
|
290
|
+
end
|
291
|
+
version
|
292
|
+
end
|
293
|
+
|
294
|
+
def get_cookbook_path(cookbook)
|
295
|
+
return cookbook_path_valid?(cookbook, false)
|
296
|
+
end
|
297
|
+
|
298
|
+
def do_commit(version, push)
|
299
|
+
cpath = get_cookbook_path(@cookbook_name)
|
300
|
+
Dir.chdir("#{cpath}")
|
301
|
+
puts cpath
|
302
|
+
output = `git commit -a -m "Deploy #{version}" 2>&1`
|
303
|
+
if $?.exitstatus != 0
|
304
|
+
if output !~ /nothing to commit/
|
305
|
+
Chef::Log.error("Could not commit #{@cookbook_name}")
|
306
|
+
puts output
|
307
|
+
exit 1
|
308
|
+
end
|
309
|
+
end
|
310
|
+
if push
|
311
|
+
output = `git push --tags 2>&1`
|
312
|
+
if $?.exitstatus != 0
|
313
|
+
Chef::Log.error("Could not push tag for: #{@cookbook_name}")
|
314
|
+
exit 1
|
315
|
+
end
|
316
|
+
output = `git push 2>&1`
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
|
321
|
+
def set_cookbook_version(version)
|
322
|
+
return unless get_cookbook_version() != version
|
323
|
+
contents = ''
|
324
|
+
cpath = get_cookbook_path(@cookbook_name)
|
325
|
+
File.foreach("#{cpath}/metadata.rb") do |line|
|
326
|
+
line.gsub!(/(version[\t\s]+)(.*)/i,"\\1 \"#{version}\"\n")
|
327
|
+
contents = contents << line
|
328
|
+
end
|
329
|
+
File.open("#{cpath}/metadata.rb", 'w') {|f| f.write(contents) }
|
330
|
+
return true
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
@@ -0,0 +1,126 @@
|
|
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
|
+
require 'chef/knife'
|
20
|
+
|
21
|
+
class Chef
|
22
|
+
class Knife
|
23
|
+
|
24
|
+
class GithubDiff < Knife
|
25
|
+
|
26
|
+
deps do
|
27
|
+
require 'chef/knife/github_base'
|
28
|
+
|
29
|
+
include Chef::Knife::GithubBase
|
30
|
+
end
|
31
|
+
|
32
|
+
banner "knife github diff COOKBOOK [version] (options)"
|
33
|
+
category "github"
|
34
|
+
|
35
|
+
option :all,
|
36
|
+
:short => "-a",
|
37
|
+
:long => "--all",
|
38
|
+
:description => "Diff all cookbooks from chef against github.",
|
39
|
+
:boolean => true
|
40
|
+
|
41
|
+
def run
|
42
|
+
|
43
|
+
# validate base options from base module.
|
44
|
+
validate_base_options
|
45
|
+
|
46
|
+
# Display information if debug mode is on.
|
47
|
+
display_debug_info
|
48
|
+
|
49
|
+
# Gather all repo information from github.
|
50
|
+
get_all_repos = get_all_repos(@github_organizations.reverse)
|
51
|
+
|
52
|
+
# Get all chef cookbooks and versions (hopefully chef does the error handeling).
|
53
|
+
cookbooks = rest.get_rest("/cookbooks?num_version=1")
|
54
|
+
|
55
|
+
# Get the cookbook name from the command line
|
56
|
+
@cookbook_name = name_args.first unless name_args.empty?
|
57
|
+
cookbook_version = name_args[1] unless name_args[1].nil?
|
58
|
+
if @cookbook_name
|
59
|
+
repo = get_all_repos.select { |k,v| v["name"] == @cookbook_name }
|
60
|
+
else
|
61
|
+
#repos = all_repos
|
62
|
+
Chef::Log.error("Please specify a cookbook name")
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
|
66
|
+
if repo.empty?
|
67
|
+
Chef::Log.error("Cannot find the repository: #{} within github")
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
github_link = (repo[@cookbook_name][(get_repo_clone_link)])
|
72
|
+
if github_link.nil? || github_link.empty?
|
73
|
+
Chef::Log.error("Cannot find the link for the repository with the name: #{@cookbook_name}")
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
puts github_link
|
77
|
+
get_clone(github_link, @cookbook_name)
|
78
|
+
version = get_cookbook_copy(@cookbook_name, cookbook_version)
|
79
|
+
do_diff(@cookbook_name, version)
|
80
|
+
FileUtils.remove_entry(@github_tmp)
|
81
|
+
end
|
82
|
+
|
83
|
+
def do_diff(name, version)
|
84
|
+
# Check to see if there is a tag matching the version
|
85
|
+
Dir.chdir("#{@github_tmp}/git/#{name}")
|
86
|
+
if `git tag`.split("\n").include?(version)
|
87
|
+
ui.info("Tag version #{version} found, checking that out for diff")
|
88
|
+
# Tag found so checkout that tag
|
89
|
+
`git checkout -b #{version}`
|
90
|
+
if !$?.exitstatus == 0
|
91
|
+
ui.error("Failed to checkout branch #{version}")
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
else
|
95
|
+
ui.info("Version #{version} of #{name} has no tag, using latest for diff")
|
96
|
+
end
|
97
|
+
|
98
|
+
FileUtils.remove_entry("#{@github_tmp}/git/#{name}/.git")
|
99
|
+
output = `git diff --color #{@github_tmp}/git/#{name} #{@github_tmp}/cb/#{name}-#{version} 2>&1`
|
100
|
+
if output.length == 0
|
101
|
+
ui.info("No differences found")
|
102
|
+
else
|
103
|
+
ui.msg(output)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_cookbook_copy(name, version)
|
108
|
+
Dir.mkdir("#{@github_tmp}/cb")
|
109
|
+
args = ['cookbook', 'download', name ]
|
110
|
+
args.push version if version
|
111
|
+
Dir.chdir("#{@github_tmp}/cb")
|
112
|
+
download = Chef::Knife::CookbookDownload.new(args)
|
113
|
+
download.config[:download_directory] = "#{@github_tmp}/cb"
|
114
|
+
download.run
|
115
|
+
|
116
|
+
Dir.entries("#{@github_tmp}/cb").each do |d|
|
117
|
+
if d =~ /#{name}-(.*)/
|
118
|
+
version = $1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
return version
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|