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.
@@ -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