perkins 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.
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