rightscale_selfservice 0.0.1 → 0.0.2

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