blend 0.1.4

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