blend 0.1.4

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,5 @@
1
+ class Fixnum
2
+ def projectize
3
+ self.to_s.gsub(/\s/,'').downcase
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class Object
2
+
3
+ # Coped from activesupport because it's useful but doesn't justify pulling in
4
+ # activesupport dependencies:
5
+ def try(*a, &b)
6
+ if a.empty? && block_given?
7
+ yield self
8
+ else
9
+ __send__(*a, &b)
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def projectize
3
+ gsub(/\s/,'').downcase
4
+ end
5
+
6
+ def strip_email
7
+ gsub(/\s*<[^>]*>\s*/,'')
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Blend
2
+ module Exceptions
3
+ class AuthenticationFailure < StandardError; end
4
+ class LoginAuthenticationFailure < StandardError; end
5
+ class AlreadyLoggedIn < StandardError; end
6
+ class AlreadyLoggedOut < StandardError; end
7
+ class HipchatAuthenticationFailure < StandardError; end
8
+
9
+ class << self
10
+ def formatted(e)
11
+ puts "#{e.backtrace.shift}: #{e.message}"
12
+ puts e.backtrace.first(10).map{|x| "\t"+x}.join("\n")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,225 @@
1
+ require 'dnsruby'
2
+ require 'whois'
3
+ require 'httpclient'
4
+
5
+ module Blend
6
+ module Status
7
+ class Domain
8
+ attr_accessor :project, :domain, :res, :environment
9
+
10
+ def initialize( project, environment, domain )
11
+ @project = project
12
+ @environment = environment
13
+ @domain = domain
14
+ @res = Dnsruby::Resolver.new
15
+ end
16
+
17
+
18
+ class << self
19
+ def check(domain)
20
+ dc = self.new(domain)
21
+
22
+ result = {}
23
+ begin
24
+
25
+ result[:registered] = dc.registered?
26
+ result[:registrar] = dc.registrar
27
+ result[:ssl] = dc.ssl
28
+ result[:expires] = dc.expires
29
+ result[:owner] = dc.owner
30
+ result[:name_servers] = dc.name_servers
31
+ result[:dns] = {
32
+ ns: dc.ns,
33
+ mx: dc.mx,
34
+ cname: dc.cname,
35
+ a: dc.a
36
+ }
37
+
38
+ rescue Dnsruby::ServFail => e
39
+ result[:error] = "DomainChecker encountered #{e.to_s}: You might just need to retry this."
40
+ end
41
+
42
+ result
43
+ end
44
+ end
45
+
46
+
47
+ ## Find out meta data
48
+
49
+ def registered?
50
+ whois.registered?
51
+ end
52
+
53
+ def registrar
54
+ whois.registrar
55
+ end
56
+
57
+ def owner
58
+ whois.registrant_contacts
59
+ end
60
+
61
+ def expires
62
+ whois.expires_on
63
+ end
64
+
65
+ def inbound_mailers
66
+ get_data(mx).sort { |a,b| a[0] <=> b[0]}
67
+ end
68
+
69
+ def name_servers
70
+ get_data(ns)
71
+ end
72
+
73
+ def ssl
74
+ if @ssl.nil?
75
+ c = HTTPClient.new
76
+ c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
77
+ p = c.get( "https://#{domain}")
78
+ @ssl = p.peer_cert
79
+ end
80
+ return nil if( ssl_common_name.nil? )
81
+ @ssl
82
+ end
83
+
84
+ def ssl_exists?
85
+ !ssl.nil?
86
+ end
87
+
88
+ def ssl_valid_until
89
+ return nil if ssl.nil?
90
+ ssl.not_after
91
+ end
92
+
93
+ def ssl_common_name
94
+ ssl if @ssl.nil?
95
+ return nil if @ssl.nil?
96
+ ret = (@ssl.subject.to_a.select { |x| x[0] == 'CN' }.first || [])[1]
97
+ return nil if ( ret =~ /herokuapp/ )
98
+ ret
99
+ end
100
+
101
+ ## Details
102
+
103
+ def lookup( type )
104
+ @results ||= {}
105
+ if @results[type].nil?
106
+ @results[type] = parse_answer( type )
107
+ if type == "A" || type == "CNAME"
108
+ @results[type] = parse_answer type, @results[type][:domains]
109
+ end
110
+ end
111
+
112
+ # @results[type]
113
+ # @results = { domain: @domain }
114
+ # @results[:ns] = parse_answer( "NS" )
115
+ # @results[:a] = parse_answer( "A" )
116
+ # @results[:a] = parse_answer( "A", @results[:a][:domains] )
117
+ # @results[:cname] = parse_answer( "CNAME" )
118
+ # @results[:cname] = parse_answer( "CNAME", @results[:cname][:domains] )
119
+ # @results[:mx] = parse_answer( "MX" )
120
+ # end
121
+ @results[type]
122
+ end
123
+
124
+ def ns
125
+ lookup "NS"
126
+ end
127
+
128
+ def a
129
+ lookup "A"
130
+ end
131
+
132
+ def mx
133
+ lookup "MX"
134
+ end
135
+
136
+ def cname
137
+ lookup "CNAME"
138
+ end
139
+
140
+ def whois
141
+ @whois ||= Whois.lookup @domain
142
+ end
143
+
144
+ def parse_answer( type, domains = false )
145
+ domain = @domain
146
+ domain = "www.#{@domain}" if domains
147
+ ret = {domains: []}
148
+ ret = {domains: domains} if domains
149
+ begin
150
+ rr = @res.query( domain, type)
151
+ # puts rr.answer
152
+ rr.answer.each do |answer|
153
+ ret[:domains] << { domain: domain, name:answer.name.to_s, ttl: answer.ttl, data: answer.rdata, type: answer.type } if answer.type == type
154
+ end
155
+ rescue Dnsruby::ResolvTimeout
156
+ ret[:error] = :timeout
157
+ rescue Dnsruby::NXDomain
158
+ ret[:error] = :notfound
159
+ end
160
+
161
+ ret
162
+ end
163
+
164
+ def get_data array
165
+ array[:domains].collect{ |x| x[:data] }
166
+ end
167
+
168
+ # def registrar
169
+ # last_2_records ns
170
+ # end
171
+
172
+ # def mailer
173
+ # records( mx ).collect { |x| x[1].labels[-2..-1].collect { |x| x.to_s.downcase }.join( "." ) }.uniq.select { |x| x != "google.com"}
174
+ # end
175
+
176
+ # def last_2_records a
177
+ # records( a ).collect { |x| x.labels[-2..-1].collect { |x| x.to_s }.join( "." ) }.uniq
178
+ # end
179
+
180
+ # def records v
181
+ # if v[:domains]
182
+ # v[:domains].collect { |x| x[:data] }
183
+ # else
184
+ # []
185
+ # end
186
+ # end
187
+
188
+ def heroku?
189
+ a_ips = a[:domains].select { |x| x[:domain] == @domain }.collect { |x| x[:data].to_s }.sort
190
+ # puts a_ips
191
+ return true if a_ips == ["174.129.212.2", "75.101.145.87", "75.101.163.44"]
192
+
193
+
194
+ # puts cname[:domains].to_s
195
+ # return false if cname[:domains].first[:data].to_s != "proxy.heroku.com"
196
+
197
+ return true
198
+ end
199
+
200
+ def check key, method
201
+ ret = __send__( method )
202
+ ret = false if ret.nil?
203
+ ret = false if ret == []
204
+ ret = ret.collect { |x| x[:name] || x['name'] }.join( "," ) if ret.is_a? Array
205
+
206
+ pass = ret
207
+
208
+ if( ret.is_a? Integer )
209
+ if( method == :dyno_redundancy)
210
+ pass = ret > 1
211
+ ret = "#{ret} dynos"
212
+ end
213
+ end
214
+
215
+ printf "%20s: ", key
216
+ if( pass )
217
+ printf "\u2713 #{ret}\n".encode('utf-8').green
218
+ else
219
+ printf "\u2718 #{ret}\n".encode('utf-8').red
220
+ end
221
+ end
222
+
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,167 @@
1
+ module Blend
2
+ module Status
3
+ class Environment
4
+ attr_accessor :project, :server, :environment
5
+
6
+ def initialize( project, server, environment )
7
+ @project = project
8
+ @server = server
9
+ @environment = environment
10
+ end
11
+
12
+ def check key, method
13
+ ret = __send__( method )
14
+ ret = false if ret.nil?
15
+ ret = false if ret == []
16
+ ret = ret.collect do |x|
17
+ if( x.is_a? Hash )
18
+ x[:name] || x['name']
19
+ else
20
+ x
21
+ end
22
+ end.join( "," ) if ret.is_a? Array
23
+
24
+ pass = ret
25
+
26
+ if( ret.is_a? Integer )
27
+ if( method == :dyno_redundancy)
28
+ pass = ret > 1
29
+ ret = "#{ret} dynos"
30
+ end
31
+ end
32
+
33
+ printf "%20s: ", key
34
+ if( pass )
35
+ printf "\u2713 #{ret}\n".encode('utf-8').green
36
+ else
37
+ printf "\u2718 #{ret}\n".encode('utf-8').red
38
+ if( @project.resolve )
39
+ r = "resolve_#{method}".to_sym
40
+ __send__(r) if respond_to? r
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ def client
47
+ Blend::Client.heroku_client
48
+ end
49
+
50
+ def log_monitoring
51
+ client.addons(@server, /(logentries|keen|papertrail|loggly|flydata)/)
52
+ end
53
+
54
+ def app_monitoring
55
+ client.addons(@server, /(relic)/)
56
+ end
57
+
58
+ def exception_handling
59
+ client.addons(@server, /(airbrake|exceptional)/)
60
+ end
61
+
62
+ def deployhooks
63
+ client.addons(@server, /deployhooks/)
64
+ end
65
+
66
+ def resolve_deployhooks
67
+ system( "echo heroku addons:add deployhooks:hipchat --auth_token=#{@project.juice_client.hipchat_api} --room=\"#{@project.config['hipchat_room']}\" --app #{@server}")
68
+ system( "heroku addons:add deployhooks:hipchat --auth_token=#{@project.juice_client.hipchat_api} --room=\"#{@project.config['hipchat_room']}\" --app #{@server}" )
69
+ Blend::Client::hipchat_client.post_message @project.config['hipchat_room'], "Heroku app: #{@server} commit hook now added"
70
+
71
+ end
72
+
73
+ def ssl
74
+ client.addons(@server, /ssl/)
75
+ end
76
+
77
+ def backups
78
+ client.backups(@server).collect { |x| x[:plan] }
79
+ end
80
+
81
+ def database
82
+ client.databases(@server).collect { |x| x[:plan] }
83
+ end
84
+
85
+ def dyno_redundancy
86
+ client.dynos(@server).to_i
87
+ end
88
+
89
+ def stack
90
+ client.stack( @server ) == 'cedar'
91
+ end
92
+
93
+ def domains
94
+ client.nonheroku_domains( @server )
95
+ end
96
+ end
97
+ end
98
+ end
99
+ __END__
100
+
101
+
102
+
103
+
104
+
105
+ desc 'check_domains [APP]', 'Check domains'
106
+ def check_domains(app)
107
+ client.nonheroku_domains(app).each do |domain|
108
+ puts "\nChecking domain #{domain}:".blue
109
+ result = DomainChecker.check(domain)
110
+
111
+ if result.include? :error
112
+ puts result[:error].red
113
+ return
114
+ end
115
+
116
+ # Check registration
117
+ if result[:registered]
118
+ r = result[:registrar]
119
+
120
+ # Registrar:
121
+ if r.nil?
122
+ printf "%25s: %s", 'Registrar', format_result(:warn, "DNS not configured correctly for #{domain}")
123
+ else
124
+ registrar = [r['name'], r['organization'], r['url']].reject(&:nil?).join('; ')
125
+ printf "%25s: %s", 'Registrar', format_result(:pass, registrar)
126
+ end
127
+ else
128
+ printf "%25s: %s", 'Registrar', format_result(:fail)
129
+ end
130
+
131
+
132
+ unless result[:expires].nil?
133
+ # Expiration:
134
+ days_to_expiration = ((result[:expires] - Time.now)/(3600.0*24.0)).to_i
135
+ formatted_dte = "#{result[:expires].strftime("%F")} (#{days_to_expiration} days from now)"
136
+ if days_to_expiration < 30
137
+ state = :fail
138
+ elsif days_to_expiration < 30*3
139
+ state = :warn
140
+ else
141
+ state = :pass
142
+ end
143
+
144
+ printf "%25s: %s", 'Expiration date', format_result(state, formatted_dte)
145
+ end
146
+
147
+ printf "%25s:\n", 'DNS'
148
+ result[:dns].each do |k,v|
149
+ v[:domains].each do |record|
150
+ if record[:type] == 'MX'
151
+ description = "#{record[:data][0]}: #{record[:data][1].to_s}"
152
+ else
153
+ description = record[:data].to_s
154
+ end
155
+ printf "%25s ", ''
156
+ printf "%5s".green, record[:type].to_s
157
+ printf "( ttl=%i ): %s\n", record[:ttl], description
158
+ end
159
+ end
160
+
161
+ end
162
+ end
163
+
164
+
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,227 @@
1
+ require 'blend/status/team'
2
+ require 'blend/status/repo'
3
+ require 'blend/status/domain'
4
+ require 'blend/status/environment'
5
+ require 'blend/status/project_resolver'
6
+
7
+ module Blend
8
+ module Status
9
+ class Project
10
+ attr_accessor :name, :juice_id, :juice_client, :resolve
11
+
12
+ def initialize( name, resolve = false )
13
+ @name = name
14
+ @juice_client = Blend::Client.juice_client
15
+ reload
16
+ @resolve = resolve
17
+ @resolver = ProjectResolver.new( self )
18
+ end
19
+
20
+ def reload
21
+ @juice_id = @juice_client.project_id_from_name @name
22
+ @teams = nil
23
+ @repos = nil
24
+ @environment_status = nil
25
+ @domains_status = nil
26
+ @config = nil
27
+ @feeds = nil
28
+ @environments = nil
29
+ end
30
+
31
+
32
+ ##
33
+ # Sub status systems
34
+ ##
35
+
36
+ def team_status
37
+ @teams ||= (github_teams || []).collect do |team|
38
+ Team.new( self, team )
39
+ end
40
+ end
41
+
42
+ def repo_status
43
+ @repos ||= (repos || []).collect do |name, repo|
44
+ Repo.new( self, name, repo )
45
+ end
46
+ end
47
+
48
+ def environment_status
49
+ @environment_status ||= (servers || []).collect do |server|
50
+ Environment.new( self, server['name'], server['environment']['name'].downcase )
51
+ end
52
+ end
53
+
54
+ def domains_status
55
+ @domains_status ||= domains.collect do |x|
56
+ Domain.new( @project, x[:environment], x[:domain] )
57
+ end
58
+ end
59
+
60
+ ##
61
+ # Project Metadata
62
+ ##
63
+
64
+ def domains
65
+ environment_status.collect do |x|
66
+ x.domains.collect do |domain|
67
+ {environment: x.environment, domain: domain }
68
+ end
69
+ end.flatten
70
+ end
71
+
72
+ def config
73
+ @config ||= (@juice_client.project( @juice_id ) || {})['blend_config']
74
+ end
75
+
76
+ def feeds
77
+ @feeds ||= @juice_client.feeds( @juice_id ).group_by { |x| x['feed_name'] }
78
+ end
79
+
80
+ def environments
81
+ @environments ||= @juice_client.environments( @juice_id ).group_by{|x| x['name'].downcase}
82
+ end
83
+
84
+ ##
85
+ # Checks
86
+ ##
87
+
88
+ def project_found
89
+ !@juice_id.nil? && @juice_id != ""
90
+ end
91
+
92
+ def read_only_teams
93
+ team_status.select { |team| team.read_only? }
94
+ end
95
+
96
+ def write_teams
97
+ team_status.select { |team| !team.read_only? }
98
+ end
99
+
100
+ def github_members
101
+ read_only_members = read_only_teams.collect { |ts| ts.members }.flatten
102
+ write_team_members = write_teams.collect { |ts| ts.members }.flatten
103
+
104
+ read_only_members -= write_team_members
105
+
106
+ write_team_members.collect do |x|
107
+ {name: x, access: :full}
108
+ end + read_only_members.collect do |x|
109
+ {name: x, access: :read}
110
+ end
111
+ end
112
+
113
+ def repos
114
+ ret = {}
115
+ team_status.each do |x|
116
+ x.repos.each do |repo|
117
+ ret[repo.full_name] = repo
118
+ end
119
+ end
120
+ ret
121
+ end
122
+
123
+ def repos_setup
124
+ repos.keys
125
+ end
126
+
127
+ def source_control
128
+ a = (feeds['github'] || []).collect { |x| "#{x['namespace']}/#{x['name']}" }
129
+ b = repos_setup
130
+
131
+ return false if (a-b).length != 0 || (b-a).length != 0
132
+ return false if a.length == 0 || b.length == 0
133
+ return a
134
+ end
135
+
136
+ def juice_users_synced_diff
137
+ team_juice_users = {}
138
+ github_members.each do |member|
139
+ user = juice_client.user_from_github_user member[:name]
140
+ team_juice_users[user['id']] = user if user
141
+ end
142
+
143
+ juice_users = juice_client.project_users( @juice_id ).group_by{ |x| x['id'] }
144
+
145
+ not_in_juice = team_juice_users.keys - juice_users.keys
146
+ end
147
+
148
+ def juice_users_synced
149
+ juice_users_synced_diff.length == 0
150
+ end
151
+
152
+ def servers( env = nil )
153
+ if( env )
154
+ (feeds['heroku'] || []).group_by { |x| x['environment']['name'].downcase }[env]
155
+ else
156
+ feeds['heroku']
157
+ end
158
+ end
159
+
160
+ def bugtracking
161
+ f = (feeds['asana'] || []) + (feeds['lighthouse'] || [])
162
+ return nil if f.length == 0
163
+ f
164
+ end
165
+
166
+ def production
167
+ environments['production']
168
+ end
169
+
170
+ def staging
171
+ environments['staging']
172
+ end
173
+
174
+ def heroku_apps( env = nil )
175
+ servers(env).collect { |x| [x['name'], x['environment']['name']]}
176
+ end
177
+
178
+ def hipchat
179
+ return nil if config.nil?
180
+ config['hipchat_room']
181
+ end
182
+
183
+ def github_teams
184
+ # return []] if @juice_id.nil?
185
+ return [] if config.nil?
186
+ config['teams']
187
+ end
188
+
189
+ def header text
190
+ puts
191
+ puts "#{text}".blue.underline
192
+ end
193
+
194
+ def check key, method
195
+ ret = __send__( method )
196
+ ret = false if ret.nil?
197
+ ret = false if ret.is_a? Array and ret.length == 0
198
+
199
+ pass = ret
200
+
201
+ if ret.is_a? Array
202
+ ret = ret.collect do |x|
203
+ if x.is_a? Hash
204
+ x[:name] || x['name']
205
+ else
206
+ x
207
+ end
208
+ end.join( ", ")
209
+ end
210
+
211
+ ret = "" if !ret
212
+
213
+ printf "%20s: ", key
214
+ if( pass )
215
+ printf "\u2713 #{ret}\n".encode('utf-8').green
216
+ else
217
+ printf "\u2718 #{ret}\n".encode('utf-8').red
218
+ if @resolve
219
+ r = "resolve_#{method}".to_sym
220
+ @resolver.__send__(r) if @resolver.respond_to? r
221
+ reload
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end