perkins 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.env.example +4 -0
  4. data/.gitignore +19 -0
  5. data/.pryrc +3 -0
  6. data/.rspec +2 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +71 -0
  10. data/Rakefile +28 -0
  11. data/TODO.md +4 -0
  12. data/bin/perkins +6 -0
  13. data/db/migrate/20150130143030_create_repo.rb +18 -0
  14. data/db/migrate/20150130143050_create_builds.rb +20 -0
  15. data/db/schema.rb +38 -0
  16. data/examples/config.rb +12 -0
  17. data/examples/database.yml +17 -0
  18. data/examples/mongo.yml +13 -0
  19. data/lib/core_ext/hash/compact.rb +8 -0
  20. data/lib/core_ext/hash/deep_merge.rb +15 -0
  21. data/lib/core_ext/hash/deep_symbolize_keys.rb +20 -0
  22. data/lib/core_ext/object/false.rb +5 -0
  23. data/lib/core_ext/string/indent.rb +5 -0
  24. data/lib/core_ext/string/unindent.rb +5 -0
  25. data/lib/perkins/.DS_Store +0 -0
  26. data/lib/perkins/application.rb +40 -0
  27. data/lib/perkins/assets/images/github.svg +4 -0
  28. data/lib/perkins/assets/images/spinner.svg +23 -0
  29. data/lib/perkins/assets/javascripts/app.js +9 -0
  30. data/lib/perkins/assets/javascripts/log_view.js.coffee +95 -0
  31. data/lib/perkins/assets/javascripts/perkings.js.coffee +40 -0
  32. data/lib/perkins/assets/javascripts/vendor/ansiparse.js +187 -0
  33. data/lib/perkins/assets/javascripts/vendor/jquery.timeago.js +189 -0
  34. data/lib/perkins/assets/javascripts/vendor/log.js +2 -0
  35. data/lib/perkins/assets/javascripts/vendor/minispade.js +55 -0
  36. data/lib/perkins/assets/stylesheets/app.css +2 -0
  37. data/lib/perkins/assets/stylesheets/log.css.scss +115 -0
  38. data/lib/perkins/assets/stylesheets/styles.css.scss +199 -0
  39. data/lib/perkins/auth/github.rb +181 -0
  40. data/lib/perkins/build/data/env.rb +84 -0
  41. data/lib/perkins/build/data/var.rb +60 -0
  42. data/lib/perkins/build/data.rb +167 -0
  43. data/lib/perkins/build/script/bundler.rb +76 -0
  44. data/lib/perkins/build/script/go.rb +100 -0
  45. data/lib/perkins/build/script/helpers.rb +39 -0
  46. data/lib/perkins/build/script/jdk.rb +43 -0
  47. data/lib/perkins/build/script/ruby.rb +31 -0
  48. data/lib/perkins/build/script/rvm.rb +73 -0
  49. data/lib/perkins/build/script/stages.rb +28 -0
  50. data/lib/perkins/build/script/templates/footer.sh +3 -0
  51. data/lib/perkins/build/script/templates/header.sh +201 -0
  52. data/lib/perkins/build/script/templates/xcode.sh +21 -0
  53. data/lib/perkins/build/script.rb +167 -0
  54. data/lib/perkins/build/shell/dsl.rb +104 -0
  55. data/lib/perkins/build/shell/node.rb +121 -0
  56. data/lib/perkins/build/shell.rb +16 -0
  57. data/lib/perkins/build.rb +27 -0
  58. data/lib/perkins/build_report.rb +11 -0
  59. data/lib/perkins/cli.rb +42 -0
  60. data/lib/perkins/commit.rb +30 -0
  61. data/lib/perkins/dsl/app_proxy.rb +23 -0
  62. data/lib/perkins/dsl.rb +12 -0
  63. data/lib/perkins/listener.rb +38 -0
  64. data/lib/perkins/logger.rb +12 -0
  65. data/lib/perkins/notifier.rb +5 -0
  66. data/lib/perkins/repo.rb +145 -0
  67. data/lib/perkins/runner.rb +124 -0
  68. data/lib/perkins/server.rb +314 -0
  69. data/lib/perkins/thor_utils.rb +79 -0
  70. data/lib/perkins/version.rb +3 -0
  71. data/lib/perkins/views/401.haml +1 -0
  72. data/lib/perkins/views/builds.haml +46 -0
  73. data/lib/perkins/views/index.haml +6 -0
  74. data/lib/perkins/views/layout.haml +53 -0
  75. data/lib/perkins/views/menu.haml +18 -0
  76. data/lib/perkins/views/orgs.haml +101 -0
  77. data/lib/perkins/views/profile.haml +31 -0
  78. data/lib/perkins/views/readme.md +20 -0
  79. data/lib/perkins/views/repos/config.haml +72 -0
  80. data/lib/perkins/views/repos/github.haml +76 -0
  81. data/lib/perkins/views/repos/menu.haml +17 -0
  82. data/lib/perkins/views/repos/repo.haml +64 -0
  83. data/lib/perkins/views/repos/spinner.haml +3 -0
  84. data/lib/perkins/webhooks/github.rb +12 -0
  85. data/lib/perkins/worker.rb +33 -0
  86. data/lib/perkins.rb +36 -0
  87. data/perkins.gemspec +52 -0
  88. data/spec/fixtures/.travis.yml +8 -0
  89. data/spec/fixtures/config.yml +6 -0
  90. data/spec/lib/build/build_spec.rb +58 -0
  91. data/spec/lib/commit_spec.rb +6 -0
  92. data/spec/lib/dsl_spec.rb +17 -0
  93. data/spec/lib/listener_spec.rb +30 -0
  94. data/spec/lib/repo_spec.rb +110 -0
  95. data/spec/lib/runner_spec.rb +76 -0
  96. data/spec/lib/server_spec.rb +108 -0
  97. data/spec/spec_helper.rb +67 -0
  98. data/spec/support/auth.rb +30 -0
  99. data/spec/support/github_api.rb +177 -0
  100. metadata +503 -0
