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 +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
|