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,89 @@
1
+ require 'restclient'
2
+ require 'uri'
3
+
4
+ module Conify
5
+ class Sso
6
+ attr_accessor :external_uuid, :url, :proxy_port, :timestamp, :token
7
+
8
+ def initialize(data)
9
+ @external_uuid = data[:external_uuid]
10
+ @external_username = data[:external_username]
11
+ @salt = data['api']['sso_salt']
12
+ env = data.fetch('env', 'test')
13
+
14
+ if @url = data['api'][env]['sso_url']
15
+ @use_post = true
16
+ @proxy_port = find_available_port
17
+ else
18
+ @url = data['api'][env].chomp('/')
19
+ end
20
+
21
+ @timestamp = Time.now.to_i
22
+ @token = make_token(@timestamp)
23
+ end
24
+
25
+ def path
26
+ self.POST? ? URI.parse(url).path : "/conflux/resources/#{external_uuid}"
27
+ end
28
+
29
+ def POST?
30
+ @use_post
31
+ end
32
+
33
+ def sso_url
34
+ self.POST? ? "http://localhost:#{@proxy_port}/" : full_url
35
+ end
36
+
37
+ def full_url
38
+ [ url, path, querystring ].join
39
+ end
40
+ alias get_url full_url
41
+
42
+ def post_url
43
+ url
44
+ end
45
+
46
+ def timestamp=(other)
47
+ @timestamp = other
48
+ @token = make_token(@timestamp)
49
+ end
50
+
51
+ def make_token(t)
52
+ Digest::SHA1.hexdigest([external_uuid, @salt, t].join(':'))
53
+ end
54
+
55
+ def querystring
56
+ return '' unless @salt
57
+ '?' + query_data
58
+ end
59
+
60
+ def query_data
61
+ query_params.map{|p| p.join('=')}.join('&')
62
+ end
63
+
64
+ def query_params
65
+ {
66
+ 'id' => external_uuid,
67
+ 'token' => @token,
68
+ 'timestamp' => @timestamp.to_s,
69
+ 'email' => @external_username
70
+ }
71
+ end
72
+
73
+ def message
74
+ if self.POST?
75
+ "POSTing #{query_data} to #{post_url} via proxy on port #{@proxy_port}"
76
+ else
77
+ "Opening #{full_url}"
78
+ end
79
+ end
80
+
81
+ def find_available_port
82
+ server = TCPServer.new('127.0.0.1', 0)
83
+ server.addr[1]
84
+ ensure
85
+ server.close if server
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,52 @@
1
+ require 'conify/helpers'
2
+ require 'uri'
3
+
4
+ module Conify
5
+ class Test
6
+ attr_accessor :data
7
+ include Conify::Helpers
8
+
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+
13
+ def env
14
+ @data.fetch('env', 'test')
15
+ end
16
+
17
+ def test(msg, &block)
18
+ raise "Failed: #{msg}" unless block.call
19
+ end
20
+
21
+ def run(klass, data)
22
+ test_name = klass.to_s.gsub('Conify::', '').split(/(?=[A-Z])/).join(' ')
23
+
24
+ begin
25
+ klass.new(data).call
26
+ rescue Exception => e
27
+ error "#{test_name} #{e.message}"
28
+ end
29
+
30
+ if klass.const_defined?('OUTPUT_COMPLETION') && klass.const_get('OUTPUT_COMPLETION')
31
+ display "#{test_name}: Looks good..."
32
+ end
33
+ end
34
+
35
+ def url
36
+ if data['api'][env].is_a? Hash
37
+ base = data['api'][env]['base_url']
38
+ uri = URI.parse(base)
39
+ uri.query = nil
40
+ uri.path = ''
41
+ uri.to_s
42
+ else
43
+ data['api'][env].chomp('/')
44
+ end
45
+ end
46
+
47
+ def api_requires?(feature)
48
+ data['api'].fetch('requires', []).include?(feature)
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,23 @@
1
+ require 'conify/test'
2
+ require 'conify/test/manifest_test'
3
+ require 'conify/test/provision_test'
4
+ require 'conify/test/plan_change_test'
5
+ require 'conify/test/deprovision_test'
6
+ require 'conify/test/sso_test'
7
+
8
+ class Conify::AllTest < Conify::Test
9
+
10
+ def call
11
+ run(Conify::ManifestTest, data)
12
+ run(Conify::ProvisionTest, data)
13
+
14
+ # data[:provision_response] has already been set from the above test,
15
+ # so add 'external_uuid' returned from inside this hash to the data object.
16
+ data[:external_uuid] = data[:provision_response]['id']
17
+
18
+ run(Conify::PlanChangeTest, data)
19
+ run(Conify::SsoTest, data)
20
+ run(Conify::DeprovisionTest, data)
21
+ end
22
+
23
+ end
@@ -0,0 +1,46 @@
1
+ require 'conify/test'
2
+ require 'conify/http'
3
+ require 'uri'
4
+
5
+ class Conify::ApiTest < Conify::Test
6
+ include Conify::HTTPForTests
7
+
8
+ def base_path
9
+ if data['api'][env].is_a?(Hash)
10
+ URI.parse(data['api'][env]['base_url']).path
11
+ else
12
+ '/conflux/resources'
13
+ end
14
+ end
15
+
16
+ def conflux_id
17
+ "app#{rand(10000)}@conify.goconflux.com"
18
+ end
19
+
20
+ def credentials
21
+ [ data['id'], data['api']['password'] ]
22
+ end
23
+
24
+ def invalid_creds
25
+ ['wrong', 'secret']
26
+ end
27
+
28
+ def callback
29
+ 'http://localhost:7779/callback/999'
30
+ end
31
+
32
+ def create_provision_payload
33
+ payload = {
34
+ conflux_id: conflux_id,
35
+ plan: 'test',
36
+ callback_url: callback,
37
+ logplex_token: nil,
38
+ uuid: SecureRandom.uuid
39
+ }
40
+
41
+ payload[:log_drain_token] = SecureRandom.hex if api_requires?('syslog_drain')
42
+
43
+ payload
44
+ end
45
+
46
+ end
@@ -0,0 +1,30 @@
1
+ require 'conify/test/api_test'
2
+
3
+ class Conify::DeprovisionTest < Conify::ApiTest
4
+
5
+ OUTPUT_COMPLETION = true
6
+
7
+ def call
8
+ external_uuid = data[:external_uuid]
9
+ raise ArgumentError, 'Deprovision Test: No external_uuid specified' if external_uuid.nil?
10
+ path = "#{base_path}/#{external_uuid.to_s}"
11
+
12
+ test 'response' do
13
+ code, _ = delete(credentials, path, nil)
14
+ if code == 200
15
+ true
16
+ elsif code == -1
17
+ error "Deprovision Test: unable to connect to #{url}"
18
+ else
19
+ error "Deprovision Test: expected 200, got #{code}"
20
+ end
21
+ end
22
+
23
+ test 'authentication' do
24
+ code, _ = delete(invalid_creds, path, nil)
25
+ error "Deprovision Test: expected 401, got #{code}" if code != 401
26
+ true
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,88 @@
1
+ require 'conify/test'
2
+
3
+ class Conify::ManifestTest < Conify::Test
4
+
5
+ OUTPUT_COMPLETION = true
6
+
7
+ def call
8
+ test 'id key exists' do
9
+ data.has_key?('id')
10
+ end
11
+
12
+ test 'id is a string' do
13
+ data['id'].is_a?(String)
14
+ end
15
+
16
+ test 'id is not blank' do
17
+ !data['id'].empty?
18
+ end
19
+
20
+ test 'api key exists' do
21
+ data.has_key?('api')
22
+ end
23
+
24
+ test 'api is a hash' do
25
+ data['api'].is_a?(Hash)
26
+ end
27
+
28
+ test 'api contains password' do
29
+ data['api'].has_key?('password') && data['api']['password'] != ''
30
+ end
31
+
32
+ test 'api contains sso_salt' do
33
+ data['api'].has_key?('sso_salt') && data['api']['sso_salt'] != ''
34
+ end
35
+
36
+ test 'api contains test url' do
37
+ data['api'].has_key?('test')
38
+ end
39
+
40
+ test 'api contains production url' do
41
+ data['api'].has_key?('production')
42
+ end
43
+
44
+ if data['api']['production'].is_a?(Hash)
45
+ test 'production url uses SSL' do
46
+ data['api']['production']['base_url'] =~ /^https:/
47
+ end
48
+
49
+ test 'sso url uses SSL' do
50
+ data['api']['production']['sso_url'] =~ /^https:/
51
+ end
52
+ else
53
+ test 'production url uses SSL' do
54
+ data['api']['production'] =~ /^https:/
55
+ end
56
+ end
57
+
58
+ if data['api'].has_key?('config_vars')
59
+ test 'contains config_vars array' do
60
+ data['api']['config_vars'].is_a?(Array)
61
+ end
62
+
63
+ test 'all config vars are uppercase strings' do
64
+ data['api']['config_vars'].each do |k, v|
65
+ if k =~ /^[A-Z][0-9A-Z_]+$/
66
+ true
67
+ else
68
+ error "#{k.inspect} is not a valid ENV key"
69
+ end
70
+ end
71
+ end
72
+
73
+ test 'all config vars are prefixed with the addon id' do
74
+ data['api']['config_vars'].each do |k|
75
+ prefix = data['id'].upcase.gsub('-', '_')
76
+
77
+ if k =~ /^#{prefix}_/
78
+ true
79
+ else
80
+ error "#{k} is not a valid ENV key - must be prefixed with #{prefix}_"
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,33 @@
1
+ require 'conify/test/api_test'
2
+
3
+ class Conify::PlanChangeTest < Conify::ApiTest
4
+
5
+ OUTPUT_COMPLETION = true
6
+
7
+ def call
8
+ external_uuid = data[:external_uuid]
9
+ raise ArgumentError, 'Plan Change Test: No external_uuid specified' if external_uuid.nil?
10
+
11
+ path = "#{base_path}/#{external_uuid.to_s}"
12
+ payload = { plan: 'new_plan', conflux_id: data[:external_username] }
13
+
14
+ test 'response' do
15
+ code, _ = put(credentials, path, payload)
16
+
17
+ if code == 200
18
+ true
19
+ elsif code == -1
20
+ error "Plan Change Test: unable to connect to #{url}"
21
+ else
22
+ error "Plan Change Test: expected 200, got #{code}"
23
+ end
24
+ end
25
+
26
+ test 'authentication' do
27
+ code, _ = put(invalid_creds, path, payload)
28
+ error "Plan Change Test: expected 401, got #{code}" if code != 401
29
+ true
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,89 @@
1
+ require 'conify/test'
2
+ require 'uri'
3
+
4
+ class Conify::ProvisionResponseTest < Conify::Test
5
+
6
+ def call
7
+ response = data[:provision_response]
8
+
9
+ test 'contains an id' do
10
+ response.is_a?(Hash) && response['id']
11
+ end
12
+
13
+ test 'id does not contain conflux_id' do
14
+ if response['id'].to_s.include? data[:external_username].scan(/app(\d+)@/).flatten.first
15
+ error 'id cannot include conflux_id'
16
+ else
17
+ true
18
+ end
19
+ end
20
+
21
+ if response.has_key?('config')
22
+ test 'is a hash' do
23
+ response['config'].is_a?(Hash)
24
+ end
25
+
26
+ test 'all config keys were previously defined in the manifest' do
27
+ response['config'].keys.each do |key|
28
+ error "#{key} is not in the manifest" unless data['api']['config_vars'].include?(key)
29
+ end
30
+ true
31
+ end
32
+
33
+ test 'all keys in the manifest are present' do
34
+ difference = data['api']['config_vars'] - response['config'].keys
35
+ unless difference.empty?
36
+ verb = (difference.size == 1) ? 'is' : 'are'
37
+ error "Config(s) #{difference.join(', ')} #{verb} missing from the provision response"
38
+ end
39
+ true
40
+ end
41
+
42
+ test 'all config values are strings' do
43
+ response['config'].each do |k, v|
44
+ if v.is_a?(String)
45
+ true
46
+ else
47
+ error "the key #{k} doesn't contain a string (#{v.inspect})"
48
+ end
49
+ end
50
+ end
51
+
52
+ test 'URL configs vars' do
53
+ response['config'].each do |key, value|
54
+ next unless key =~ /_URL$/
55
+ begin
56
+ uri = URI.parse(value)
57
+ error "#{value} is not a valid URI - missing host" unless uri.host
58
+ error "#{value} is not a valid URI - missing scheme" unless uri.scheme
59
+ error "#{value} is not a valid URI - pointing to localhost" if env == 'production' && uri.host == 'localhost'
60
+ rescue URI::Error
61
+ error "#{value} is not a valid URI"
62
+ end
63
+ end
64
+ end
65
+
66
+ test 'log_drain_url is returned if required' do
67
+ if !api_requires?('syslog_drain')
68
+ true
69
+ else
70
+ drain_url = response['log_drain_url']
71
+
72
+ if !drain_url || drain_url.empty?
73
+ error 'must return a log_drain_url'
74
+ else
75
+ true
76
+ end
77
+
78
+ unless drain_url =~ /\A(https|syslog):\/\/[\S]+\Z/
79
+ error 'must return a syslog_drain_url like syslog://log.example.com:9999'
80
+ else
81
+ true
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+
89
+ end