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 +8 -8
- data/README.md +261 -1
- data/lib/rightscale_selfservice/api/client.rb +88 -10
- data/lib/rightscale_selfservice/api/resource.rb +9 -1
- data/lib/rightscale_selfservice/cli/base.rb +4 -0
- data/lib/rightscale_selfservice/cli/execution.rb +48 -0
- data/lib/rightscale_selfservice/cli/main.rb +9 -0
- data/lib/rightscale_selfservice/cli/operation.rb +49 -0
- data/lib/rightscale_selfservice/cli/template.rb +142 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YWNkZWUzNjgzZThhOWQ3MTEwNTY4N2IyYzA4NGQxOTU5NzVjNjYwOA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZjI4NGY5ZjYxM2JjMjQ0MzZlMDU2NDJmYjVlMWYxYWE2OWJhMDY0NA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZTkyYjE0ODNmZDcxYWVlNDcwZDNhODUwOTUyMGY0ODc0ODkwZTY5NGUwMzhk
|
10
|
+
M2E2YTUxOGFiNTcyYjk2YmYxOTYzOGJkYWExZmZhODRmZTc2Yzg5MDRhNmQz
|
11
|
+
OGY5ZTc5YTY3YTgyMzRhNWI0YTlkNTc0M2IwZjhlOWZhMDI1NDE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
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
|
-
|
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
|
|
@@ -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
|
-
|
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.
|
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-
|
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
|