rightscale_selfservice 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NmYzNzk0MWJmMzc4MTIxNGIyOWZkMDJiMTUxMTczNDVhOTg0OGNhMw==
4
+ YWNkZWUzNjgzZThhOWQ3MTEwNTY4N2IyYzA4NGQxOTU5NzVjNjYwOA==
5
5
  data.tar.gz: !binary |-
6
- YzA5YjA2NjAyMWE2ODliY2RmMTE3ZjliMmVlYzY3ZGNjNDY4NzVlNA==
6
+ ZjI4NGY5ZjYxM2JjMjQ0MzZlMDU2NDJmYjVlMWYxYWE2OWJhMDY0NA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Y2Q1OTJlNGY4YTQ4NjNjZjU5NTZlYzEyMTBjMjIzZTViNzFkYTM1MDA5N2Rl
10
- YzliM2E2MTI2Yjc5MThmYmJhYjg5Yjg4ODE5OTFiMjNhZTE4OGIzNjdiZTg2
11
- MTgwYzJjOWU1Mjk0MGNmOTczOGNiZDY4NzU4ZTBjMmI5MTQ2ODM=
9
+ ZTkyYjE0ODNmZDcxYWVlNDcwZDNhODUwOTUyMGY0ODc0ODkwZTY5NGUwMzhk
10
+ M2E2YTUxOGFiNTcyYjk2YmYxOTYzOGJkYWExZmZhODRmZTc2Yzg5MDRhNmQz
11
+ OGY5ZTc5YTY3YTgyMzRhNWI0YTlkNTc0M2IwZjhlOWZhMDI1NDE=
12
12
  data.tar.gz: !binary |-
13
- MTNkNGI2ZmU2ZmI4MzRmYjdiZDkzZDQ5MzU4MDBkNDY0NjRlMTQ5OTdhNDUz
14
- MjFlM2E1OGNlOTMxYjQyNWM4MmI3OTY4Yzg1NzNjOGNhNWU0M2ZhZjRjYmFm
15
- ZjY0Yjc4YzU0NzA4MDkyNGQ5ZmExY2MxMDA0ZTc3OTY1NjkwZTE=
13
+ NjQzMzU2ZDFkNGEzYjNjYjA0YmRlYzQ1MjYzNGViODU1NDQ1YjE0YTZkMjBl
14
+ MjJhMTY5MTFlMDVjZDJiMTZiMDAzYjhlNzVmNDk0ZGFmNDU0ZGY2MGU0NjQ4
15
+ MmFiNjJlNWNjNjFmZTZlNmI1YzE1OTc3MWE5Yzg0YmFjM2I5NTM=
data/README.md CHANGED
@@ -1,4 +1,264 @@
1
1
  rightscale_selfservice
2
2
  ======================
3
3
 
