conify 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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