conify 0.0.1
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/.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
|