miasma-google 0.1.0
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 +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE +0 -0
- data/README.md +46 -0
- data/lib/miasma-google/version.rb +3 -0
- data/lib/miasma/contrib/google.rb +261 -0
- data/lib/miasma/contrib/google/orchestration.rb +338 -0
- data/miasma-google.gemspec +21 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e2d2cab6950ca96ad4fb8c6a3d190bbb3a43dab3
|
4
|
+
data.tar.gz: 25e2c4132fc3a15959b1ea3fc154c0bbe84f8dc2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 80b7edcbb1da6434ed0fc57b35c16d9b6b5b75066255c4142a2f2582939bfc4522bbc218a7a0595e842b07f296c5fb2529372cd49da76c744d84b43758ff37dd
|
7
|
+
data.tar.gz: e87bde17346316b5e81f2dcf2b952f899a58eb31f21746dd71918971d3a151b74f51937ee46f28494c12472d9d600d46535bf60dee33bc26a7b7eb33005ff48d
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Miasma Google
|
2
|
+
|
3
|
+
Google API plugin for the miasma cloud library
|
4
|
+
|
5
|
+
## Supported credential attributes:
|
6
|
+
|
7
|
+
Supported attributes used in the credentials section of API
|
8
|
+
configurations:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
Miasma.api(
|
12
|
+
:type => :orchestration,
|
13
|
+
:provider => :google,
|
14
|
+
:credentials => {
|
15
|
+
...
|
16
|
+
}
|
17
|
+
)
|
18
|
+
```
|
19
|
+
|
20
|
+
### Credential attributes
|
21
|
+
|
22
|
+
` `google_project` - ID of the google project to use
|
23
|
+
* `google_service_account_email` - Email address for service account
|
24
|
+
* `google_service_account_private_key` - Path to private key for service account
|
25
|
+
* `google_auth_scope` - Scope requested for user (default: `'cloud-platform'`)
|
26
|
+
* `google_auth_base` - URL endpoint for authorization (default: `'https://www.googleapis.com/auth'`)
|
27
|
+
* `google_assertion_target` - URL for permission assertion (default: `'https://www.googleapis.com/oauth2/v4/token'`)
|
28
|
+
* `google_assertion_expiry` - Number of seconds token is valid (default: `120`)
|
29
|
+
* `google_api_base_endpoint` - URL for requests (default: `'https://www.googleapis.com'`)
|
30
|
+
|
31
|
+
## Current support matrix
|
32
|
+
|
33
|
+
|Model |Create|Read|Update|Delete|
|
34
|
+
|--------------|------|----|------|------|
|
35
|
+
|AutoScale | | | | |
|
36
|
+
|BlockStorage | | | | |
|
37
|
+
|Compute | | | | |
|
38
|
+
|DNS | | | | |
|
39
|
+
|LoadBalancer | | | | |
|
40
|
+
|Network | | | | |
|
41
|
+
|Orchestration | X | X | X | X |
|
42
|
+
|Queues | | | | |
|
43
|
+
|Storage | | | | |
|
44
|
+
|
45
|
+
## Info
|
46
|
+
* Repository: https://github.com/miasma-rb/miasma-google
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
require 'base64'
|
3
|
+
require 'digest/sha2'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
module Miasma
|
7
|
+
|
8
|
+
module Contrib
|
9
|
+
|
10
|
+
module Google
|
11
|
+
|
12
|
+
# Base signature class
|
13
|
+
class Signature
|
14
|
+
|
15
|
+
# @return [String] algorithm of signature
|
16
|
+
attr_reader :algorithm
|
17
|
+
# @return [String] format of signature
|
18
|
+
attr_reader :format
|
19
|
+
# @return [Smash] signature claims
|
20
|
+
attr_reader :claims
|
21
|
+
|
22
|
+
# Create a new signature
|
23
|
+
#
|
24
|
+
# @param [String, Symbol] algorithm used for signature
|
25
|
+
# @param [String, Symbol] format of signature
|
26
|
+
# @param claims [Hash] request claims
|
27
|
+
# @return [self]
|
28
|
+
def initialize(algo, fmt, clms)
|
29
|
+
@algorithm = algo
|
30
|
+
@format = fmt
|
31
|
+
@claims = clms.to_smash
|
32
|
+
end
|
33
|
+
|
34
|
+
# Generate signature
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
def generate
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
# JSON Web Token signature
|
42
|
+
class Jwt < Signature
|
43
|
+
|
44
|
+
# Required items within claims
|
45
|
+
REQUIRED_CLAIMS = [
|
46
|
+
:iss, # email address of service account
|
47
|
+
:scope, # space-delimited list of permissions requested
|
48
|
+
:aud, # intended target of assertion
|
49
|
+
:exp, # expiration time of assertion
|
50
|
+
:iat # time assertion was issued
|
51
|
+
]
|
52
|
+
|
53
|
+
# Create a new JWT signature instance
|
54
|
+
#
|
55
|
+
# @param private_key_path [String] private signing key path
|
56
|
+
# @param claims [Hash] request claims
|
57
|
+
# @return [self]
|
58
|
+
def initialize(private_key_path, i_claims)
|
59
|
+
super('RS256', 'JWT', i_claims)
|
60
|
+
claims[:iat] ||= Time.now.to_i
|
61
|
+
claims[:exp] ||= Time.now.to_i + 120
|
62
|
+
@private_key = private_key_path
|
63
|
+
validate_claims!
|
64
|
+
validate_key!
|
65
|
+
end
|
66
|
+
|
67
|
+
# Generate signature
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
def generate
|
71
|
+
"#{encoded_header}.#{encoded_claims}.#{encoded_signature}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [String] encoded header
|
75
|
+
def encoded_header
|
76
|
+
Base64.urlsafe_encode64(header.to_json)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [String] header
|
80
|
+
def header
|
81
|
+
Smash.new(
|
82
|
+
:alg => algorithm,
|
83
|
+
:typ => format
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String] encoded claims set
|
88
|
+
def encoded_claims
|
89
|
+
t_claims = claims.to_smash
|
90
|
+
if(t_claims.key?(:scope))
|
91
|
+
t_claims[:scope] = [t_claims[:scope]].flatten.compact.join(' ')
|
92
|
+
end
|
93
|
+
Base64.urlsafe_encode64(t_claims.to_json)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [String] encoded signature
|
97
|
+
def encoded_signature
|
98
|
+
Base64.urlsafe_encode64(signature)
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [String] JWT signature
|
102
|
+
def signature
|
103
|
+
token = "#{encoded_header}.#{encoded_claims}"
|
104
|
+
hasher = OpenSSL::Digest::SHA256.new
|
105
|
+
author = OpenSSL::PKey::RSA.new(File.read(@private_key))
|
106
|
+
author.sign(hasher, token)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Check for required claims and raise error if unset
|
110
|
+
#
|
111
|
+
# @return [TrueClass]
|
112
|
+
# @raises [KeyError]
|
113
|
+
def validate_claims!
|
114
|
+
REQUIRED_CLAIMS.each do |claim|
|
115
|
+
unless(claims.key?(claim))
|
116
|
+
raise KeyError.new "Missing required claim key `#{claim}`"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
# Check that the private key exists, is readable, and is
|
123
|
+
def validate_key!
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
module ApiCommon
|
131
|
+
|
132
|
+
def self.included(klass)
|
133
|
+
klass.class_eval do
|
134
|
+
attribute :google_service_account_email, String, :required => true
|
135
|
+
attribute :google_service_account_private_key, String, :required => true
|
136
|
+
attribute :google_auth_scope, String, :required => true, :multiple => true, :default => 'cloud-platform'
|
137
|
+
attribute :google_auth_base, String, :default => 'https://www.googleapis.com/auth'
|
138
|
+
attribute :google_assertion_target, String, :required => true, :default => 'https://www.googleapis.com/oauth2/v4/token'
|
139
|
+
attribute :google_assertion_expiry, Integer, :required => true, :default => 120
|
140
|
+
attribute :google_project, String, :required => true
|
141
|
+
attribute :google_api_base_endpoint, String, :required => true, :default => 'https://www.googleapis.com'
|
142
|
+
end
|
143
|
+
|
144
|
+
klass.const_set(:TOKEN_GRANT_TYPE, 'urn:ietf:params:oauth:grant-type:jwt-bearer')
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [String]
|
148
|
+
def endpoint
|
149
|
+
point = google_api_base_endpoint.dup
|
150
|
+
if(self.class.const_defined?(:GOOGLE_SERVICE_PATH))
|
151
|
+
point << "/#{self.class.const_get(:GOOGLE_SERVICE_PATH)}"
|
152
|
+
end
|
153
|
+
if(self.class.const_defined?(:GOOGLE_SERVICE_PROJECT) && self.class.const_get(:GOOGLE_SERVICE_PROJECT))
|
154
|
+
point << "/projects/#{google_project}"
|
155
|
+
end
|
156
|
+
point
|
157
|
+
end
|
158
|
+
|
159
|
+
# Setup for API connections
|
160
|
+
def connect
|
161
|
+
@oauth_token_information = Smash.new
|
162
|
+
end
|
163
|
+
|
164
|
+
def oauth_token_information
|
165
|
+
@oauth_token_information
|
166
|
+
end
|
167
|
+
|
168
|
+
# @return [HTTP] connection for requests (forces headers)
|
169
|
+
def connection
|
170
|
+
super.headers(
|
171
|
+
'Authorization' => "Bearer #{client_access_token}"
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [Contrib::Google::Signature::Jwt]
|
176
|
+
def signer
|
177
|
+
Contrib::Google::Signature::Jwt.new(
|
178
|
+
google_service_account_private_key,
|
179
|
+
:iss => google_service_account_email,
|
180
|
+
:scope => [google_auth_scope].flatten.compact.map{|scope|
|
181
|
+
"#{google_auth_base}/#{scope}"
|
182
|
+
},
|
183
|
+
:aud => google_assertion_target,
|
184
|
+
:exp => Time.now.to_i + google_assertion_expiry
|
185
|
+
)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Request a new authentication token from the remote API
|
189
|
+
#
|
190
|
+
# @return [Smash] token information - :access_token, :token_type, :expires_in, :expires_on
|
191
|
+
def request_client_token
|
192
|
+
token_signer = signer
|
193
|
+
result = HTTP.post(
|
194
|
+
google_assertion_target,
|
195
|
+
:form => {
|
196
|
+
:grant_type => self.class.const_get(:TOKEN_GRANT_TYPE),
|
197
|
+
:assertion => token_signer.generate
|
198
|
+
}
|
199
|
+
)
|
200
|
+
unless(result.code == 200)
|
201
|
+
raise Miasma::Error::ApiError.new(
|
202
|
+
'Request for client authentication token failed',
|
203
|
+
:response => result
|
204
|
+
)
|
205
|
+
end
|
206
|
+
@oauth_token_information = MultiJson.load(result.body.to_s).to_smash
|
207
|
+
@oauth_token_information[:expires_on] = Time.at(
|
208
|
+
@oauth_token_information[:expires_in] + token_signer.claims[:iat].to_i
|
209
|
+
)
|
210
|
+
@oauth_token_information
|
211
|
+
end
|
212
|
+
|
213
|
+
# @return [String] auth token
|
214
|
+
def client_access_token
|
215
|
+
request_client_token if access_token_expired?
|
216
|
+
oauth_token_information[:access_token]
|
217
|
+
end
|
218
|
+
|
219
|
+
# @return [TrueClass, FalseClass]
|
220
|
+
def access_token_expired?
|
221
|
+
if(oauth_token_information[:expires_on])
|
222
|
+
oauth_token_information[:expires_on] < Time.now
|
223
|
+
else
|
224
|
+
true
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# When in debug mode, do not retry requests
|
229
|
+
#
|
230
|
+
# @return [TrueClass, FalseClass]
|
231
|
+
def retryable_allowed?(*_)
|
232
|
+
if(ENV['DEBUG'])
|
233
|
+
false
|
234
|
+
else
|
235
|
+
super
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Define when request should be retried
|
240
|
+
#
|
241
|
+
# @param exception [Exception]
|
242
|
+
# @return [TrueClass, FalseClass]
|
243
|
+
def perform_request_retry(exception)
|
244
|
+
if(exception.is_a?(Error::ApiError::RequestError))
|
245
|
+
exception.response.code >= 500
|
246
|
+
else
|
247
|
+
false
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
Models::Storage.autoload :Google, 'miasma/contrib/google/storage'
|
256
|
+
Models::Orchestration.autoload :Google, 'miasma/contrib/google/orchestration'
|
257
|
+
|
258
|
+
# Models::Compute.autoload :Google, 'misama/contrib/google/compute'
|
259
|
+
# Models::LoadBalancer.autoload :Google, 'misama/contrib/google/load_balancer'
|
260
|
+
# Models::AutoScale.autoload :Google, 'misama/contrib/google/auto_scale'
|
261
|
+
end
|
@@ -0,0 +1,338 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'miasma'
|
3
|
+
|
4
|
+
module Miasma
|
5
|
+
module Models
|
6
|
+
class Orchestration
|
7
|
+
class Google < Orchestration
|
8
|
+
|
9
|
+
include Contrib::Google::ApiCommon
|
10
|
+
|
11
|
+
GOOGLE_SERVICE_PATH = '/deploymentmanager/v2'
|
12
|
+
GOOGLE_SERVICE_PROJECT = true
|
13
|
+
|
14
|
+
# Determine stack state based on last operation information
|
15
|
+
#
|
16
|
+
# @param operation [Hash]
|
17
|
+
# @option operation [String] :operationType
|
18
|
+
# @option operation [String] :status
|
19
|
+
# @return [Symbol]
|
20
|
+
def determine_state(operation)
|
21
|
+
prefix = case operation[:operationType]
|
22
|
+
when 'insert'
|
23
|
+
'create'
|
24
|
+
when 'update'
|
25
|
+
'update'
|
26
|
+
when 'delete'
|
27
|
+
'delete'
|
28
|
+
end
|
29
|
+
suffix = case operation[:status]
|
30
|
+
when 'RUNNING', 'PENDING'
|
31
|
+
'in_progress'
|
32
|
+
when 'DONE'
|
33
|
+
'complete'
|
34
|
+
end
|
35
|
+
if(operation[:error])
|
36
|
+
suffix = 'failed'
|
37
|
+
end
|
38
|
+
if(prefix.nil? || suffix.nil?)
|
39
|
+
:unknown
|
40
|
+
else
|
41
|
+
"#{prefix}_#{suffix}".to_sym
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create stack data hash from information
|
46
|
+
#
|
47
|
+
# @param info [Hash]
|
48
|
+
# @option info [String] :insertTime
|
49
|
+
# @option info [String] :description
|
50
|
+
# @option info [String] :name
|
51
|
+
# @option info [Hash] :operation
|
52
|
+
# @return [Hash]
|
53
|
+
def basic_stack_data_format(info)
|
54
|
+
info = info.to_smash
|
55
|
+
Smash.new(
|
56
|
+
:id => info[:id],
|
57
|
+
:created => Time.parse(info[:insertTime]),
|
58
|
+
:updated => Time.parse(info.fetch(:operation, :endTime, info.fetch(:operation, :startTime, info[:insertTime]))),
|
59
|
+
:description => info[:description],
|
60
|
+
:name => info[:name],
|
61
|
+
:state => determine_state(info.fetch(:operation, {})),
|
62
|
+
:status => determine_state(info.fetch(:operation, {})).to_s.split('_').map(&:capitalize).join(' '),
|
63
|
+
:custom => info
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Array<Stack>]
|
68
|
+
def stack_all
|
69
|
+
result = request(
|
70
|
+
:path => 'global/deployments'
|
71
|
+
)
|
72
|
+
result.fetch(:body, :deployments, []).map do |item|
|
73
|
+
new_stack = Stack.new(self)
|
74
|
+
new_stack.load_data(basic_stack_data_format(item)).valid_state
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Save stack state
|
79
|
+
#
|
80
|
+
# @param stack [Stack]
|
81
|
+
# @return [Stack]
|
82
|
+
def stack_save(stack)
|
83
|
+
unless(stack.persisted?)
|
84
|
+
result = request(
|
85
|
+
:path => 'global/deployments',
|
86
|
+
:method => :post,
|
87
|
+
:json => {
|
88
|
+
:name => stack.name,
|
89
|
+
:target => template_data_unformat(stack.template)
|
90
|
+
}
|
91
|
+
)
|
92
|
+
else
|
93
|
+
result = request(
|
94
|
+
:path => "global/deployments/#{stack.name}",
|
95
|
+
:method => :put,
|
96
|
+
:json => {
|
97
|
+
:name => stack.name,
|
98
|
+
:target => template_data_unformat(stack.template),
|
99
|
+
:fingerprint => stack.custom[:fingerprint]
|
100
|
+
}
|
101
|
+
)
|
102
|
+
end
|
103
|
+
stack.id = result.get(:body, :id)
|
104
|
+
stack.valid_state
|
105
|
+
stack.reload
|
106
|
+
end
|
107
|
+
|
108
|
+
# Fetch the stack template
|
109
|
+
#
|
110
|
+
# @param stack [Stack]
|
111
|
+
# @return [Hash]
|
112
|
+
def stack_template_load(stack)
|
113
|
+
if(stack.persisted?)
|
114
|
+
result = request(
|
115
|
+
:endpoint => stack.custom.fetch(:manifest, stack.custom.get(:update, :manifest))
|
116
|
+
)
|
117
|
+
cache_template = stack.template = template_data_format(result[:body])
|
118
|
+
stack.custom = stack.custom.merge(result[:body])
|
119
|
+
if(stack.custom['expandedConfig'])
|
120
|
+
stack.custom['expandedConfig'] = YAML.load(stack.custom['expandedConfig']).to_smash
|
121
|
+
end
|
122
|
+
if(stack.custom['layout'])
|
123
|
+
stack.custom['layout'] = YAML.load(stack.custom['layout']).to_smash
|
124
|
+
end
|
125
|
+
stack.valid_state
|
126
|
+
cache_template
|
127
|
+
else
|
128
|
+
Smash.new
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Pack template data for shipping to API
|
133
|
+
#
|
134
|
+
# @param data [Hash]
|
135
|
+
# @option data [Hash] :config
|
136
|
+
# @option data [Array<Hash>] :imports
|
137
|
+
# @return [Hash]
|
138
|
+
def template_data_unformat(data)
|
139
|
+
Hash.new.tap do |result|
|
140
|
+
if(v = data.to_smash.get(:config, :content))
|
141
|
+
result[:config] = {
|
142
|
+
:content => yamlize(v)
|
143
|
+
}
|
144
|
+
end
|
145
|
+
if(data[:imports])
|
146
|
+
result[:imports] = data[:imports].map do |item|
|
147
|
+
Smash.new(
|
148
|
+
:name => item['name'],
|
149
|
+
:content => yamlize(item['content'])
|
150
|
+
)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Convert value to YAML if not string
|
157
|
+
#
|
158
|
+
# @param value [Object]
|
159
|
+
# @return [String, Object]
|
160
|
+
def yamlize(value)
|
161
|
+
unless(value.is_a?(String))
|
162
|
+
if(value.is_a?(Hash) && value.respond_to?(:to_hash))
|
163
|
+
value = value.to_hash
|
164
|
+
end
|
165
|
+
value.to_yaml(:header => true)
|
166
|
+
else
|
167
|
+
value
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Unpack received template data for local model instance
|
172
|
+
#
|
173
|
+
# @param data [Hash]
|
174
|
+
# @option data [Hash] :config
|
175
|
+
# @option data [Array<Hash>] :imports
|
176
|
+
# @return [Hash]
|
177
|
+
def template_data_format(data)
|
178
|
+
data = data.to_smash
|
179
|
+
Smash.new.tap do |result|
|
180
|
+
result[:config] = data.fetch(:config, Smash.new)
|
181
|
+
result[:imports] = data.fetch(:imports, []).map do |item|
|
182
|
+
begin
|
183
|
+
Smash.new(
|
184
|
+
:name => item[:name],
|
185
|
+
:content => YAML.load(item[:content])
|
186
|
+
)
|
187
|
+
rescue
|
188
|
+
item
|
189
|
+
end
|
190
|
+
end
|
191
|
+
if(result.get(:config, :content))
|
192
|
+
result[:config][:content] = YAML.load(result[:config][:content]) || Smash.new
|
193
|
+
else
|
194
|
+
result[:config][:content] = Smash.new
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Reload the stack data
|
200
|
+
#
|
201
|
+
# @param stack [Stack]
|
202
|
+
# @return [Stack]
|
203
|
+
def stack_reload(stack)
|
204
|
+
if(stack.persisted?)
|
205
|
+
result = request(
|
206
|
+
:path => "global/deployments/#{stack.name}"
|
207
|
+
)
|
208
|
+
deploy = result[:body]
|
209
|
+
stack.load_data(basic_stack_data_format(deploy)).valid_state
|
210
|
+
stack_template_load(stack)
|
211
|
+
set_outputs_if_available(stack)
|
212
|
+
end
|
213
|
+
stack
|
214
|
+
end
|
215
|
+
|
216
|
+
# Set outputs into stack instance
|
217
|
+
#
|
218
|
+
# @param stack [Stack]
|
219
|
+
# @return [TrueClass, FalseClass]
|
220
|
+
def set_outputs_if_available(stack)
|
221
|
+
outputs = extract_outputs(stack.custom.fetch(:layout, {}))
|
222
|
+
unless(outputs.empty?)
|
223
|
+
stack.outputs = outputs
|
224
|
+
stack.valid_state
|
225
|
+
true
|
226
|
+
else
|
227
|
+
false
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Extract outputs from stack hash
|
232
|
+
#
|
233
|
+
# @param stack_hash [Hash]
|
234
|
+
# @return [Array<Hash>]
|
235
|
+
def extract_outputs(stack_hash)
|
236
|
+
outputs = []
|
237
|
+
if(stack_hash[:outputs])
|
238
|
+
outputs += stack_hash[:outputs].map do |output|
|
239
|
+
Smash.new(:key => output[:name], :value => output[:finalValue])
|
240
|
+
end
|
241
|
+
end
|
242
|
+
stack_hash.fetch(:resources, []).each do |resource|
|
243
|
+
outputs += extract_outputs(resource)
|
244
|
+
end
|
245
|
+
outputs
|
246
|
+
end
|
247
|
+
|
248
|
+
# Delete stack
|
249
|
+
#
|
250
|
+
# @param stack [Stack]
|
251
|
+
# @return [TrueClass, FalseClass]
|
252
|
+
def stack_destroy(stack)
|
253
|
+
if(stack.persisted?)
|
254
|
+
request(
|
255
|
+
:path => "global/deployments/#{stack.name}",
|
256
|
+
:method => :delete
|
257
|
+
)
|
258
|
+
true
|
259
|
+
else
|
260
|
+
false
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Fetch all events
|
265
|
+
#
|
266
|
+
# @param stack [Stack]
|
267
|
+
# @return [Array<Stack::Event>]
|
268
|
+
def event_all(stack, evt_id=nil)
|
269
|
+
result = request(
|
270
|
+
:path => 'global/operations',
|
271
|
+
:params => {
|
272
|
+
:filter => "targetId eq #{stack.id}"
|
273
|
+
}
|
274
|
+
)
|
275
|
+
result.fetch(:body, :operations, []).map do |event|
|
276
|
+
status_msg = [
|
277
|
+
event[:statusMessage],
|
278
|
+
*event.fetch(:error, :errors, []).map{|e| e[:message]}
|
279
|
+
].compact.join(' -- ')
|
280
|
+
Stack::Event.new(
|
281
|
+
stack,
|
282
|
+
:id => event[:id],
|
283
|
+
:resource_id => event[:targetId],
|
284
|
+
:resource_name => stack.name,
|
285
|
+
:resource_logical_id => stack.name,
|
286
|
+
:resource_state => determine_state(event),
|
287
|
+
:resource_status => determine_state(event).to_s.split('_').map(&:capitalize).join(' '),
|
288
|
+
:resource_status_reason => "#{event[:status]} - #{event[:progress]}% complete #{status_msg}",
|
289
|
+
:time => Time.parse(event.fetch(:startTime, event[:insertTime]))
|
290
|
+
).valid_state
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Fetch all stack resources
|
295
|
+
#
|
296
|
+
# @param stack [Stack]
|
297
|
+
# @return [Array<Stack::Resource>]
|
298
|
+
# @todo Add status reason extraction
|
299
|
+
def resource_all(stack)
|
300
|
+
request(
|
301
|
+
:path => "global/deployments/#{stack.name}/resources"
|
302
|
+
).fetch('body', 'resources', []).map do |resource|
|
303
|
+
Stack::Resource.new(stack,
|
304
|
+
:id => resource[:id],
|
305
|
+
:type => resource[:type],
|
306
|
+
:name => resource[:name],
|
307
|
+
:logical_id => resource[:name],
|
308
|
+
:created => Time.parse(resource[:insertTime]),
|
309
|
+
:updated => resource[:updateTime] ? Time.parse(resource[:updateTime]) : nil,
|
310
|
+
:state => :create_complete,
|
311
|
+
:status => 'OK',
|
312
|
+
:status_reason => resource.fetch(:warnings, []).map{|w| w[:message]}.join(' ')
|
313
|
+
).valid_state
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Reload resource data
|
318
|
+
#
|
319
|
+
# @param resource [Stack::Resource]
|
320
|
+
# @return [Stack::Resource]
|
321
|
+
def resource_reload(resource)
|
322
|
+
resource.stack.resources.reload
|
323
|
+
resource.stack.resources.get(resource.id)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Reload event data
|
327
|
+
#
|
328
|
+
# @param event [Stack::Event]
|
329
|
+
# @return event [Stack::Event]
|
330
|
+
def event_reload(event)
|
331
|
+
event.stack.events.reload
|
332
|
+
event.stack.events.get(event.id)
|
333
|
+
end
|
334
|
+
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'miasma-google/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'miasma-google'
|
5
|
+
s.version = MiasmaGoogle::VERSION.version
|
6
|
+
s.summary = 'Smoggy Google API'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'code@chrisroberts.org'
|
9
|
+
s.homepage = 'https://github.com/miasma-rb/miasma-google'
|
10
|
+
s.description = 'Smoggy Google API'
|
11
|
+
s.license = 'Apache 2.0'
|
12
|
+
s.require_path = 'lib'
|
13
|
+
s.add_runtime_dependency 'miasma', '>= 0.2.12'
|
14
|
+
s.add_development_dependency 'pry'
|
15
|
+
s.add_development_dependency 'minitest'
|
16
|
+
s.add_development_dependency 'vcr'
|
17
|
+
s.add_development_dependency 'webmock'
|
18
|
+
s.add_development_dependency 'psych', '>= 2.0.8'
|
19
|
+
s.add_runtime_dependency 'mime-types'
|
20
|
+
s.files = Dir['lib/**/*'] + %w(miasma-google.gemspec README.md CHANGELOG.md LICENSE)
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: miasma-google
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Roberts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: miasma
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.12
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.12
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: vcr
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: psych
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.0.8
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.0.8
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: mime-types
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Smoggy Google API
|
112
|
+
email: code@chrisroberts.org
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- CHANGELOG.md
|
118
|
+
- LICENSE
|
119
|
+
- README.md
|
120
|
+
- lib/miasma-google/version.rb
|
121
|
+
- lib/miasma/contrib/google.rb
|
122
|
+
- lib/miasma/contrib/google/orchestration.rb
|
123
|
+
- miasma-google.gemspec
|
124
|
+
homepage: https://github.com/miasma-rb/miasma-google
|
125
|
+
licenses:
|
126
|
+
- Apache 2.0
|
127
|
+
metadata: {}
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 2.4.8
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: Smoggy Google API
|
148
|
+
test_files: []
|
149
|
+
has_rdoc:
|