vcoworkflows 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ require_relative 'constants'
2
+ require 'rest_client'
3
+
4
+ # VcoWorkflows
5
+ module VcoWorkflows
6
+ # VcoSession
7
+ class VcoSession
8
+ # Accessor for rest-client object, primarily for testing purposes
9
+ attr_reader :rest_resource
10
+
11
+ # Initialize the session
12
+ #
13
+ # @param [String] uri URI for the vCenter Orchestrator API endpoint
14
+ # @param [String] user User name for vCO
15
+ # @param [String] password Password for vCO
16
+ # @param [Boolean] verify_ssl Whether or not to verify SSL certificates
17
+ def initialize(uri, user: nil, password: nil, verify_ssl: true)
18
+ api_url = "#{uri.gsub(/\/$/, '')}/vco/api"
19
+ RestClient.proxy = ENV['http_proxy'] # Set a proxy if present
20
+ @rest_resource = RestClient::Resource.new(api_url,
21
+ user: user,
22
+ password: password,
23
+ verify_ssl: verify_ssl)
24
+ end
25
+
26
+ # Perform a REST GET operation against the specified endpoint
27
+ #
28
+ # @param [String] endpoint REST endpoint to use
29
+ # @param [Hash] headers Optional headers to use in request
30
+ # @return [String] JSON response body
31
+ def get(endpoint, headers = {})
32
+ headers = { accept: :json }.merge(headers)
33
+ @rest_resource[endpoint].get headers
34
+ end
35
+
36
+ # Perform a REST POST operation against the specified endpoint with the
37
+ # given data body
38
+ #
39
+ # @param [String] endpoint REST endpoint to use
40
+ # @param [String] body JSON data body to post
41
+ # @param [Hash] headers Optional headers to use in request
42
+ # @return [String] JSON response body
43
+ def post(endpoint, body, headers = {})
44
+ headers = { accept: :json, content_type: :json }.merge(headers)
45
+ @rest_resource[endpoint].post body, headers
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ # VcoWorkflows
2
+ module VcoWorkflows
3
+ VERSION = '0.1.1'
4
+ end
@@ -0,0 +1,304 @@
1
+ require_relative 'constants'
2
+ require_relative 'workflowservice'
3
+ require_relative 'workflowpresentation'
4
+ require_relative 'workflowtoken'
5
+ require_relative 'workflowparameter'
6
+ require 'json'
7
+
8
+ # VcoWorkflows
9
+ module VcoWorkflows
10
+ # rubocop:disable ClassLength
11
+
12
+ # Class to represent a Workflow as presented by vCenter Orchestrator.
13
+ class Workflow
14
+ attr_reader :id
15
+ attr_reader :name
16
+ attr_reader :version
17
+ attr_reader :description
18
+ attr_reader :input_parameters
19
+ attr_reader :output_parameters
20
+ attr_accessor :service
21
+ attr_reader :execution_id
22
+
23
+ attr_reader :source_json
24
+
25
+ # rubocop:disable CyclomaticComplexity, PerceivedComplexity, MethodLength, LineLength
26
+
27
+ # Create a Workflow object given vCenter Orchestrator's JSON description
28
+ #
29
+ # When passed `url`, `username` and `password` the necessary session and
30
+ # service objects will be created behind the scenes. Alternatively you can
31
+ # pass in a VcoSession object or a WorkflowService object if you have
32
+ # constructed them yourself.
33
+ # @param [String] name Name of the requested workflow
34
+ # @param [Hash] options Hash of options, see README.md for details
35
+ # @return [VcoWorkflows::Workflow]
36
+ def initialize(name = nil, options = {})
37
+ @options = {
38
+ id: nil,
39
+ url: nil,
40
+ username: nil,
41
+ password: nil,
42
+ verify_ssl: true,
43
+ service: nil
44
+ }.merge(options)
45
+
46
+ @service = nil
47
+ @execution_id = nil
48
+
49
+ # -------------------------------------------------------------
50
+ # Figure out how to get a workflow service. If I can't, I die.
51
+ # (DUN dun dun...)
52
+
53
+ if options[:service]
54
+ @service = options[:service]
55
+ elsif @options[:url] && @options[:username] && @options[:password]
56
+ session = VcoWorkflows::VcoSession.new(@options[:url],
57
+ user: @options[:username],
58
+ password: @options[:password],
59
+ verify_ssl: @options[:verify_ssl])
60
+ @service = VcoWorkflows::WorkflowService.new(session)
61
+ end
62
+
63
+ fail(IOError, 'Unable to create/use a WorkflowService!') if @service.nil?
64
+
65
+ # -------------------------------------------------------------
66
+ # Retrieve the workflow and parse it into a data structure
67
+ # If we're given both a name and ID, prefer the id
68
+ if @options[:id]
69
+ workflow_json = @service.get_workflow_for_id(@options[:id])
70
+ else
71
+ workflow_json = @service.get_workflow_for_name(name)
72
+ end
73
+ workflow_data = JSON.parse(workflow_json)
74
+
75
+ # Set up the attributes if they exist in the data json, otherwise nil them
76
+ @id = workflow_data.key?('id') ? workflow_data['id'] : nil
77
+ @name = workflow_data.key?('name') ? workflow_data['name'] : nil
78
+ @version = workflow_data.key?('version') ? workflow_data['version'] : nil
79
+ @description = workflow_data.key?('description') ? workflow_data['description'] : nil
80
+
81
+ # Process the input parameters
82
+ if workflow_data.key?('input-parameters')
83
+ @input_parameters = Workflow.parse_parameters(workflow_data['input-parameters'])
84
+ else
85
+ @input_parameters = {}
86
+ end
87
+
88
+ # Identify required input_parameters
89
+ wfpres = VcoWorkflows::WorkflowPresentation.new(@service, @id)
90
+ wfpres.required.each do |req_param|
91
+ @input_parameters[req_param].required(true)
92
+ end
93
+
94
+ # Process the output parameters
95
+ if workflow_data.key?('output-parameters')
96
+ @output_parameters = Workflow.parse_parameters(workflow_data['output-parameters'])
97
+ else
98
+ @output_parameters = {}
99
+ end
100
+ end
101
+ # rubocop:enable CyclomaticComplexity, PerceivedComplexity, MethodLength, LineLength
102
+
103
+ def url
104
+ options[:url]
105
+ end
106
+
107
+ def username
108
+ options[:username]
109
+ end
110
+
111
+ def password
112
+ options[:password]
113
+ end
114
+
115
+ def verify_ssl?
116
+ options[:verify_ssl]
117
+ end
118
+
119
+ # rubocop:disable MethodLength, LineLength
120
+
121
+ # Parse json parameters and return a nice hash
122
+ # @param [Array] parameter_data JSON document of parameters as defined
123
+ # by vCO
124
+ # @return [Hash]
125
+ def self.parse_parameters(parameter_data = [])
126
+ wfparams = {}
127
+ parameter_data.each do |parameter|
128
+ wfparam = VcoWorkflows::WorkflowParameter.new(parameter['name'], parameter['type'])
129
+ if parameter['value']
130
+ if wfparam.type.eql?('Array')
131
+ value = []
132
+ begin
133
+ parameter['value'][wfparam.type.downcase]['elements'].each do |element|
134
+ value << element[element.keys.first]['value']
135
+ end
136
+ rescue StandardError => error
137
+ parse_failure(error)
138
+ end
139
+ else
140
+ begin
141
+ value = parameter['value'][parameter['value'].keys.first]['value']
142
+ rescue StandardError => error
143
+ parse_failure(error)
144
+ end
145
+ end
146
+ value = nil if value.eql?('null')
147
+ wfparam.set(value)
148
+ end
149
+ wfparams[parameter['name']] = wfparam
150
+ end
151
+ wfparams
152
+ end
153
+ # rubocop:enable MethodLength, LineLength
154
+
155
+ # rubocop:disable LineLength
156
+
157
+ # Process exceptions raised in parse_parameters by bravely ignoring them
158
+ # and forging ahead blindly!
159
+ # @param [Exception] error
160
+ def self.parse_failure(error)
161
+ $stderr.puts "\nWhoops!"
162
+ $stderr.puts "Ran into a problem parsing parameter #{wfparam.name} (#{wfparam.type})!"
163
+ $stderr.puts "Source data: #{JSON.pretty_generate(parameter)}\n"
164
+ $stderr.puts error.message
165
+ $stderr.puts "\nBravely forging on and ignoring parameter #{wfparam.name}!"
166
+ end
167
+ # rubocop:enable LineLength
168
+
169
+ # rubocop:disable LineLength
170
+
171
+ # Get an array of the names of all the required input parameters
172
+ # @return [Hash] Hash of WorkflowParameter input parameters which are required for this workflow
173
+ def required_parameters
174
+ required = {}
175
+ @input_parameters.each_value { |v| required[v.name] = v if v.required? }
176
+ required
177
+ end
178
+ # rubocop:enable LineLength
179
+
180
+ # Get the value of a specific input parameter
181
+ # @param [String] parameter_name Name of the parameter whose value to get
182
+ # @return [VcoWorkflows::WorkflowParameter]
183
+ def parameter(parameter_name)
184
+ @input_parameters[parameter_name]
185
+ end
186
+
187
+ # rubocop:disable LineLength
188
+
189
+ # Set a parameter to a value
190
+ # @param [String] parameter_name name of the parameter to set
191
+ # @param [Object] value value to set
192
+ def set_parameter(parameter_name, value)
193
+ if @input_parameters.key?(parameter_name)
194
+ @input_parameters[parameter_name].set value
195
+ else
196
+ $stderr.puts "\nAttempted to set a value for a non-existent WorkflowParameter!"
197
+ $stderr.puts "It appears that there is no parameter \"#{parameter}\"."
198
+ $stderr.puts "Valid parameter names are: #{@input_parameters.keys.join(', ')}"
199
+ $stderr.puts ''
200
+ fail(IOError, ERR[:no_such_parameter])
201
+ end
202
+ end
203
+ # rubocop:enable LineLength
204
+
205
+ # rubocop:disable LineLength
206
+
207
+ # Get the value for an input parameter
208
+ # @param [String] parameter_name Name of the input parameter whose value to get
209
+ # @return [Object]
210
+ def get_parameter(parameter_name)
211
+ @input_parameters[parameter_name].value
212
+ end
213
+ # rubocop:enable LineLength
214
+
215
+ # rubocop:disable LineLength
216
+
217
+ # Verify that all mandatory input parameters have values
218
+ private def verify_parameters
219
+ required_parameters.each do |name, wfparam|
220
+ if wfparam.required? && (wfparam.value.nil? || wfparam.value.size == 0)
221
+ fail(IOError, ERR[:param_verify_failed] << "#{name} required but not present.")
222
+ end
223
+ end
224
+ end
225
+ # rubocop:enable LineLength
226
+
227
+ # rubocop:disable LineLength
228
+
229
+ # Execute this workflow
230
+ # @param [VcoWorkflows::WorkflowService] workflow_service
231
+ # @return [String] Workflow Execution ID
232
+ def execute(workflow_service = nil)
233
+ # If we're not given an explicit workflow service for this execution
234
+ # request, use the one defined when we were created.
235
+ workflow_service = @service if workflow_service.nil?
236
+ # If we still have a nil workflow_service, go home.
237
+ fail(IOError, ERR[:no_workflow_service_defined]) if workflow_service.nil?
238
+ # Make sure we didn't forget any required parameters
239
+ verify_parameters
240
+ # Let's get this thing running!
241
+ @execution_id = workflow_service.execute_workflow(@id, input_parameter_json)
242
+ end
243
+ # rubocop:enable LineLength
244
+
245
+ # Get a list of all the executions of this workflow. Wrapper for
246
+ # VcoWorkflows::WorkflowService#get_execution_list
247
+ # @return [Hash]
248
+ def executions
249
+ @service.get_execution_list(@id)
250
+ end
251
+
252
+ # Return a WorkflowToken
253
+ # @param [String] execution_id optional execution id to get logs for
254
+ # @return [VcoWorkflows::WorkflowToken]
255
+ def token(execution_id = nil)
256
+ execution_id = @execution_id if execution_id.nil?
257
+ VcoWorkflows::WorkflowToken.new(@service, @id, execution_id)
258
+ end
259
+
260
+ # Return logs for the given execution
261
+ # @param [String] execution_id optional execution id to get logs for
262
+ # @return [VcoWorkflows::WorkflowExecutionLog]
263
+ def log(execution_id = nil)
264
+ execution_id = @execution_id if execution_id.nil?
265
+ log_json = @service.get_log(@id, execution_id)
266
+ VcoWorkflows::WorkflowExecutionLog.new(log_json)
267
+ end
268
+
269
+ # rubocop:disable MethodLength
270
+
271
+ # Stringify the workflow
272
+ # @return [String]
273
+ def to_s
274
+ string = "Workflow: #{@name}\n"
275
+ string << "ID: #{@id}\n"
276
+ string << "Description: #{@description}\n"
277
+ string << "Version: #{@version}\n"
278
+
279
+ string << "\nInput Parameters:\n"
280
+ if @input_parameters.size > 0
281
+ @input_parameters.each_value { |wf_param| string << " #{wf_param}" }
282
+ end
283
+
284
+ string << "\nOutput Parameters:" << "\n"
285
+ if @output_parameters.size > 0
286
+ @output_parameters.each_value { |wf_param| string << " #{wf_param}" }
287
+ end
288
+
289
+ # Assert
290
+ string
291
+ end
292
+ # rubocop:enable MethodLength
293
+
294
+ # Convert the input parameters to a JSON document
295
+ # @return [String]
296
+ private def input_parameter_json
297
+ tmp_params = []
298
+ @input_parameters.each_value { |v| tmp_params << v.as_struct if v.set? }
299
+ param_struct = { parameters: tmp_params }
300
+ param_struct.to_json
301
+ end
302
+ end
303
+ # rubocop:enable ClassLength
304
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'workflowservice'
2
+ require 'json'
3
+
4
+ # VcoWorkflows
5
+ module VcoWorkflows
6
+ # WorkflowExecutionLog is a simple object to contain the log for an
7
+ # execution of a workflow.
8
+ class WorkflowExecutionLog
9
+ attr_reader :messages
10
+
11
+ # Create an execution log object
12
+ # @param [String] log_json JSON document as string
13
+ # @return [VcoWorkflows::WorkflowExecutionLog]
14
+ def initialize(log_json)
15
+ @messages = {}
16
+ JSON.parse(log_json)['logs'].each do |log_entry|
17
+ messages[log_entry['entry']['time-stamp']] = log_entry['entry']
18
+ end
19
+ end
20
+
21
+ # rubocop:disable MethodLength, LineLength
22
+
23
+ # Stringify the log
24
+ # @return [String]
25
+ def to_s
26
+ message = ''
27
+ @messages.keys.sort.each do |timestamp|
28
+ message << "#{Time.at(timestamp / 1000)}"
29
+ message << " #{@messages[timestamp]['severity']}: #{@messages[timestamp]['user']}:"
30
+ message << " #{@messages[timestamp]['short-description']}"
31
+ unless @messages[timestamp]['short-description'].eql?(@messages[timestamp]['long-description'])
32
+ message << "; #{@messages[timestamp]['long-description']}"
33
+ end
34
+ message << "\n"
35
+ end
36
+ message
37
+ end
38
+ # rubocop:enable MethodLength, LineLength
39
+ end
40
+ end
@@ -0,0 +1,145 @@
1
+ require_relative 'constants'
2
+ require 'json'
3
+
4
+ module VcoWorkflows
5
+ # WorkflowParameter is an object wrapper for workflow input and output
6
+ # parameters.
7
+ class WorkflowParameter
8
+ attr_reader :name
9
+ attr_reader :type
10
+ attr_reader :subtype
11
+ attr_reader :value
12
+
13
+ # rubocop:disable MethodLength
14
+
15
+ # Create a new workflow parameter object
16
+ # @param [String] name Name of the workflow parameter
17
+ # @param [String] type Data type of the parameter (according to vCO)
18
+ # @return [VcoWorkflows::WorkflowParameter]
19
+ def initialize(name = nil, type = nil, options = {})
20
+ # Merge provided options with our defaults
21
+ options = {
22
+ required: false,
23
+ value: nil
24
+ }.merge(options)
25
+
26
+ @name = name
27
+
28
+ case type
29
+ when /\//
30
+ @type = type.gsub(/\/.*$/, '')
31
+ @subtype = type.gsub(/^.*\//, '')
32
+ else
33
+ @type = type
34
+ @subtype = nil
35
+ end
36
+
37
+ @required = options[:required]
38
+
39
+ # If value is supposed to be an array but we dont' have a value yet,
40
+ # create an empty array. If it's not supposed to be an array, just
41
+ # set the value, even if it's still nil.
42
+ if options[:value].nil?
43
+ @type.eql?('Array') ? @value = [] : @value = nil
44
+ else
45
+ @value = set(options[:value])
46
+ end
47
+ end
48
+ # rubocop:enable MethodLength
49
+
50
+ # rubocop:disable CyclomaticComplexity
51
+
52
+ # Set the parameter value
53
+ # @param [Object] value Value for the parameter
54
+ def set(value)
55
+ # Do some basic checking for Arrays.
56
+ case @type
57
+ when 'Array'
58
+ fail(IOError, ERR[:param_verify_failed]) unless value.is_a?(Array)
59
+ end unless value.nil?
60
+ @value = value
61
+ end
62
+ # rubocop:enable CyclomaticComplexity
63
+
64
+ # Has a value been set for this parameter?
65
+ # @return [Boolean]
66
+ def set?
67
+ case value.class
68
+ when Array
69
+ value.size == 0 ? false : true
70
+ else
71
+ value.nil? ? false : true
72
+ end
73
+ end
74
+
75
+ # rubocop:disable TrivialAccessors
76
+ # rubocop:disable LineLength
77
+
78
+ # Set whether or not this WorkflowParameter is required
79
+ # @param [Boolean] required Set this parameter as required (if not specified)
80
+ def required(required = true)
81
+ @required = required
82
+ end
83
+ # rubocop:enable LineLength
84
+
85
+ # Determine whether or not this WorkflowParameter has been marked as
86
+ # required
87
+ # @return [Boolean]
88
+ def required?
89
+ @required
90
+ end
91
+ # rubocop:enable TrivialAccessors
92
+
93
+ # rubocop:disable CyclomaticComplexity, PerceivedComplexity, MethodLength
94
+
95
+ # Return a string representation of the parameter
96
+ # @return [String] Pretty-formatted string
97
+ def to_s
98
+ string = "#{@name}"
99
+ # If value is either nil or an empty array
100
+ if @value.nil? || @value.is_a?(Array) && @value.size == 0
101
+ string << " (#{@type}"
102
+ string << "/#{@subtype}" if @subtype
103
+ string << ')'
104
+ string << ' [required]' if @required
105
+ else
106
+ if @type.eql?('Array')
107
+ string << ' ='
108
+ @value.each { |v| string << "\n - #{v}" }
109
+ else
110
+ string << " = #{@value}"
111
+ end
112
+ end
113
+ string << "\n"
114
+ end
115
+ # rubocop:enable CyclomaticComplexity, PerceivedComplexity, MethodLength
116
+
117
+ # Public
118
+ # Return a JSON document representation of this object
119
+ # @return [String] JSON representation
120
+ def to_json
121
+ as_struct.to_json
122
+ end
123
+
124
+ # rubocop:disable LineLength
125
+
126
+ # Hashify the parameter (primarily useful for converting to JSON or YAML)
127
+ # @return [Hash] Contents of this object as a hash
128
+ def as_struct
129
+ attributes = { type: @type, name: @name, scope: 'local' }
130
+
131
+ # If the value is an array, we need to build it in the somewhat silly
132
+ # manner that vCO requires it to be presented. Otherwise, just paste
133
+ # it on the end of the hash.
134
+ if @type.eql?('Array')
135
+ fail(IOError, ERR[:wtf]) unless @value.is_a?(Array)
136
+ attributes[:value] = { @type.downcase => { elements: [] } }
137
+ @value.each { |val| attributes[:value][@type.downcase][:elements] << { @subtype => { value: val } } }
138
+ else
139
+ attributes[:value] = { @type => { value: @value } }
140
+ end
141
+ attributes
142
+ end
143
+ # rubocop:enable LineLength
144
+ end
145
+ end