vmc_knife 0.0.01
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +24 -0
- data/README.md +14 -0
- data/bin/vmc_knife +6 -0
- data/lib/restclient/restclient_add_timeout.rb +40 -0
- data/lib/vmc_knife/cli_extensions.rb +138 -0
- data/lib/vmc_knife/commands/knife_cmds.rb +186 -0
- data/lib/vmc_knife/json_diff.rb +83 -0
- data/lib/vmc_knife/json_expander.rb +95 -0
- data/lib/vmc_knife/version.rb +8 -0
- data/lib/vmc_knife/vmc_knife.rb +748 -0
- data/lib/vmc_knife.rb +23 -0
- metadata +149 -0
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
21
|
+
This software downloads additional open source software components upon install
|
22
|
+
that are distributed under separate terms and conditions. Please see the license
|
23
|
+
information provided in the individual software components for more information.
|
24
|
+
|
data/README.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# VMC Knife
|
2
|
+
Extensions for vmc: the VMware Cloud CLI; the command line interface to VMware's Application Platform
|
3
|
+
|
4
|
+
MIT license
|
5
|
+
|
6
|
+
## Usecase
|
7
|
+
Prepare a file that describes the list of applications to deploy, their settings, their environment variables, required data-services and names, build file for the apps.
|
8
|
+
Run the deployment file on the command: will either install update, modify the apps on the cloudfoundry.
|
9
|
+
Run the deployment file via a web-interface.
|
10
|
+
|
11
|
+
prepare a vmc deployment file.
|
12
|
+
vmc_knife vmc_deployment_file.json
|
13
|
+
|
14
|
+
|
data/bin/vmc_knife
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
|
3
|
+
# Make sure the rest-client won't time out too quickly:
|
4
|
+
# vmc uses the rest-client for its rest calls but does not let us configure the timeouts.
|
5
|
+
# We monkey patch here.
|
6
|
+
module RestClient
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :timeout
|
10
|
+
attr_accessor :open_timeout
|
11
|
+
end
|
12
|
+
|
13
|
+
class Request
|
14
|
+
|
15
|
+
def self.execute(args, &block)
|
16
|
+
#puts "Calling overriden RestClient::Request execute"
|
17
|
+
timeouts = {:timeout=>90000000, :open_timeout=>90000000}
|
18
|
+
args.merge!(timeouts)
|
19
|
+
#puts "Req args #{args}"
|
20
|
+
new(args).execute(& block)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.post(url, payload, headers={}, &block)
|
26
|
+
Request.execute(:method => :post,
|
27
|
+
:url => url,
|
28
|
+
:payload => payload,
|
29
|
+
:headers => headers,
|
30
|
+
:timeout=>@timeout,
|
31
|
+
:open_timeout=>@open_timeout,
|
32
|
+
&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
#RestClient.open_timeout = 90000000
|
39
|
+
#RestClient.timeout = 90000000
|
40
|
+
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cli' #this is the cli from vmc.
|
3
|
+
|
4
|
+
# Adds some new commands to the vmc's cli.
|
5
|
+
#
|
6
|
+
# Reconfigure applications according to a saas recipe.
|
7
|
+
# The SaaS recipe is a json object that contains the manifest of each application.
|
8
|
+
# as well as a short declaration of the services expected and their nature.
|
9
|
+
# Usage: edit the json recipe.
|
10
|
+
# vmc_knife configure-applications
|
11
|
+
#
|
12
|
+
# Also bundles utilities to reconfigure the hostname of the cloud_controller and the gateways accordingly:
|
13
|
+
# vmc_knife configure-vcap
|
14
|
+
# and publish the urls in the deployed apps with zeroconf on ubuntu (avahi)
|
15
|
+
# vmc configure-vcap-mdns
|
16
|
+
class VMC::Cli::KnifeRunner < VMC::Cli::Runner
|
17
|
+
|
18
|
+
def parse_command!
|
19
|
+
# just return if already set, happends with -v, -h
|
20
|
+
return if @namespace && @action
|
21
|
+
|
22
|
+
verb = @args.first
|
23
|
+
case verb
|
24
|
+
|
25
|
+
when 'expand-manifest'
|
26
|
+
usage('vmc_knife expand-manifest <path_to_json_manifest> <path_to_destination_expanded_manifest>')
|
27
|
+
@args.shift # consumes the argument.
|
28
|
+
if @args.size == 1
|
29
|
+
set_cmd(:knife, :expand_manifest, 1)
|
30
|
+
elsif @args.size == 2
|
31
|
+
set_cmd(:knife, :expand_manifest, 2)
|
32
|
+
else
|
33
|
+
set_cmd(:knife, :expand_manifest)
|
34
|
+
end
|
35
|
+
when 'login', 'target'
|
36
|
+
usage('vmc_knife login [<path_to_json_manifest>]')
|
37
|
+
@args.shift # consumes the argument.
|
38
|
+
if @args.size == 1
|
39
|
+
set_cmd(:knifemisc, :login, 1)
|
40
|
+
else
|
41
|
+
set_cmd(:knifemisc, :login)
|
42
|
+
end
|
43
|
+
when 'configure-all'
|
44
|
+
usage('vmc_knife configure-all [<path_to_cloud_controller_config_yml>] [<path_to_json_manifest_or_uri>]')
|
45
|
+
@args.shift # consumes the argument.
|
46
|
+
if @args.size <= 2
|
47
|
+
set_cmd(:knife, :configure_all, @args.size)
|
48
|
+
else
|
49
|
+
set_cmd(:knife, :configure_all, @args.size) # too many
|
50
|
+
end
|
51
|
+
when 'configure-vcap'
|
52
|
+
usage('vmc_knife configure-vcap [<path_to_cloud_controller_config_yml>] [<path_to_json_manifest_or_uri>]')
|
53
|
+
@args.shift # consumes the argument.
|
54
|
+
if @args.size <= 2
|
55
|
+
set_cmd(:knife, :configure_cloud_controller, @args.size)
|
56
|
+
else
|
57
|
+
set_cmd(:knife, :configure_cloud_controller, @args.size) # too many
|
58
|
+
end
|
59
|
+
when 'configure-vcap-etc-hosts'
|
60
|
+
usage('vmc_knife configure-vap-etc-hosts [<path_to_etc_hosts>] [<path_to_json_manifest_or_uri>]')
|
61
|
+
@args.shift # consumes the argument.
|
62
|
+
if @args.size <= 2
|
63
|
+
set_cmd(:knife, :configure_etc_hosts, @args.size)
|
64
|
+
else
|
65
|
+
set_cmd(:knife, :configure_etc_hosts, @args.size) # too many
|
66
|
+
end
|
67
|
+
when 'configure-vcap-mdns'
|
68
|
+
usage('vmc_knife configure-vap-mdns [<path_to_aliases>]')
|
69
|
+
@args.shift # consumes the argument.
|
70
|
+
if @args.size <= 2
|
71
|
+
set_cmd(:knife, :configure_etc_avahi_aliases, @args.size)
|
72
|
+
else
|
73
|
+
set_cmd(:knife, :configure_etc_avahi_aliases, @args.size) # too many
|
74
|
+
end
|
75
|
+
when 'configure-applications'
|
76
|
+
usage('vmc_knife configure-applications [<applications_regexp>]')
|
77
|
+
@args.shift # consumes the argument.
|
78
|
+
if @args.size <= 2
|
79
|
+
set_cmd(:knifeapps, :configure_applications, @args.size)
|
80
|
+
else
|
81
|
+
set_cmd(:knifeapps, :configure_applications, @args.size) # too many
|
82
|
+
end
|
83
|
+
when 'configure-services'
|
84
|
+
usage('vmc_knife configure-services [<services_regexp>]')
|
85
|
+
@args.shift # consumes the argument.
|
86
|
+
if @args.size <= 2
|
87
|
+
set_cmd(:knifeapps, :configure_services, @args.size)
|
88
|
+
else
|
89
|
+
set_cmd(:knifeapps, :configure_services, @args.size) # too many
|
90
|
+
end
|
91
|
+
when 'configure-recipes'
|
92
|
+
usage('vmc_knife configure-recipes [<recipes_regexp>]')
|
93
|
+
@args.shift # consumes the argument.
|
94
|
+
if @args.size <= 2
|
95
|
+
set_cmd(:knifeapps, :configure_recipes, @args.size)
|
96
|
+
else
|
97
|
+
set_cmd(:knifeapps, :configure_recipes, @args.size) # too many
|
98
|
+
end
|
99
|
+
when 'upload-applications'
|
100
|
+
usage('vmc_knife upload-applications [<applications_regexp>]')
|
101
|
+
@args.shift # consumes the argument.
|
102
|
+
if @args.size <= 2
|
103
|
+
set_cmd(:knifeapps, :upload_applications, @args.size)
|
104
|
+
else
|
105
|
+
set_cmd(:knifeapps, :upload_applications, @args.size) # too many
|
106
|
+
end
|
107
|
+
when 'start-applications'
|
108
|
+
usage('vmc_knife start-applications [<applications_regexp>]')
|
109
|
+
@args.shift # consumes the argument.
|
110
|
+
if @args.size <= 2
|
111
|
+
set_cmd(:knifeapps, :start_applications, @args.size)
|
112
|
+
else
|
113
|
+
set_cmd(:knifeapps, :start_applications, @args.size) # too many
|
114
|
+
end
|
115
|
+
when 'stop-applications'
|
116
|
+
usage('vmc_knife stop-applications [<applications_regexp>]')
|
117
|
+
@args.shift # consumes the argument.
|
118
|
+
if @args.size <= 2
|
119
|
+
set_cmd(:knifeapps, :stop_applications, @args.size)
|
120
|
+
else
|
121
|
+
set_cmd(:knifeapps, :stop_applications, @args.size) # too many
|
122
|
+
end
|
123
|
+
when 'restart-applications'
|
124
|
+
usage('vmc_knife restart-applications [<applications_regexp>]')
|
125
|
+
@args.shift # consumes the argument.
|
126
|
+
if @args.size <= 2
|
127
|
+
set_cmd(:knifeapps, :restart_applications, @args.size)
|
128
|
+
else
|
129
|
+
set_cmd(:knifeapps, :restart_applications, @args.size) # too many
|
130
|
+
end
|
131
|
+
when 'help'
|
132
|
+
display "vmc_knife expand-manifest|login|start/stop/restart-applications|upload-applications|configure|configure-recipes|configure-applications|configure-services|configure-vcap|configure-vcap-mdns|configure-vcap-etc-hosts [<manifest_path>]"
|
133
|
+
else
|
134
|
+
super
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# Commands for vmc_knife.
|
2
|
+
|
3
|
+
module VMC::KNIFE::Cli
|
4
|
+
|
5
|
+
#loads the manifest file.
|
6
|
+
#when the path is not specified, look in the current directory.
|
7
|
+
#when the path is a directory, look for the first json file it can find.
|
8
|
+
#the json file actually loaded is set as the attribute @manifest_path
|
9
|
+
def load_manifest(manifest_file_path=nil)
|
10
|
+
manifest_file_path = Dir.pwd if manifest_file_path.nil?
|
11
|
+
if File.directory? manifest_file_path
|
12
|
+
#look for the first .json file if possible that is not an expanded.json
|
13
|
+
Dir.glob(File.join(manifest_file_path,"*.json")).each do |file|
|
14
|
+
@manifest_path = file
|
15
|
+
if VMC::Cli::Config.trace
|
16
|
+
display "Using the manifest #{@manifest_path}"
|
17
|
+
end
|
18
|
+
return VMC::KNIFE::JSON_EXPANDER.expand_json @manifest_path
|
19
|
+
end
|
20
|
+
raise "Unable to find a *.json file in #{manifest_file_path}"
|
21
|
+
else
|
22
|
+
@manifest_path = manifest_file_path
|
23
|
+
return VMC::KNIFE::JSON_EXPANDER.expand_json @manifest_path
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module VMC::Cli::Command
|
29
|
+
|
30
|
+
class Knife < Base
|
31
|
+
include VMC::KNIFE::Cli
|
32
|
+
|
33
|
+
# expands the json manifest. outputs it in the destination path.
|
34
|
+
def expand_manifest(manifest_file_path, destination=nil)
|
35
|
+
res = VMC::KNIFE::JSON_EXPANDER.expand_json manifest_file_path
|
36
|
+
if destination.nil?
|
37
|
+
noextension = File.basename(manifest_file_path, File.extname(manifest_file_path))
|
38
|
+
destination = File.join File.dirname(manifest_file_path), "#{noextension}.expanded.json"
|
39
|
+
end
|
40
|
+
display "Expanding the manifest #{manifest_file_path} into #{destination}"
|
41
|
+
if VMC::Cli::Config.trace
|
42
|
+
display JSON.pretty_generate(res)
|
43
|
+
end
|
44
|
+
File.open(destination, 'w') {|f| f.write(JSON.pretty_generate(res)) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# updates the cloud_controller
|
48
|
+
def configure_cloud_controller(cloud_controller_yml=nil,manifest_file_path_or_uri=nil)
|
49
|
+
__update(manifest_file_path_or_uri,cloud_controller_yml,VMC::KNIFE::VCAPUpdateCloudControllerConfig,"cloud_controller")
|
50
|
+
end
|
51
|
+
# updates /etc/hosts
|
52
|
+
def configure_etc_hosts(etc_hosts=nil,manifest_file_path_or_uri=nil)
|
53
|
+
__update(manifest_file_path_or_uri,etc_hosts,VMC::KNIFE::VCAPUpdateEtcHosts,"/etc/hosts")
|
54
|
+
end
|
55
|
+
# updates /etc/avahi/aliases
|
56
|
+
def configure_etc_avahi_aliases(etc_avahi_aliases=nil,manifest_file_path=nil)
|
57
|
+
man = load_manifest(manifest_file_path)
|
58
|
+
update_aliases = VMC::KNIFE::VCAPUpdateAvahiAliases.new(etc_avahi_aliases,man,client)
|
59
|
+
update_aliases.do_exec = true
|
60
|
+
update_aliases.execute
|
61
|
+
end
|
62
|
+
|
63
|
+
def configure_all(manifest_file_path_or_uri=nil)
|
64
|
+
display "Stop applications ..."
|
65
|
+
VMC::Cli::Command::Knifeapps.new(@options).stop_applications(nil,manifest_file_path_or_uri)
|
66
|
+
display "Configure_cloud_controller ..."
|
67
|
+
configure_cloud_controller(nil,manifest_file_path_or_uri)
|
68
|
+
display "Configure_etc_hosts ..."
|
69
|
+
configure_etc_hosts(nil,manifest_file_path_or_uri)
|
70
|
+
display "Login again ..."
|
71
|
+
VMC::Cli::Command::Knifemisc.new(@options).login(manifest_file_path_or_uri)
|
72
|
+
display "Configure_applications ..."
|
73
|
+
VMC::Cli::Command::Knifeapps.new(@options).configure_applications(nil,manifest_file_path_or_uri)
|
74
|
+
display "Configure_etc_avahi_aliases ..."
|
75
|
+
configure_etc_avahi_aliases(nil,manifest_file_path_or_uri)
|
76
|
+
display "Start applications ..."
|
77
|
+
VMC::Cli::Command::Knifeapps.new(@options).start_applications(nil,manifest_file_path_or_uri)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def __update(manifest_file_path_or_uri,config,_class,msg_label)
|
82
|
+
unless manifest_file_path_or_uri.nil?
|
83
|
+
if File.exists? manifest_file_path_or_uri
|
84
|
+
man = load_manifest(manifest_file_path_or_uri)
|
85
|
+
uri = man['target']
|
86
|
+
else
|
87
|
+
uri = manifest_file_path_or_uri
|
88
|
+
end
|
89
|
+
else
|
90
|
+
man = load_manifest(nil)
|
91
|
+
uri = man['target']
|
92
|
+
end
|
93
|
+
raise "No uri defined" unless uri
|
94
|
+
update_cc = _class.new(uri,config)
|
95
|
+
if update_cc.update_pending()
|
96
|
+
display "Configuring #{msg_label} with uri: #{uri}" if VMC::Cli::Config.trace
|
97
|
+
update_cc.execute()
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
class Knifemisc < Misc
|
104
|
+
include VMC::KNIFE::Cli
|
105
|
+
|
106
|
+
# configures the target and login according to the info in the manifest.
|
107
|
+
def login(manifest_file_path=nil)
|
108
|
+
man = load_manifest(manifest_file_path)
|
109
|
+
target_url = man['target']
|
110
|
+
raise "No target defined in the manifest #{@manifest_path}" if target_url.nil?
|
111
|
+
set_target(target_url)
|
112
|
+
|
113
|
+
email = man['email']
|
114
|
+
password = man['password']
|
115
|
+
@options[:email] = email if email
|
116
|
+
@options[:password] = password if password
|
117
|
+
|
118
|
+
tries ||= 0
|
119
|
+
# login_and_save_token:
|
120
|
+
token = client.login(email, password)
|
121
|
+
VMC::Cli::Config.store_token(token)
|
122
|
+
|
123
|
+
rescue VMC::Client::TargetError
|
124
|
+
display "Problem with login, invalid account or password.".red
|
125
|
+
retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
|
126
|
+
exit 1
|
127
|
+
rescue => e
|
128
|
+
display "Problem with login, #{e}, try again or register for an account.".red
|
129
|
+
display e.backtrace
|
130
|
+
exit 1
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
class Knifeapps < Apps
|
137
|
+
include VMC::KNIFE::Cli
|
138
|
+
|
139
|
+
def configure_applications(app_names_regexp=nil,manifest_file_path=nil)
|
140
|
+
configure(nil,nil,app_names_regexp,manifest_file_path)
|
141
|
+
end
|
142
|
+
def configure_services(services_names_regexp=nil,manifest_file_path=nil)
|
143
|
+
configure(nil,nil,services_names_regexp,manifest_file_path)
|
144
|
+
end
|
145
|
+
def configure_recipes(recipe_names_regexp=nil,manifest_file_path=nil)
|
146
|
+
configure(recipe_names_regexp,nil,nil,manifest_file_path)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Configure the applications according to their manifest.
|
150
|
+
# The parameters are related to selecting a subset of the applications to configure.
|
151
|
+
# nil means all apps for all recipes found in the manifest are configured.
|
152
|
+
# @param recipes The list of recipes: nil: search the apps in all recipes
|
153
|
+
# @param app_role_names The names of the apps in each recipe; nil: configure all apps found.
|
154
|
+
def configure(recipes_regexp=nil,app_names_regexp=nil,service_names_regexp=nil,manifest_file_path=nil)
|
155
|
+
man = load_manifest(manifest_file_path)
|
156
|
+
configurer = VMC::KNIFE::RecipesConfigurationApplier.new(man,client,recipes_regexp,app_names_regexp,service_names_regexp)
|
157
|
+
if VMC::Cli::Config.trace
|
158
|
+
display "Pending updates"
|
159
|
+
display JSON.pretty_generate(configurer.updates_pending)
|
160
|
+
end
|
161
|
+
configurer.execute
|
162
|
+
end
|
163
|
+
|
164
|
+
def upload_applications(app_names_regexp=nil,manifest_file_path=nil)
|
165
|
+
recipe_configuror(:upload,nil,nil,app_names_regexp,manifest_file_path)
|
166
|
+
end
|
167
|
+
def start_applications(app_names_regexp=nil,manifest_file_path=nil)
|
168
|
+
recipe_configuror(:start,nil,nil,app_names_regexp,manifest_file_path)
|
169
|
+
end
|
170
|
+
def stop_applications(app_names_regexp=nil,manifest_file_path=nil)
|
171
|
+
recipe_configuror(:stop,nil,nil,app_names_regexp,manifest_file_path)
|
172
|
+
end
|
173
|
+
def restart_applications(app_names_regexp=nil,manifest_file_path=nil)
|
174
|
+
recipe_configuror(:restart,nil,nil,app_names_regexp,manifest_file_path)
|
175
|
+
end
|
176
|
+
|
177
|
+
def recipe_configuror(method_sym_name,recipes_regexp=nil,app_names_regexp=nil,service_names_regexp=nil,manifest_file_path=nil)
|
178
|
+
man = load_manifest(manifest_file_path)
|
179
|
+
configurer = VMC::KNIFE::RecipesConfigurationApplier.new(man,client,recipes_regexp,app_names_regexp,service_names_regexp)
|
180
|
+
method_object = configurer.method(method_sym_name)
|
181
|
+
method_object.call
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'json'
|
2
|
+
module VMC
|
3
|
+
module KNIFE
|
4
|
+
module JSON_DIFF
|
5
|
+
|
6
|
+
DIFF_NODE_NAME = "__diff__"
|
7
|
+
TYPE_NODE_NAME = "__type__"
|
8
|
+
PLUS="+"
|
9
|
+
MINUS="-"
|
10
|
+
CHANGED="=>"
|
11
|
+
DISPLAY_TYPE_ONLY_WHEN_CHANGE=true
|
12
|
+
|
13
|
+
def self.compare(a,b,pretty=true)
|
14
|
+
if a.kind_of?(String) && b.kind_of?(String) && File.exists?(a) && File.exists?(b)
|
15
|
+
a = JSON.parse File.open(a).read
|
16
|
+
b = JSON.parse File.open(b).read
|
17
|
+
end
|
18
|
+
if pretty
|
19
|
+
JSON.pretty_generate compare_tree(a, b)
|
20
|
+
else
|
21
|
+
compare_tree(a, b).to_json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.compare_tree(a, b)
|
26
|
+
typeA = a.class.name
|
27
|
+
typeB = b.class.name
|
28
|
+
|
29
|
+
aString = a.to_s unless is_array_or_hash?(a)
|
30
|
+
bString = b.to_s unless is_array_or_hash?(b)
|
31
|
+
|
32
|
+
node = Hash.new
|
33
|
+
if a.nil?
|
34
|
+
node[DIFF_NODE_NAME]=PLUS
|
35
|
+
node[TYPE_NODE_NAME]=typeB unless DISPLAY_TYPE_ONLY_WHEN_CHANGE
|
36
|
+
node['value']=bString if bString
|
37
|
+
elsif b.nil?
|
38
|
+
node[DIFF_NODE_NAME]=MINUS
|
39
|
+
node[TYPE_NODE_NAME]=typeA unless DISPLAY_TYPE_ONLY_WHEN_CHANGE
|
40
|
+
node['value']=aString if aString
|
41
|
+
elsif (typeA != typeB) || (!aString.nil? && a != b)
|
42
|
+
node[DIFF_NODE_NAME]=CHANGED
|
43
|
+
if typeA != typeB
|
44
|
+
node[TYPE_NODE_NAME]="#{typeA} => #{typeB}"
|
45
|
+
else
|
46
|
+
node[TYPE_NODE_NAME]=typeA unless DISPLAY_TYPE_ONLY_WHEN_CHANGE
|
47
|
+
end
|
48
|
+
node['value']="#{aString} => #{bString}" if aString
|
49
|
+
else
|
50
|
+
node[TYPE_NODE_NAME]=typeA unless DISPLAY_TYPE_ONLY_WHEN_CHANGE
|
51
|
+
node['value']=aString if aString
|
52
|
+
end
|
53
|
+
|
54
|
+
if aString
|
55
|
+
return node
|
56
|
+
end
|
57
|
+
child_node=node
|
58
|
+
# child_node = Hash.new
|
59
|
+
# node['child']=child_node
|
60
|
+
keys = Array.new
|
61
|
+
keys = collect_keys(a,keys)
|
62
|
+
keys = collect_keys(b,keys)
|
63
|
+
keys.sort!
|
64
|
+
for i in 0..(keys.length-1)
|
65
|
+
if (keys[i] != keys[i-1])
|
66
|
+
value = compare_tree(a && a[keys[i]], b && b[keys[i]]);
|
67
|
+
child_node[keys[i]]=value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
node
|
71
|
+
end
|
72
|
+
private
|
73
|
+
def self.is_array_or_hash?(obj)
|
74
|
+
obj.kind_of?(Array) || obj.kind_of?(Hash)
|
75
|
+
end
|
76
|
+
def self.collect_keys(obj, collector)
|
77
|
+
return Array.new unless obj.kind_of? Hash
|
78
|
+
collector + obj.keys
|
79
|
+
end
|
80
|
+
|
81
|
+
end # end of JSON_DIFF
|
82
|
+
end # end of KNIFE
|
83
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'vmc/client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module VMC
|
5
|
+
module KNIFE
|
6
|
+
module JSON_EXPANDER
|
7
|
+
|
8
|
+
# Loads a json file.
|
9
|
+
# Makes up to 10 passes evaluating ruby in the values that contain #{}.
|
10
|
+
def self.expand_json(file_path)
|
11
|
+
raise "The file #{file_path} does not exist" unless File.exists? file_path
|
12
|
+
data = JSON.parse File.open(file_path).read
|
13
|
+
#puts "got data #{data.to_json}"
|
14
|
+
passes = 0
|
15
|
+
while passes < 100
|
16
|
+
#puts "pass #{passes}"
|
17
|
+
break unless expand_data(data,data)
|
18
|
+
passes += 1
|
19
|
+
end
|
20
|
+
puts data.to_json unless passes < 100
|
21
|
+
raise "More than 100 passes evaluating the ruby template in the json file" unless passes < 100
|
22
|
+
#puts "got data #{data.to_json}"
|
23
|
+
data
|
24
|
+
end
|
25
|
+
|
26
|
+
# Traverses the JSON object
|
27
|
+
# Eval the values that are strings and contain a #{}
|
28
|
+
# Does not do it recursively
|
29
|
+
# data The root data passed as 'this' in the binding to the eval function
|
30
|
+
# @return true if there was a change.
|
31
|
+
def self.expand_data(data,current)
|
32
|
+
at_least_one_eval = false
|
33
|
+
if current.kind_of? Hash
|
34
|
+
current.each_pair do | k, v |
|
35
|
+
if v.kind_of? String
|
36
|
+
if /\#{.+}/ =~ v
|
37
|
+
at_least_one_eval = true
|
38
|
+
begin
|
39
|
+
evalled = eval_v(v,data,current)
|
40
|
+
current[k] = evalled unless evalled.nil?
|
41
|
+
rescue => e
|
42
|
+
raise "Error thrown evaluating #{v}: #{e.inspect}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
at_least_one_eval ||= expand_data(data,v)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
elsif current.kind_of? Array
|
50
|
+
index = 0
|
51
|
+
current.each do | v |
|
52
|
+
if v.kind_of? String
|
53
|
+
if /\#{.+}/ =~ v
|
54
|
+
at_least_one_eval = true
|
55
|
+
begin
|
56
|
+
evalled = eval_v(v,data,current)
|
57
|
+
current[index] = evalled unless evalled.nil?
|
58
|
+
rescue => e
|
59
|
+
raise "Error thrown evaluating #{v}: #{e.inspect}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
else
|
63
|
+
at_least_one_eval ||= expand_data(data,v)
|
64
|
+
end
|
65
|
+
index+=1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
at_least_one_eval
|
69
|
+
end
|
70
|
+
|
71
|
+
# internal eval a reference.
|
72
|
+
# the reference is always wrapped in a json string.
|
73
|
+
# however if it is purely a ruby script ("#{ruby here}" ) we unwrap it
|
74
|
+
# to avoid casting the result into a string.
|
75
|
+
def self.eval_v(v,data,current)
|
76
|
+
#puts "evalling #{v}"
|
77
|
+
if /^\#{([^}]*)}$/ =~ v
|
78
|
+
val = $1
|
79
|
+
else
|
80
|
+
val = '"'+v+'"'
|
81
|
+
end
|
82
|
+
evalled = eval(val,get_binding(data,current))
|
83
|
+
#puts "evaluating #{v} => #{evalled} class #{evalled.class.name}"
|
84
|
+
evalled
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.get_binding(this,current)
|
88
|
+
binding
|
89
|
+
end
|
90
|
+
|
91
|
+
end #end of JSON_EXPANDER
|
92
|
+
|
93
|
+
end # end of KNIFE
|
94
|
+
|
95
|
+
end
|