conify 0.0.1

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