conify 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +9 -0
- data/Rakefile +6 -0
- data/bin/conify +30 -0
- data/config.ru +7 -0
- data/conify.gemspec +27 -0
- data/lib/.DS_Store +0 -0
- data/lib/conify.rb +2 -0
- data/lib/conify/api.rb +4 -0
- data/lib/conify/api/abstract_api.rb +78 -0
- data/lib/conify/api/addons.rb +19 -0
- data/lib/conify/api/users.rb +18 -0
- data/lib/conify/cli.rb +29 -0
- data/lib/conify/command.rb +286 -0
- data/lib/conify/command/abstract_command.rb +15 -0
- data/lib/conify/command/global.rb +109 -0
- data/lib/conify/helpers.rb +223 -0
- data/lib/conify/http.rb +56 -0
- data/lib/conify/manifest.rb +58 -0
- data/lib/conify/okjson.rb +606 -0
- data/lib/conify/sso.rb +89 -0
- data/lib/conify/test.rb +52 -0
- data/lib/conify/test/all_test.rb +23 -0
- data/lib/conify/test/api_test.rb +46 -0
- data/lib/conify/test/deprovision_test.rb +30 -0
- data/lib/conify/test/manifest_test.rb +88 -0
- data/lib/conify/test/plan_change_test.rb +33 -0
- data/lib/conify/test/provision_response_test.rb +89 -0
- data/lib/conify/test/provision_test.rb +52 -0
- data/lib/conify/test/sso_test.rb +58 -0
- data/lib/conify/version.rb +3 -0
- metadata +165 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'conify/command'
|
2
|
+
require 'conify/helpers'
|
3
|
+
|
4
|
+
class Conify::Command::AbstractCommand
|
5
|
+
include Conify::Helpers
|
6
|
+
|
7
|
+
attr_reader :args
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
def initialize(args = [], options = {})
|
11
|
+
@args = args
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'conify/command/abstract_command'
|
2
|
+
require 'conify/manifest'
|
3
|
+
require 'conify/test/all_test'
|
4
|
+
require 'conify/test/manifest_test'
|
5
|
+
require 'conify/api/users'
|
6
|
+
require 'conify/api/addons'
|
7
|
+
|
8
|
+
class Conify::Command::Global < Conify::Command::AbstractCommand
|
9
|
+
|
10
|
+
def init
|
11
|
+
# Error out if conflux-manifest.json already exists
|
12
|
+
if File.exists?(manifest_path)
|
13
|
+
error "Manifest File #{manifest_filename} already exists."
|
14
|
+
else
|
15
|
+
begin
|
16
|
+
# Create new conflux-manifest.json file from template
|
17
|
+
File.open(manifest_path, 'w+') do |f|
|
18
|
+
f.write(Conify::Manifest.template)
|
19
|
+
end
|
20
|
+
|
21
|
+
display([
|
22
|
+
"Created new manifest template at #{manifest_filename}.",
|
23
|
+
"Modify it to your service's specs and then run 'conify test'."
|
24
|
+
].join("\n"))
|
25
|
+
rescue Exception => e
|
26
|
+
File.delete(manifest_path) # Remove file if something screws up during file creation
|
27
|
+
display "Error initializing conify manifest: #{e.message}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test
|
33
|
+
begin
|
34
|
+
# Get the content from conflux-manifest.json and add the 'env'
|
35
|
+
# key specifying which environment to test.
|
36
|
+
data = manifest_content
|
37
|
+
data['env'] = (@args[0] === '--production') ? 'production' : 'test'
|
38
|
+
|
39
|
+
# Run all tests to ensure Conflux integration is set up correctly
|
40
|
+
Conify::AllTest.new(data).call
|
41
|
+
|
42
|
+
display "Everything checks out!\nRun 'conify push' to push your service to Conflux."
|
43
|
+
rescue Exception => e
|
44
|
+
display e.message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def push
|
49
|
+
# First ensure manifest exists.
|
50
|
+
if !File.exists?(manifest_path)
|
51
|
+
error "No Conflux manifest exists yet.\nRun 'conflux init' to create a new manifest."
|
52
|
+
end
|
53
|
+
|
54
|
+
# Run Manifest Test to ensure file is valid.
|
55
|
+
Conify::ManifestTest.new(manifest_content).call
|
56
|
+
|
57
|
+
# Request Conflux email/password creds.
|
58
|
+
creds = ask_for_conflux_creds
|
59
|
+
|
60
|
+
# Login to Conflux with these creds, returning a valid user-token.
|
61
|
+
auth_resp = Conify::Api::Users.new.login(creds)
|
62
|
+
|
63
|
+
# Push new service to Conflux.
|
64
|
+
push_resp = Conify::Api::Addons.new.push(manifest_content, auth_resp['user_token'])
|
65
|
+
|
66
|
+
display "Successfully pushed draft service to Conflux!\nRun 'conify open' to finish editing your service's information."
|
67
|
+
end
|
68
|
+
|
69
|
+
def open
|
70
|
+
# First ensure manifest exists.
|
71
|
+
if !File.exists?(manifest_path)
|
72
|
+
error "No Conflux manifest exists yet.\nRun 'conflux init' to create a new manifest."
|
73
|
+
end
|
74
|
+
|
75
|
+
service_id = manifest_content['id'] || ''
|
76
|
+
error 'Manifest must have an "id" field.' if service_id.empty?
|
77
|
+
|
78
|
+
edit_service_url = "#{site_url}/services/#{service_id}/edit"
|
79
|
+
display "Opening Conflux Service at: #{edit_service_url}"
|
80
|
+
open_url(edit_service_url)
|
81
|
+
end
|
82
|
+
|
83
|
+
#----------------------------------------------------------------------------
|
84
|
+
|
85
|
+
module CommandInfo
|
86
|
+
|
87
|
+
module Init
|
88
|
+
DESCRIPTION = 'Create a new Conflux manifest'
|
89
|
+
VALID_ARGS = [ [] ]
|
90
|
+
end
|
91
|
+
|
92
|
+
module Test
|
93
|
+
DESCRIPTION = 'Test that your Conflux integration is set up correctly'
|
94
|
+
VALID_ARGS = [ [], ['--production'] ]
|
95
|
+
end
|
96
|
+
|
97
|
+
module Push
|
98
|
+
DESCRIPTION = 'Push your draft service to Conflux'
|
99
|
+
VALID_ARGS = [ [] ]
|
100
|
+
end
|
101
|
+
|
102
|
+
module Open
|
103
|
+
DESCRIPTION = 'Open the url to edit your service on Conflux'
|
104
|
+
VALID_ARGS = [ [] ]
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Conify
|
2
|
+
module Helpers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def error(msg = '')
|
6
|
+
$stderr.puts(format_with_bang(msg))
|
7
|
+
exit(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Add a bang to an error message
|
11
|
+
def format_with_bang(message)
|
12
|
+
return message if !message.is_a?(String)
|
13
|
+
return '' if message.to_s.strip == ''
|
14
|
+
" ! " + message.encode('utf-8', 'binary', invalid: :replace, undef: :replace).split("\n").join("\n ! ")
|
15
|
+
end
|
16
|
+
|
17
|
+
def display(msg = '')
|
18
|
+
puts(msg)
|
19
|
+
$stdout.flush
|
20
|
+
end
|
21
|
+
|
22
|
+
# convert string from underscores to camelcase
|
23
|
+
def camelize(str)
|
24
|
+
str.split('_').collect(&:capitalize).join
|
25
|
+
end
|
26
|
+
|
27
|
+
# format some data into a table to then be displayed to the user
|
28
|
+
def to_table(data, headers)
|
29
|
+
column_lengths = []
|
30
|
+
gutter = 2
|
31
|
+
table = ''
|
32
|
+
|
33
|
+
# Figure out column widths based on longest string in each column (including the header string)
|
34
|
+
headers.each { |header|
|
35
|
+
width = data.map { |_| _[header] }.max_by(&:length).length
|
36
|
+
|
37
|
+
width = header.length if width < header.length
|
38
|
+
|
39
|
+
column_lengths << width
|
40
|
+
}
|
41
|
+
|
42
|
+
# format the length of a table cell string to make it as wide as the column (by adding extra spaces)
|
43
|
+
format_row_entry = lambda { |entry, i|
|
44
|
+
entry + (' ' * (column_lengths[i] - entry.length + gutter))
|
45
|
+
}
|
46
|
+
|
47
|
+
# Add headers
|
48
|
+
headers.each_with_index { |header, i|
|
49
|
+
table += format_row_entry.call(header, i)
|
50
|
+
}
|
51
|
+
|
52
|
+
table += "\n"
|
53
|
+
|
54
|
+
# Add line breaks under headers
|
55
|
+
column_lengths.each { |length|
|
56
|
+
table += (('-' * length) + (' ' * gutter))
|
57
|
+
}
|
58
|
+
|
59
|
+
table += "\n"
|
60
|
+
|
61
|
+
# Add rows
|
62
|
+
data.each { |row|
|
63
|
+
headers.each_with_index { |header, i|
|
64
|
+
table += format_row_entry.call(row[header], i)
|
65
|
+
}
|
66
|
+
|
67
|
+
table += "\n"
|
68
|
+
}
|
69
|
+
|
70
|
+
table
|
71
|
+
end
|
72
|
+
|
73
|
+
def allow_user_response
|
74
|
+
$stdin.gets.to_s.strip
|
75
|
+
end
|
76
|
+
|
77
|
+
def running_on_windows?
|
78
|
+
RUBY_PLATFORM =~ /mswin32|mingw32/
|
79
|
+
end
|
80
|
+
|
81
|
+
def running_on_a_mac?
|
82
|
+
RUBY_PLATFORM =~ /-darwin\d/
|
83
|
+
end
|
84
|
+
|
85
|
+
def ask_for_password_on_windows
|
86
|
+
require 'Win32API'
|
87
|
+
char = nil
|
88
|
+
password = ''
|
89
|
+
|
90
|
+
while char = Win32API.new('msvcrt', '_getch', [ ], 'L').Call do
|
91
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
92
|
+
if char == 127 || char == 8 # backspace and delete
|
93
|
+
password.slice!(-1, 1)
|
94
|
+
else
|
95
|
+
# windows might throw a -1 at us so make sure to handle RangeError
|
96
|
+
(password << char.chr) rescue RangeError
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
puts
|
101
|
+
password
|
102
|
+
end
|
103
|
+
|
104
|
+
def ask_for_password
|
105
|
+
begin
|
106
|
+
echo_off # make the password input hidden
|
107
|
+
password = allow_user_response
|
108
|
+
puts
|
109
|
+
ensure
|
110
|
+
echo_on # flip input visibility back on
|
111
|
+
end
|
112
|
+
|
113
|
+
password
|
114
|
+
end
|
115
|
+
|
116
|
+
# Hide user input
|
117
|
+
def echo_off
|
118
|
+
with_tty do
|
119
|
+
system 'stty -echo'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Show user input
|
124
|
+
def echo_on
|
125
|
+
with_tty do
|
126
|
+
system 'stty echo'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def with_tty(&block)
|
131
|
+
return unless $stdin.isatty
|
132
|
+
begin
|
133
|
+
yield
|
134
|
+
rescue
|
135
|
+
# fails on windows
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def ask_for_conflux_creds
|
140
|
+
# Ask for Conflux Credentials
|
141
|
+
puts 'Enter your Conflux credentials.'
|
142
|
+
|
143
|
+
# Email:
|
144
|
+
print 'Email: '
|
145
|
+
email = allow_user_response
|
146
|
+
|
147
|
+
# Password
|
148
|
+
print 'Password (typing will be hidden): '
|
149
|
+
|
150
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
151
|
+
|
152
|
+
{ email: email, password: password }
|
153
|
+
end
|
154
|
+
|
155
|
+
# Strip the protocol + following slashes off of a url
|
156
|
+
def host
|
157
|
+
host_url.gsub(/http:\/\/|https:\/\//, '')
|
158
|
+
end
|
159
|
+
|
160
|
+
def host_url
|
161
|
+
ENV['CONFLUX_HOST'] || 'https://api.goconflux.com'
|
162
|
+
end
|
163
|
+
|
164
|
+
def site_url
|
165
|
+
ENV['CONFLUX_SITE_URL'] || 'https://goconflux.com'
|
166
|
+
end
|
167
|
+
|
168
|
+
# Get an array (of symbols) of the user-defined methods for a klass
|
169
|
+
def manually_added_methods(klass)
|
170
|
+
klass.instance_methods(false)
|
171
|
+
end
|
172
|
+
|
173
|
+
def manifest_path
|
174
|
+
File.join(Dir.pwd, manifest_filename)
|
175
|
+
end
|
176
|
+
|
177
|
+
def manifest_filename
|
178
|
+
'conflux-manifest.json'
|
179
|
+
end
|
180
|
+
|
181
|
+
def manifest_content
|
182
|
+
JSON.parse(File.read(manifest_path)) rescue {}
|
183
|
+
end
|
184
|
+
|
185
|
+
def kensa_manifest_path
|
186
|
+
File.join(Dir.pwd, kensa_manifest_name)
|
187
|
+
end
|
188
|
+
|
189
|
+
def kensa_manifest_name
|
190
|
+
'addon-manifest.json'
|
191
|
+
end
|
192
|
+
|
193
|
+
def exclusive_deep_merge(merge_to, merge_from)
|
194
|
+
merged = merge_to.clone
|
195
|
+
|
196
|
+
merge_from.each do |key, value|
|
197
|
+
# Only override existing key
|
198
|
+
if merged.keys.include?(key)
|
199
|
+
# Deep merge for nested hash
|
200
|
+
if value.is_a?(Hash) && merged[key].is_a?(Hash)
|
201
|
+
merged[key] = exclusive_deep_merge(merged[key], value)
|
202
|
+
else
|
203
|
+
merged[key] = value
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
merged
|
209
|
+
end
|
210
|
+
|
211
|
+
def open_url(url)
|
212
|
+
if running_on_a_mac?
|
213
|
+
system "open #{url}"
|
214
|
+
elsif running_on_windows?
|
215
|
+
system "explorer #{url}"
|
216
|
+
else
|
217
|
+
# Probably some flavor of Linux
|
218
|
+
system "xdg-open #{url}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
data/lib/conify/http.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
require 'conify/okjson'
|
3
|
+
|
4
|
+
# Used for making http requests when testing Conflux compatibility
|
5
|
+
module Conify
|
6
|
+
module HTTPForTests
|
7
|
+
|
8
|
+
def get(path, params={})
|
9
|
+
path = "#{path}?" + params.map { |k, v| "#{k}=#{v}" }.join('&') unless params.empty?
|
10
|
+
request(:get, [], path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(credentials, path, payload=nil)
|
14
|
+
request(:post, credentials, path, payload)
|
15
|
+
end
|
16
|
+
|
17
|
+
def put(credentials, path, payload=nil)
|
18
|
+
request(:put, credentials, path, payload)
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(credentials, path, payload=nil)
|
22
|
+
request(:delete, credentials, path, payload)
|
23
|
+
end
|
24
|
+
|
25
|
+
def request(method, credentials, path, payload=nil)
|
26
|
+
code = nil
|
27
|
+
body = nil
|
28
|
+
|
29
|
+
begin
|
30
|
+
args = [{ accept: 'application/json' }]
|
31
|
+
|
32
|
+
if payload
|
33
|
+
args.first[:content_type] = 'application/json'
|
34
|
+
args.unshift(payload.to_json)
|
35
|
+
end
|
36
|
+
|
37
|
+
user, pass = credentials
|
38
|
+
body = RestClient::Resource.new(url, user: user, password: pass, verify_ssl: false)[path].send(
|
39
|
+
method,
|
40
|
+
*args
|
41
|
+
).to_s
|
42
|
+
|
43
|
+
code = 200
|
44
|
+
rescue RestClient::ExceptionWithResponse => e
|
45
|
+
code = e.http_code
|
46
|
+
body = e.http_body
|
47
|
+
rescue Errno::ECONNREFUSED
|
48
|
+
code = -1
|
49
|
+
body = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
[code, body]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'conify/helpers'
|
3
|
+
|
4
|
+
module Conify
|
5
|
+
module Manifest
|
6
|
+
extend self
|
7
|
+
extend Conify::Helpers
|
8
|
+
|
9
|
+
def password_gen(size = 8)
|
10
|
+
SecureRandom.hex(size)
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_port
|
14
|
+
3000
|
15
|
+
end
|
16
|
+
|
17
|
+
def template
|
18
|
+
# Use conflux template as default
|
19
|
+
manifest = JSON.parse(conflux_template)
|
20
|
+
|
21
|
+
# If the kensa manifest exists, return the exclusively merged two manifests.
|
22
|
+
if File.exists?(kensa_manifest_name)
|
23
|
+
kensa_manifest = JSON.parse(File.read(kensa_manifest_path)) rescue {}
|
24
|
+
manifest = exclusive_deep_merge(manifest, kensa_manifest)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Don't copy over password or sso_salt, so just set them now:
|
28
|
+
manifest['api']['password'] = password_gen
|
29
|
+
manifest['api']['sso_salt'] = password_gen
|
30
|
+
|
31
|
+
JSON.pretty_generate(manifest)
|
32
|
+
end
|
33
|
+
|
34
|
+
def conflux_template
|
35
|
+
<<-JSON
|
36
|
+
{
|
37
|
+
"id": "myservice",
|
38
|
+
"api": {
|
39
|
+
"config_vars": [
|
40
|
+
"MYSERVICE_URL"
|
41
|
+
],
|
42
|
+
"password": "",
|
43
|
+
"sso_salt": "",
|
44
|
+
"production": {
|
45
|
+
"base_url": "https://yourapp.com/conflux/resources",
|
46
|
+
"sso_url": "https://yourapp.com/conflux/sso"
|
47
|
+
},
|
48
|
+
"test": {
|
49
|
+
"base_url": "http://localhost:#{default_port}/conflux/resources",
|
50
|
+
"sso_url": "http://localhost:#{default_port}/conflux/sso"
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
JSON
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|