4
- A rubygem with a buncha useful CLI bits for RightScale Self Service, including a test harness for Cloud Application Templates
4
+ A rubygem with a self service [API client](#api-client), and buncha useful [CLI](#cli) bits for
5
+ RightScale Self Service, including a test harness for Cloud Application Templates
6
+
7
+ Travis Build Status: [<img src="https://travis-ci.org/rgeyer/rightscale_selfservice.png" />](https://travis-ci.org/rgeyer/rightscale_selfservice)
8
+
9
+ ## Quick Start
10
+
11
+ ```
12
+ gem install rightscale_selfservice
13
+ ```
14
+
15
+ Setup some [Authentication](#authentication) details
16
+
17
+ Explore the [CLI](#cli).
18
+ ```
19
+ rightscale_selfservice help
20
+ ```
21
+
22
+ Explore the [API Client](#api-client).
23
+
24
+ ## Authentication
25
+
26
+ rightscale_selfservice can authenticate with the APIs using (roughly) the same
27
+ properties as [right_api_client](https://github.com/rightscale/right_api_client).
28
+
29
+ The only additional required property is "selfservice_url".
30
+
31
+ The CLI can use a \*.yml file containing those properties or have them passed in
32
+ on the commandline. The API Client accepts an input hash with these same
33
+ properties.
34
+
35
+ An example \*.yml file can be found [here](https://github.com/rightscale/right_api_client/blob/v1.5.24/config/login.yml.example)
36
+
37
+ A working \*.yml file might look like;
38
+ ```
39
+ :account_id: 12345
40
+ :email: user@domain.com
41
+ :password: password
42
+ :api_url: https://us-4.rightscale.com
43
+ :selfservice_url: https://selfservice-4.rightscale.com
44
+ ```
45
+
46
+ ## CLI
47
+
48
+ ### Template Includes
49
+ For any of the template commands, the template will be preprocessed
50
+ to replace any #include:/path/to/another/cat/file with the contents of that file.
51
+
52
+ This allows for shared libraries to be built and stored along side your CATs.
53
+
54
+ Example:
55
+
56
+ Main template
57
+ ```
58
+ name 'cat-with-includes'
59
+ rs_ca_ver 20131202
60
+ short_description 'has some includes'
61
+
62
+ #include:../definitions/foo.cat.rb
63
+ ```
64
+
65
+ foo.cat.rb
66
+ ```
67
+ define foo() return @clouds do
68
+ @clouds = rs.clouds.get()
69
+ end
70
+ ```
71
+
72
+ Results in
73
+ ```
74
+ name 'cat-with-includes'
75
+ rs_ca_ver 20131202
76
+ short_description 'has some includes'
77
+
78
+ ###############################################################################
79
+ # BEGIN Include from ../definitions/foo.cat.rb
80
+ ###############################################################################
81
+ define foo() return @clouds do
82
+ @clouds = rs.clouds.get()
83
+ end
84
+ ###############################################################################
85
+ # END Include from ../definitions/foo.cat.rb
86
+ ###############################################################################
87
+ ```
88
+
89
+ You can simply run the preprocessor on your CAT and get a file with all the
90
+ appropriate includes with the "preprocess" command.
91
+
92
+ ```
93
+ rightscale_selfservice template preprocess ~/Code/cat/somecat.cat.rb -o /tmp/processedcat.cat.rb
94
+ ```
95
+
96
+ Then maybe check if all your syntax is good by using the API to "compile" your CAT
97
+
98
+ ```
99
+ rightscale_selfservice template compile ~/Code/cat/somecat.cat.rb --auth-file=~./.right_api_client/login.yml
100
+ ```
101
+
102
+ If that works, maybe start up a Cloud App from it
103
+
104
+ ```
105
+ rightscale_selfservice template execute ~/Code/cat/somecat.cat.rb --auth-file=~/.right_api_client/login.yml
106
+ ```
107
+
108
+ ## API Client
109
+
110
+ The [CLI](#cli) bits consume the API client which is deployed with this gem. The
111
+ goal of this API client is to be as "dumb" and lightweight as is practical.
112
+
113
+ In practical terms this means that the client doesn't make assumptions about the
114
+ return values from the API, and does not attempt to turn them into ruby objects.
115
+
116
+ In fact, the default "language" of this client is
117
+ [Rest Client](http://rubygems.org/gems/rest-client) requests and responses.
118
+
119
+ The API client does handle some of the more mundane things for you though, such
120
+ as authenticating with the API using various means (access_token, refresh_token,
121
+ email & password), and assembling the correct URL for a particular resource
122
+ action.
123
+
124
+ ### Supported Services, Resources and Actions
125
+ The client fetches metadata about the SelfService API using a Rake task which
126
+ generates a JSON file which is deployed with the client/gem. This means that
127
+ a given version of the client supports the API interface as it existed at the
128
+ time that version of the client/gem was created.
129
+
130
+ Fear not, supporting the latest functionality is as simple as.
131
+
132
+ ```
133
+ rake update_inteface_json
134
+ # Bump the gemspec
135
+ rake gem
136
+ gem push pkg/<new gem file>
137
+ ```
138
+
139
+ ### How it works
140
+ You can access a specific action with the following syntax.
141
+
142
+ ```
143
+ client.<service_name>(<optional_service_version>).<resource_name>.<action_name>(<optional_params_hash>,<optional_boolean>)
144
+ ```
145
+
146
+ Where;
147
+ * service_name is one of the (currently 3) services listed the [docs](http://support.rightscale.com/12-Guides/Self-Service#Self-Service_API)
148
+ * optional_service_version is a string version of the service
149
+ * resource_name is the resource you'd like to operate on (E.G. template, execution, operation, etc.)
150
+ * action_name is the action you'd like to take (index, show, create, etc.)
151
+ * optional_params_hash is the parameters you wish to pass to that action
152
+ * optional_boolean if not supplied, a RestClient::Request will be created and
153
+ executed and a RestClient::Response will be returned. If supplied and true
154
+ a ready to execute RestClient::Request will be returned
155
+
156
+ #### Href Tokens
157
+ If the Href for a particular action contains tokens, any parameter passed to the
158
+ action with the same name as that token will be substituted in the request.
159
+
160
+ Take a [show](#show-a-template) action on the template resource for example.
161
+
162
+ The Href is;
163
+ ```
164
+ /collections/:collection_id/templates/:id
165
+ ```
166
+
167
+ Which contains the token :id. In order to specify the template id while calling
168
+ the action, put it in as a parameter.
169
+
170
+ ```
171
+ client.designer.templates.show(:id => "abc123")
172
+ ```
173
+
174
+ The Href will have :id substituted with "abc123", and the id parameter will be
175
+ stripped from the body of the request.
176
+
177
+ #### Automatic Multipart
178
+ The client automatically URL encodes parameters and puts them in the body. But
179
+ if you pass any Ruby object which has the methods "path" and "read" (basically
180
+ any file or IO resource) it'll assume you're performing a multipart request
181
+ and build the RestClient::Request accordingly.
182
+
183
+ See the [Multipart Detection](#multipart-detection) example..
184
+
185
+ ### Some examples...
186
+
187
+ #### Authentication
188
+ Create the client with email and password
189
+ ```
190
+ client = RightScaleSelfService::Client.new(
191
+ :email => "user@domain.com",
192
+ :password => "password",
193
+ :selfservice_url => "https://selfservice-4.rightscale.com",
194
+ :api_url => "https://us-4.rightscale.com"
195
+ )
196
+ ```
197
+
198
+ Or with an access token
199
+ ```
200
+ client = RightScaleSelfService::Client.new(
201
+ :access_token => "access_token",
202
+ :selfservice_url => "https://selfservice-4.rightscale.com",
203
+ :api_url => "https://us-4.rightscale.com"
204
+ )
205
+ ```
206
+
207
+ Or with a refresh token
208
+ ```
209
+ client = RightScaleSelfService::Client.new(
210
+ :refresh_token => "refresh_token",
211
+ :selfservice_url => "https://selfservice-4.rightscale.com",
212
+ :api_url => "https://us-4.rightscale.com"
213
+ )
214
+ ```
215
+
216
+ #### Service Versions
217
+ You can specify which version of a service you want. If you don't the "newest"
218
+ version will be used.
219
+
220
+ Get version 1.1 (which doesn't presently exist) of the "manager" service
221
+ ```
222
+ client.manager("1.1")
223
+ ```
224
+
225
+ Just use the latest version of the "manager" service
226
+ ```
227
+ client.manager
228
+ ```
229
+
230
+ #### Executing Actions
231
+
232
+ ##### List operations
233
+ ```
234
+ client.manager.operation.index
235
+ ```
236
+
237
+ ##### Show a template
238
+ ```
239
+ client.designer.template.show(:id => "abc123")
240
+ ```
241
+
242
+ ##### Get RestClient::Request Instead of Executing Action
243
+ ```
244
+ request = client.catalog.application.index({}, true)
245
+ request.execute
246
+ ```
247
+
248
+ ##### Format Errors
249
+ ```
250
+ begin
251
+ client.designer.template.compile(:source => some_source_file)
252
+ rescue RestClient::ExceptionWithResponse => e
253
+ puts "Failed to compile the template"
254
+ puts RightScaleSelfService::Api::Client.format_error(e)
255
+ end
256
+ ```
257
+
258
+ ##### Multipart Detection
259
+ This'll automatically format the payload for RestClient::Request as a multipart
260
+ request, rather than a standard request with a URL encoded body.
261
+ ```
262
+ file = File.open("some.cat.rb", "rb")
263
+ client.designer.template.create(:source => file)
264
+ ```
@@ -21,8 +21,27 @@ require 'rest-client'
21
21
 
22
22
  module RightScaleSelfService
23
23
  module Api
24
+ # @!attribute [rw] selfservice_url
25
+ # @return [String] URL to use for Self Service API requests.
26
+ # I.E. https://selfservice-4.rightscale.com
27
+ # @!attribute [rw] api_url
28
+ # @return [String] URL to use for Cloud Management API requests. Only
29
+ # used once to authenticate.
30
+ # @!attribute [rw] logger
31
+ # @return [Logger] A logger which will be used, mostly for debug
32
+ # purposes
33
+ # @!attribute [rw] account_id
34
+ # @return [String] A RightScale account id
35
+ # @!attribute [r] interface
36
+ # @return [Hash] interface A hash containing details about the services,
37
+ # resources, and actions available in this client.
24
38
  class Client
25
39
 
40
+ # A list of tokens which might appear in hrefs which need to be replaced
41
+ # with the RightScale account_id. This will likely change over time and
42
+ # some of these will likely go away or change meaning
43
+ #
44
+ # @return [Array<String>]
26
45
  def self.get_known_account_id_tokens
27
46
  [":account_id",":catalog_id",":collection_id",":project_id"]
28
47
  end
@@ -37,20 +56,24 @@ module RightScaleSelfService
37
56
 
38
57
  attr_reader :interface
39
58
 
59
+
60
+ # @param params [Hash] a hash of parameters where the possible values are
61
+ # * account_id [String] (required) A RightScale account id
62
+ # * selfservice_url [String] (required) URL to use for Self Service API
63
+ # requests. I.E. https://selfservice-4.rightscale.com
64
+ # * api_url [String] (required) URL to use for Cloud Management API
65
+ # requests. Only used once to authenticate.
66
+ # I.E. https://us-4.rightscale.com
67
+ # * access_token [String] A RightScale API OAuth Access Token
68
+ # * refresh_token [String] A RightScale API OAuth Refresh Token, which
69
+ # will be exchanged for an access token
70
+ # * email [String] A RightScale user email address
71
+ # * password [String] A RightScale user password
72
+ # * logger [Logger] A logger which will be used, mostly for debug purposes
40
73
  def initialize(params)
41
74
  @services = {}
42
75
  @auth = {"cookie" => {}, "authorization" => {}}
43
76
  required_params = [:account_id,:selfservice_url,:api_url]
44
- # allowed_params = [
45
- # :access_token,
46
- # :refresh_token,
47
- # :account_id,
48
- # :selfservice_url,
49
- # :api_url,
50
- # :email,
51
- # :password,
52
- # :logger
53
- # ]
54
77
 
55
78
  # Use the defined logger, or log to a blackhole
56
79
  if params.include?(:logger)
@@ -143,6 +166,15 @@ module RightScaleSelfService
143
166
  end
144
167
  end
145
168
 
169
+ # Accepts request parameters and returns a Rest Client Request which has
170
+ # necessary authentication details appended.
171
+ #
172
+ # @param request_params [Hash] A hash of params to be passed to
173
+ # RestClient::Request.new after it has had API authentication details
174
+ # injected
175
+ #
176
+ # @return [RestClient::Request] A request which is ready to be executed
177
+ # cause it's got necessary authentication details
146
178
  def get_authorized_rest_client_request(request_params)
147
179
  if @auth["cookie"].length > 0
148
180
  request_params[:cookies] = @auth["cookie"]
@@ -159,6 +191,18 @@ module RightScaleSelfService
159
191
  RestClient::Request.new(request_params)
160
192
  end
161
193
 
194
+ # Returns a service of the specified (or newest) version
195
+ #
196
+ # @param name [String] The name of the desired service
197
+ #
198
+ # @return [RightScaleSelfService::Api::Service]
199
+ #
200
+ # @example Get latest version (1.0) of designer service
201
+ # service = client.designer
202
+ # service.version #=> "1.0"
203
+ # @example Get specified version of designer service
204
+ # service = client.designer("1.1")
205
+ # service.version #=> "1.1"
162
206
  def method_missing(name, *args)
163
207
  unless interface["services"].has_key?(name.to_s)
164
208
  raise "No service named \"#{name}\" can not be found. Available services are [#{interface["services"].keys.join(',')}]"
@@ -183,6 +227,40 @@ module RightScaleSelfService
183
227
  @services[service_hash_key] = service
184
228
  end
185
229
  end
230
+
231
+ # Converts the input param to a relative href.
232
+ # I.E. /api/service/:account_id/resource
233
+ #
234
+ # @param url [String] The full url to get the relative href from
235
+ #
236
+ # @return [String] A relative href
237
+ def get_relative_href(url)
238
+ url.gsub!(@selfservice_url,"")
239
+ end
240
+
241
+ # Accepts various possible responses and formats it into useful error text
242
+ #
243
+ # @param [RestClient::ExceptionWithResponse] error The response or error
244
+ # to format
245
+ def self.format_error(error)
246
+ formatted_text = ""
247
+ if error
248
+ if error.is_a?(RestClient::ExceptionWithResponse)
249
+ formatted_text = "HTTP Response Code: #{error.http_code}\nMessage:\n"
250
+ if error.response.headers[:content_type] == "application/json"
251
+ formatted_text += JSON.pretty_generate(
252
+ JSON.parse(error.response.body)
253
+ ).gsub('\n',"\n")
254
+ else
255
+ formatted_text += error.response.body
256
+ end
257
+ end
258
+ else
259
+ formatted_text = "Nothing supplied for formatting"
260
+ end
261
+ formatted_text
262
+ end
263
+
186
264
  end
187
265
  end
188
266
  end
@@ -56,7 +56,15 @@ module RightScaleSelfService
56
56
  end
57
57
 
58
58
  if args[0].length > 0
59
- params[:payload] = URI.encode_www_form(args[0])
59
+ # Detect if a param is a file, using the same mechanism as
60
+ # rest-client
61
+ #
62
+ # https://github.com/rest-client/rest-client/blob/master/lib/restclient/payload.rb#L33
63
+ if args[0].select{|k,v| v.respond_to?(:path) && v.respond_to?(:read) }.length > 0
64
+ params[:payload] = args[0]
65
+ else
66
+ params[:payload] = URI.encode_www_form(args[0])
67
+ end
60
68
  end
61
69
  end
62
70
 
@@ -46,6 +46,10 @@ EOF
46
46
 
47
47
  RightScaleSelfService::Api::Client.new(client_auth_params)
48
48
  end
49
+
50
+ def logger()
51
+ Logger.new(STDOUT)
52
+ end
49
53
  }
50
54
  end
51
55
  end
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2014 Ryan Geyer
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ # this software and associated documentation files (the "Software"), to deal in
5
+ # the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ # the Software, and to permit persons to whom the Software is furnished to do so,
8
+ # subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # 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, FITNESS
15
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ # Require base.rb first, to satisfy Travis CI
21
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
22
+
23
+ module RightScaleSelfService
24
+ module Cli
25
+ class Execution < Base
26
+ desc "list", "List all executions (CloudApps)"
27
+ option :property, :type => :array, :desc => "When supplied, only the specified properties will be displayed. By default the entire response is supplied."
28
+ def list
29
+ client = get_api_client()
30
+
31
+ begin
32
+ response = client.manager.execution.index
33
+ executions = JSON.parse(response.body)
34
+ if @options["property"]
35
+ executions.each do |execution|
36
+ execution.delete_if{|k,v| !(@options["property"].include?(k))}
37
+ end
38
+ end
39
+ puts JSON.pretty_generate(executions)
40
+ rescue RestClient::ExceptionWithResponse => e
41
+ shell = Thor::Shell::Color.new
42
+ message = "Failed to list executions\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
43
+ logger.error(shell.set_color message, :red)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -17,13 +17,22 @@
17
17
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
18
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
19
 
20
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base.rb'))
21
+ require File.expand_path(File.join(File.dirname(__FILE__), 'execution.rb'))
20
22
  require File.expand_path(File.join(File.dirname(__FILE__), 'template.rb'))
23
+ require File.expand_path(File.join(File.dirname(__FILE__), 'operation.rb'))
21
24
 
22
25
  module RightScaleSelfService
23
26
  module Cli
24
27
  class Main < Base
28
+ desc "execution", "Self Service Execution Commands"
29
+ subcommand "execution", Execution
30
+
25
31
  desc "template", "Self Service Template Commands"
26
32
  subcommand "template", Template
33
+
34
+ desc "operation", "Self Service Operation Commands"
35
+ subcommand "operation", Operation
27
36
  end
28
37
  end
29
38
  end
@@ -0,0 +1,49 @@
1
+ # Copyright (c) 2014 Ryan Geyer
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ # this software and associated documentation files (the "Software"), to deal in
5
+ # the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ # the Software, and to permit persons to whom the Software is furnished to do so,
8
+ # subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # 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, FITNESS
15
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ # Require base.rb first, to satisfy Travis CI
21
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
22
+
23
+ module RightScaleSelfService
24
+ module Cli
25
+ class Operation < Base
26
+ desc "create <operation_name> <execution_id_or_href>", "Creates a new operation with the name <operation_name> on the execution specified by <execution_id_or_href>. Optionally pass input parameters using --options-file."
27
+ option :options_file, :type => :string, :desc => "A filepath to a JSON file containing data which will be passed into the \"options\" parameter of the API call."
28
+ def create(operation_name, execution_id_or_href)
29
+ execution_id = execution_id_or_href.split("/").last
30
+ client = get_api_client()
31
+ params = {:execution_id => execution_id, :name => operation_name}
32
+ if @options["options_file"]
33
+ options_filepath = File.expand_path(@options["options_file"], Dir.pwd)
34
+ options_str = File.open(File.expand_path(options_filepath), 'r') { |f| f.read }
35
+ params["options"] = options_str
36
+ end
37
+
38
+ begin
39
+ response = client.manager.operation.create(params)
40
+ logger.info("Successfully started operation \"#{operation_name}\" on execution id \"#{execution_id}\". Href: #{response.headers[:location]}")
41
+ rescue RestClient::ExceptionWithResponse => e
42
+ shell = Thor::Shell::Color.new
43
+ message = "Failed to create operation \"#{operation_name}\" on execution id \"#{execution_id}\"\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
44
+ logger.error(shell.set_color message, :red)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -17,6 +17,9 @@
17
17
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
18
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
19
 
20
+ # Require base.rb first, to satisfy Travis CI
21
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
22
+
20
23
  module RightScaleSelfService
21
24
  module Cli
22
25
  class Template < Base
@@ -27,8 +30,12 @@ module RightScaleSelfService
27
30
  source_filename = File.basename(source_filepath)
28
31
  source_dir = File.dirname(source_filepath)
29
32
  dest_filepath = @options.has_key?('o') ? File.expand_path(@options['o'], Dir.pwd) : File.join(source_dir, "processed-#{source_filename}")
33
+
34
+ logger.info("Preprocessing #{source_filepath} and writing result to #{dest_filepath}")
35
+
30
36
  result = RightScaleSelfService::Utilities::Template.preprocess(source_filepath)
31
37
  File.open(dest_filepath, 'w') {|f| f.write(result)}
38
+ logger.info("Done! Find your file at #{dest_filepath}")
32
39
  end
33
40
 
34
41
  desc "compile <filepath>", "Uploads <filepath> to SS, validating the syntax. Will report errors if any are found."
@@ -38,8 +45,142 @@ module RightScaleSelfService
38
45
  source_dir = File.dirname(source_filepath)
39
46
  result = RightScaleSelfService::Utilities::Template.preprocess(source_filepath)
40
47
  client = get_api_client()
41
- client.designer.template.compile(:source => result)
48
+ logger.info("Uploading #{source_filepath} to validate syntax")
49
+ begin
50
+ client.designer.template.compile(:source => result)
51
+ logger.info("#{source_filepath} compiled successfully!")
52
+ rescue RestClient::ExceptionWithResponse => e
53
+ shell = Thor::Shell::Color.new
54
+ message = "Failed to compile template\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
55
+ logger.error(shell.set_color message, :red)
56
+ end
57
+ end
58
+
59
+ desc "upsert <filepath>", "Upload <filepath> to SS as a new template or updates an existing one (based on name)"
60
+ def upsert(filepath)
61
+ template_href = ""
62
+ source_filepath = File.expand_path(filepath, Dir.pwd)
63
+ source_filename = File.basename(source_filepath)
64
+ source_dir = File.dirname(source_filepath)
65
+ template = RightScaleSelfService::Utilities::Template.preprocess(source_filepath)
66
+ client = get_api_client()
67
+
68
+ matches = template.match(/^name\s*"(?<name>.*)"/)
69
+ tmp_file = matches["name"].gsub("/","-").gsub(" ","-")
70
+ name = matches["name"]
71
+
72
+ logger.info("Fetching a list of existing templates to see if \"#{name}\" exists...")
73
+
74
+ templates = JSON.parse(client.designer.template.index.body)
75
+ existing_templates = templates.select{|t| t["name"] == name }
76
+
77
+ tmpfile = Tempfile.new([tmp_file,".cat.rb"])
78
+ begin
79
+ tmpfile.write(template)
80
+ tmpfile.rewind
81
+ if existing_templates.length != 0
82
+ logger.info("A template named \"#{name}\" already exists, updating it...")
83
+ template_id = existing_templates.first()["id"]
84
+ request = client.designer.template.update({:id => template_id, :source => tmpfile}, true)
85
+ response = request.execute
86
+ template_href = client.get_relative_href(request.url)
87
+ else
88
+ logger.info("Creating template \"#{name}\"...")
89
+ response = client.designer.template.create({:source => tmpfile})
90
+ template_href = response.headers[:location]
91
+ end
92
+ logger.info("Successfully upserted \"#{name}\". Href: #{template_href}")
93
+ rescue RestClient::ExceptionWithResponse => e
94
+ shell = Thor::Shell::Color.new
95
+ message = "Failed to update or create template\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
96
+ logger.error(shell.set_color message, :red)
97
+ ensure
98
+ tmpfile.close!()
99
+ end
100
+ template_href
101
+ end
102
+
103
+ desc "publish <filepath>", "Update and publish a template (based on name)"
104
+ option :override, :type => :boolean, :default => false, :desc => "When supplied the template will be published even if it already exists in the catalog. False by default, so an error will be raised if the application already exists."
105
+ def publish(filepath)
106
+ shell = Thor::Shell::Color.new
107
+ template_href = upsert(filepath)
108
+ template_id = template_href.split("/").last
109
+ client = get_api_client()
110
+ logger.info("Publishing template Href: #{template_href}")
111
+ publish_params = {:id => template_id}
112
+ begin
113
+ response = client.designer.template.publish(publish_params)
114
+ logger.info("Successfully published template.")
115
+ rescue RestClient::ExceptionWithResponse => e
116
+ if e.http_code == 409 && @options["override"]
117
+ logger.warn("Template id \"#{template_id}\" has already been published, but --override was set so we'll try to publish again with the overridden_application_href parameter.")
118
+ begin
119
+ app_response = client.catalog.application.index()
120
+ applications = JSON.parse(app_response.body)
121
+ matching_apps = applications.select{|a| a["template_info"]["href"] == template_href}
122
+ if matching_apps == 0
123
+ logger.error(shell.set_color "Unable to find the published application for template id \"#{template_id}\"")
124
+ else
125
+ publish_params[:overridden_application_href] = matching_apps.first["href"]
126
+ retry
127
+ end
128
+ rescue RestClient::ExceptionWithResponse => e
129
+ message = "Failed to get a list of existing published templates\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
130
+ logger.error(shell.set_color message, :red)
131
+ end
132
+ else
133
+ message = "Failed to publish template\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
134
+ logger.error(shell.set_color message, :red)
135
+ end
136
+ end
137
+ end
138
+
139
+ desc "list", "Lists all templates"
140
+ option :property, :type => :array, :desc => "When supplied, only the specified properties will be displayed. By default the entire response is supplied."
141
+ def list
142
+ client = get_api_client()
143
+ begin
144
+ list_response = client.designer.template.index()
145
+ templates = JSON.parse(list_response.body)
146
+ if @options["property"]
147
+ templates.each do |template|
148
+ template.delete_if{|k,v| !(@options["property"].include?(k))}
149
+ end
150
+ end
151
+ puts JSON.pretty_generate(templates)
152
+ rescue RestClient::ExceptionWithResponse => e
153
+ shell = Thor::Shell::Color.new
154
+ message = "Failed to list templates\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
155
+ logger.error(shell.set_color message, :red)
156
+ end
157
+ end
158
+
159
+ desc "execute <filepath>", "Create a new execution (CloudApp) from a template. Optionally supply parameter values"
160
+ option :options_file, :type => :string, :desc => "A filepath to a JSON file containing data which will be passed into the \"options\" parameter of the API call."
161
+ def execute(filepath)
162
+ source_filepath = File.expand_path(filepath, Dir.pwd)
163
+ source_filename = File.basename(source_filepath)
164
+ source_dir = File.dirname(source_filepath)
165
+ result = RightScaleSelfService::Utilities::Template.preprocess(source_filepath)
166
+ client = get_api_client()
167
+ params = {:source => result}
168
+ if @options["options_file"]
169
+ options_filepath = File.expand_path(@options["options_file"], Dir.pwd)
170
+ options_str = File.open(File.expand_path(options_filepath), 'r') { |f| f.read }
171
+ params["options"] = options_str
172
+ end
173
+
174
+ begin
175
+ exec_response = client.manager.execution.create(params)
176
+ logger.info("Successfully started execution. Href: #{exec_response.headers[:location]}")
177
+ rescue RestClient::ExceptionWithResponse => e
178
+ shell = Thor::Shell::Color.new
179
+ message = "Failed to create execution from template\n\n#{RightScaleSelfService::Api::Client.format_error(e)}"
180
+ logger.error(shell.set_color message, :red)
181
+ end
42
182
  end
183
+
43
184
  end
44
185
  end
45
186
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rightscale_selfservice
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan J. Geyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-10 00:00:00.000000000 Z
11
+ date: 2014-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -69,7 +69,9 @@ files:
69
69
  - lib/rightscale_selfservice/api/resource.rb
70
70
  - lib/rightscale_selfservice/api/service.rb
71
71
  - lib/rightscale_selfservice/cli/base.rb
72
+ - lib/rightscale_selfservice/cli/execution.rb
72
73
  - lib/rightscale_selfservice/cli/main.rb
74
+ - lib/rightscale_selfservice/cli/operation.rb
73
75
  - lib/rightscale_selfservice/cli/template.rb
74
76
  - lib/rightscale_selfservice/utilities/template.rb
75
77
  homepage: https://github.com/rgeyer/rightscale_selfservice