@@ -0,0 +1,199 @@
1
+ $serif: "freight-sans-pro",sans-serif !important;
2
+ $georgia: "ff-tisa-web-pro",Georgia,Cambria,"Times New Roman",Times,serif !important;
3
+ $background-color: #F9F9F6;
4
+ $soft-separator: #cecece;
5
+ $link-color: #5100a9;
6
+ $link-up: #222;
7
+
8
+ body {
9
+ padding-top: 50px;
10
+ position: relative;
11
+ background-color: $background-color;
12
+ font-size: 1.6em;
13
+ font-family: "ff-tisa-web-pro",Georgia,Cambria,"Times New Roman",Times,serif !important;;
14
+ a{
15
+ outline: none !important;
16
+ color:$link-color;
17
+ &:hover{
18
+ color: $link-up;
19
+ }
20
+ }
21
+ }
22
+
23
+ .container{
24
+ width: 100%;
25
+ }
26
+
27
+ nav{
28
+ background-color: #F9F9F6 !important;
29
+ }
30
+
31
+ h2, .h2, h4 {
32
+ font-family: "ff-tisa-web-pro",Georgia,Cambria,"Times New Roman",Times,serif !important;;
33
+ &.title{
34
+ font-family: $serif;
35
+ text-transform: uppercase;
36
+ color:#0e0e0e;
37
+ font-weight: bold;
38
+ }
39
+ &.centered{
40
+ margin: 43px 20px;
41
+ text-align: center;
42
+ color:#0e0e0e;
43
+ }
44
+ font-weight: 700;
45
+ }
46
+
47
+
48
+ nav.transparent.navbar {
49
+ //background: rgba(0,0,0,0.4);
50
+ background: transparent;
51
+ //border-bottom: 1px solid #1E1E1E;
52
+ a {
53
+ color: #222;
54
+ }
55
+ .navbar-inner a {
56
+ font-weight: lighter;
57
+ color: #222;
58
+ text-transform: uppercase;
59
+ font-size: 0.8em;
60
+ }
61
+ }
62
+ .navbar-toggle {
63
+ background-color: rgba(0, 0, 0, 0);
64
+ border: 1px solid #000;
65
+ &:focus{
66
+ background-color: #ddd;
67
+ }
68
+ }
69
+ .navbar-toggle .icon-bar {
70
+ background-color: #000;
71
+ }
72
+
73
+ #footer {
74
+ border-top: 1px solid #ccc;
75
+ color: #888888;
76
+ margin-top: 0;
77
+ padding-bottom: 17px;
78
+ padding-top: 17px;
79
+ text-align: center;
80
+ //text-transform: uppercase;
81
+ }
82
+
83
+ .navbar {
84
+ background-color: #FFFFFF;
85
+ //border-bottom: 1px solid #ECECEC;
86
+ border-bottom: 1px solid #ccc;
87
+ font-weight: 500;
88
+ //position: relative;
89
+ transition: border-bottom-color 0s ease 0.1s;
90
+ }
91
+
92
+ .navbar-brand{
93
+ //color: #5100A9;
94
+ float: left;
95
+ font-family: $serif;
96
+
97
+ font-size: 37px;
98
+ font-style: inherit;
99
+ font-weight: 900;
100
+ margin-left: -15px;
101
+ margin-right: 5px;
102
+ text-transform: uppercase;
103
+
104
+ }
105
+ .navbar .nav,
106
+ .navbar .nav > li {
107
+ float:none;
108
+ display:inline-block;
109
+ *display:inline; /* ie7 fix */
110
+ *zoom:1; /* hasLayout ie7 trigger */
111
+ vertical-align: top;
112
+ a{
113
+ font-family:$serif;
114
+ }
115
+ }
116
+
117
+ .navbar-inner {
118
+ display: block;
119
+ //font-family: Runda-1;
120
+ //font-size: 1.5em;
121
+ line-height: 20px;
122
+ margin-left: auto;
123
+ margin-right: auto;
124
+ //max-width: 687px;
125
+ text-align: center;
126
+ }
127
+
128
+ .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
129
+ background-color: background;
130
+ border-radius: 0;
131
+ color: #fff;
132
+ }
133
+
134
+ .nav-pills > li > a, .nav-pills > li > a:hover, .nav-pills > li > a:focus {
135
+ background-color: #eee;
136
+ border-radius: 0;
137
+ color: #000;
138
+ }
139
+
140
+
141
+ .avatar{
142
+ border-radius: 50%;
143
+ }
144
+
145
+
146
+ #left-wrap{
147
+ min-width: 250px;
148
+ }
149
+
150
+ #sidebar{
151
+ border-right: 1px solid #cecece;
152
+ display: inline-block;
153
+ height: 100%;
154
+ left: 0px;
155
+ padding: 0;
156
+ width: 253px;
157
+ background-color: $background-color;
158
+ #sidebar-header{
159
+ border-bottom: 1px solid #cecece;
160
+ padding: 15px 15px 11px;
161
+ }
162
+ }
163
+
164
+ #main-content{
165
+ min-width: 600px;
166
+ }
167
+
168
+ #repo-menu{
169
+ //margin-top: 29px;
170
+ margin-top: 13px;
171
+ i{
172
+ padding: 3.7px;
173
+ }
174
+ }
175
+
176
+ /*
177
+ pre#log{
178
+ background-color: #222;
179
+ border: 1px solid #ddd;
180
+ clear: left;
181
+ color: #f1f1f1;
182
+ counter-reset: line-numbering;
183
+ font-family: monospace;
184
+ font-size: 12px;
185
+ line-height: 19px;
186
+ margin-top: 1em;
187
+ min-height: 12px;
188
+ padding: 15px 23px;
189
+ position: relative;
190
+ white-space: pre-wrap;
191
+ word-wrap: break-word;
192
+ }*/
193
+
194
+ .build-row td{
195
+ font-size: 13px;
196
+ padding: 5px 20px 5px 0;
197
+ text-align: left;
198
+ vertical-align: top;
199
+ }
@@ -0,0 +1,181 @@
1
+ require 'sinatra/base'
2
+ require 'warden/github'
3
+
4
+ #https://github.com/atmos/sinatra_auth_github
5
+
6
+ module Perkins
7
+ module Auth
8
+ module Github
9
+ # Simple way to serve an image early in the stack and not get blocked by
10
+ # application level before filters
11
+ class AccessDenied < Sinatra::Base
12
+ enable :raise_errors
13
+ disable :show_exceptions
14
+
15
+ get '/_images/securocat.png' do
16
+ send_file(File.join(File.dirname(__FILE__), "views", "securocat.png"))
17
+ end
18
+ end
19
+
20
+ # The default failure application, this is overridable from the extension config
21
+ class BadAuthentication < Sinatra::Base
22
+ enable :raise_errors
23
+ disable :show_exceptions
24
+
25
+ helpers do
26
+ def unauthorized_template
27
+ @unauthenticated_template ||= "401"
28
+ #File.read(File.join(File.dirname(__FILE__), "../../views", "401.html"))
29
+ end
30
+ end
31
+
32
+ get '/unauthenticated' do
33
+ status 403
34
+ unauthorized_template
35
+ end
36
+ end
37
+
38
+ module Helpers
39
+ def warden
40
+ env['warden']
41
+ end
42
+
43
+ def authenticate!(*args)
44
+ warden.authenticate!(*args)
45
+ end
46
+
47
+ def authenticated?(*args)
48
+ warden.authenticated?(*args)
49
+ end
50
+
51
+ def logout!
52
+ warden.logout
53
+ end
54
+
55
+ # The authenticated user object
56
+ #
57
+ # Supports a variety of methods, name, full_name, email, etc
58
+ def github_user
59
+ warden.user
60
+ end
61
+
62
+ # Send a V3 API GET request to path
63
+ #
64
+ # path - the path on api.github.com to hit
65
+ #
66
+ # Returns a rest client response object
67
+ #
68
+ # Examples
69
+ # github_raw_request("/user")
70
+ # # => RestClient::Response
71
+ def github_raw_request(path)
72
+ github_user.github_raw_request(path)
73
+ end
74
+
75
+ # Send a V3 API GET request to path and parse the response body
76
+ #
77
+ # path - the path on api.github.com to hit
78
+ #
79
+ # Returns a parsed JSON response
80
+ #
81
+ # Examples
82
+ # github_request("/user")
83
+ # # => { 'login' => 'atmos', ... }
84
+ def github_request(path)
85
+ github_user.github_request(path)
86
+ end
87
+
88
+ # See if the user is a public member of the named organization
89
+ #
90
+ # name - the organization name
91
+ #
92
+ # Returns: true if the user is public access, false otherwise
93
+ def github_public_organization_access?(name)
94
+ github_user.publicized_organization_member?(name)
95
+ end
96
+
97
+ # See if the user is a member of the named organization
98
+ #
99
+ # name - the organization name
100
+ #
101
+ # Returns: true if the user has access, false otherwise
102
+ def github_organization_access?(name)
103
+ github_user.organization_member?(name)
104
+ end
105
+
106
+ # See if the user is a member of the team id
107
+ #
108
+ # team_id - the team's id
109
+ #
110
+ # Returns: true if the user has access, false otherwise
111
+ def github_team_access?(team_id)
112
+ github_user.team_member?(team_id)
113
+ end
114
+
115
+ # Enforce user membership to the named organization
116
+ #
117
+ # name - the organization to test membership against
118
+ #
119
+ # Returns an execution halt if the user is not a member of the named org
120
+ def github_public_organization_authenticate!(name)
121
+ authenticate!
122
+ halt([401, "Unauthorized User"]) unless github_public_organization_access?(name)
123
+ end
124
+
125
+ # Enforce user membership to the named organization if membership is publicized
126
+ #
127
+ # name - the organization to test membership against
128
+ #
129
+ # Returns an execution halt if the user is not a member of the named org
130
+ def github_organization_authenticate!(name)
131
+ authenticate!
132
+ halt([401, "Unauthorized User"]) unless github_organization_access?(name)
133
+ end
134
+
135
+ # Enforce user membership to the team id
136
+ #
137
+ # team_id - the team_id to test membership against
138
+ #
139
+ # Returns an execution halt if the user is not a member of the team
140
+ def github_team_authenticate!(team_id)
141
+ authenticate!
142
+ halt([401, "Unauthorized User"]) unless github_team_access?(team_id)
143
+ end
144
+
145
+ def _relative_url_for(path)
146
+ request.script_name + path
147
+ end
148
+ end
149
+
150
+ def self.registered(app)
151
+ app.use AccessDenied
152
+ app.use BadAuthentication
153
+
154
+ app.use Warden::Manager do |manager|
155
+ manager.default_strategies :github
156
+
157
+ manager.failure_app = app.github_options[:failure_app] || BadAuthentication
158
+
159
+ manager.scope_defaults :default, :config => {
160
+ :client_id => app.github_options[:client_id] || ENV['GITHUB_CLIENT_ID'],
161
+ :client_secret => app.github_options[:secret] || ENV['GITHUB_CLIENT_SECRET'],
162
+ :scope => app.github_options[:scopes] || "admin:repo_hook,repo,user:email",
163
+ :redirect_uri => app.github_options[:callback_url] || '/auth/github/callback'
164
+ }
165
+ end
166
+
167
+ app.helpers Helpers
168
+
169
+ app.get '/auth/github/callback' do
170
+ if params["error"]
171
+ redirect "/unauthenticated"
172
+ else
173
+ authenticate!
174
+ return_to = session.delete('return_to') || _relative_url_for('/')
175
+ redirect return_to
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,84 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'shellwords'
3
+
4
+ module Perkins
5
+ module Build
6
+ class Data
7
+ class Env
8
+ delegate :secure_env_enabled?, :pull_request, :config, :build, :job, :repository, to: :data
9
+
10
+ class Group < Struct.new(:source, :vars)
11
+ def initialize(source, vars)
12
+ super(source, vars || [])
13
+ end
14
+
15
+ def announce?
16
+ source != 'travis' && vars.length > 0
17
+ end
18
+ end
19
+
20
+ attr_reader :data
21
+
22
+ def initialize(data)
23
+ @data = data
24
+ end
25
+
26
+ def vars
27
+ travis_vars + settings_vars + config_vars
28
+ end
29
+
30
+ def vars_groups
31
+ [Group.new('travis', travis_vars),
32
+ Group.new('repository settings', settings_vars),
33
+ Group.new('.travis.yml', config_vars)]
34
+ end
35
+
36
+ private
37
+
38
+ def travis_vars
39
+ to_vars(
40
+ TRAVIS_PULL_REQUEST: pull_request || false,
41
+ TRAVIS_SECURE_ENV_VARS: secure_env_vars?,
42
+ TRAVIS_BUILD_ID: build[:id],
43
+ TRAVIS_BUILD_NUMBER: build[:number],
44
+ TRAVIS_BUILD_DIR: [ BUILD_DIR, slug.shellescape ].join('/'),
45
+ TRAVIS_JOB_ID: job[:id],
46
+ TRAVIS_JOB_NUMBER: job[:number],
47
+ TRAVIS_BRANCH: job[:branch].shellescape,
48
+ TRAVIS_COMMIT: job[:commit],
49
+ TRAVIS_COMMIT_RANGE: job[:commit_range],
50
+ TRAVIS_REPO_SLUG: slug.shellescape,
51
+ TRAVIS_OS_NAME: config[:os],
52
+ TRAVIS_TAG: job[:tag]
53
+ )
54
+ end
55
+
56
+ def slug
57
+ repository[:slug] || ''
58
+ end
59
+
60
+ def extract_config_vars(vars)
61
+ vars = to_vars(Array(vars).compact.reject(&:empty?))
62
+ vars.reject!(&:secure?) unless secure_env_enabled?
63
+ vars
64
+ end
65
+
66
+ def config_vars
67
+ extract_config_vars(config[:global_env]) + extract_config_vars(config[:env])
68
+ end
69
+
70
+ def settings_vars
71
+ data.raw_env_vars.map { |var| Var.new(var[:name], var[:value], !var[:public]) }
72
+ end
73
+
74
+ def to_vars(args)
75
+ args.to_a.map { |args| Var.create(*args) }.flatten
76
+ end
77
+
78
+ def secure_env_vars?
79
+ secure_env_enabled? && config_vars.any?(&:secure?)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,60 @@
1
+ module Perkins
2
+ module Build
3
+ class Data
4
+ class Var
5
+ PATTERN = /(?:SECURE )?([\w]+)=(("|')(.*?)(\3)|\$\(.*?\)|[^"' ]+)/
6
+
7
+ class << self
8
+ def create(*args)
9
+ if args.size == 1
10
+ parse(args.first).map { |key, value| Var.new(key, value) }
11
+ else
12
+ [Var.new(*args)]
13
+ end
14
+ end
15
+
16
+ def parse(line)
17
+ secure = line =~ /^SECURE /
18
+ line.scan(PATTERN).map { |match| [(secure ? "SECURE #{match[0]}" : match[0]), match[1]] }
19
+ end
20
+ end
21
+
22
+ attr_reader :value
23
+
24
+ def initialize(key, value, secure = nil)
25
+ @key = key.to_s
26
+ @value = value.to_s
27
+ @secure = secure
28
+ end
29
+
30
+ def key
31
+ strip_secure(@key)
32
+ end
33
+
34
+ def to_s
35
+ if travis?
36
+ false
37
+ elsif secure?
38
+ "export #{[key, '[secure]'].join('=')}"
39
+ else
40
+ "export #{[key, value].join('=')}"
41
+ end
42
+ end
43
+
44
+ def travis?
45
+ @key =~ /^TRAVIS_/
46
+ end
47
+
48
+ def secure?
49
+ @secure.nil? ? @key =~ /^SECURE / : @secure
50
+ end
51
+
52
+ private
53
+
54
+ def strip_secure(string)
55
+ string.gsub('SECURE ', '')
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,167 @@
1
+ require 'core_ext/hash/deep_merge'
2
+ require 'core_ext/hash/deep_symbolize_keys'
3
+ require 'base64'
4
+
5
+ # actually, the worker payload can be cleaned up a lot ...
6
+
7
+ module Perkins
8
+ module Build
9
+ class Data
10
+ autoload :Env, 'perkins/build/data/env'
11
+ autoload :Var, 'perkins/build/data/var'
12
+
13
+ DEFAULTS = { }
14
+
15
+ DEFAULT_CACHES = {
16
+ apt: false,
17
+ bundler: false,
18
+ cocoapods: false,
19
+ composer: false
20
+ }
21
+
22
+ attr_reader :data
23
+
24
+ def initialize(data, defaults = {})
25
+ data = data.deep_symbolize_keys
26
+ defaults = defaults.deep_symbolize_keys
27
+ @data = DEFAULTS.deep_merge(defaults.deep_merge(data))
28
+ end
29
+
30
+ def urls
31
+ data[:urls] || {}
32
+ end
33
+
34
+ def config
35
+ data[:config]
36
+ end
37
+
38
+ def hosts
39
+ data[:hosts] || {}
40
+ end
41
+
42
+ def paranoid_mode?
43
+ data.fetch(:paranoid, false)
44
+ end
45
+
46
+ def skip_resolv_updates?
47
+ data.fetch(:skip_resolv_updates, false)
48
+ end
49
+
50
+ def skip_etc_hosts_fix?
51
+ data.fetch(:skip_etc_hosts_fix, false)
52
+ end
53
+
54
+ def cache_options
55
+ data[:cache_options] || {}
56
+ end
57
+
58
+ def cache(input = config[:cache])
59
+ case input
60
+ when Hash then input
61
+ when Array then input.map { |e| cache(e) }.inject(:merge)
62
+ when String, Symbol then { input.to_sym => true }
63
+ when nil then {} # for ruby 1.9
64
+ when false then Hash[DEFAULT_CACHES.each_key.with_object(false).to_a]
65
+ else input.to_h
66
+ end
67
+ end
68
+
69
+ def cache?(type, default = DEFAULT_CACHES[type])
70
+ type &&= type.to_sym
71
+ !!cache.fetch(type) { default }
72
+ end
73
+
74
+ def env_vars
75
+ @env_vars ||= Env.new(self).vars
76
+ end
77
+
78
+ def env_vars_groups
79
+ @env_vars_groups ||= Env.new(self).vars_groups
80
+ end
81
+
82
+ def raw_env_vars
83
+ data[:env_vars] || []
84
+ end
85
+
86
+ class SshKey < Struct.new(:value, :source, :encoded)
87
+ def value
88
+ if encoded?
89
+ Base64.decode64(super)
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ def encoded?
96
+ encoded
97
+ end
98
+
99
+ def fingerprint
100
+ rsa_key = OpenSSL::PKey::RSA.new(value)
101
+ public_ssh_rsa = "\x00\x00\x00\x07ssh-rsa" + rsa_key.e.to_s(0) + rsa_key.n.to_s(0)
102
+ OpenSSL::Digest::MD5.new(public_ssh_rsa).hexdigest.scan(/../).join(':')
103
+ end
104
+ end
105
+
106
+ def ssh_key
107
+ if ssh_key = data[:ssh_key]
108
+ SshKey.new(ssh_key[:value], ssh_key[:source], ssh_key[:encoded])
109
+ elsif source_key = data[:config][:source_key]
110
+ SshKey.new(source_key, nil, true)
111
+ end
112
+ end
113
+
114
+ def pull_request
115
+ job[:pull_request]
116
+ end
117
+
118
+ def secure_env_enabled?
119
+ job[:secure_env_enabled]
120
+ end
121
+
122
+ def source_host
123
+ source_url =~ %r(^(?:https?|git)(?:://|@)([^/]*?)(?:/|:)) && $1
124
+ end
125
+
126
+ def api_url
127
+ repository[:api_url]
128
+ end
129
+
130
+ def source_url
131
+ repository[:source_url]
132
+ end
133
+
134
+ def slug
135
+ repository[:slug]
136
+ end
137
+
138
+ def commit
139
+ job[:commit]
140
+ end
141
+
142
+ def branch
143
+ job[:branch]
144
+ end
145
+
146
+ def ref
147
+ job[:ref]
148
+ end
149
+
150
+ def job
151
+ data[:job] || {}
152
+ end
153
+
154
+ def build
155
+ data[:source] || data[:build] || {} # TODO standarize the payload on :build
156
+ end
157
+
158
+ def repository
159
+ data[:repository] || {}
160
+ end
161
+
162
+ def token
163
+ data[:oauth_token]
164
+ end
165
+ end
166
+ end
167
+ end